文档章节

Spring Aop标签解析原理详解

爱宝贝丶
 爱宝贝丶
发布于 2018/08/05 18:46
字数 1925
阅读 429
收藏 4

       对于Spring Aop的实现,是非常复杂的,其实现过程主要包含xml标签的解析,切面表达式的解析,判断bean是否需要应用切面逻辑,以及使用Jdk代理或者是Cglib代理生成代理类。本文主要讲解Xml标签的解析的实现原理,在接下来几篇文章中,会依次对Spring Aop剩余的实现过程进行讲解。

       关于Spring Aop的实现,由于其是使用自定义标签进行驱动的,因而读者朋友如果对Spring如何实现自定义标签比较熟悉,那么可以继续往下阅读,否则可以阅读完本文后再本人前面的文章Spring自定义标签解析与实现

1. Aop使用示例

首先我们声明了一个切面类如下:

@Aspect
public class DogAspect {
  @Around("execution(public void com.business.Dog.*(..))")
  public Object aspect(ProceedingJoinPoint joinPoint) throws Throwable {
    System.out.println("before run.");
    Object result = joinPoint.proceed();
    System.out.println("after run.");
    return result;
  }
}

       该切面类主要用于环绕com.business.Dog类中的public类型的,并且返回值是void的所有方法,下面我们就在com.business包中声明一个Dog类如下:

public class Dog {
  public void run() {
    System.out.println("Tidy is running.");
  }
}

       这里切面类和目标类都已经声明完成,但如果不将其加入Spring容器中,其是不会工作的,加入容器的方式非常简单,下面就是一种方式:

<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <bean id="dog" class="com.business.Dog"/>

    <bean id="aspect" class="com.business.DogAspect"/>

    <aop:aspectj-autoproxy/>
</beans>

       这里需要说明的是,将DogAspect声明为一个bean并不能使其工作,因为其也仅仅只是一个bean而已,要使其工作还需要使用上面的<aop:aspectj-autoproxy/>标签实现切面的自动装配。下面使我们运行整个程序的驱动类:

public class DogApp {
  public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    Dog dog = context.getBean(Dog.class);
    dog.run();
  }
}

       执行结果如下:

before run.
Tidy is running.
after run.

       可以看到,我们在驱动类中获取的是Dog的实例,并且运行其run()方法,但是最终的运行结果中也运行了切面类中的环绕逻辑。

2. 实现原理

       根据前面对Spring自定义标签使用的讲解,我们知道这里<aop:aspectj-autoproxy/>就是一个自定义标签,并且该标签会在相应jar包的META-INF目录下有一个spring.handlers文件,该文件中声明了解析该标签的类。通过查看该类我们得到如下配置:

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

       这里我们打开AopNamespaceHandler,其实现如下:

public class AopNamespaceHandler extends NamespaceHandlerSupport {
	@Override
	public void init() {
		registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
		registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
		registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
		registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
	}
}

       可以看到,我们需要解析的标签解析器在这个类中进行了注册,即AspectJAutoProxyBeanDefinitionParser,打开这个类其主要实现如下:

class AspectJAutoProxyBeanDefinitionParser implements BeanDefinitionParser {

    // 解析标签的时候将会执行的方法
    @Override
    @Nullable
    public BeanDefinition parse(Element element, ParserContext parserContext) {
        // 注册一个类型为AnnotationAwareAspectJAutoProxyCreator的bean到Spring容器中
        AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
        // 通过读取配置文件对扩展相关属性   
        extendBeanDefinition(element, parserContext);
        return null;
    }

