SpringBoot + Vue 后台管理系统(六):系统操作日志记录(AOP)

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}

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,793评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,567评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,342评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,825评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,814评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,680评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,033评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,687评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,175评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,668评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,775评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,419评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,020评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,206评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,092评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,510评论 2 343

推荐阅读更多精彩内容