前言

在Spring中AOP是我们使用的非常频繁的一个特性。通过AOP我们可以补足一些面向对象编程中不足或难以实现的部分。

AOP

前置理论

首先在学习源码之前我们需要了解关于AOP的相关概念如切点切面等,以及如何使用AOP,这里可以看我之前的文章:Spring系列之AOP的原理及手动实现

创建AOP相关对象

对于Java这种面向对象语言来说任何功能的实现都是依赖于对象,AOP也不例外。

首先我们先来准备好在配置文件中配置好AOP相关的属性。

spring.xml

   <bean id="aspect" class="cn.javass.spring.chapter6.aop.HelloWorldAspect"/>

   <bean id="beforeAdvice" class="cn.javass.spring.chapter6.aop.BeforeAdviceImpl"/>
<aop:aspectj-autoproxy/>
<aop:config>
<aop:pointcut id="pointcutA" expression="execution(* cn.javass..*.sayAfterReturning(..))"></aop:pointcut>
<aop:advisor id="advisor" pointcut="execution(* cn.javass..*.sayAdvisorBefore(..))"
advice-ref="beforeAdvice"/>
<aop:aspect id="aspects" ref="aspect">
<aop:before pointcut="execution(* cn.javass..*.sayBefore(..)) and args(param)" method="beforeAdvice(java.lang.String)" arg-names="param"/> <aop:after-returning pointcut-ref="pointcutA" method="afterReturningAdvice" arg-names="retVal" returning="retVal"/> <aop:after-throwing pointcut="execution(* cn.javass..*.sayAfterThrowing(..))" method="afterThrowingAdvice" arg-names="exception" throwing="exception"/> <aop:after pointcut="execution(* cn.javass..*.sayAfterFinally(..))" method="afterFinallyAdvice"/> <aop:around pointcut="execution(* cn.javass..*.sayAround(..))" method="aroundAdvice"/>
</aop:aspect>
</aop:config>

在上面的配置中创建了几种不同的advice。这些配置在spring启动时会被相应的创建为对象。

AopNamespaceHandler

在前面IOC文章中我们有提到在Spring中通过spi的机制来确定解析配置文件中不同标签的解析类。

在aop包中找到spring.handlers文件:

http\://www.springframework.org/schema/aop=org.springframework.aop.config.AopNamespaceHandler

可以确定的是处理aop相关标签的就是AopNamespaceHandler这个类。

    public void init() {
this.registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
this.registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
this.registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
this.registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
}

AopNamespaceHandler中除了构造函数就只有上面的init方法,上面代码就是注册解析不同标签的解析器的BeanDefinition。我们需要关注的是aspectj-autoproxy标签的解析器类AspectJAutoProxyBeanDefinitionParser

aspectj-autoproxy标签是aop功能的开关,不管是通过注解还是配置文件方式都需要显示的注明该标签。通过aop命名空间的aop:aspectj-autoproxy/声明自动为spring容器中那些配置@aspectJ切面的bean创建代理,织入切面。

AopConfigUtils#registerOrEscalateApcAsRequired

跟踪AspectJAutoProxyBeanDefinitionParser的parse方法最终会进入到AopConfigUtils#registerOrEscalateApcAsRequired中。

	private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry, Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
int requiredPriority = findPriorityForClass(cls);
if (currentPriority < requiredPriority) {
apcDefinition.setBeanClassName(cls.getName());
}
}
return null;
}
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}

这里的常量AUTO_PROXY_CREATOR_BEAN_NAME = "org.springframework.aop.config.internalAutoProxyCreator";

首先要明白的是internalAutoProxyCreator并不是spring中的一个实际的类,AUTO_PROXY_CREATOR_BEAN_NAME是一个用于创建aop类的Beandefinition的名字。

在上面的代码逻辑中如果AUTO_PROXY_CREATOR_BEAN_NAME表示的Beandefinition已经存在则判断新需要注册的类其优先级和已经存在的类定义进行比较,如果新需要注册的优先级较高则进行替换。

如果不存在已经注册的Beandefinition则将其进行注册。被注册的Beandefinition表示的类为AspectJAwareAdvisorAutoProxyCreator

完成aop功能需要创建的对象

在前面IOC文章中分析过了在解析完配置文件后需要创建的对象都会将其BeanDefinition注册到IOC容器中,所以我们可以将断点设置在配置文件解析完成之后就可以看到需要创建那些对象了。

如上图helloWorldService就是需要被增强的类。

public interface IHelloWorldService {

    void sayHello();
void sayBefore(String param);
boolean sayAfterReturning();
void sayAfterThrowing();
boolean sayAfterFinally();
void sayAround(String param);
void sayAdvisorBefore(String param);
}