    private void extendBeanDefinition(Element element, ParserContext parserContext) {
        // 获取前面注册的AnnotationAwareAspectJAutoProxyCreator对应的BeanDefinition
        BeanDefinition beanDef =
            parserContext.getRegistry().getBeanDefinition(AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
        // 解析当前标签的子标签
        if (element.hasChildNodes()) {
            addIncludePatterns(element, parserContext, beanDef);
        }
    }

    // 解析子标签中的name属性,其可以有多个,这个name属性最终会被添加到
    // AnnotationAwareAspectJAutoProxyCreator的includePatterns属性中,
    // Spring在判断一个类是否需要进行代理的时候会判断当前bean的名称是否与includePatterns中的
    // 正则表达式相匹配,如果不匹配,则不进行代理
    private void addIncludePatterns(Element element, ParserContext parserContext, BeanDefinition beanDef) {
        ManagedList<TypedStringValue> includePatterns = new ManagedList<>();
        NodeList childNodes = element.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node node = childNodes.item(i);
            if (node instanceof Element) {
                Element includeElement = (Element) node;
                // 解析子标签中的name属性
                TypedStringValue valueHolder = new TypedStringValue(includeElement.getAttribute("name"));
                valueHolder.setSource(parserContext.extractSource(includeElement));
                includePatterns.add(valueHolder);
            }
        }

        // 将解析到的name属性设置到AnnotationAwareAspectJAutoProxyCreator
        // 的includePatterns属性中
        if (!includePatterns.isEmpty()) {
            includePatterns.setSource(parserContext.extractSource(element));
            beanDef.getPropertyValues().add("includePatterns", includePatterns);
        }
    }
}

       这里我们继续看AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);方法,该方法的实现如下:

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
			ParserContext parserContext, Element sourceElement) {
    // 注册AnnotationAwareAspectJAutoProxyCreator的BeanDefinition
    BeanDefinition beanDefinition = 
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
	parserContext.getRegistry(), parserContext.extractSource(sourceElement));
    // 解析标签中的proxy-target-class和expose-proxy属性值,
    // proxy-target-class主要控制是使用Jdk代理还是Cglib代理实现,expose-proxy用于控制
    // 是否将生成的代理类的实例防御AopContext中,并且暴露给相关子类使用
	useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
    // 将注册的BeanDefinition封装到BeanComponentDefinition中
	registerComponentIfNecessary(beanDefinition, parserContext);
}

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, 
       @Nullable Element sourceElement) {
    if (sourceElement != null) {
        // 解析标签中的proxy-target-class属性值
        boolean proxyTargetClass =
            Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));
        if (proxyTargetClass) {
            // 将解析得到的proxy-target-class属性值设置到上面生成的
            // AnnotationAwareAspectJAutoProxyCreator的BeanDefinition的proxyTargetClass
            // 属性中
            AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
        }
        // 解析标签中的expose-proxy属性值
        boolean exposeProxy = 
            Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));
        if (exposeProxy) {
            // 将解析得到的expose-proxy属性值设置到
            // AnnotationAwareAspectJAutoProxyCreator的exposeProxy属性中
            AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
        }
    }
}

private static void registerComponentIfNecessary(@Nullable BeanDefinition beanDefinition, 
       ParserContext parserContext) {
    // 如果生成的AnnotationAwareAspectJAutoProxyCreator的BeanDefinition成功,则将其封装到
    // BeanComponentDefinition中,并且将其添加到ParserContext中
    if (beanDefinition != null) {
        BeanComponentDefinition componentDefinition =
            new BeanComponentDefinition(beanDefinition, 
                AopConfigUtils.AUTO_PROXY_CREATOR_BEAN_NAME);
        parserContext.registerComponent(componentDefinition);
    }
}

       这里可以看到AnnotationAwareAspectJAutoProxyCreatorBeanDefinition在第一步进行了注册,然后读取标签中的proxy-target-class和expose-proxy属性,并且将属性值设置到生成的BeanDefinition中。最后将生成的BeanDefinition注册到ParserContext中。这里我们继续看AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement))方法,其实现如下:

@Nullable
public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,
       @Nullable Object source) {
    // 注册AnnotationAwareAspectJAutoProxyCreator类型的BeanDefinition
    return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class,
       registry, source);
}

