• //本文作者:cuifuan
  • //本文将收录到菜单栏:《Spring全家桶》专栏中

面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP)。

OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面。

准备工作

首先,使用AOP要在build.gradle中加入依赖

  1. //引入AOP依赖
  2. compile "org.springframework.boot:spring-boot-starter-aop:${springBootVersion}"

然后在application.yml中加入

  1. spring:
  2. aop:
  3. proxy-target-class: true

1.@Pointcut 切入点

定义一个切点。

例如我们要在一个方法加上切入点,根据方法的返回的对象,方法名,修饰词来写成一个表达式或者是具体的名字

我们现在来定义一个切点

  1. package com.example.aop;
  2.  
  3. import org.aspectj.lang.annotation.Aspect;
  4. import org.aspectj.lang.annotation.Pointcut;
  5. import org.slf4j.Logger;
  6. import org.slf4j.LoggerFactory;
  7. import org.springframework.stereotype.Component;
  8.  
  9. /**
  10. * 类定义为切面类
  11. */
  12. @Aspect
  13. @Component
  14. public class AopTestController {
  15. private static final Logger logger = LoggerFactory.getLogger(AopTestController.class);
  16. /**
  17. * 定义一个切点
  18. */
  19. @Pointcut(value = "execution(public String test (..))")
  20. public void cutOffPoint() {
  21. }
  22. }

这里的切点定义的方法是

  1. @GetMapping("hello")
  2. public String test(){
  3. logger.info("欢迎关注Java知音");
  4. return "i love java";
  5. }

如果你想写个切入点在所有返回对象为Area的方法,如下
@Pointcut("execution(public com.example.entity.Area (..))")
等很多写法,也可以直接作用在某些包下
注意:private修饰的无法拦截

2.@Before前置通知

在切入点开始处切入内容

在之前的AopTestController类中加入对test方法的前置通知

  1. @Before("cutOffPoint()")
  2. public void beforeTest(){
  3. logger.info("我在test方法之前执行");
  4. }

这里@Before里的值就是切入点所注解的方法名

在方法左侧出现的图标跟过去以后就是所要通知的方法 这里就是配置正确了,我们来浏览器调用一下方法

联想一下,这样的效果可以用在哪里,想像如果要扩展一些代码,在不需要动源代码的基础之上就可以进行拓展,美滋滋

3.@After 后置通知

和前置通知相反,在切入点之后执行

  1. @After("cutOffPoint()")
  2. public void doAfter(){
  3. logger.info("我是在test之后执行的");
  4. }

控制台执行结果

这里定义一个通知需要重启启动类,而修改通知方法的内容是可以热部署的

4.@Around环绕通知

和前两个写法不同,实现的效果包含了前置和后置通知。

当使用环绕通知时,proceed方法必须调用,否则拦截到的方法就不会再执行了

环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的

  1. ThreadLocal<Long> startTime = new ThreadLocal<>();
  2. @Around("cutOffPoint()")
  3. public Object doAround(ProceedingJoinPoint pjp){
  4. startTime.set(System.currentTimeMillis());
  5. logger.info("我是环绕通知执行");
  6. Object obj;
  7. try{
  8. obj = pjp.proceed();
  9. logger.info("执行返回值 : " + obj);
  10. logger.info(pjp.getSignature().getName()+"方法执行耗时: " + (System.currentTimeMillis() - startTime.get()));
  11. } catch (Throwable throwable) {
  12. obj=throwable.toString();
  13. }
  14. return obj;
  15. }

执行结果:

1.环绕通知可以项目做全局异常处理
2.日志记录
3.用来做数据全局缓存
4.全局的事物处理 等

5.@AfterReturning

切入点返回结果之后执行,也就是都前置后置环绕都执行完了,这个就执行了

  1. /**
  2. * 执行完请求可以做的
  3. * @param result
  4. * @throws Throwable
  5. */
  6. @AfterReturning(returning = "result", pointcut = "cutOffPoint()")
  7. public void doAfterReturning(Object result) throws Throwable {
  8. logger.info("大家好,我是@AfterReturning,他们都秀完了,该我上场了");
  9. }

