Spring AOP

基于spring 4.1.6

一、配置

只讨论<aop:config>形式的配置,对于ProxyFactory和@Aspect 形式暂不讨论,这两种形式只是前置流程不一样。

配置如下:

<!-- 目标服务 --> 
<bean id="targetService"  />
<!-- 通知 --> 
<bean id="advisorService"  />
<!-- 切面和代理 --> 
<aop:config> 
  <aop:aspect id="advisor" ref="advisorService">
<aop:pointcut id="addAllMethod" expression="execution(* org.test.target.TargetServiceImpl.*(..))" />              
<aop:before method="printTime" pointcut-ref="addAllMethod" />   
<aop:after method="printTime" pointcut-ref="addAllMethod" /> 
  </aop:aspect> 
</aop:config>

二、AOP启动入口

spring AOP所有的一切都要从XML Namespace 说起,也就是AOP的启动入口。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans  http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

上述文件是Spring容器的配置文件的根标签,Spring解析到上述xmlns:aop后,会去一个Map中寻找其对应的Class全路径,对应关系所在文件位置:

aop jar包 META-INF/spring.handlers

内容:

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

通过这个类全路径可以加载一个类,该类会把IOC配置文件中的AOP配置加载容器中,此处注意,Spring中的很多组件都是这种方式加载到Spring的,另外Dubbo、RabbitMQ等也是如此。

public class AopNamespaceHandler extends NamespaceHandlerSupport {
    /**
     * Register the {@link BeanDefinitionParser BeanDefinitionParsers} for the
     * '{@code config}', '{@code spring-configured}', '{@code aspectj-autoproxy}'
     * and '{@code scoped-proxy}' tags.
     */
    @Override
    public void init() {
        // In 2.0 XSD as well as in 2.1 XSD.
        registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
        registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
        registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());

        // Only in 2.0 XSD: moved to context namespace as of 2.1
        registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
    }
} 

三、AOP初始化配置

接着上面的Namespace继续分析,下面是Namespacce解析类,代码中会区分默认Namespace和第三方组件Namespace,第三方中间件对接到Spring中,大部分通过Namespace形式对接。DefaultBeanDefinitionDocumentReader.class解析如下:

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
    if (delegate.isDefaultNamespace(root)) {
        // nl中包含所有的类""标签
        NodeList nl = root.getChildNodes();
        for (int i = 0; i < nl.getLength(); i++) {
            Node node = nl.item(i);
            if (node instanceof Element) {
                Element ele = (Element) node;
                if (delegate.isDefaultNamespace(ele)) {
                    parseDefaultElement(ele, delegate);
                } else {
                    // 每个Node中包含Namespace属性,第三方Namespace解析
                    delegate.parseCustomElement(ele);
                }
            }
        }
    } else {
        // 非标签解析
        delegate.parseCustomElement(root);
    }
}

我们跟【delegate.parseCustomElement(ele)】进去,在BeanDefinitionParserDelegate.class中完成NamespaceHandler的获取,即【二、AOP启动入口】

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
    String namespaceUri = getNamespaceURI(ele);
    // Namespace对应的类反射加载
    NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
    if (handler == null) {
        error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
        return null;
    }
    // 这里解析 ParserContext包含了bean解析过程中所有的相关配置
    return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

解析【<aop:config>】,通过this.parsers.get()获取对应的解析类,该解析类参考【org.springframework.aop.config.AopNamespaceHandler】

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    return findParserForElement(element, parserContext).parse(element, parserContext);
}

private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
    String localName = parserContext.getDelegate().getLocalName(element);
    BeanDefinitionParser parser = this.parsers.get(localName);
    if (parser == null) {
        parserContext.getReaderContext().fatal(
        "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
    }
    return parser;
}

获取到BeanDefinitionParser,跟进到BeanDefinitionParser的实现类ConfigBeanDefinitionParser的parse(....)方法中。configureAutoProxyCreator(parserContext, element)非常关键,后续的针对这个对象进行AOP代理的生成,也是AOP的核心。

@Override
public BeanDefinition parse(Element element, ParserContext parserContext) {
    CompositeComponentDefinition compositeDef =
        new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element));
    parserContext.pushContainingComponent(compositeDef);
    
    // 生成AspectJAwareAdvisorAutoProxyCreator对象,注册到Spring
    configureAutoProxyCreator(parserContext, element);
    
    List childElts = DomUtils.getChildElements(element);
    for (Element elt: childElts) {
        String localName = parserContext.getDelegate().getLocalName(elt);
        if (POINTCUT.equals(localName)) {
            parsePointcut(elt, parserContext);
        } else if (ADVISOR.equals(localName)) {
            parseAdvisor(elt, parserContext);
        } else if (ASPECT.equals(localName)) {
            parseAspect(elt, parserContext);
        }
    }
}

