说起spring,我们知道其最核心的两个功能就是AOP(面向切面)和IOC(控制反转),这边文章来总结一下SpringBoot如何整合使用AOP。

一、示例应用场景:对所有的web请求做切面来记录日志。

1、pom中引入SpringBoot的web模块和使用AOP相关的依赖:


其中:
cglib包是用来动态代理用的,基于类的代理;
aspectjrt和aspectjweaver是与aspectj相关的包,用来支持切面编程的;
aspectjrt包是aspectj的runtime包;
aspectjweaver是aspectj的织入包;

2、实现一个简单的web请求入口(实现传入name参数,返回“hello xxx”的功能):

注意:在完成了引入AOP依赖包后,一般来说并不需要去做其他配置。使用过Spring注解配置方式的人会问是否需要在程序主类中增加@EnableAspectJAutoProxy来启用,实际并不需要。

因为在AOP的默认配置属性中,spring.aop.auto属性默认是开启的,也就是说只要引入了AOP依赖后,默认已经增加了@EnableAspectJAutoProxy。

3、定义切面类,实现web层的日志切面

要想把一个类变成切面类,需要两步,
① 在类上使用 @Component 注解 把切面类加入到IOC容器中
② 在类上使用 @Aspect 注解 使之成为切面类

package com.example.aop;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;/*** Created by lmb on 2018/9/5.*/
@Aspect
@Component
public class WebLogAcpect {private Logger logger = LoggerFactory.getLogger(WebLogAcpect.class);/*** 定义切入点,切入点为com.example.aop下的所有函数*/@Pointcut("execution(public * com.example.aop..*.*(..))")public void webLog(){}/*** 前置通知:在连接点之前执行的通知* @param joinPoint* @throws Throwable*/@Before("webLog()")public void doBefore(JoinPoint joinPoint) throws Throwable {// 接收到请求,记录请求内容ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = attributes.getRequest();// 记录下请求内容logger.info("URL : " + request.getRequestURL().toString());logger.info("HTTP_METHOD : " + request.getMethod());logger.info("IP : " + request.getRemoteAddr());logger.info("CLASS_METHOD : " + joinPoint.getSignature().getDeclaringTypeName() + "." + joinPoint.getSignature().getName());logger.info("ARGS : " + Arrays.toString(joinPoint.getArgs()));}@AfterReturning(returning = "ret",pointcut = "webLog()")public void doAfterReturning(Object ret) throws Throwable {// 处理完请求,返回内容logger.info("RESPONSE : " + ret);}
}

以上的切面类通过 @Pointcut定义的切入点为com.example.aop包下的所有函数做切人,通过 @Before实现切入点的前置通知,通过 @AfterReturning记录请求返回的对象。

访问http://localhost:8004/hello?name=lmb得到控制台输出如下:

详细代码参见本人的Github:SpringBoot整合AOP

二、AOP支持的通知
1、前置通知@Before:在某连接点之前执行的通知,除非抛出一个异常,否则这个通知不能阻止连接点之前的执行流程。

/** * 前置通知,方法调用前被调用 * @param joinPoint/null*/
@Before(value = POINT_CUT)
public void before(JoinPoint joinPoint){logger.info("前置通知");//获取目标方法的参数信息  Object[] obj = joinPoint.getArgs();  //AOP代理类的信息  joinPoint.getThis();  //代理的目标对象  joinPoint.getTarget();  //用的最多 通知的签名  Signature signature = joinPoint.getSignature();  //代理的是哪一个方法  logger.info("代理的是哪一个方法"+signature.getName());  //AOP代理类的名字  logger.info("AOP代理类的名字"+signature.getDeclaringTypeName());  //AOP代理类的类(class)信息  signature.getDeclaringType();  //获取RequestAttributes  RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();  //从获取RequestAttributes中获取HttpServletRequest的信息  HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);  //如果要获取Session信息的话,可以这样写:  //HttpSession session = (HttpSession) requestAttributes.resolveReference(RequestAttributes.REFERENCE_SESSION);  //获取请求参数Enumeration<String> enumeration = request.getParameterNames();  Map<String,String> parameterMap = Maps.newHashMap();  while (enumeration.hasMoreElements()){  String parameter = enumeration.nextElement();  parameterMap.put(parameter,request.getParameter(parameter));  }  String str = JSON.toJSONString(parameterMap);  if(obj.length > 0) {  logger.info("请求的参数信息为:"+str);}
}

注意:这里用到了JoinPoint和RequestContextHolder。
1)、通过JoinPoint可以获得通知的签名信息,如目标方法名、目标方法参数信息等;
2)、通过RequestContextHolder来获取请求信息,Session信息;