执行结果

应用场景可以用来在订单支付完成之后就行二次的结果验证,重要参数的二次校验,防止在方法执行中的时候参数被修改等等

6.@AfterThrowing

这个是在切入执行报错的时候执行

  1. // 声明错误e时指定的抛错类型法必会抛出指定类型的异常
  2. // 此处将e的类型声明为Throwable,对抛出的异常不加限制
  3. @AfterThrowing(throwing = "e",pointcut = "cutOffPoint()")
  4. public void doAfterReturning(Throwable e) {
  5. logger.info("大家好,我是@AfterThrowing,他们犯的错误,我来背锅");
  6. logger.info("错误信息"+e.getMessage());
  7. }

在其他切入内容中随意整个错误出来,制造一个环境。

下面是@AfterThrowing的执行结果

7.AOP用在全局异常处理

定义切入点拦截ResultBean或者PageResultBean

  1. @Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")
  2. public void handlerPageResultBeanMethod() {
  3. }
  4.  
  5. @Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")
  6. public void handlerResultBeanMethod() {
  7. }

下面是AopController.java

  1. package com.example.aop;
  2.  
  3. import com.example.beans.PageResultBean;
  4. import com.example.beans.ResultBean;
  5. import com.example.entity.UnloginException;
  6. import com.example.exception.CheckException;
  7. import org.aspectj.lang.ProceedingJoinPoint;
  8. import org.aspectj.lang.annotation.Around;
  9. import org.aspectj.lang.annotation.Aspect;
  10. import org.aspectj.lang.annotation.Pointcut;
  11. import org.slf4j.Logger;
  12. import org.slf4j.LoggerFactory;
  13. import org.springframework.stereotype.Component;
  14.  
  15. /**
  16. * 使用@Aspect注解将此类定义为切面类
  17. * 根据晓风轻著的ControllerAOP所修改
  18. * 晓风轻大佬(很大的佬哥了):https://xwjie.github.io/
  19. */
  20. @Aspect
  21. @Component
  22. public class AopController {
  23.  
  24. private static final Logger logger = LoggerFactory.getLogger(AopController.class);
  25.  
  26. ThreadLocal<ResultBean> resultBeanThreadLocal = new ThreadLocal<>();
  27. ThreadLocal<PageResultBean<?>> pageResultBeanThreadLocal = new ThreadLocal<>();
  28. ThreadLocal<Long> start = new ThreadLocal<>();
  29.  
  30. /**
  31. * 定义一个切点
  32. */
  33. @Pointcut(value = "execution(public com.example.beans.PageResultBean *(..)))")
  34. public void handlerPageResultBeanMethod() {
  35. }
  36.  
  37. @Pointcut(value = "execution(public com.example.beans.ResultBean *(..)))")
  38. public void handlerResultBeanMethod() {
  39. }
  40.  
  41. @Around("handlerPageResultBeanMethod()")
  42. public Object handlerPageResultBeanMethod(ProceedingJoinPoint pjp) {
  43. start.set(System.currentTimeMillis());
  44. try {
  45. pageResultBeanThreadLocal.set((PageResultBean<?>)pjp.proceed());
  46. logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get()));
  47. } catch (Throwable e) {
  48. ResultBean<?> resultBean = handlerException(pjp , e);
  49. pageResultBeanThreadLocal.set(new PageResultBean<>().setMsg(resultBean.getMsg()).setCode(resultBean.getCode()));
  50. }
  51. return pageResultBeanThreadLocal.get();
  52. }
  53.  
  54. @Around("handlerResultBeanMethod()")
  55. public Object handlerResultBeanMethod(ProceedingJoinPoint pjp) {
  56. start.set(System.currentTimeMillis());
  57. try {
  58. resultBeanThreadLocal.set((ResultBean<?>)pjp.proceed());
  59. logger.info(pjp.getSignature() + " 方法执行耗时:" + (System.currentTimeMillis() - start.get()));
  60. } catch (Throwable e) {
  61. resultBeanThreadLocal.set(handlerException(pjp , e));
  62. }
  63. return resultBeanThreadLocal.get();
  64. }
  65. /**
  66. * 封装异常信息,注意区分已知异常(自己抛出的)和未知异常
  67. */
  68. private ResultBean<?> handlerException(ProceedingJoinPoint pjp, Throwable e) {
  69.  
  70. ResultBean<?> result = new PageResultBean();
  71. logger.error(pjp.getSignature() + " error ", e);
  72.  
  73. // 已知异常
  74. if (e instanceof CheckException) {
  75. result.setMsg(e.getLocalizedMessage());
  76. result.setCode(ResultBean.FAIL);
  77. } else if (e instanceof UnloginException) {
  78. result.setMsg("Unlogin");
  79. result.setCode(ResultBean.NO_LOGIN);
  80. } else {
  81. result.setMsg(e.toString());
  82. result.setCode(ResultBean.FAIL);
  83. }
  84. return result;
  85. }
  86. }

