Spring源码解析系列之AOP(二)

前言

在开始分析AOP之前,大家可以参考我的前2篇博客。
Java动态代理(https://www.jianshu.com/p/e6914f48f2a9)
Spring IOC(https://www.jianshu.com/p/3c70f548481d)
AOP即面向切面编程,其本质上就是把大多数接口都要做或者必须做的事情委托给代理类来做。比如所有人都要买房子,那么大家可以把这件事交给房产交易平台。网上博客翻了个底朝天也没有找到哪个博客把AOP源码解析部分讲的比较透彻的,大多数都让人摸不着头脑,即便给了aop入口说的也不是很清晰。本篇博客将以赵本山和宋丹丹的经典小品钟点工为例,讲讲把大象装冰箱的故事,来详细的描述下面向切面编程。
所用代码地址:https://github.com/KouLouYiMaSi/SpringDecode

一、Spring配置面向切面编程

1、ApplicationContext.xml配置

除了两个bean:zhaobenshan和adviceElephantTools,其他bean可以忽略。Aop配置中配置了切面myaop(切面想象成大象装冰箱服务公司),待切入的切入点logPointcut(这里使用了aspectj表达式,看看就行),切入的两个方法before和after。
值得注意的是本xml头部信息里需要有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-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd
          http://www.springframework.org/schema/aop 
            http://www.springframework.org/schema/aop/spring-aop-2.0.xsd">
    <bean id="springHelloWorld"
        class="com.yiibai.tutorial.spring.helloworld.impl.SpringHelloWorld"></bean>
    <bean id="strutsHelloWorld"
        class="com.yiibai.tutorial.spring.helloworld.impl.StrutsHelloWorld"></bean>
    <bean id="helloWorldService" class="com.yiibai.tutorial.spring.helloworld.HelloWorldService">
        <property name="helloWorld" ref="strutsHelloWorld" />
    </bean>
    <bean id="zhaobenshan" class="com.yiibai.tutorial.spring.aop.Zhaobenshan" />
    <bean id="adviceElephantTools" class="com.yiibai.tutorial.spring.aop.AdviceElephantTools" />
    <aop:config>
        <aop:aspect id="myaop" ref="adviceElephantTools">  <!--切面名称-->
            <aop:pointcut id="logPointcut"
                expression="execution(* com.yiibai.tutorial.spring.aop.*.*(..))" /> <!--切入点-->
            <aop:before method="before" pointcut-ref="logPointcut" /> <!--前置方法-->
            <aop:after method="after" pointcut-ref="logPointcut" /> <!--后置方法-->
        </aop:aspect>
    </aop:config>
</beans>

2、两个bean配置

这里为了使用JDK动态代理而设计了一个接口,spring aop会默认使用jdk动态代理,在没有接口的情况下会使用cglib动态代理。
赵本山需要实现大象工具接口来把大象装冰箱里,但是大象工具切面类还提供了打开冰箱门和关闭冰箱门等操作。如果世界上有1000个赵本山那么只需要一个大象切面类就能为所有赵本山提供打开冰箱门和关闭冰箱门操作。

public interface PuttingTool {
    public abstract void putElephantToRefrigerator();
}
public class Zhaobenshan implements PuttingTool {

    public void putElephantToRefrigerator() {
        System.out.println("把大象装进冰箱里!");
    }
}
public class AdviceElephantTools {

    public void before() {
        System.out.println("打开冰箱门。。。");
    }

    public void after() {
        System.out.println("关上冰箱门。。。");
    }

    public void on() {
        System.out.println("把大象塞进冰箱");
    }

}

3、测试类和打印结果

由于我们上面配置了aopbefore和aopafter,所以在切点方法前后执行了打开冰箱门和关闭冰箱门操作,这里的赵本山本质上不是赵本山,而是赵本山的代理类。所以打印isInstance会是false。

public class HelloProgram {
    public static void main(String[] args) {
        @SuppressWarnings("resource")
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        PuttingTool zhaoBenShan = (PuttingTool) context.getBean("zhaobenshan");
        System.out.println(zhaoBenShan.getClass().isInstance(Zhaobenshan.class));
        zhaoBenShan.putElephantToRefrigerator();
    }
}
打印结果:
false
打开冰箱门。。。
把大象装进冰箱里!
关上冰箱门。。。

二、源码追踪

由于我们没有设置bean的懒加载,所以在初始化容器的时候就已经把bean生成好了,在追踪源码之前希望你有几样东西,Eclipse+maven(下载源代码勾选上)+ctrl按键

step1

ctrl+鼠标左键单击,按照调用链进入方法
ClassPathXmlApplicationContext-->ClassPathXmlApplicationContext.this(new String[] {configLocation}, true, null);-->ClassPathXmlApplicationContext.refresh()。这个refresh方法我们比较熟悉,但对于aop来说我们只关注obtainFreshBeanFactory和registerBeanPostProcessors以及finishRefresh方法。

    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            prepareRefresh();
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            prepareBeanFactory(beanFactory);
            try {
                postProcessBeanFactory(beanFactory);
                invokeBeanFactoryPostProcessors(beanFactory);
                registerBeanPostProcessors(beanFactory);
                initMessageSource();
                initApplicationEventMulticaster();
                onRefresh();
                registerListeners();
                finishBeanFactoryInitialization(beanFactory);
                finishRefresh();
            }
-----省略异常-----
    }

step2

来看obtainFreshBeanFactory方法调用链
ctrl+鼠标左键单击 AbstractApplicationContext.refreshBeanFactory-->AbstractRefreshableApplicationContext.refreshBeanFactory-->AbstractXmlApplicationContext.loadBeanDefinitions-->AbstractBeanDefinitionReader.loadBeanDefinitions-->XmlBeanDefinationReader.loadBeanDefinitions-->XmlBeanDefinationReader.doLoadBeanDefinitions-->XmlBeanDefinationReader.registerBeanDefinitions-->DefaultBeanDefinitionDocumentReader.registerBeanDefinitions-->DefaultBeanDefinitionDocumentReader.doRegisterBeanDefinitions-->DefaultBeanDefinitionDocumentReader.parseBeanDefinitions
这个parseBeanDefinitions方法就把xml中的所有bean都解析了,并且调用了一个parseCustomElement专门用于处理代理类

protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
        if (delegate.isDefaultNamespace(root)) {
            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 {
                        delegate.parseCustomElement(ele);
                    }
                }
            }
        }
        else {
            delegate.parseCustomElement(root);
        }
    }

