文档章节

Spring 源码分析(三) —— AOP(三)实现思路

水门-kay
 水门-kay
发布于 2016/03/08 03:51
字数 2143
阅读 1770
收藏 7

核心逻辑  

        上文中提到了 AOP 创建代理等等的具体操作都是在 AnnotationAwareAspectAutoProxyCreator 类中来成的,通过上文的自动注册,下面让我们看 AnnotationAwareAspectAutoProxyCreator 是如何工作的,首先是 AnnotationAwareAspectAutoProxyCreator 的继承关系图:

        然后是 AnnotationAwareAspectAutoProxyCreator 的层次结构图:

        这里需要特别注意的是 BeanPostProcessor 接口,我们知道实际运用中,如果你需要对项目中的 Bean 进行代理,在 Spring 的 xml 的配置一个 BeanPostProcessor 就行。由此,我们可以知道 AnnotationAwareAspectAutoProxyCreator 实现代理也是通过 BeanPostProcessor 接口来完成的,所以我们对于 AOP 逻辑分析也是由 BeanPostProcessor 实例化前的 postProcessAfterInitialization 方法开始,而 AnnotationAwareAspectAutoProxyCreator 的 postProcessAfterInitialization 具体实现是在其父类 AbstractAutoProxyCreator 中完成的。我们对 AOP 逻辑的分析也由此开始。

AbstractAutoProxyCreator.java

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
   if (bean != null) {
      // 根据给定的 bean 的 class 和 name 构建出个 key,格式:beanClassName_beanName
      Object cacheKey = getCacheKey(bean.getClass(), beanName);
      if (!this.earlyProxyReferences.contains(cacheKey)) {
         // 如果它适合被代理,则需要封装指定 bean。
         return wrapIfNecessary(bean, beanName, cacheKey);
      }
   }
   return bean;
}

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
   // 是否已经处理过
   if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
      return bean;
   }
   // 无需增强
   if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
      return bean;
   }
   // 给定的 bean 类是否代表一个基础设施类,基础设施类不应代理,或者配置了指定 bean 不需要自动代理
   if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
      this.advisedBeans.put(cacheKey, Boolean.FALSE);
      return bean;
   }

   // 如果存在增强方法则创建代理
   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;
}

        以上两个方法构成了创建代理的雏形,当然,开始前还有些判断工作。而创建代理的核心逻辑部分是在 AbstractAutoProxyCreator 类中完成的,而创建代理前的准备工作主要分为两步:(1)获取增强方法或者增强器。(2)根据获取的增强进行代理。下面是 AbstractAutoProxyCreator 的时序图:

        结合时序图,我们知道真正创建代理地方是从 getAdvicesAndAdvisorsForBean 开始的。虽然看起来很简单,但其实每一步都有大量复杂的逻辑。但在分析源码前,我们必须先对增强以及其具体逻辑有所了解。


设计的基石

        增强

        Advice(也翻作 通知)定义了连接点做什么,为切面增强提供了织入的接口。在 Spring AOP 中,它主要描述 Spring AOP 围绕方法调用而注入的切面行为。Advice 是 AOP 联盟定义的一个接口,具体的接口定义在 org.aopalliance.aop.Advice 中。在 Spring AOP 的实现中,使用了这个统一接口,并通过这个接口,为 AOP 切面增强的注入功能做了更多的细化和扩展,比如前面提到的具体通知类型,如BeforeAdvice、AfterAdvice、ThrowsAdvice等。作为 Spring AOP 定义的接口类,具体的切面增强可以通过这些接口集成到 AOP 框架中去发挥作用。对于这些接口类,下面是他的主要接口继承图:

        下面是 Advice 接口继承的层次图:

        看一个程序的具体设计思路没有比看接口来的更直接的了,下面我们就从第一个 BeforeAdvice 的继承接口 MethodBeforeAdvice 开始(BeforeAdvice 里没有任何东西)

MthodInterceptor.java