用上面的环绕通知可以对所有返回ResultBean或者PageResultBean的方法进行切入,这样子就不用在业务层去捕捉错误了,只需要去打印自己的info日志。

看下面一段代码

  1. @Transactional
  2. @Override
  3. public int insertSelective(Area record) {
  4. record.setAddress("test");
  5. record.setPostalcode(88888);
  6. record.setType(3);
  7. int i=0;
  8. try {
  9. i = areaMapper.insertSelective(record);
  10. }catch (Exception e){
  11. logger.error("AreaServiceImpl insertSelective error:"+e.getMessage());
  12. }
  13. return i;
  14. }

假如上面的插入操作失败出错了? 你认为会回滚吗?

答案是:不会。

为什么?

因为你把错误捕捉了,事物没检测到异常就不会回滚。

那么怎么才能回滚呢?

在catch里加throw new RuntimeException().

可是那么多业务方法每个设计修改的操作都加,代码繁琐,怎么进行处理呢?

在这里用到上面的AOP切入处理,错误不用管,直接抛,抛到控制层进行处理,这样的话,接口调用的时候,出错了,接口不会什么都不返回,而是会返回给你错误代码,以及错误信息,便于开发人员查错。

8.以上用的是log4j2的日志处理

先移除springboot自带的log日志处理

在build.gradle中增加

  1. configurations {
  2. providedRuntime
  3. // 去除SpringBoot自带的日志
  4. all*.exclude group: 'org.springframework.boot', module: 'spring-boot-starter-logging'
  5. }
  6. ext {
  7. springBootVersion = '2.0.1.RELEASE'
  8. }
  9. dependencies {
  10. compile "org.springframework.boot:spring-boot-starter-log4j2:${springBootVersion}"
  11. }

然后在application.yml中增加

  1. logging:
  2. level:
  3. com:
  4. example:
  5. dao: debug
  6. config: classpath:log4j2-spring.xml