我们只针对ASPECT的解析进行分析,其他标签原理类似:


001.png

1、内部是for循环用于解析所有的<aop:advice>;

2、进一步解析<aop:advice>为RootBeanDefinition;

3、根据标签名称获取其类对象(比如AspectJMethodBeforeAdvice.class),然后获取<aop:before>的属性封装到ConstructorArgumentValues(用于封装构造函数参数);

4、第三步获取的AspectJMethodBeforeAdvice对象已经封装了相应的属性值,这些然后封装到AspectJPointcutAdvisor.class,该类中持有Pointcut、Advice和Order参数,这样就生成了一个Aspect;

5、获取所有的<aop:pointcut>标签

6、解析出来<aop:pointcut>中的表达式属性,并解析成RootBeanDefinition,然后注册到Spring

四、AOP生成代理

第二步已经把所有的标签生成了RootBeanDefinition,configureAutoProxyCreator(parserContext, element)这个方法生成了AOP核心类AspectJAwareAdvisorAutoProxyCreator,该类的类关系图:


002.png

AspectJAwareAdvisorAutoProxyCreator是BeanPostProcessor的实现类,核心就在postProcessAfterInitialization(....)这里,该方法会生成代理类。

Spring的启动过程在AbstractApplicationContext的refresh()进行。

@Override
public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        // Prepare this context for refreshing.
        prepareRefresh();
        
        // 创建BeanFactory,没有就创建,第二步就在这里发生的(BeanDefinition的装载)
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        
        // Prepare the bean factory for use in this context.
        prepareBeanFactory(beanFactory);
        
        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);
            
            // 子类可以获取BeanFactoreanFactory
            invokeBeanFactoryPostProcessors(beanFactory);
            
            // 注册所有实现实现了BeanPostProcessor的类,存储在DefaultListableBeanFactory,beanFactory持有上述加载的所有的Bean,其中AspectJAwareAdvisorAutoProxyCreator也在其中 
            
            // 其中BeanId是org.springframework.aop.config.internalAutoProxyCreator,获取不到就create
            registerBeanPostProcessors(beanFactory);
            
            // Initialize message source for this context.
            initMessageSource();
            
            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();
            
            // Initialize other special beans in specific context subclasses.
            onRefresh();
            
            // Check for listener beans and register them.
            registerListeners();
            
            // 最关键的Instantiate all remaining (non-lazy-init) singletons.
            finishBeanFactoryInitialization(beanFactory);
            
            // Last step: publish corresponding event.
            finishRefresh();
        } catch (BeansException ex) {
            if (logger.isWarnEnabled()) {
                logger.warn("Exception encountered during context initialization - " +
                "cancelling refresh attempt: " + ex);
            }
        
            // Destroy already created singletons to avoid dangling resources.
            destroyBeans();
            
            // Reset 'active' flag.
            cancelRefresh(ex);
            
            // Propagate exception to caller.
            throw ex;
        } finally {
            // Reset common introspection caches in Spring's core, since we
            // might not ever need metadata for singleton beans anymore...
            resetCommonCaches();
        }
    }
}

最关键的是finishBeanFactoryInitialization(beanFactory),实例化所有非懒加载的Bean(回顾Bean的生命周期):

1、通过构造函数实例化

2、设置属性

3、回调BeanNameAware、BeanFactoryAware等接口子类的方法

4、遍历BeanPostProcessor的子类,然后回调他们的实现的两个方法(所谓BeanProcessor回调)

5、回调InitializingBean

6、ini-method属性....后续都是Bean的生命周期中的内容,关键要看第4步

当前Spring中有6个Bean和4个BeanPostProcessor:


003.png
004.png

举例说明,当根据Spring Bean生命周期,math在实例化(create)之前,调用BeanPostProcessor的子类的before方法,下面是代码片段:

005.png

上面的红框是用于调用BeanPostProcessor的前置方法,AOP的代理类在BeanPostProcessor的后置方法中生成。

@Override
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;
}