public interface MethodInterceptor extends Interceptor {
    void before(Method method, Object[] args, Object target) throws Throwable;
}

        明显能够看出这是一个回调函数,他的具体参数有:Method 对象,这个参数是目标方法的反射对象,Object[]对象数组,这个对象数组中包含没办法发的输入参数。而我们根据继承关系看他的具体实现类 AspectJMethodBeforeAdvice。

AspectJMethodBeforeAdvice.java 

@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
    invokeAdviceMethod(getJoinPointMatch(), null, null);
}

AbstractAspectJAdvice.java

protected Object invokeAdviceMethod(JoinPointMatch jpMatch, Object returnValue, Throwable ex) throws Throwable {
   return invokeAdviceMethodWithGivenArgs(argBinding(getJoinPoint(), jpMatch, returnValue, ex));
}

protected Object invokeAdviceMethodWithGivenArgs(Object[] args) throws Throwable {
   Object[] actualArgs = args;
   if (this.aspectJAdviceMethod.getParameterTypes().length == 0) {
      actualArgs = null;
   }
   try {
      ReflectionUtils.makeAccessible(this.aspectJAdviceMethod);
      // 激活增强
      return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), actualArgs);
   }
   catch (IllegalArgumentException ex) {
      throw new AopInvocationException("Mismatch on arguments to advice method [" +
            this.aspectJAdviceMethod + "]; pointcut expression [" +
            this.pointcut.getPointcutExpression() + "]", ex);
   }
   catch (InvocationTargetException ex) {
      throw ex.getTargetException();
   }
}

        invokeAdviceMethodWithGivenArgs 方法中的 aspectJAdviceMethod 正是对于前置增强的方法,在这里实行了调用。下面是 AfterAdvice 的继承接口 AfterReturningAdvice 接口:

AfterReturningAdvice.java 

public interface AfterReturningAdvice extends AfterAdvice {
   void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable;
}

        而 AfterReturningAdvice 接口的核心逻辑是在其实现父类 AspectJAfterReturningAdvice 中完成的。

AspectJAfterReturningAdvice.java 

@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
   if (shouldInvokeOnReturnValueOf(method, returnValue)) {
      invokeAdviceMethod(getJoinPointMatch(), returnValue, null);
   }
}

        后面的和 BeforeAdvice 一样。根据分析我们可以知道 BeforeAdvice 和 AfterAdvice。尽管,这两个增强行为一致,旦因为它实现的 AOP 通知不同,所以就被 AOP 编织到不同的调用场合中了。而其他的增强基本思路都是如此,这里就不展开了。


        切点

        Pointcut(关注点,也称 切点)用于决定 Advice 增强作用于哪个连接点,也就是说通过 Pointcut 来定义需要增强的方法集合,而这些集合的选取可以通过一定的规则来完成,例如:这些需要增强的地方可以由某个正则表达式来进行标识,或根据某个方法名来进行匹配等。下面是 Pointcut 的层次结构图:

        下面是 Pointcut 接口:

Pointcut.java

public interface Pointcut {
   ClassFilter getClassFilter();
   MethodMatcher getMethodMatcher();
   Pointcut TRUE = TruePointcut.INSTANCE;
}

        通过 Pointcut 接口的基本定义我们可以看到,需要返回一个 MethodMatcher。对于 Point 的匹配判断功能,具体是由这个返回的 MethodMatcher 来完成的,也就是说,有这个 MethodMatcher 来判断是否需要对当前方法调用进行增强或者配置应用。我接着对 MethodMatcher 接口进行分析:

MethodMatcher.java

public interface MethodMatcher {
   boolean matches(Method method, Class<?> targetClass);
   boolean isRuntime();
   boolean matches(Method method, Class<?> targetClass, Object[] args);
   MethodMatcher TRUE = TrueMethodMatcher.INSTANCE;
}

        通过阅读注释我们知道了 Pointcut 的核心逻辑是在 matches 方法中完成的,我就以通过方法名匹配的 NameMatchMetchMethodPointcut 类的 matches 方法来说明:

