一、What is AOP ?

传统的OOP开发中的代码逻辑是自上而下的,而这些过程会产生一些横切性问题,这些横切性的问题和我们的业务逻辑关系不大,这些横切性问题不会影响到主逻辑实现的,但是会散落到代码的各个部分,难以维护。AOP是处理一些横切性问题,AOP的编程思想就是把这些问题和主业务逻辑分开,达到与主业务逻辑解耦的目的。使代码的重用性和开发效率更高。
Aop是一种概念,SpringAop、AspectJ都是Aop的实现,SpringAop有自己的语法,但是语法复杂,所以SpringAop借助了AspectJ的注解,但是底层实现还是自己的。

二、应用场景

  1. 日志记录
  2. 权限验证
  3. 效率检查
  4. 事务管理
  5. exception

三、底层技术

JDK动态代理 CGLIB代理
编译时期织入还是运行时期织入 运行期 运行期
初始化时期织入还是获取对象时期织入 初始化 初始化

四、AOP Concepts

Aspect:切面,切点的载体,一定要给spring去管理
PointCut:切点表示连接点的集合
PointCutJoinPoint__的谓语,这是一个动作,主要是告诉通知连接点在哪里,切点表达式决定JoinPoint 的数量
JoinPoint:连接点 目标对象中的方法
JoinPoint__是要关注和增强的方法,也就是我们要作用的点
Weaving:把代理逻辑加入到目标对象上的过程叫做织入
Target:目标对象 原始对象
Aop Proxy:代理对象 包含了原始对象的代码和增加后的代码的那个对象
Advice:通知 位置

  1. /**
  2. * Advice通知类型:
  3. * Before 连接点执行之前,但是无法阻止连接点的正常执行,除非该段执行抛出异常
  4. * After 连接点正常执行之后,执行过程中正常执行返回退出,非异常退出
  5. * After throwing 执行抛出异常的时候
  6. * After (finally) 无论连接点是正常退出还是异常退出,都会执行
  7. * Around通知可以在方法调用之前和之后执行自定义行为。它需要用到ProceedingJoinPoint 类。这是最有用的切面方式。
  8. */
  1. /**
  2. * ProceedingJoinPoint 和JoinPoint的区别:
  3. * Proceedingjoinpoint 继承了JoinPoint
  4. * JoinPoint仅能获取相关参数,无法执行连接点。而Proceedingjoinpoint还可以用proceed()执行目标对象的方法
  5. * JoinPoint的方法
  6. * 1.java.lang.Object[] getArgs():获取连接点方法运行时的入参列表;
  7. * 2.Signature getSignature() :获取连接点的方法签名对象;
  8. * 3.java.lang.Object getTarget() :获取连接点所在的目标对象;
  9. * 4.java.lang.Object getThis() :获取代理对象本身;
  10. * proceed()有重载,有个带参数的方法,可以修改目标方法的的参数
  11. */

Introductions 引入、扩展

  1. #声明一个父类,默认实现是UserDaoImpl.class,这样只要dao包下的类其实都引入了Dao接口,
  2. #那么对该接口方法怎么办呐?这就用到defaultImpl,它给你提供默认实现即UserDaoImpl
  3. #这样其他类也可以调用UserDaoImpl里的方法
  4. @DeclareParent(value="com.example.dao.*",defaultImpl=UserDaoImpl.class)
  5. public static Dao dao;