log4j2-spring.xml

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
  3. <!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
  4. <!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
  5. <configuration status="INFO" monitorInterval="30">
  6. <!--先定义所有的appender-->
  7. <appenders>
  8. <!--这个输出控制台的配置-->
  9. <console name="Console" target="SYSTEM_OUT">
  10. <!--输出日志的格式-->
  11. <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
  12. </console>
  13.  
  14. <!--文件会打印出所有信息,这个log每次运行程序会自动清空,由append属性决定,这个也挺有用的,适合临时测试用-->
  15. <File name="Test" fileName="logs/test.log" append="false">
  16. <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
  17. </File>
  18.  
  19. <RollingFile name="RollingFileInfo" fileName="logs/log.log" filePattern="logs/info.log.%d{yyyy-MM-dd}">
  20. <!-- 只接受level=INFO以上的日志 -->
  21. <ThresholdFilter level="info" onMatch="ACCEPT" onMismatch="DENY"/>
  22. <PatternLayout pattern="%highlight{[ %p ] [%-d{yyyy-MM-dd HH:mm:ss}] [ LOGID:%X{logid} ] [%l] %m%n}"/>
  23. <Policies>
  24. <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
  25. <SizeBasedTriggeringPolicy/>
  26. </Policies>
  27. </RollingFile>
  28.  
  29. <RollingFile name="RollingFileError" fileName="logs/error.log" filePattern="logs/error.log.%d{yyyy-MM-dd}">
  30. <!-- 只接受level=WARN以上的日志 -->
  31. <Filters>
  32. <ThresholdFilter level="warn" onMatch="ACCEPT" onMismatch="DENY" />
  33. </Filters>
  34. <PatternLayout pattern="%highlight{[ %p ] %-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] [%l] %m%n}"/>
  35. <Policies>
  36. <TimeBasedTriggeringPolicy modulate="true" interval="1"/>
  37. <SizeBasedTriggeringPolicy/>
  38. </Policies>
  39. </RollingFile>
  40.  
  41. </appenders>
  42.  
  43. <!--然后定义logger,只有定义了logger并引入的appender,appender才会生效-->
  44. <loggers>
  45. <!--过滤掉spring和mybatis的一些无用的DEBUG信息-->
  46. <logger name="org.springframework" level="INFO"></logger>
  47. <logger name="org.mybatis" level="INFO"></logger>
  48. <root level="all">
  49. <appender-ref ref="Console"/>
  50. <appender-ref ref="Test"/>
  51. <appender-ref ref="RollingFileInfo"/>
  52. <appender-ref ref="RollingFileError"/>
  53. </root>
  54. </loggers>
  55. </configuration>

之后在你要打印日志的类中增加

  1. private static final Logger logger = LoggerFactory.getLogger(你的类名.class);
  2.  
  3. public static void main(String[] args) {
  4. logger.error("error级别日志");
  5. logger.warn("warning级别日志");
  6. logger.info("info级别日志");
  7. }

有了日志后就很方便了,在你的方法接收对象时打印下,然后执行了逻辑之后打印下, 出错之后很明确了,就会很少去Debug的,养成多打日志的好习惯,多打印一点info级别的日志,用来在开发环境使用,在上线的时候把打印的最低级别设置为warning,这样你的info级别日志也不会影响到项目的重要Bug的打印

写这个博客的时候我也在同时跑着这个项目,有时候会出现一些错误,例如jar包版本,业务层引用无效,AOP设置不生效等等,也同时在排查解决,如果你遇到了同样的错误,可以去我的GitHub联系我,如小弟有时间或许也能帮到你,谢谢

Github地址:https://github.com/cuifuan