NameMatchMetchMethodPointcut.java 

@Override
public boolean matches(Method method, Class<?> targetClass) {
   for (String mappedName : this.mappedNames) {
      if (mappedName.equals(method.getName()) || isMatch(method.getName(), mappedName)) {
         return true;
      }
   }
   return false;
}

        从源码我们看到他的实现是非常简单的,匹配条件就是方法名相同或者方法名匹配。


        通知器

        Advisor通知器)用一个对象将对目标方法的切面增强设计(Advice)和关注点的设计(Pointcut)结合起来。下面是 Advisor 接口的层次继承图:

        这是一个 Advisor 接口的设计,我们从中可以看出通过 Advisor,可以定义应该使用哪个增强并且在哪个关注点使用它,也就是通过 Advisor,把 Advice 和 Pointcut 结合起来,这个结合为使用 IOC 容器配置 AOP应用,提供了便利。下面是 Advisor 接口:

Advisor.java

public interface Advisor {
   Advice getAdvice();
   boolean isPerInstance();
}

        下面我们以一个 Advisor 的实现(DefaultPointcutAdvisor)为例,进而了解 Advisor 的工作原理。

DefaultPointcutAdvisor.java

public class DefaultPointcutAdvisor extends AbstractGenericPointcutAdvisor implements Serializable {

   private Pointcut pointcut = Pointcut.TRUE;

   public DefaultPointcutAdvisor() {
   }

   public DefaultPointcutAdvisor(Advice advice) {
      this(Pointcut.TRUE, advice);
   }

   public DefaultPointcutAdvisor(Pointcut pointcut, Advice advice) {
      this.pointcut = pointcut;
      setAdvice(advice);
   }

   public void setPointcut(Pointcut pointcut) {
      this.pointcut = (pointcut != null ? pointcut : Pointcut.TRUE);
   }

   @Override
   public Pointcut getPointcut() {
      return this.pointcut;
   }

   @Override
   public String toString() {
      return getClass().getName() + ": pointcut [" + getPointcut() + "]; advice [" + getAdvice() + "]";
   }
}

        这个类主要职责是 Pointcut 的设置或者获取,在 DefaultPointcutAdvisor 中,Pointcut 默认被设置为 Pointcut.True,这个 Pointcut.True 接口被定义为 Pointcut True = TruePointcut.INSTANCE。而关于 Advice 部分的设置与获取是由其父类 AbstractGenericPointcutAdvisor 来完成的。


指定Bean的增强方法

        下面就让我们回到源码分析,前面已经提到了通知器,这里就不冗述了,他的主要作用是用来整合切面增强设计(Advice)和切入点设计(Pointcut)。对于指定 bean 的增强方法的获取,一般包含获取所有增强以及寻找所有增强中适合于 bean 的增强并应用这两个步骤。

AnnotationAwareAspectAutoProxyCreator.java

@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) {
   List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
   if (advisors.isEmpty()) {
      return DO_NOT_PROXY;
   }
   return advisors.toArray();
}

protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {
   List<Advisor> candidateAdvisors = findCandidateAdvisors();
   List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
   extendAdvisors(eligibleAdvisors);
   if (!eligibleAdvisors.isEmpty()) {
      eligibleAdvisors = sortAdvisors(eligibleAdvisors);
   }
   return eligibleAdvisors;
}

        从源码看,这两个步骤一定是由 findCandidateAdvisors 和 findAdvisorsThatCanApply 来完成。还有值得注意的是,如果无法找到对应的通知器便会返回 DO_NOT_PROXY(null)。



——水门(2016年3月于杭州)

© 著作权归作者所有

水门-kay
粉丝 451
博文 19
码字总数 59660
作品 0
杭州
后端工程师
私信 提问
加载中

评论(2)