Aspect 由于交给spring容器管理,所以它是单例的,这样对于多线程环境下就会出现问题。解决办法这里给出官网说明:Spring supports AspectJ’s perthis and pertarget instantiation models
Spring 官网提供了perthispertarget

  1. /**
  2. * 要求:
  3. * 1.AspectJ对象的注入类型为prototype
  4. * 2.目标对象也必须是prototype的
  5. * 原因为:只有目标对象是原型模式的,每次getBean得到的对象才是不一样的,
  6. * 由此针对每个对象就会产生新的切面对象,才能产生不同的切面结果。
  7. * 使用方式如下:
  8. * perthis表示限定在这个com.chenss.dao.IndexDaoImpl切面
  9. @Aspect("perthis(this(com.chenss.dao.IndexDaoImpl))")
  10. @Scope("prototype")

五、Spring AOP支持AspectJ

Spring AOP提供两种编程风格

方式 解释
@AspectJ support 利用aspectj的注解
Schema-based AOP support xml aop:config 命名空间

1、启用@AspectJ支持

要使用Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy注释

  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. public class AppConfig {
  4. }

要使用基于xml的配置启用@AspectJ支持,可以使用aop:aspectj-autoproxy元素

  1. <aop:aspectj-autoproxy/>

2、声明一个Aspect

申明一个@Aspect注释类,并且定义成一个bean交给Spring管理。

  1. @Component
  2. @Aspect
  3. public class UserAspect {
  4. }

3、声明一个PointCut

切入点表达式由@Pointcut注释表示,切入点声明由两部分组成:
1.一个签名其实就是空方法。
2.一个切入点表达式。该表达式确定我们对哪个方法执行感兴趣。

  1. @Pointcut("execution(* transfer(..))")// 切入点表达式
  2. private void anyOldTransfer() {}// 切入点签名

4、声明一个Advice

Advice通知与PointCut切入点表达式相关联,并在切入点匹配的方法执行@Before之前、@After之后或前后运行。

  1. /**
  2. * 申明Aspect,并且交给spring容器管理
  3. */
  4. @Component
  5. @Aspect
  6. public class UserAspect {
  7. /**
  8. * 申明切入点,匹配UserDao所有方法调用
  9. * execution匹配方法执行连接点
  10. * within:将匹配限制为特定类型中的连接点
  11. * args:参数
  12. * target:目标对象
  13. * this:代理对象
  14. */
  15. @Pointcut("execution(* com.yao.dao.UserDao.*(..))")
  16. public void pintCut(){
  17. System.out.println("point cut");
  18. }
  19. /**
  20. * 申明before通知,在pintCut切入点前执行
  21. * 通知与切入点表达式相关联,
  22. * 并在切入点匹配的方法执行之前、之后或前后运行。
  23. * 切入点表达式可以是对指定切入点的简单引用,也可以是在适当位置声明的切入点表达式。
  24. */
  25. @Before("pintCut()")
  26. public void beforeAdvice(){
  27. System.out.println("before");
  28. }
  29. }

六、连接点表达式

1、execution

由于Spring切面粒度最小是达到方法级别,而execution表达式可以用于明确指定方法返回类型,类名,方法名和参数名等与方法相关的信息,并且在Spring中,大部分需要使用AOP的业务场景也只需要达到方法级别即可,因而execution表达式的使用是最为广泛的。

  1. /**
  2. * execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
  3. * 这里问号表示当前项可以有也可以没有,其中各项的语义如下:
  4. * modifiers-pattern:方法的可见性,如public,protected
  5. * ret-type-pattern:方法的返回值类型,如int,void等
  6. * declaring-type-pattern:方法所在类的全路径名,如com.spring.Aspect
  7. * name-pattern:方法名类型,如buisinessService()
  8. * param-pattern:方法的参数类型,如java.lang.String
  9. * throws-pattern:方法抛出的异常类型,如java.lang.Exception
  10. * example:
  11. */
  12. //匹配com.chenss.dao包下的任意接口和类的任意方法
  13. @Pointcut("execution(* com.chenss.dao.*.*(..))")
  14. //匹配com.chenss.dao包下的任意接口和类的public方法
  15. @Pointcut("execution(public * com.chenss.dao.*.*(..))")
  16. //匹配com.chenss.dao包下的任意接口和类的public 无方法参数的方法
  17. @Pointcut("execution(public * com.chenss.dao.*.*())")
  18. //匹配com.chenss.dao包下的任意接口和类的第一个参数为String类型的方法
  19. @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String, ..))")
  20. //匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
  21. @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")
  22. //匹配com.chenss.dao包下的任意接口和类的只有一个参数,且参数为String类型的方法
  23. @Pointcut("execution(* com.chenss.dao.*.*(java.lang.String))")
  24. //匹配任意的public方法
  25. @Pointcut("execution(public * *(..))")
  26. //匹配任意的以te开头的方法
  27. @Pointcut("execution(* te*(..))")
  28. //匹配com.chenss.dao.IndexDao接口中任意的方法
  29. @Pointcut("execution(* com.chenss.dao.IndexDao.*(..))")
  30. //匹配com.chenss.dao包及其子包中任意的方法
  31. @Pointcut("execution(* com.chenss.dao..*.*(..))")

