Springboot 自定义注解+AOP简单实例介绍

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
简介: Springboot 自定义注解+AOP简单实例介绍

前言:



该篇以记录接口调用的传入参数日志为场景,来介绍下使用自定义注解作为切点,AOP切面方式去记录每个接口的传入参数以及可扩展的业务处理。

 

正文:



项目目录:


image.png


先是创建自定义注解, LogTrack:


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
 * @Author : JCccc
 * @CreateTime : 2020/4/13
 * @Description :
 **/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogTrack {
    String value() default "logTracking";
}


上面的自定义注解,我只用了一个默认参,所以不用命名,写value就可以。

 

然后写切点对应的代码,LogTrackAspect:


用到了fastjson,导入依赖:


        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.58</version>
        </dependency>


/**
 * @Author : JCccc
 * @CreateTime : 2020/4/13
 * @Description :
 **/
@Component
@Aspect
public class LogTrackAspect  {
    private static final Logger log = LoggerFactory.getLogger(LogTrackAspect.class);
    //这里需要注意了,这个是将自己自定义注解作为切点的根据,路径一定要写正确了
    @Pointcut(value = "@annotation(com.jc.mytest.aop.logRecord.LogTrack)")
    public void access() {
    }
    //进来切点世界,先经过的第一个站
    @Before("access()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        System.out.println("-aop 日志记录启动-" + new Date());
    }
    //环绕增强,是在before前就会触发
    @Around("@annotation(logTrack)")
    public Object around(ProceedingJoinPoint pjp, LogTrack logTrack) throws Throwable {
        System.out.println("-aop 日志环绕阶段-" + new Date());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
//        GET 请求其实可以从request里获取出参数
//       Map<String,String[]> map=request.getParameterMap();
//        System.out.println("获取参数:"+map.get("username")[0])
        String url = request.getRequestURL().toString();
        String ip = IpUtil.getIpAddr(request);
        String logTrackValue = logTrack.value();
        Object[] pipArrary = pjp.getArgs();
        if (pipArrary.length>1){ //多参,不是Map/JsonObject方式
            List<Object> argList = new ArrayList<>();
            for (Object arg : pjp.getArgs()) {
          // request/response无法使用toJSON
            if (arg instanceof HttpServletRequest) {
                       argList.add("request");
                 } else if (arg instanceof HttpServletResponse) {
                  argList.add("response");
                 } else {
                    argList.add(JSON.toJSON(arg));
            }
            }
         Signature signature = pjp.getSignature();
         MethodSignature methodSignature = (MethodSignature) signature;
        // 参数名数组
        String[] parameterNames = ((MethodSignature) signature).getParameterNames();
            System.out.println("参数名数组:"+new ArrayList(Arrays.asList(parameterNames)));
            System.out.println("参数是:"+argList.toString());
            System.out.println("logTrackValue:"+logTrackValue);
            System.out.println("url:"+url);
            System.out.println("ip:"+ip);
            return pjp.proceed();
        }
        Object param =  pipArrary[0];
        System.out.println("logTrackValue:"+logTrackValue);
        System.out.println("url:"+url);
        System.out.println("ip:"+ip);
        System.out.println("param:"+param.toString());
        return pjp.proceed();
    }
    //进来切点这,最后经过的一个站,也是方法正常运行结束后
    @After("access()")
    public void after(JoinPoint joinPoint) {
        System.out.println("-aop 日志记录结束-" + new Date());
    }
}


ps:return pjp.proceed(); 这个是从切点的环绕增强里面脱离出来,接下来会进入before阶段 ,然后回到接口,再回来after阶段。


所以扩展业务逻辑处理的话,可以放在return pjp.proceed();这行代码之前,例如判断用户密码是否正确;判断用户权限等等。


接下来是在Controller编写接口,并用上自定义注解,MyTestController:


/**
 * @Author : JCccc
 * @CreateTime : 2020/3/27
 * @Description :
 **/
@Controller
@RequestMapping("/test")
public class MyTestController {
    @ResponseBody
    @GetMapping("/testGet1")
    @LogTrack("testGet1 接口")
    public void testGet1(@RequestParam("userId") String userId, @RequestParam("toUserId") String toUserId) {
        System.out.println("已经进入GET测试接口,参数userId:" + userId+  "参数toUserId:"+toUserId);
    }
    @ResponseBody
    @GetMapping("/testGet2")
    @LogTrack("testGet2 接口")
    public void testGet2(@RequestParam Map map) {
        System.out.println("已经进入GET测试接口,参数:" + map.get("userId"));
    }
    @ResponseBody
    @PostMapping("/testPost1")
    @LogTrack("testPost1 接口")
    public void testPost1(@RequestBody Map map) {
        System.out.println("已经进入POST测试接口,参数:" + map.toString());
    }
    @ResponseBody
    @PostMapping("/testPost2")
    @LogTrack("testPost2 接口")
    public void testPost2(@RequestBody JSONObject jsonObject) {
        System.out.println("已经进入POST测试接口,参数:" + jsonObject.toString());
    }
}