2、后置通知@AfterReturning:在某连接点之后执行的通知,通常在一个匹配的方法返回的时候执行(可以在后置通知中绑定返回值)。

/** * 后置返回通知 * 这里需要注意的是: *      如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息 *      如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数 *       returning:限定了只有目标方法返回值与通知方法相应参数类型时才能执行后置返回通知,否则不执行,*       对于returning对应的通知方法参数为Object类型将匹配任何目标返回值 * @param joinPoint * @param keys */
@AfterReturning(value = POINT_CUT,returning = "keys")
public void doAfterReturningAdvice1(JoinPoint joinPoint,Object keys){  logger.info("第一个后置返回通知的返回值:"+keys);
}  @AfterReturning(value = POINT_CUT,returning = "keys",argNames = "keys")
public void doAfterReturningAdvice2(String keys){  logger.info("第二个后置返回通知的返回值:"+keys);
}

3、后置异常通知@AfterThrowing:在方法抛出异常退出时执行的通知。

/** * 后置异常通知 *  定义一个名字,该名字用于匹配通知实现方法的一个参数名,当目标方法抛出异常返回后,将把目标方法抛出的异常传给通知方法; *  throwing:限定了只有目标方法抛出的异常与通知方法相应参数异常类型时才能执行后置异常通知,否则不执行, *           对于throwing对应的通知方法参数为Throwable类型将匹配任何异常。 * @param joinPoint * @param exception */
@AfterThrowing(value = POINT_CUT,throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint,Throwable exception){  //目标方法名:  logger.info(joinPoint.getSignature().getName());  if(exception instanceof NullPointerException){  logger.info("发生了空指针异常!!!!!");  }
}  

4、后置最终通知@After:当某连接点退出时执行的通知(不论是正常返回还是异常退出)。

/** * 后置最终通知(目标方法只要执行完了就会执行后置通知方法) * @param joinPoint */
@After(value = POINT_CUT)
public void doAfterAdvice(JoinPoint joinPoint){ logger.info("后置最终通知执行了!!!!");
}  

5、环绕通知@Around:包围一个连接点的通知,如方法调用等。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点或者直接返回它自己的返回值或抛出异常来结束执行。

环绕通知最强大,也最麻烦,是一个对方法的环绕,具体方法会通过代理传递到切面中去,切面中可选择执行方法与否,执行几次方法等。环绕通知使用一个代理ProceedingJoinPoint类型的对象来管理目标对象,所以此通知的第一个参数必须是ProceedingJoinPoint类型。在通知体内调用ProceedingJoinPoint的proceed()方法会导致后台的连接点方法执行。proceed()方法也可能会被调用并且传入一个Object[]对象,该数组中的值将被作为方法执行时的入参。

/** * 环绕通知: *   环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。 *   环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型 */
@Around(value = POINT_CUT)
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){  logger.info("环绕通知的目标方法名:"+proceedingJoinPoint.getSignature().getName());  try {  Object obj = proceedingJoinPoint.proceed();  return obj;  } catch (Throwable throwable) {  throwable.printStackTrace();  }  return null;
}  

6、有时候我们定义切面的时候,切面中需要使用到目标对象的某个参数,如何使切面能得到目标对象的参数呢?可以使用args来绑定。如果在一个args表达式中应该使用类型名字的地方使用一个参数名字,那么当通知执行的时候对象的参数值将会被传递进来。