@Nullable
private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls,
       BeanDefinitionRegistry registry, @Nullable Object source) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

    // 如果已经注册过AnnotationAwareAspectJAutoProxyCreator的Definition,如果其
    // 和当前将要注册的BeanDefinition是同一个类型,则不再注册,如果不同,则判断其优先级比
    // 当前将要注册的BeanDefinition要高,则将其类名设置为当前要注册的BeanDefinition的名称
    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;
    }

    // 如果不存在已经注册的Aop的bean,则生成一个,并且设置其执行优先级为最高优先级,并且标识
    // 该bean为Spring的系统Bean,设置完之后则对该bean进行注册
    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;
}

       可以看到,在真正生成AnnotationAwareAspectJAutoProxyCreatorBeanDefinition的时候,首先会判断是否已经生成过该bean,这里不会将已经生成的bean进行覆盖;如果没有生成该bean,则创建一个并进行注册。这里需要说明的是,Spring注册该bean的时候使用的order是Ordered.HIGHEST_PRECEDENCE,这么设置的原因在于Spring使用该bean进行切面逻辑的织入,因而这个bean必须在所有用户自定义的bean实例化之前进行实例化,而用户自定义的bean的实例化优先级是比较低的,这样才能实现织入代理逻辑的功能。

3. 小结

       本文首先使用一个简单的示例展示了Spring Aop的使用方式,然后对标签中的<aop:aspectj-autoproxy/>解析过程进行了讲解。可以看到,该标签的解析过程最终是生成了一个AnnotationAwareAspectJAutoProxyCreatorBeanDefinition,关于Spring是如何使用该类实现代理的逻辑将在下一篇文章中进行讲解。

© 著作权归作者所有

爱宝贝丶

爱宝贝丶

粉丝 324
博文 129
码字总数 427858
作品 0
武汉
程序员
私信 提问
加载中

评论(7)

东方未明
东方未明
AnnotationAwareAspectJAutoProxyCreator是一个bpp 他在invokeBeanFactoryPostProcessors的时候就会因为
priorityOrderedPostProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));实例一次吧 而普通bean 是在finishBeanFactoryInitialization的时候 实例话的 和Ordered.HIGHEST_PRECEDENCE没啥关系吧
爱宝贝丶
爱宝贝丶 博主
你说这是一个bpp,我看半天才看出来你说的是BeanPostProcessor😂。我感觉你这边是有点搞混了,BeanPostProcessor是在一个bean实例化之后才会调用的,主要作用是对已经生成的bean进行一些封装处理的,而你说的invokeBeanFactoryPostProcessors()方法是用来调用BeanFactoryPostProcessor的,这个主要是对BeanDefinition进行一些额外处理的。 然后就是这里的AnnotationAwareAspectJAutoProxyCreator实现的是BeanPostProcessor,这个类里面的postProcessAfterInitialization()方法就是供给AnnotationAwareAspectJAutoProxyCreator来给目标bean生成代理对象的。前面我的讲述中说,AnnotationAwareAspectJAutoProxyCreator对应的BeanDefinition的order是Ordered.HIGHEST_PRECEDENCE,这样可以保证AnnotationAwareAspectJAutoProxyCreator.postProcessAfterInitialization()一定会在最开始就调用,这是因为BeanPostProcessor是一个公共接口,spring提供这个接口就是给我们做定制使用的,但是spring也将自己的某些类来实现这个接口,就比如这里的AnnotationAwareAspectJAutoProxyCreator,用以实现某些功能。这样就会出现一个问题是,用户实现的类的postProcessAfterInitialization()方法可能会在spring实现的类的这个方法之前就被调用了,所以spring就使用了这种排序的方式,用以保证spring提供的类一定会在用户实现的类之前执行。具体的可以看下面的代码:
爱宝贝丶
爱宝贝丶 博主
@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; }
爱宝贝丶
爱宝贝丶 博主
osc把代码压缩了,不好调整,其实比较简单,你可以看一下,在AbstractAutowireCapableBeanFactory这个类里面。可以看到,这里获取BeanPostProcessor的方式就是获取所有的BeanPostProcessor,而无论这些实现类是spring内置的还是用户提供的,所以要进行排序。
东方未明
东方未明
不能放图片啊 好烦
东方未明
东方未明
我debug错了 不好意思
东方未明
东方未明
最近阅读了源码 现在发现了这篇文章 写的很棒
Spring AOP 功能使用详解