然后我们来运行一下,看看控制台,就知道整个切点以及环绕的流程了:

 

首先是测试GET方式的接口,通过@RequestParam单个参数获取的情况:


image.png


调用接口:


image.png


运行结果:


image.png


接下来还是GET方式 ,通过Map去接收多参:


image.png


调用接口:


image.png


运行结果:


image.png


然后是调用Post请求:


image.pngimage.png


ps: 如果发现按照上面配置了,但是aop切点的方法好像没触发,

那么可以试试


1.检查jar包是否有导入正确


        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.8.9</version>
        </dependency>


2.在启动类上加上注解@EnableAspectJAutoProxy  这个其实springboot是默认帮我们已经开启为true状态的。


本篇使用到的一些工具类:


IpUtil:


import javax.servlet.http.HttpServletRequest;
import java.net.InetAddress;
import java.net.UnknownHostException;
/**
 * @Author : JCccc
 * @CreateTime : 2018-11-23
 * @Description :
 * @Point: Keep a good mood
 **/
public class IpUtil {
    public static String getIpAddr(HttpServletRequest request) {
        String ipAddress = null;
        try {
            ipAddress = request.getHeader("x-forwarded-for");
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getHeader("WL-Proxy-Client-IP");
            }
            if (ipAddress == null || ipAddress.length() == 0 || "unknown".equalsIgnoreCase(ipAddress)) {
                ipAddress = request.getRemoteAddr();
                if (ipAddress.equals("127.0.0.1")) {
                    // 根据网卡取本机配置的IP
                    InetAddress inet = null;
                    try {
                        inet = InetAddress.getLocalHost();
                    } catch (UnknownHostException e) {
                        e.printStackTrace();
                    }
                    ipAddress = inet.getHostAddress();
                }
            }
            // 对于通过多个代理的情况,第一个IP为客户端真实IP,多个IP按照','分割
            if (ipAddress != null && ipAddress.length() > 15) { // "***.***.***.***".length()
                // = 15
                if (ipAddress.indexOf(",") > 0) {
                    ipAddress = ipAddress.substring(0, ipAddress.indexOf(","));
                }
            }
        } catch (Exception e) {
            ipAddress="";
        }
        // ipAddress = this.getRequest().getRemoteAddr();
        return ipAddress;
    }
}
相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
XML Java 开发者
Spring Boot中的AOP实现
Spring AOP(面向切面编程)允许开发者在不修改原有业务逻辑的情况下增强功能,基于代理模式拦截和增强方法调用。Spring Boot通过集成Spring AOP和AspectJ简化了AOP的使用,只需添加依赖并定义切面类。关键概念包括切面、通知和切点。切面类使用`@Aspect`和`@Component`注解标注,通知定义切面行为,切点定义应用位置。Spring Boot自动检测并创建代理对象,支持JDK动态代理和CGLIB代理。通过源码分析可深入了解其实现细节,优化应用功能。
127 6
|
27天前
|
Java Maven 开发者
编写SpringBoot的自定义starter包
通过本文的介绍,我们详细讲解了如何创建一个Spring Boot自定义Starter包,包括自动配置类、配置属性类、`spring.factories`文件的创建和配置。通过自定义Starter,可以有效地复用公共配置和组件,提高开发效率。希望本文能帮助您更好地理解和应用Spring Boot自定义Starter,在实际项目中灵活使用这一强大的功能。
47 17
|
2月前
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
97 8
|
3月前
|
NoSQL Java Redis
Spring Boot 自动配置机制:从原理到自定义
Spring Boot 的自动配置机制通过 `spring.factories` 文件和 `@EnableAutoConfiguration` 注解,根据类路径中的依赖和条件注解自动配置所需的 Bean,大大简化了开发过程。本文深入探讨了自动配置的原理、条件化配置、自定义自动配置以及实际应用案例,帮助开发者更好地理解和利用这一强大特性。
223 14
|
4月前
|
监控 安全 Java
什么是AOP?如何与Spring Boot一起使用?
什么是AOP?如何与Spring Boot一起使用?
118 5
|
5月前
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
574 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
4月前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
85 1
|
4月前
|
安全 Java 应用服务中间件
如何将Spring Boot应用程序运行到自定义端口
如何将Spring Boot应用程序运行到自定义端口
138 0
|
10月前
|
Java API Spring
Spring容器如何使用一个注解来指定一个类型为配置类型
Spring容器如何使用一个注解来指定一个类型为配置类型
96 0
|
1月前
|
XML Java 测试技术
Spring IOC—基于注解配置和管理Bean 万字详解(通俗易懂)
Spring 第三节 IOC——基于注解配置和管理Bean 万字详解!
129 26