@Before("execution(* findById*(..)) &&" + "args(id,..)")public void twiceAsOld1(Long id){System.err.println ("切面before执行了。。。。id==" + id);}

注意:任何通知方法都可以将第一个参数定义为org.aspectj.lang.JoinPoint类型(环绕通知需要定义第一个参数为ProceedingJoinPoint类型,它是 JoinPoint 的一个子类)。JoinPoint接口提供了一系列有用的方法,比如 getArgs()(返回方法参数)、getThis()(返回代理对象)、getTarget()(返回目标)、getSignature()(返回正在被通知的方法相关信息)和 toString()(打印出正在被通知的方法的有用信息)。

三、切入点表达式
定义切入点的时候需要一个包含名字和任意参数的签名,还有一个切入点表达式,如execution(public * com.example.aop...(..))

切入点表达式的格式:execution([可见性]返回类型[声明类型].方法名(参数)[异常])
其中[]内的是可选的,其它的还支持通配符的使用:
1) *:匹配所有字符
2) ..:一般用于匹配多个包,多个参数
3) +:表示类及其子类
4)运算符有:&&,||,!

切入点表达式关键词用例:
1)execution:用于匹配子表达式。
//匹配com.cjm.model包及其子包中所有类中的所有方法,返回类型任意,方法参数任意
@Pointcut(“execution(* com.cjm.model...(..))”)
public void before(){}

2)within:用于匹配连接点所在的Java类或者包。
//匹配Person类中的所有方法
@Pointcut(“within(com.cjm.model.Person)”)
public void before(){}
//匹配com.cjm包及其子包中所有类中的所有方法
@Pointcut(“within(com.cjm..*)”)
public void before(){}

3) this:用于向通知方法中传入代理对象的引用。
@Before(“before() && this(proxy)”)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}

4)target:用于向通知方法中传入目标对象的引用。
@Before(“before() && target(target)
public void beforeAdvide(JoinPoint point, Object proxy){
//处理逻辑
}

5)args:用于将参数传入到通知方法中。
@Before(“before() && args(age,username)”)
public void beforeAdvide(JoinPoint point, int age, String username){
//处理逻辑
}

6)@within :用于匹配在类一级使用了参数确定的注解的类,其所有方法都将被匹配。
@Pointcut(“@within(com.cjm.annotation.AdviceAnnotation)”)
- 所有被@AdviceAnnotation标注的类都将匹配
public void before(){}

7)@target :和@within的功能类似,但必须要指定注解接口的保留策略为RUNTIME。
@Pointcut(“@target(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}

8)@args :传入连接点的对象对应的Java类必须被@args指定的Annotation注解标注。
@Before(“@args(com.cjm.annotation.AdviceAnnotation)”)
public void beforeAdvide(JoinPoint point){
//处理逻辑
}

9)@annotation :匹配连接点被它参数指定的Annotation注解的方法。也就是说,所有被指定注解标注的方法都将匹配。
@Pointcut(“@annotation(com.cjm.annotation.AdviceAnnotation)”)
public void before(){}

10)bean:通过受管Bean的名字来限定连接点所在的Bean。该关键词是Spring2.5新增的。
@Pointcut(“bean(person)”)
public void before(){}

参考资料:https://www.cnblogs.com/lic309/p/4079194.html

