0%

Spring-AOP

AOP

概念

AOP,Aspect Oriented Programming,面向切面编程

AOP的理念:将分散在各个业务逻辑代码中相同的代码通过横向切面的方式,抽取到一个独立的模块中

SpringAOP底层原理就是动态代理

代理的意义:增强对象的行为,使用动态代理实质上就是调用时拦截对象方法对方法进行改造、增强

Spring AOP使用纯JAVA实现,它不需要专门的编译过程,也不需要特殊的类装载器,它在运行期通过代理方式向目标织入增强代码

来源《Spring 实战 (第4版)》一句话:

SpringAOP构建在动态代理之上,因此,Spring对AOP的支持局限于方法拦截。

在java中动态代理有两种方式:

1.JDK动态代理(针对接口)

2.CGlib动态代理(针对类)

CGlib代理其生成的动态代理对象是目标类的子类

SpringAOP默认是使用JDK动态代理,如果代理的类没有接口则会使用CGlib代理

那么JDK代理和CGLib代理我们该用哪个呢??在《精通Spring4.x 企业应用开发实战》给出了建议:

如果是单例模式我们最好使用CGlib,如果是多例最好使用JDK动态代理

原因:

1.JDK在创建代理对象时的性能要高于CGlib,而生成代理对象的运行性能却比CGlib低

CGLIB代码实践

单例Bean顺序

TestService–>推断构造方法–>普通对象–>依赖注入–>初始化前(@PostConstruct)–>初始化(InitializingBean)–>初始化后(AOP)–>代理对象–>放入Map单例池–>Bean对象

AOP对象内部逻辑

父子类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//现象:调用TestServiceProxy时TestService为Null,但当使用TestServiceProxy.test()方法时,TestService则有值
//内部方式(个人理解):
// 1. TestService生成普通对象
// 2. AOP针对TestService生成TestServiceProxy放入单例池
// 3. BeanName不能相同,在TestServiceProxy中将TestService命名为target,此target是TestService普通对象
// 4. 通过byType--byName方式使用经过依赖注入的普通对象
// 此时TestServiceProxy中的test()方法可以被调用,实际调用的是TestService的test()方法

class TestService {
public void test(){
System.println("test");
}
}

class TestServiceProxy extends TestService {

TestService target;

public void test(){
//@Before Advisor等切面逻辑
//target.test();

}
}

注意点

Spring事务也是AOP代理实现的,因此当Proxy.a()调用本类中的b()方法时,b()方法的@Transactional 会出现失效的状态,原因即为targer实际为this.test()方法,此时相当于Proxy代理类中new Bean.test()导致无法管制

解决方法

将调用方法变为代理方法

  • 移入其他类
  • 自己注入自己
  • 上下午获取自己

解决原因

调用时相当于再找一个Proxy对象,因此属于代理对象调用代理对象,此刻不属于new Bean调用

业务支撑

  • 日志打印
  • 性能监控
  • 事务处理

组件能力

  • XML
  • @Annotation

Aspect - Pointcut - Advice

底层实现

  • Advisor
    • aspect
    • pointcut
    • advice

多个切点转换为多个Advisor,形成Chain,递归方法层层调用

实践

  • 接口的实现类打AOP注解使用动态代理方式

    Cglib动态代理

  • 使用JDK动态代理时机

    • 当Bean实现接口时,Spring就会用JDK的动态代理
    • 当Bean没有实现接口时,Spring使用CGlib是实现
    • 可以强制使用CGlib(@EnableAspectJAutoProxy(proxyTargetClass = true))
  • 注解打在接口方法上能否识别

    • JDK动态代理不能直接识别

      • 接口没有实现方法,spring依赖注入管理的是对象,接口spring可管理不到,既然不是spirng管理的对象,spirng+aop的配置肯定是失效的
      • 注解是可以继承,但只有类的注解是可以继承的,还需要@Inherited,方法的注解继承不了,spring管理的是实际对象,加不上注解,更不可能拦截到

      由注解继承规则,该实现类的方法并不能继承接口方法上的注解,因而spring也就无法为该实现类生成代理,aop也就拦截失败

    • CGLIB动态代理

      有类Parent,类Sub,Sub继承自Parent。根据注解的继承原则,切点注解在父类方法,

      若Sub覆写了父类所有带注解方法,实际Sub中并未被AOP标识,所有spring并不会为Sub创建代理类,也就不会被拦截。

      若Sub覆写了父类部分带注解方法,spring会为Sub创建代理类,对于覆写了父类的方法,由于注解未被继承,不会被拦截;未覆写的方法,可以被拦截。

启动Bean的流程

  1. Refresh

    • BeanFactory、后置处理完成
  2. preInstantiateSingleton

  3. CreateBean

    • 查找到被Aspect标记的Bean
  4. 在populate后进行InitalizeBean

  5. 后置处理

    • AbstractAutoProxycreator.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      @Override
      public Object postProcessAfterInitialization(@Nullable Object bean, String beanName){
      if (bean != null) {
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (this.earlyProxyReferences.remove(cacheKey) != bean){
      return wrapIfNecessary(bean, beanName, cacheKey);
      }
      }
      return bean;
      }
  6. JDK/CGLIB动态代理

  7. 切面转为advisor

  8. createAopProxy返回一个Proxy用于其他类的使用

执行动态代理流程

聚合所有的Chain 递归

  • 递归
    • 结束条件:Chain执行结束
    • Before/After invoke()
来源:

https://www.bilibili.com/video/BV1tS4y1s7ax

https://blog.51cto.com/u_15360778/4195832

https://blog.csdn.net/qianhuan_/article/details/118034747

https://blog.csdn.net/qq_37130607/article/details/113844786

https://blog.csdn.net/xybz1993/article/details/80627432

https://blog.csdn.net/u012938226/article/details/103505875

https://segmentfault.com/q/1010000010652475

https://www.jianshu.com/p/e91ee83f2348

https://www.bilibili.com/video/BV1Q44y1V7