aspect,beforeAdvice,pointcutA,advisor都是我们在配置文件中配置过的,是切点,切面和处理方法的实现类。org.springframework.aop.config.internalAutoProxyCreator是上面分析过的用于创建aop代理的实现类。

而后面的以org.springframework.aop.aspectj.AspectJPointcutAdvisor开头的几个类实际上就是包含了切点和通知的一个切面的实现类,也就是它来决定哪些类需要被增强。

功能增强

增强时机

如果看过前面手写aop文章的同学应该知道当时我们分析aop增强时机时有说过aop的增强功能实际上是依赖于动态代理实现的。而动态代理如果要对一个对象进行增强那么首先需要持有该对象才行。

所以我们在对对象进行增强的前提是该对象已经被创建完成之后。而且我们要清楚的是一个类对象被增强后我们所有需要使用该对象的地方都应该使用该对象,这样就确定了类增强的时机一定是在类对象创建之后并且在完成注入之前。

AspectJAwareAdvisorAutoProxyCreator

前面有说过创建代理对象实际上是通过AspectJAwareAdvisorAutoProxyCreator来完成,先来了解一下该类,查看该类的继承体系。

可以看到实际上该类本身还是一个BeanPostProcessor,那么可以肯定的是我们只要找到执行BeanPostProcessor的地方并且是在实例化后执行的地方即可。经过调试后定位到AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsAfterInitialization方法。

	public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName) throws BeansException {

		Object result = existingBean;
for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
result = beanProcessor.postProcessAfterInitialization(result, beanName);
if (result == null) {
return result;
}
}
return result;
}

这里是对后置处理器进行遍历,对于aop我们需要关注的是AspectJAwareAdvisorAutoProxyCreator这一个处理器。

wrapIfNecessary
	protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
//more code
// Create proxy if we have advice.
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
if (specificInterceptors != DO_NOT_PROXY) {
this.advisedBeans.put(cacheKey, Boolean.TRUE);
Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
this.proxyTypes.put(cacheKey, proxy.getClass());
return proxy;
} this.advisedBeans.put(cacheKey, Boolean.FALSE);
return bean;
}

这里去掉了前面一些代码,getAdvicesAndAdvisorsForBean方法是用来获取和当前对象匹配的切面。这里获取相匹配的切面类是通过AbstractAdvisorAutoProxyCreator来实现。

	protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
List<Advisor> candidateAdvisors = findCandidateAdvisors();
List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
extendAdvisors(eligibleAdvisors);
if (!eligibleAdvisors.isEmpty()) {
//根据在配置文件中配置的order属性或者注解@order()进行从小到大的排序
//order的值越小其优先级越高
eligibleAdvisors = sortAdvisors(eligibleAdvisors);
}
return eligibleAdvisors;
}

首先获取到所有的切面类,然后通过AopUtils.findAdvisorsThatCanApply方法来确定哪些类能够匹配。

AopUtils.findAdvisorsThatCanApply
	public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {
if (candidateAdvisors.isEmpty()) {
return candidateAdvisors;
}
List<Advisor> eligibleAdvisors = new LinkedList<Advisor>();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {
eligibleAdvisors.add(candidate);
}
}
boolean hasIntroductions = !eligibleAdvisors.isEmpty();
for (Advisor candidate : candidateAdvisors) {
if (candidate instanceof IntroductionAdvisor) {
// already processed
continue;
}
if (canApply(candidate, clazz, hasIntroductions)) {
eligibleAdvisors.add(candidate);
}
}
return eligibleAdvisors;
} public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) {
if (advisor instanceof IntroductionAdvisor) {
return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass);
}
else if (advisor instanceof PointcutAdvisor) {
PointcutAdvisor pca = (PointcutAdvisor) advisor;
return canApply(pca.getPointcut(), targetClass, hasIntroductions);
}
else {
// It doesn't have a pointcut so we assume it applies.
return true;
}
}

实现逻辑很简单,遍历所有的advisor调用canApply确定是否匹配。

切面是有切入点和通知组成,切入点用来确定哪些对象需要被增强,而通知决定如何进行增强。所以很明显这里确定类对象是否匹配是由切入点(pointCut)决定的。

我们先来看一下切入点是什么。

public interface Pointcut {
//类过滤器 用于确定类对象是否匹配 只有当类对象匹配后才进行方法的匹配
ClassFilter getClassFilter(); //方法匹配器 用于确定具体哪一些方法需要被增强。
MethodMatcher getMethodMatcher();
//生成一个pointcut对象实例
Pointcut TRUE = TruePointcut.INSTANCE; }