Spring全家桶系列–SpringBoot之AOP详解的更多相关文章

  1. Spring全家桶系列–SpringBoot渐入佳境

    //本文作者:cuifuan //本文将收录到菜单栏:<Spring全家桶>专栏中 首发地址:https://www.javazhiyin.com/20913.html 萌新:小哥,我在实 ...

  2. Spring全家桶系列–[SpringBoot入门到跑路]

    //本文作者:cuifuan Spring全家桶————[SpringBoot入门到跑路] 对于之前的Spring框架的使用,各种配置文件XML.properties一旦出错之后错误难寻,这也是为什么 ...

  3. Spring全家桶系列–SpringBoot与Mybatis结合

    //本文作者:cuifuan Mybatis 是一个持久层ORM框架,负责Java与数据库数据交互,也可以简易理解为中介,相对于它,还有个中介是hibernate,不过在mybatis中sql语句的灵 ...

  4. Spring全家桶系列–SpringBoot之入门JPA

    //本文作者:cuifuan 什么是JPA? 一种规范,并非ORM框架,也就是ORM上统一的规范 用了之后可以做什么,为什么要用? 代码解释: 实体类 package com.example.spri ...

  5. Spring全家桶——SpringBoot之AOP详解

    Spring全家桶--SpringBoot之AOP详解 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方 ...

  6. Spring全家桶一一SpringBoot与Mybatis

    Spring全家桶系列一一SpringBoot与Mybatis结合 本文授权"Java知音"独家发布. Mybatis 是一个持久层ORM框架,负责Java与数据库数据交互,也可以 ...

  7. Spring之旅第五篇-AOP详解

    一.什么是AOP? Aspect oritention programming(面向切面编程),AOP是一种思想,高度概括的话是“横向重复,纵向抽取”,如何理解呢?举个例子:访问页面时需要权限认证,如 ...

  8. 深入浅出学习Spring框架(三):AOP 详解

    AOP的英文解释——AOPAspect Oriented Programming面向切面编程.主要目的是通过预编译方式和运行期动态代理实现在不修改源代码的情况下给程序动态统一添加功能的一种技术. 在反 ...

  9. Spring框架 之IOC容器 和AOP详解

    主要分析点: 一.Spring开源框架的简介  二.Spring下IOC容器和DI(依赖注入Dependency injection) 三.Spring下面向切面编程(AOP)和事务管理配置  一.S ...

随机推荐

  1. android-glsurfaceview Activity框架程序

    两个基本的类让我们使用OpenGL ES API来创建和操纵图形:GLSurfaceView和 GLSurfaceView.Renderer. 1. GLSurfaceView: 这是一个视图类,你可 ...

  2. Android-Java-子类实例化过程(内存图)

    案例一: package android.java.oop15; // 描述Person对象 class Person { // 构造方法就算不写 默认有一个隐式的无参构造方法:public Pers ...

  3. 开源性能测试工具Locust使用篇(一)

    1. 环境准备 安装python3.6 ,安装步骤略 pip install locust 安装完成后使用locust -V检查 2.locust使用,先编辑一个简单的load_test.py的脚本 ...

  4. [源码]Dephi溢出demo( Shellcode for XP)

    [源码]Dephi溢出demo( Shellcode for XP) unit Unit1; interface uses Windows, Messages, SysUtils, Variants, ...

  5. linux中一些简便的命令之tr

    tr是个简单字符处理命令,主要有以下几个用法: 1.替换字符: echo "hello,world" | tr 'a-z' 'A-Z' 执行结果:HELLO,WORLD 注释:这里 ...

  6. Linux编程 3 (初识bash shell与man查看手册)

    一.初识bash shell 1.1 启动 shell   GNU bash shell 能提供对Linux系统的交互式访问.通常是在用户登录终端时启动,登录时系统启动shell依赖于用户账户的配置. ...

  7. Spring Data JPA例子[基于Spring Boot、Mysql]

    关于Spring Data Spring社区的一个顶级工程,主要用于简化数据(关系型&非关系型)访问,如果我们使用Spring Data来开发程序的话,那么可以省去很多低级别的数据访问操作,如 ...

  8. salesforce零基础学习(八十九)使用 input type=file 以及RemoteAction方式上传附件

    在classic环境中,salesforce提供了<apex:inputFile>标签用来实现附件的上传以及内容获取.salesforce 零基础学习(二十四)解析csv格式内容中有类似的 ...

  9. quartz配置参数org.quartz.jobStore.misfireThreshold含义解释

    配置定时任务参数 quartz.properties文件时 需要配置jobStore的超过时间数 默认为60秒(这里单位为毫秒) org.quartz.jobStore.misfireThreshol ...

  10. docker化java web应用

    一.简介 Docker是一个使用Go语言开发的开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的机器上.Docker的发展速度和火爆程度着实令人惊叹,一 ...