AOP

面向切面编程。主要功能是对方法的加强。其实现是居于代理模式使用。
Spring事务就是居于AOP的实现。
首先了解一下相关概念

  • 切面(Aspect): 通常是一个类,定义切入点和通知
  • 连接点(Join point): 程序执行过程中方法的调用
  • 通知(Advice):切面在特定的连接点的增强。
    • 前置通知: 连接点执行之前的增强,但不能阻止连接点执行(除非抛出异常)
    • 后置通知: 连接点正常执行之后的增强
    • 异常通知: 连接点抛出异常的增强
    • 执行通知: 连接点执行后(不论是否抛出异常)的增强
    • 围绕通知: 在连接点执行前后的增强
  • 切入点(Pointcut): 执行切面的匹配点。(特定名称的方法,特定的注解等)
  • AOP代理(AOP proxy): AOP框架创建的对象,代理就是目标对象的加强。
  • 引用(introduction): 准许目标对象引入新的接口以及相应的实现。
  • 目标对象(Target object): 被加强的对象。
  • 编织(Weaving):将切面与其他应用程序类型或对象链接,以创建通知的对象。这可以在编译时(例如,使用AspectJ编译器)、加载时或运行时完成。与其他纯Java AOP框架一样,Spring AOP在运行时执行编织。
    具体参考:SpringAOP

AOP使用-日志记录

  • 创建表记录数据