接下来调用AbstractAutoProxyCreator的postProcessAfterInitialization(....):

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean != null) {
        Object cacheKey = getCacheKey(bean.getClass(), beanName);
        if (!this.earlyProxyReferences.contains(cacheKey)) {
            return wrapIfNecessary(bean, beanName, cacheKey);
        }
    }
    return bean;
}

我们跟wrapIfNecessary(bean, beanName, cacheKey),该方法只对math做了进一步的处理:

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
    if (beanName != null && this.targetSourcedBeans.contains(beanName)) {
        return bean;
    }
    // this.advisedBeans这个map中包含beanI->boolean,不可以进一步则进入到下一步判断
    if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
        return bean;
    }
    if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }
    
    // 当前beanName如果有advice则可以为其创建代理,这里先去查找符合条件的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;
}

查找代理,接下来对当前beanName的符合条件的Advice进行查找:

AbstractAdvisorAutoProxyCreator

@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass, String beanName, TargetSource targetSource) {
    // 查找Advice
    List advisors = findEligibleAdvisors(beanClass, beanName);
    if (advisors.isEmpty()) {
        return DO_NOT_PROXY;
    }
    return advisors.toArray();
}

protected List findEligibleAdvisors(Class beanClass, String beanName) {
    // 所有的潜在Advice
    List candidateAdvisors = findCandidateAdvisors();
    // 选取符合条件的Advice
    List eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);
        extendAdvisors(eligibleAdvisors);    // 在链表头添加一个默认ExposeInvocationInterceptor
    if (!eligibleAdvisors.isEmpty()) {
        eligibleAdvisors = sortAdvisors(eligibleAdvisors);
    }
    return eligibleAdvisors;
}

/**
 * Search the given candidate Advisors to find all Advisors that
 * can apply to the specified bean.
 * @param candidateAdvisors the candidate Advisors
 * @param beanClass the target's bean class
 * @param beanName the target's bean name
 * @return the List of applicable Advisors
 * @see ProxyCreationContext#getCurrentProxiedBeanName()
 */
protected List<Advisor> findAdvisorsThatCanApply(
      List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) {

   ProxyCreationContext.setCurrentProxiedBeanName(beanName);
   try {
      return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass);
   }
   finally {
      ProxyCreationContext.setCurrentProxiedBeanName(null);
   }
}

AopUtils

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;
   }
}

/**
 * Can the given pointcut apply at all on the given class?
 * <p>This is an important test as it can be used to optimize
 * out a pointcut for a class.
 * @param pc the static or dynamic pointcut to check
 * @param targetClass the class to test
 * @param hasIntroductions whether or not the advisor chain
 * for this bean includes any introductions
 * @return whether the pointcut can apply on any method
 */
 /** 获取pc的正则表达式,然后获取target的方法列表和正则表达式对比,满足返回true **/
public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {
   Assert.notNull(pc, "Pointcut must not be null");
   if (!pc.getClassFilter().matches(targetClass)) {
      return false;
   }

    // Pointcut的正则表达式
   MethodMatcher methodMatcher = pc.getMethodMatcher();
   IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;
   if (methodMatcher instanceof IntroductionAwareMethodMatcher) {
      introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;
   }

   Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));
   classes.add(targetClass);
   for (Class<?> clazz : classes) {
      Method[] methods = clazz.getMethods(); // 目标对象的方法
      for (Method method : methods) {
         if ((introductionAwareMethodMatcher != null &&
               introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||
               methodMatcher.matches(method, targetClass)) {
            return true;
         }
      }
   }

   return false;
}

获取了所有的Adivce后,就可以生成代理对象了:

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

    if (this.beanFactory instanceof ConfigurableListableBeanFactory) {
        AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);
    }
    
    ProxyFactory proxyFactory = new ProxyFactory();
    proxyFactory.copyFrom(this);
    // proxy-target-class属性
    if (!proxyFactory.isProxyTargetClass()) {
        if (shouldProxyTargetClass(beanClass, beanName)) {
            proxyFactory.setProxyTargetClass(true);
        } else {
            evaluateProxyInterfaces(beanClass, proxyFactory);
        }
    }
    
    // Advice封装到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());
}

// 根据配置生成代理
@Override
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() || Proxy.isProxyClass(targetClass)) {
            return new JdkDynamicAopProxy(config);
        }
        return new ObjenesisCglibAopProxy(config);
    } else {
        return new JdkDynamicAopProxy(config);
    }
}

public Object getProxy(ClassLoader classLoader) {
    // createAopProxy()获取代理,然后具体细节在getProxy()中
    return createAopProxy().getProxy(classLoader);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容