继续BeanDefinitionParserDelegate.parseCustomElement方法,这里的namespaceUri就是我们前面提到过的那个xml头http://www.springframework.org/schema/aop。从这个namespaceUri中解析出来一个处理器NamespaceHandler,该处理器持有
{aspectj-autoproxy=org.springframework.aop.config.AspectJAutoProxyBeanDefinitionParser@149e0f5d, spring-configured=org.springframework.aop.config.SpringConfiguredBeanDefinitionParser@1b1473ab, config=org.springframework.aop.config.ConfigBeanDefinitionParser@2f7c2f4f}
这三个parser用来处理定制的bean。

public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) {
        String namespaceUri = getNamespaceURI(ele);
        NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
        if (handler == null) {
            error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
            return null;
        }
        return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
    }

继续,上面的handler.parse最终调用的是ConfigBeanDefinationParser.parse这个方法,这个方法就一目了然了,这里对xml中aop:config标签进行了分析,具体的分析逻辑这里就不详细介绍了

    public BeanDefinition parse(Element element, ParserContext parserContext) {
        CompositeComponentDefinition compositeDef =new CompositeComponentDefinition(element.getTagName(),parserContext.extractSource(element));
        parserContext.pushContainingComponent(compositeDef);
        configureAutoProxyCreator(parserContext, element);
        List<Element> 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);
            }
        }
        parserContext.popAndRegisterContainingComponent();
        return null;
    }