ljxbbss
ljxbbss
通过阅读注释我们知道了 Pointcut 的核心逻辑是在 matches 方法中完成的,我就以通过方法名匹配的 NameMatchMetchMethodPointcut 类的 matches 方法来说明:
这个地方类名打错了,应该是NameMatchMethodPointcut;
希望楼主不要介意我的行为
ljxbbss
ljxbbss
看一个程序的具体设计思路没有比看接口来的更直接的了,下面我们就从第一个 BeforeAdvice 的继承接口 MethodBeforeAdvice 开始(BeforeAdvice 里没有任何东西)

MthodInterceptor.java

public interface MethodInterceptor extends Interceptor {
void before(Method method, Object[] args, Object target) throws Throwable;
}
明显能够看出这是一个回调函数,他的具体参数有:Method 对象,这个参数是目标方法的反射对象,Object[]对象数组,这个对象数组中包含没办法发的输入参数。而我们根据继承关系看他的具体实现类 AspectJMethodBeforeAdvice。
这个地方是不是笔误?还是spring版本的问题?应该是MethodBeforeAdvice里面的方法吧,而且MthodInterceptor.java打错了,少了一个e
Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密...

小致dad
2018/08/03
0
0
那些年,我们一起追的Spring

学无止境,但仍需及时总结。 自去年开始写作以来,写了一些关于Spring的文章,今天将它们汇总起来,一方面方便大家阅读,另一方面,也是一次小的复盘总结。 IOC 首先是Spring的IOC,也就是控...

SexyCode
2018/08/14
0
0
Spring 源码分析(三) —— AOP(六)源码分析与总结

Spring AOP 源码分析 虽然我们仅仅分析了一部分,但在 Spring AOP 的基本实现中,我们可以看到 Proxy 代理对象的使用,在程序中是一个非常重要的部分,Spring AOP 充分利用 Java 的Proxy、反...

水门-kay
2016/03/17
1K
1
【转载】spring-session负载均衡原理分析

注明转载:https://www.jianshu.com/p/beaf18704c3c 第一部分:我会用循序渐进的方式来展示源码,从大家最熟悉的地方入手,而不是直接从系统启动来debug源码。直接debug源码看到后来大家都会...

zhu_kai1
2018/11/24
0
0
SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 博客分类:

SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 博客分类: SpringMVC 本文是专栏文章(SpringMVC深度探险)系列的文章之一,博客地址为:http://downpour.iteye.com/blog/13...

xiguashare
2013/12/09
0
0

没有更多内容

加载失败,请刷新页面

加载更多

八、RabbitMQ的集群原理

集群架构 写在前面 RabbitMQ集群是按照低延迟环境设计的,千万不要跨越WAN或者互联网来搭建RabbitMQ集群。如果一定要在高延迟环境下使用RabbitMQ集群,可以参考使用Shovel和Federation工具。...

XuePeng77
今天
1
0
mac系统下,brew 安装mysql,用终端可以连接,navicat却连接不上?

问题: 1.报错? 2059 - Authentication plugin 'caching_sha2_password' cannot be loaded: dlopen(../Frameworks/caching_sha2_password.so, 2): image not found 2.自己通过设置,已经把密......

写bug的攻城狮
昨天
2
0
老生常谈,HashMap的死循环

问题 最近的几次面试中,我都问了是否了解HashMap在并发使用时可能发生死循环,导致cpu100%,结果让我很意外,都表示不知道有这样的问题,让我意外的是面试者的工作年限都不短。 由于HashMap...

群星纪元
昨天
5
0
拉普拉斯算子

拉普拉斯算子是二阶微分算子。 我们知道,一维离散信号一阶微分公式如下: 相应的,一维离散信号二阶微分公式如下: 由于图像有x和y两个方向,因此图像信号属于二维离散信号。其在x,y两个...

yepanl
昨天
3
0
记录"正则表达式"

详细请查看我的博客:https://blog.enjoytoshare.club/article/RegularExpression.html 1 写在前面 正则表达式(Regular Expression)在代码中常常简写为regex。正则表达式通常被用来检索、替...

wugenqiang
昨天
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部