2、within

  1. //within与execution相比,粒度更大,仅能实现到包和接口、类级别。而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等
  2. @Pointcut("within(com.chenss.dao.*)")//匹配com.chenss.dao包中的任意方法
  3. @Pointcut("within(com.chenss.dao..*)")//匹配com.chenss.dao包及其子包中的任意方法

3、args

  1. /**
  2. * args表达式的作用是匹配指定参数类型和指定参数数量的方法,与包名和类名无关
  3. * args同execution不同的地方在于:
  4. * args匹配的是运行时传递给方法的参数类型
  5. * execution(* *(java.io.Serializable))匹配的是方法在声明时指定的方法参数类型。
  6. */
  7. @Pointcut("args(java.io.Serializable)")//匹配运行时传递的参数类型为指定类型的、且参数个数和顺序匹配
  8. @Pointcut("@args(com.chenss.anno.Chenss)")//接受一个参数,并且传递的参数的运行时类型具有@Classified

4、this和target

  1. /**
  2. * this JDK代理时,指向接口和代理类proxy,cglib代理时 指向接口和子类(不使用proxy)
  3. * arget 指向接口和子类
  4. *
  5. * Spring AOP defaults to using standard JDK dynamic proxies for AOP proxies. This enables any interface (or set of interfaces) to be proxied.
  6. * 此处需要注意的是,如果配置设置proxyTargetClass=false,默认为false,则是用JDK代理,否则使用的是CGLIB代理
  7. * JDK代理的实现方式是基于接口实现,代理类继承Proxy,实现接口。
  8. * 而CGLIB继承被代理的类来实现。
  9. * 所以使用target会保证目标不变,关联对象不会受到这个设置的影响。
  10. * 但是使用this对象时,会根据该选项的设置,判断是否能找到对象。
  11. */
  12. //目标对象,也就是被代理的对象。限制目标对象为com.chenss.dao.IndexDaoImpl类
  13. @Pointcut("target(com.chenss.dao.IndexDaoImpl)")
  14. //当前对象,也就是代理对象,代理对象时通过代理目标对象的方式获取新的对象,与原值并非一个
  15. @Pointcut("this(com.chenss.dao.IndexDaoImpl)")
  16. //具有@Chenss的目标对象中的任意方法
  17. @Pointcut("@target(com.chenss.anno.Chenss)")
  18. //等同于@target
  19. @Pointcut("@within(com.chenss.anno.Chenss)")

5、@annotation

  1. /**
  2. * 这个很简单,作用方法级别,表示所有加了xx注解的类,和包名无关)
  3. * 注意:上述所有的表达式可以混合使用 (|| && !)
  4. */
  5. @Retention(RetentionPolicy.RUNTIME)
  6. public @interface Chenss{
  7. }
  8. //匹配带有com.chenss.anno.Chenss注解的方法
  9. @Pointcut("@annotation(com.chenss.anno.Chenss)")

6、bean

  1. @Pointcut("bean(dao1)")//名称为dao1的bean上的任意方法
  2. @Pointcut("bean(dao*)")