上面这些方法全都执行完之后,即loadBeanDefinition方法执行完之后beanFactory就已经有构建zhaobenshan,
adviceElephantTools,
org.springframework.aop.config.internalAutoProxyCreator,
org.springframework.aop.aspectj.AspectJPointcutAdvisor#0,
org.springframework.aop.aspectj.AspectJPointcutAdvisor#1,
logPointcut
这些bean的能力了。

step3

回到refresh方法,查看registerBeanPostProcessors调用链
AbstractApplicationContext.registerBeanPostProcessors-->PostProcessorRegistrationDelegate.registerBeanPostProcessors。这个方法实际上是在beanFactory即IOC容器中注册了PostProcessors,这些被注册的Processors会在bean创建完之后调用,你可能会问这个processor是啥,实际上由于前面BeanFactory已经有了proxy相关的bean定义,所以这个processor就是用来创建代理类的。

step4

回到refresh方法,沿着调用链finishBeanFactoryInitialization-->DefaultListableBeanFactory.preInstantiateSingletons,这个方法会提前加载一些bean进入内存,即那些没有设置为懒加载的bean都会通过该类被放到内存中。

public void preInstantiateSingletons() throws BeansException {
        if (this.logger.isDebugEnabled()) {
            this.logger.debug("Pre-instantiating singletons in " + this);
        }
        List<String> beanNames = new ArrayList<String>(this.beanDefinitionNames);
        for (String beanName : beanNames) {
            RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
            if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
                if (isFactoryBean(beanName)) {
                    final FactoryBean<?> factory = (FactoryBean<?>) getBean(FACTORY_BEAN_PREFIX + beanName);
                    boolean isEagerInit;
                    if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
                        isEagerInit = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
                            @Override
                            public Boolean run() {
                                return ((SmartFactoryBean<?>) factory).isEagerInit();
                            }
                        }, getAccessControlContext());
                    }
                    else {
                        isEagerInit = (factory instanceof SmartFactoryBean &&
                                ((SmartFactoryBean<?>) factory).isEagerInit());
                    }
                    if (isEagerInit) {
                        getBean(beanName);
                    }
                }
                else {
                    getBean(beanName);
                }
            }
        }

当创建到zhaobenshan的bean的时候,执行了AbstractBeanFactory.getBean-->AbstractBeanFactory.doGetBean-->AbstractAutowireCapableBeanFactory.doGetBean-->AbstractAutowireCapableBeanFactory.createBean-->AbstractAutowireCapableBeanFactory.doCreateBean-->AbstractAutowireCapableBeanFactory.initializeBean-->AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization-->AbstractAutoProxyCreator.postProcessAfterInitialization


image.png

image.png

image.png

就是下面这个方法,执行了代理类的创建。

/**
     * Create a proxy with the configured interceptors if the bean is
     * identified as one to proxy by the subclass.
     * @see #getAdvicesAndAdvisorsForBean
     */
    @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;
    }
    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;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
            this.advisedBeans.put(cacheKey, Boolean.FALSE);
            return bean;
        }

        // 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;
    }
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());
    }
image.png

OK,经过上面的过程我们可以看到,最终底层调用了JDK或者cglib代理创建代理类。这样实例化出来的类就是代理类了,结合前两篇博客就可以把aop过程吃透了。

总结

如果你有兴趣自己研究源码可以使用debug断点的模式,我是F5一个一个往里面进的,这个过程虽然麻烦,但是确实管用。
代理类的两个重要方法实际上是在:registerBeanPostProcessors + applyBeanPostProcessorsAfterInitialization(beanProcessor.postProcessAfterInitialization(result, beanName))这两个方法上,前者用于注册AOP创建代理Processor,后者用于调用Processor生成代理类!

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