上面我们可以看到实际上就是通过ClassFilter和MethodMatcher相互配合来实现的,具体的实现过程会因为实现方式大同小异。其中实现方式包括比如正则匹配,AspectJ匹配等,在我们之前的手写系列中就是通过正则来进行匹配的,这里匹配的实现不深入探讨。

通过上面的逻辑便可以确定好增强该类会用到哪些advisor。

createProxy

当确定好需要用到的advisor和其顺序后就开始进行创建代理对象了。创建代理对象的方法由前面提到的wrapIfNecessary来调用createProxy方法实现。

	protected Object createProxy(Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) {

		ProxyFactory proxyFactory = new ProxyFactory();
proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) {
if (shouldProxyTargetClass(beanClass, beanName)) {
proxyFactory.setProxyTargetClass(true);
}
else {
evaluateProxyInterfaces(beanClass, proxyFactory);
}
} Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);
for (Advisor advisor : advisors) {
proxyFactory.addAdvisor(advisor);
} proxyFactory.setTargetSource(targetSource);
customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy);
if (advisorsPreFiltered()) {
proxyFactory.setPreFiltered(true);
} return proxyFactory.getProxy(getProxyClassLoader());
}

创建实际上市代理通过代理工厂类(ProxyFactory)实现的。

JDK代理还是cglib

在创建代理对象时需要确定使用JDK代理还是cglib代理,前面有提到过如果在配置文件中配置了proxy-target-class="true"的话那么就只会使用cglib进行代理。但是如果没有配置的话则需要通过实际情况来决定是JDK代理还是cglib。

而除了proxy-target-class外,我们实际上还可以配置一个属性optimize,该属性默认值为false,如果我们将其置为true那么就表示允许spring对代理生成策略进行优化,意思就是如果该类有接口,就代理接口(使用JDK代理);如果没有接口,就代理类(使用CGLIB代理)。而不是像如果只配置proxyTargetClass=true时强制代理类,而不去考虑代理接口的方式。

综上在spring中使用代理方式的策略如下:

  • 如果没有配置optimizeproxy-target-class并且该类实现了接口,那么使用JDK动态代理。
  • 如果没有配置optimizeproxy-target-class并且该类没有实现接口,那么使用cglib动态代理。
  • 如果配置了optimizeproxy-target-class并且该类实现了接口,那么使用JDK动态代理。
  • 如果配置了optimizeproxy-target-class并且该类没有实现接口,那么使用cglib动态代理。

实现代码如下:

	public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface()) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}

现在已经取到了创建代理对象的策略和目标对象,就可以直接创建代理对象了。如果对这放面有兴趣的可以自行搜索。

创建好代理对象之后使用代理对象替代之前创建好的对象,那么在使用的时候就会调用增强后的方法完成功能。

多个advisor如何确定顺序

在实际开发过程中,可能会存在一个方法被多个advisor增强,可能有的在方法执行前增强有的在方法执行后进行增强。那么在spring中如何确定每一个增强方法的调用时机保证不会出问题的呢?

在手写aop系列中有讲过这个问题,当时我们是通过责任链模式来解决这个问题。实际上spring中就是通过责任链模式来解决该问题的。

JdkDynamicAopProxyinvoke方法中,会通过getInterceptorsAndDynamicInterceptionAdvice方法来获取增强当前调用方法的所有advisor的chain,但是需要注意的是这个chain并不是根据实际应该的执行顺序排列的。仅仅只是所有会被执行的增强方法的集合。

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

获取的chain会被封装成一个ReflectiveMethodInvocation,实际执行过程通过该类的proceed()方法完成。

invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);
retVal = invocation.proceed();

ReflectiveMethodInvocation#proceed

	public Object proceed() throws Throwable {
if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {
return invokeJoinpoint();
} Object interceptorOrInterceptionAdvice =
this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);
if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {
InterceptorAndDynamicMethodMatcher dm =
(InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;
if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {
return dm.interceptor.invoke(this);
}else {
return proceed();
}
}else {
return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);
}
}

责任链模式中存在着一个index的变量,通过这个索引来决定链中的对象的执行。如果当前对象暂时无法执行则会通过当前的对象来调用下一个执行的对象。而对于不同类型的对象通过多态来进行不同的处理。比如如果是一个before类型的advice。

MethodBeforeAdviceInterceptor#invoke

	public Object invoke(MethodInvocation mi) throws Throwable {
this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis() );
return mi.proceed();
}

对于before类型的advice会直接调用,然后继续调用下一个对象执行。

对于after类型的advice:

AspectJAfterAdvice#invoke

	public Object invoke(MethodInvocation mi) throws Throwable {
try {
return mi.proceed();
}
finally {
invokeAdviceMethod(getJoinPointMatch(), null, null);
}
}

代码意思很明显了,after类型的advice则是当前不进行增强操作,而是先调用下一个处理对象。然后由于这些对象是类似递归那样嵌套调用的,所以只需要将处理逻辑置后那么只需要等嵌套中心的代码执行完成,后面的代码还是会执行的。

基本逻辑如下图。

Spring中AOP相关源码解析的更多相关文章

  1. Spring中AOP相关的API及源码解析

    Spring中AOP相关的API及源码解析 本系列文章: 读源码,我们可以从第一行读起 你知道Spring是怎么解析配置类的吗? 配置类为什么要添加@Configuration注解? 谈谈Spring ...

  2. Scala 深入浅出实战经典 第65讲:Scala中隐式转换内幕揭秘、最佳实践及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  3. Scala 深入浅出实战经典 第61讲:Scala中隐式参数与隐式转换的联合使用实战详解及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载: 百度云盘:http://pan.baidu.com/s/1c0noOt ...

  4. Scala 深入浅出实战经典 第60讲:Scala中隐式参数实战详解以及在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-87讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  5. Scala 深入浅出实战经典 第48讲:Scala类型约束代码实战及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...

  6. Spring中Bean命名源码分析

    Spring中Bean命名源码分析 一.案例代码 首先是demo的整体结构 其次是各个部分的代码,代码本身比较简单,不是我们关注的重点 配置类 /** * @Author Helius * @Crea ...

  7. Spring Boot @Enable*注解源码解析及自定义@Enable*

      Spring Boot 一个重要的特点就是自动配置,约定大于配置,几乎所有组件使用其本身约定好的默认配置就可以使用,大大减轻配置的麻烦.其实现自动配置一个方式就是使用@Enable*注解,见其名知 ...

  8. 【Spring】Spring IOC原理及源码解析之scope=request、session

    一.容器 1. 容器 抛出一个议点:BeanFactory是IOC容器,而ApplicationContex则是Spring容器. 什么是容器?Collection和Container这两个单词都有存 ...

  9. Volley 图片加载相关源码解析

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/47721631: 本文出自:[张鸿洋的博客] 一 概述 最近在完善图片加载方面的 ...

随机推荐

  1. 深入理解最强桌面地图控件GMAP.NET --- 街景地图(StreetView)

    原文:深入理解最强桌面地图控件GMAP.NET --- 街景地图(StreetView) 很久没有更新博客了,今天无事把GMAP.NET的代码又重新翻了翻,看到了街景地图的例子. 街景地图是谷歌最早提 ...

  2. 第一泰泽(Tizen)智能手机发布在俄罗斯

    请看下图: 这是韩国三星公司在俄罗斯境内公布的第一款泰泽(Tizen)智能手机(今年6月2日).这说明,Tizen操作系统没有死去. 在泰泽官网上将泰泽操作系统定义为:"The OS of ...

  3. WPF与缓动(三) 指数缓动

    原文:WPF与缓动(三) 指数缓动 WPF与缓动(三) 指数缓动                                                                     ...

  4. WPF Binding妙处-既无Path也无Source

    <Window x:Class="XamlTest.Window12"        xmlns="http://schemas.microsoft.com/win ...

  5. 开源数据源使用 DBCP 和 C3PO

    jar包: commons-dbcp-1.4.jar commons-pool-1.5.6.jar mysql-connector-java-5.0.8-bin.jar 建立dbcp的配置文件 dbc ...

  6. jquery模拟飞秋

    <!DOCTYPE html><html lang="en" xmlns="http://www.w3.org/1999/xhtml"> ...

  7. Android手机导出文件

    因为要写联系人相关的东西,所以得把db导出来看看 第一步:Root手机 尝试了几个Root工具,发现就KingRoot能root 第二个:编写bat脚本 脚本内容是先将DB文件从/data/data ...

  8. flume本地调试

    本机idea远程调试flume:https://blog.csdn.net/u012373815/article/details/60601118 遇到 [root@hadoop02 bin]# ./ ...

  9. 有什么很好的软件是用 Qt 编写的?(尘中远)

    作者:尘中远链接:http://www.zhihu.com/question/19630324/answer/19365369来源:知乎 http://www.cnblogs.com/grandyan ...

  10. Win10的UWP之标题栏的返回键(二)

    原文:Win10的UWP之标题栏的返回键(二) 关于Win10的UWP的返回键的第二种处理的方法,是介于标题栏的强行修改,不是像上期的那样直接调用系统内置的API. - - - - - - - - - ...