IOC :用于管理 JAVA 对象之间的依赖关系;
AOP :用于解耦业务代码和公共服务代码,实现更松的耦合性和可测试性。
在面向对象程序设计中,要为多个非继承关系的对象添加某特定行为时,我们需要在每个类中重复引入大量重复代码,极大降低程序的可唯独性。AOP 的诞生就是为了解决这一问题。
AOP 使用举例
AOP 自定义标签解析
AOP 可以理解成通过自定义标签解析的方式来实现的,同时,只要声明了自定义注解(aop:aspectj-autoproxy)则一定在程序中注册了对应的解析器。
通过全局搜索 aspectj-autoproxy,我们找到了 AOP 对应的命名空间处理类 AopNamespaceHandler :
这里和自定义标签解析中的逻辑完全对应上,可以认为是通过自定义标签解析的方式来实现了 AOP 的功能。
对应 Parser 中做了三件事(注册,指定加强和通知):
-
注册 AnnotationAwareAspectJAutoProxyCreator 到 beanDefinitionMap 中去;
对应注册 key 值为:AUTO_PROXY_CREATOR_BEAN_NAME = ”org.springframework.aop.config.internalAutoProxyCreator“
-
对 proxy-tasgt-class 以及 expose-proxy 属性配置进行处理
。推荐采用默认方式(JDK 代理), 代理类至少要实现一个接口;强制使用 CGLIB 代理则要将对应属性置 true;
。代理增强功能主要用于自我调用中切面的增强; 注册组件并进行通知(略)。
这里并没有看到如我们自定义标签解析中看到的对属性等元素解析并返回 beanDefiniton 的逻辑;原因呢?各位可以自己想一下。
AOP 代理创建
我们知道在 createBean 的时候,我们有一步处理是 resolveBeforeInstantiation() , 在这里我们进行了 AOP 代理的处理。也就是在创建指定 bean 时调用了 beanPostProcessor#postBeanProcessAfterInitialization 来实现。
这里记个问题 :如何确定当前bean是否需要进行 AOP 后置处理呢?
创建代理包含下面两步:
- 获取当前 bean 可用的所有增强器;
- 根据对应增强器递归生成代理对象并返回。
获取所有可用增强器
AnnotationAwareAspectJAutoProxyCreator#findCandidateAdvisors
-- 如果是自己的话会如何实现
前面DI时我们已经将所有的bean都进行了注册(包括AspectJ类bean对象),所以这里的逻辑想一下应该是:
1)遍历所有的注册bean,通过beanClass.getDeclaredAnnotation和AspectJ类比较;
2)对所有包含AspectJ注册的类依次获取对应增强方法(遍历所有AspectJ类的所有增强方法);
3)获取当前bean的所有方法(包括父类和接口),
4)依次将当前方法和增强方法进行match匹配来获取;
5)已获取数据需要加入缓存。
实际代码实现逻辑如下:
protected List findCandidateAdvisors() {
//1.获取所有类型的bean名称,包括了代理类
String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludeAncestors(Object.class);
//2.遍历获取
for(beanName : beanNames) {
//2.1 获取对应bean类对象
Class beanClassType = beanFactory.getType(beanName);
//2.2 当前bean类是否包含Aspect标注,当前类是否为代理类判断
if(beanClassType.getDeclaredAnnotation(Aspect.class)) {
// 这里需要从对应的Aspect类中解析出所有的增强器
List classAdvisors = advisorFactory.getAdvisors(beanClassType);
//2.3 将结果添加到对应缓存中
advisorsCache.put(beanName, classAdvisors);
}
}
}
上面代码中确定当前类对象包含Aspect类注解了,那么这个Aspect类中包含哪些增强器呢?如何得到这些增强器呢?
-- 如果是自己的话会如何实现
前面已经确定了所有Aspect,以及当前需要加载的bean,那么如何获取所有可以适用于该类的增强器呢?
自己的话会如何实现?
1、获取该Aspect类中所有非切点的增强器(方法);
2、获取对应bean所有生命方法(a这里包含父类和接口中的非抽象方法);
3、选择与该bean匹配的增强器方法并返回。
具体获取所有增强器代码实现如下:
整体逻辑是对该类所有方法进行遍历,查看满足定义annotationClass2lookFor中声明标注,满足则返回对应Advisor.
//入参可以认为是Aspect类class对象
//这里只是将对应所有的增强器获取并返回,并没有与对应当前获取bean进行匹配
public List getAdvisors(MetadataAwareAspectInstanceFactory maaif) {
//所有定义的需要结息的方法标注类
Class>[] class2lookFor = new Class[] {
Before.class, Around.class, After.class, AfterReturing.class, ……
}
//遍历对应Aspect类所有非pointcut方法
for(method : maaif.getAspectClass().getDeclaredMethod()) {
//所有非切点的增强器方法都要在这里进行获取
if(method.getAnnotation.getClass <> Pointcut.class) {
for(annotationClass : class2lookFor) {
if(method.annotation == annotationClass) {
addAdvisorList(AspectAnnotation(metod, annotationClass));
}
}
//延迟加载处理逻辑在这里
advisors.add(0, new SyncInstantiationAdvisor())
}
}
查看源码后发现和所想的有一点不一样,那就是获取了切点信息后(这里指我们AspectJ标注类中的切点方法),需要根据切点信息生成增强,返回对应的Advisor。
也就是时候,上面在获取可用于对应bean 增强的代理时,返回了对应增强器。
如图返回new InstantiationModelAwarePointCutAdvisorImpl 对象时,其根据切点类型(Aroud,methodBefore等),根据工厂类 AspectJAdvisorFactory生成对应的代理增强器
过滤不匹配增强器
这里拿到上述所有增强器,同时根据beanClass获取所有方法(包括接口),依次过滤
public List canApply(List allList, Class> beanClass) {
for((PointCutAdvisor)advisor : allList) {
for(method : beanClass.getAllMethodIncludeInterface()) {
if(advisor.pointCutMatch(method)){
returnList.add(advisor);
}
}
}
}
创建对应代理类
我们先想一下这里可能的实现逻辑:回想前面DI获取bean时,如果有增强,这里直接会返回对应代理类,从而结束
bean的获取。也就是说,这里对要获取的bean进行代理后需要直接返回对应代理类。同时要将上面适配的所有增强
器进行代理;
同时,我们在前面自定义标签解析的时候添加了对应代理设置与代理增强,这里是否会根据配置进行指定?
整个动态代理又应该怎么实现呢?这个参考后面备注的内容。
源码实现思路如下:
所有代理的映射实现交由ProxyFactory来实现,这里仅对需要的参数进行了组装,之后对ProxyFactory进行调用。
//所有
public Object createProxy(TargetSource source, List adlist) {
proxyFactory.addInterface(source.getAllInterface);
//这里对所有的增强器进行封装,统一为DefaultPointCutAdvisor
proxyFactory.addAdvisorList(Swarp(adlist));
proxyFactory.addTargetSource(source);
proxyFactory.getProxy(this.classLoader);
}
这里在ProxyFactory获取代理的时候有JDK代理和CGLIB代理,所以这里面DefaultAopProxyFactory一定吃了映射成哪一种代理的逻辑:
- 采用激进优化策略 || 指定了代理类(proxy-target-class = true) || 不存在代理接口 则采用CGLIB代理;
- 非上述场景或 对应目标类是一个接口,则需要采用JDK代理;
JDK代理和CGLIB代理简单总结如下 :
• 目标对象实现了接口,默认采用JDK代理,但也可以强制使用CGLIB代理;
• 目标对象没有实现接口则一定需要采用CGLIB代理,Spring根据上述逻辑判断来进行JDK代理和CGlib代理转换。
• CGLIB主要针对类生成子类,覆盖其中方法,所以对应方法不应该采用Final声明。
这里也就是说如果目标对象没有采用接口实际上不一定采用的是CGLIB代理的。
JDK 动态代理类实现
对应的jdkDynamicAopProxy是被代理工厂类直接调用的,这里可以看到对应jdkDynamicAopProxy中实现了接口类InvocationHandler中的invoke以及对应AopProxy#getProxy方法。
getProxy 主要是返回代理对象,可以确定是采用Proxy.newProxyInstance(classLoader, interfaces, args[])
invoke 为主代码逻辑,对应AOP返回动态代理类能够被多次拦截代理的实现也在这里。
-- 猜一下
这里是不是会采用责任链模式不断返回对应已代理的类,然后继续被代理。
好吧,这里和想的不一样,这里在代理类的invoke核心逻辑中进行了是否加强代理判断(exposeProxy = true)后更多的是委托给了ReflectiveMethodInvocation来执行对应逻辑:
这里维护了一个所有拦截器调用的计数器,同时采用了递归的方式实现了对所有拦截器的逐个调用。
CGLIB 动态代理实现
CGLIB动态代理在使用上来说的话,是通过构建对应Enhancer, 设置对应的代理类,回调函数等来实现。这里不对这个做更细的探讨。
备注
JDK和CGLIB代理区别(建议采用JDK):
JDK代理要求被代理对象至少实现一个接口,通过运行期间创建一个接口的实现类来完成对目标类代理;
CGLIB通过运行期生成针对目标类拓展的子类,CGLIB底层依赖ASM操作字节码实现,性能强于JDK;
CGLIB存在两个问题:
1)无法实现对Final方法的代理;
2)需要将CGLIB二进制发行包放置于classPath路径下。
代理增强(expose-proxy):
public interface Aservice {
public void a();
public void b();
}
@Service()
public class AserviceImp implements Aservice {
@Transactional(propagation=Propagation.REQUIRED)
public void a() {
// 采用代理增强后需要改用下面方式来解决对应自身调用无法被增强问题
//((Aservice)AopContext.currentProxy()).b();
this.b();
}
@Transactional(propagation=Propagation.REQUIRES_NEW)
public void b(){
}
}
这里在a()中对b()的调用将不会执行事务增强,也就是说b()方法上的 @Transactional(propagation=Propagation.REQUIRES_NEW) 不会被执行,需要我们在声明对应aop代理类时采用下面方式声明,同时变更调用.