【SpringBoot】SpingBoot整合AOP相关推荐

  1. java切面类整合_SpringBoot2.x【五】整合AOP切面编程

    SpringBoot2.x[五]整合AOP切面编程 面向方面编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象编程(OOP). OOP中模块化的关键单元是类,而在AOP中,模块化单元是方面. ...

  2. 十、springboot注解式AOP(@Aspect)统一日志管理

    springboot注解式AOP(@Aspect)统一日志管理 简介 AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功 ...

  3. springboot+security整合(1)

    说明 springboot 版本 2.0.3 源码地址:点击跳转 系列 springboot+security 整合(1) springboot+security 整合(2) springboot+s ...

  4. spingBoot整合mybatis+generator+pageHelper

    spingBoot整合mybatis+generator+pageHelper 环境/版本一览: 开发工具:Intellij IDEA 2018.1.4 springboot: 2.0.4.RELEA ...

  5. Springboot -Shiro整合JWT(注解形式)

    Springboot -Shiro整合JWT(注解形式) 在这里只展示核心代码,具体的请访问github 参考timo 依赖导入 <dependencies><dependency& ...

  6. 小册上新 | 掌握 SpringBoot 场景整合,成为开发多面手!

    只会 SpringBoot 还远远不够 SpringBoot 的强大不言而喻,其底层 SpringFramework 强大的 IOC 容器和 AOP 机制,加之 SpringBoot 的自动装配,使得 ...

  7. SpringBoot:整合Redis(概述,数据类型,持久化,RedisTemplate)

    1,Redis概述 1.1,Redis基本概念 在传统的Java Web项目中,使用数据库进行存储数据,但是有一些致命的弊端,这些弊端主要来自于性能方面.比如一些商品抢购的场景,或者是主页访问量瞬间较 ...

  8. 【Spring Boot】整合 AOP

    认识AOP 1.1 什么是AOP 1.2 AOP中的概念 SpringBoot整合AOP代码示例 2.1 使用execution(路径表达式) 2.2 使用annotation(注解) JoinPoi ...

  9. Docker 部署 SpringBoot 项目整合 Redis 镜像做访问计数Demo

    Docker 部署SpringBoot项目整合 Redis 镜像做访问计数Demo 最终效果如下 大概就几个步骤 1.安装 Docker CE 2.运行 Redis 镜像 3.Java 环境准备 4. ...

最新文章

  1. 在深度学习的路上,哪些框架或学习平台值得推荐?
  2. Mysql 查询统计练习
  3. 简单的测试可以防止最严重的故障
  4. 理解交换机通过逆向自学习算法建立地址转发表的过程_交换机与 VLAN 到底是怎么来的...
  5. 3D图像生成和编辑研究成果大放送!朱俊彦团队放出两篇论文实现代码 | 资源...
  6. Collection如何转成stream以及Spliterator对其操作的实现
  7. [使用心得]maven2之m2eclipse使用手册之二m2eclipse功能介绍
  8. linux下不是很完美的提高android虚拟机的启动速度
  9. 占位棋 python_Python开发象棋小游戏(总体思路分析)
  10. matlab $r$n$m,维纳滤波器推导以及MATLAB代码(Wiener Filter)
  11. 消费金融公司可开展哪些业务类型?
  12. 中望3d快捷键命令大全_3d快捷键怎么设置|中望3D快捷键设置
  13. 八皇后问题(详解带注释)
  14. QQ空间相册照片批量导出
  15. 计算机表格复制粘贴,在Excel同一个工作表中,如何复制表格格式(excel表格粘贴复制技巧)...
  16. 1.3 可移植性和标准
  17. Unbuntu下U盘突然权限只读,无法重命名和复制粘贴文件的问题修复
  18. python用cartopy包画地图_python绘制地图的利器Cartopy使用说明
  19. Flutter开发日常练习-小猫咪杂货店(新增欢迎页,广告页和侧滑页面)
  20. c#qq群 群号:11069698 欢迎喜欢和爱好c#的朋友加入!

热门文章

  1. nginx for discuz 伪静态规则
  2. configure: error: Please fix the library issues listed above and try again.解决方案
  3. 《Effective C#》Item 17:减少装箱(Boxing)和拆箱(Unboxing)操作
  4. MYSQL 表锁情况查看
  5. 【性能优化】之 表分析及动态采样
  6. 出现ORA - 1017用户名/口令无效; 登录被拒绝 的问题
  7. Android多开和虚拟化--Docker概念的详细介绍
  8. android Context的使用
  9. Typeface 字体样式
  10. 【Android.mk】android编译系统makefile文件Android.mk的写法