相关文章 Spring 中 bean 注册的源码解析 Spring bean 创建过程源码解析 Spring 的 getBean 方法源码解析 前言 AOP 既熟悉又陌生,了解过 Spring 人的都知道 AOP 的概念,即面向切面编程,可...

TSMYK
2018/12/25
241
2
Spring AOP 创建代理的源码解析

相关文章 Spring AOP 注解方式源码解析 Spring AOP 功能使用详解 Spring 的 getBean 方法源码解析 Spring bean 创建过程源码解 Spring 中 bean 注册的源码解析 前言 在上篇文章 Spring AOP 注...

TSMYK
01/01
135
0
dubbo源码理解(1)启动初始化与bean加载

今天看了一些博文,都是关于dubbo源码解析方面的。觉得有必要记一下。 问题1:spring 如何注入dubbo 的?或者说怎么集成dubbo 的,或者说 dubbo启动时怎么启动spring的? 1、首先想要实现 在...

明瞐
2018/10/13
122
0
2019BATJ面试题详解:MyBatis+MySQL+Spring+Redis+多线程

这里为大家分享一些面试的一手资料,供大家迎接接下来的金三银四跳槽季 Spring Spring 概述 什么是spring? 使用Spring框架的好处是什么? Spring由哪些模块组成? 解释AOP模块 Spring配置文件...

别打我会飞
03/20
226
0
Spring事务用法示例与实现原理

关于事务,简单来说,就是为了保证数据完整性而存在的一种工具,其主要有四大特性:原子性,一致性,隔离性和持久性。对于Spring事务,其最终还是在数据库层面实现的,而Spring只是以一种比较...

爱宝贝丶
2018/08/28
7.5K
3

没有更多内容

加载失败,请刷新页面

加载更多

作为一个(IT)程序员!聊天没有话题?试试这十二种技巧

首先呢?我是一名程序员,经常性和同事没话题。 因为每天都会有自己的任务要做,程序员对于其他行业来说;是相对来说比较忙的。你会经常看到程序员在发呆、调试密密麻麻代码、红色报错发呆;...

小英子wep
今天
15
0
【SpringBoot】产生背景及简介

一、SpringBoot介绍 Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 Spring 应用的初始搭建以及开发过程,该框架使用了特定的方式来进行配置,从而使开发人员不再需要...

zw965
今天
5
0
简述并发编程分为三个核心问题:分工、同步、互斥。

总的来说,并发编程可以总结为三个核心问题:分工、同步、互斥。 所谓分工指的是如何高效地拆解任务并分配给线程,而同步指的是线程之间如何协作,互斥则是保证同一时刻只允许一个线程访问共...

dust8080
今天
6
0
OSChina 周四乱弹 —— 当你简历注水但还是找到了工作

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @花间小酌 :#今日歌曲推荐# 分享成龙的单曲《男儿当自强》。 《男儿当自强》- 成龙 手机党少年们想听歌,请使劲儿戳(这里) @hxg2016 :刚在...

小小编辑
今天
3.4K
22
靠写代码赚钱的一些门路

作者 @mezod 译者 @josephchang10 如今,通过自己的代码去赚钱变得越来越简单,不过对很多人来说依然还是很难,因为他们不知道有哪些门路。 今天给大家分享一个精彩的 GitHub 库,这个库整理...

高级农民工
昨天
10
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部