CREATE TABLE `sys_logger`  (
  `id` int(11) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT '记录ID',
  `create_date` datetime(0) NOT NULL COMMENT '创建时间',
  `modify_date` datetime(0) NOT NULL COMMENT '修改时间',
  `describes` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '描述',
  `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '执行的方法-类全命名.方法',
  `params` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '传入的参数',
  `host` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '主机',
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '地址',
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Compact;
  • 创建实体类
@Data
@EqualsAndHashCode(callSuper = true)
@Accessors(chain = true)
@TableName("sys_logger")
public class Logger extends BaseEntity { 
    private static final long serialVersionUID = 1L; 
    private String describes; 
    private String method; 
    private String params; 
    private String host; 
    private String address; 
}
  • 切面类(具体业务逻辑替换为日志打印)
@Aspect
@Component
public class RecordLoggerAcpect {
    private static final Logger log = LoggerFactory.getLogger(RecordLoggerAcpect.class);

    /**
     * @Description: 定义切入点 
     */
    //被注解CustomAopAnnotation表示的方法
    @Pointcut("@annotation(bertram.wang.vueweb.annotation.RecordLoggerAnnotation)") 
    public void pointCut(){}

    /**
     * @Description: 定义前置通知 
     */
    @Before("pointCut()")
    public void before(JoinPoint joinPoint) throws Throwable {
        // 接收到请求,记录请求内容
        log.info("【注解:Before】------------------切面  before");
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 记录下请求内容
        log.info("【注解:Before】浏览器输入的网址=URL : " + request.getRequestURL().toString());
        log.info("【注解:Before】HTTP_METHOD : " + request.getMethod());
        log.info("【注解:Before】IP : " + request.getRemoteAddr());
        log.info("【注解:Before】执行的业务方法名=CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());
        log.info("【注解:Before】业务方法获得的参数=ARGS : " + Arrays.toString(joinPoint.getArgs()));

    }

    /**
     * @Description: 后置返回通知 
     */
    @AfterReturning(returning = "ret", pointcut = "pointCut()")
    public void afterReturning(Object ret) throws Throwable {
        // 处理完请求,返回内容
        log.info("【注解:AfterReturning】这个会在切面最后的最后打印,方法的返回值 : " + ret);
    }

    /**
     * @Description: 后置异常通知 
     */
    @AfterThrowing("pointCut()")
    public void afterThrowing(JoinPoint jp){
        log.info("【注解:AfterThrowing】方法异常时执行.....");
    }

    /**
     * @Description: 后置最终通知,final增强,不管是抛出异常或者正常退出都会执行 
     */
    @After("pointCut()")
    public void after(JoinPoint jp){
        log.info("【注解:After】方法最后执行.....");
    }

    /**
     * @Description: 环绕通知,环绕增强 
     * @return
     */
    @Around("pointCut()")
    public Object around(ProceedingJoinPoint pjp) {
        log.info("【注解:Around . 环绕前】方法环绕start.....");
        try {
            //如果不执行这句,会不执行切面的Before方法及controller的业务方法
            Object o =  pjp.proceed();
            log.info("【注解:Around. 环绕后】方法环绕proceed,结果是 :" + o);
            return o;
        } catch (Throwable e) {
            e.printStackTrace();
            return null;
        }
    }
}
  • 自定义注解切入点
public @interface RecordLoggerAnnotation { 
    String value() default "";
}
  • 测试控制器
@RestController
@RequestMapping("/hello")
public class HelloController {
    private static final Logger log = LoggerFactory.getLogger(HelloController.class); 
    @GetMapping("/testone")
    @RecordLoggerAnnotation("testone")
    public MyReponse<?> testone(@RequestParam Map<String, Object> params) {
        log.info("params:{}", params);
        return success();
    } 
}
  • 测试单元
@Test
public void testone() throws Exception {
    String requestGET = requestGET("/hello/testone?name=123");
    log.info("===================rest:{}", requestGET);
}
正常执行的日志
2019-06-09 01:22:49.924  INFO 14436 --- [           main] bertram.wang.test.ApplicationTest        : Started ApplicationTest in 34.477 seconds (JVM running for 35.444)
2019-06-09 01:22:50.108 DEBUG 14436 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@16944b587 pairs: {GET /vueweb/hello/testone?name=123 HTTP/1.1: null}{Accept: text/plain, text/plain, application/json, application/json, application/*+json, application/*+json, */*, */*}{Content-type: application/json;charset=utf-8;Accept:application/json;}{Authorization: dcf08714-7cb6-451a-8dc1-56e2192a6933}{User-Agent: Java/1.8.0_191}{Host: localhost:55305}{Connection: keep-alive}
2019-06-09 01:22:50.804  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Around . 环绕前】方法环绕start.....
2019-06-09 01:22:50.804  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】------------------切面  before
2019-06-09 01:22:50.804  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】浏览器输入的网址=URL : http://localhost:55305/vueweb/hello/testone
2019-06-09 01:22:50.805  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】HTTP_METHOD : GET
2019-06-09 01:22:50.805  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】IP : 127.0.0.1
2019-06-09 01:22:50.806  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】执行的业务方法名=CLASS_METHOD : bertram.wang.vueweb.rest.HelloController.testone
2019-06-09 01:22:50.806  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Before】业务方法获得的参数=ARGS : [{name=123}]
2019-06-09 01:22:50.809  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.rest.HelloController       : params:{name=123}
2019-06-09 01:22:50.810  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:Around. 环绕后】方法环绕proceed,结果是 :MyReponse(code=0, message=成功, time=1560014570, data=null)
2019-06-09 01:22:50.810  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:After】方法最后执行.....
2019-06-09 01:22:50.811  INFO 14436 --- [o-auto-1-exec-1] b.wang.vueweb.acpect.RecordLoggerAcpect  : 【注解:AfterReturning】这个会在切面最后的最后打印,方法的返回值 : MyReponse(code=0, message=成功, time=1560014570, data=null)
2019-06-09 01:22:50.846 DEBUG 14436 --- [           main] s.n.www.protocol.http.HttpURLConnection  : sun.net.www.MessageHeader@1dbc607d7 pairs: {null: HTTP/1.1 200}{Access-Control-Allow-Headers: Authorization,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type}{Access-Control-Allow-Methods: OPTIONS,GET,POST,DELETE,PUT}{Access-Control-Allow-Credentials: true}{Content-Type: application/json;charset=UTF-8}{Transfer-Encoding: chunked}{Date: Sat, 08 Jun 2019 17:22:50 GMT}
2019-06-09 01:22:50.851  INFO 14436 --- [           main] bertram.wang.test.ApplicationTest        : ===================rest:{"code":0,"message":"成功","time":1560014570}

从日志打印不难看出执行的顺序: 环绕前--前置--控制器(被加强的方法)--环绕后--后置(没有异常所以后置异常通知没有执行)
最后就是把日志记录的业务逻辑修改 就行了。然后控制器的方法上添加注解即可。

Logo

前往低代码交流专区

更多推荐