上文简要总结了一些AOP的基本概念,并在此基础上叙述了Spring AOP的基本原理,并且辅以一个简单例子帮助理解。从本文开始,我们要开始深入到源码层面来一探Spring AOP魔法的原理了。

  要使用Spring AOP,第一步是要将这一功能开启,一般有两种方式:

  • 通过xml配置文件的方式;
  • 通过注解的方式;

1. 配置文件开启AOP功能

  我们先来看一下配置文件的方式,这个上文也提到过,在xml文件中加上对应的标签,而且别忘了加上对应的名称空间(即下面的xmlns:aop。。。):

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  3. xmlns="http://www.springframework.org/schema/beans"
  4. xmlns:aop = "http://www.springframework.org/schema/aop"
  5. xsi:schemaLocation="http://www.springframework.org/schema/beans
  6. http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
  7. http://www.springframework.org/schema/aop
  8. http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
  9.  
  10. <aop:aspectj-autoproxy/>
  11.  
  12. </beans>

  这里是通过标签<aop:aspectj-autoproxy/>来完成开启AOP功能,这是一个自定义标签,需要自定义其解析,而这些spring都已经实现好了,前面专门写过一篇文章讲述spring是如何解析自定义xml标签的,我们这里大致回顾一下解析流程:

  • 定义一个XML文件来描述你的自定义标签元素;
  • 创建一个Handler,扩展自NamespaceHandlerSupport,用于注册下面的parser;
  • 创建若干个BeanDefinitionParser的实现,用来解析XML文件中的定义;
  • 将上述文件注册到Spring中,这里其实是做一下配置;

  我们就不照着这个步骤来了,我们直接参考spring对这个自定义标签的解析过程,上面的4个步骤只是作为参考,在整个解析过程中都会涉及到。

  前面讲解析自定义xml标签时候提到过,解析的流程大致如下:

  • 首先会去获取自定义标签对应的名称空间;
  • 然后根据名称空间找到对应的NamespaceHandler;
  • 调用自定义的NamespaceHandler进行解析;

1.1 获取名称空间

  这里<aop:aspectj-autoproxy/>对应的名称空间是什么呢?在上面的开启aop的配置文件里面名称空间那里给出了一些线索,其实就是下面这个:

  1. http://www.springframework.org/schema/aop

  至于名称空间的获取,也无甚好说的,其实就是直接调用org.w3c.dom.Node提供的相应方法来完成名称空间的提取。

1.2 获取handler

  然后又是如何根据名称空间找到对应的NamespaceHandler呢?之前也说到过,在找对应的NamespaceHandler时会去META-INF/spring.handlers这个目录下加载资源文件,我们来找一下spring.handlers这个文件看看(需要去spring-aop对应的jar报下找):

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

  看到没,这里是以key-value的形式维护着名称空间和对应handler的关系的,所以对应的handler就是这个AopNamespaceHandler。spring根据名称空间找到这个handler之后,会通过反射的方式将这个类加载,并缓存起来。

1.3 解析标签

  上面的handler只有一个自定义的方法:

  1. public void init() {
  2. // In 2.0 XSD as well as in 2.1 XSD.
  3. registerBeanDefinitionParser("config", new ConfigBeanDefinitionParser());
  4. registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
  5. registerBeanDefinitionDecorator("scoped-proxy", new ScopedProxyBeanDefinitionDecorator());
  6.  
  7. // Only in 2.0 XSD: moved to context namespace as of 2.1
  8. registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
  9. }

  这是一个初始化方法,在加载的时候会执行,主要作用就是注册一些解析器,这里我们主要关注AspectJAutoProxyBeanDefinitionParser,这就是我们要找的,它的作用就是解析<aop:aspectj-autoproxy/>标签的。主要流程就是,spring会调用上一步拿到的AopNamespaceHandler的parse()方法,在这个方法里面,会将解析的工作委托给AspectJAutoProxyBeanDefinitionParser来完成具体解析工作,我们就来看一下具体干了啥吧。

  开始解析的工作从这里开始:

  1. return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));

  此时我们拿到的handler其实是我们自定义的AopNamespaceHandler了,但是它并没有实现parse()方法,所以这里这个应该是调用的父类(NamespaceHandlerSupport)中的parse()方法:

  1. public BeanDefinition parse(Element element, ParserContext parserContext) {
  2. // 寻找解析器并进行解析操作
  3. return findParserForElement(element, parserContext).parse(element, parserContext);
  4. }
  5.  
  6. private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
  7. // 获取元素名称,也就是<aop:aspectj-autoproxy/>中的aspectj-autoproxy
  8. String localName = parserContext.getDelegate().getLocalName(element);
  9. // 根据aspectj-autoproxy找到对应的解析器,也就是在registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());
  10. // 注册的解析器
  11. BeanDefinitionParser parser = this.parsers.get(localName);
  12. if (parser == null) {
  13. parserContext.getReaderContext().fatal(
  14. "Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
  15. }
  16. return parser;
  17. }

  首先是寻找元素对应的解析器,然后调用其parse()方法。结合我们前面的示例,其实就是首先获取在AopNamespaceHandler类中的init()方法中注册对应的AspectJAutoProxyBeanDefinitionParser实例,并调用其parse()方法进行进一步解析:

  1. public BeanDefinition parse(Element element, ParserContext parserContext) {
  2. AopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);
  3. extendBeanDefinition(element, parserContext);
  4. return null;
  5. }
  6.  
  7. // 下面的代码在AopConfigUtils中
  8. public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  9. ParserContext parserContext, Element sourceElement) {
  10.  
  11. BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(
  12. parserContext.getRegistry(), parserContext.extractSource(sourceElement));
  13. useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);
  14. registerComponentIfNecessary(beanDefinition, parserContext);
  15. }
  16.  
  17. public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {
  18. return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
  19. }
  20.  
  21. private static BeanDefinition registerOrEscalateApcAsRequired(Class cls, BeanDefinitionRegistry registry, Object source) {
  22. Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
  23. if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
  24. BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
  25. if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
  26. int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
  27. int requiredPriority = findPriorityForClass(cls);
  28. if (currentPriority < requiredPriority) {
  29. apcDefinition.setBeanClassName(cls.getName());
  30. }
  31. }
  32. return null;
  33. }
  34. RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
  35. beanDefinition.setSource(source);
  36. beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
  37. beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
  38. registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
  39. return beanDefinition;
  40. }

  上面这一堆代码最核心的部分就在后两个方法中,就是完成了对AnnotationAwareAspectJAutoProxyCreator类的注册,到这里对自定义标签<aop:aspectj-autoproxy/>的解析也就完成了,可以看到其最核心的部分就是完成了对AnnotationAwareAspectJAutoProxyCreator类的注册,那为什么注册了这个类就开启了aop功能呢?这里先卖个关子,后面详细说。

  这里再回过头来看一下上面说到的spring对自定义标签解析的4个步骤,其实第一步的schema对应的是在org.springframework.aop.config路径下的spring-aop-3.0.xsd文件,其映射关系是维护在META-INF/spring.schemas文件中的,而spring-aop-3.0.xsd的主要作用就是描述自定义标签。

  当通过META-INF/spring.handlers找到对应的AopNamespaceHandler,并通过在其加载后执行init()方法过程中完成了AspectJAutoProxyBeanDefinitionParser的注册,有这个parser再来完成对自定义标签的解析工作,这对应上面4个步骤中的第二步和第三部。至于第四步的配置工作,无非就是将spring.schemas和spring.handlers这两个配置文件放在META-INF/目录下罢了。

  关于这部分解析过程,写得不是非常详细,如果有不明白,可以参考之前一篇文章,讲spring是如何解析自定义xml标签

2. 注解方式开启aop

  另一种开启spring aop的方式是通过注解的方式,使用的注解是@EnableAspectJAutoProxy,可以通过配置类的方式完成注册:

  1. @Configuration
  2. @EnableAspectJAutoProxy
  3. public class AppConfig {
  4.  
  5. }

  也可以在启动类上直接加上这个注解,这在springboot中比较常见,其实质也是上面的方式。通过这种方式配置之后,就开启了aop功能,那具体又是如何实现的呢?我们看一下这个注解:

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. @Documented
  4. @Import(AspectJAutoProxyRegistrar.class)
  5. public @interface EnableAspectJAutoProxy {
  6.  
  7. /**
  8. * Indicate whether subclass-based (CGLIB) proxies are to be created as opposed
  9. * to standard Java interface-based proxies. The default is {@code false}.
  10. */
  11. boolean proxyTargetClass() default false;
  12.  
  13. /**
  14. * Indicate that the proxy should be exposed by the AOP framework as a {@code ThreadLocal}
  15. * for retrieval via the {@link org.springframework.aop.framework.AopContext} class.
  16. * Off by default, i.e. no guarantees that {@code AopContext} access will work.
  17. * @since 4.3.1
  18. */
  19. boolean exposeProxy() default false;
  20.  
  21. }

  这里我们的关注点是其通过@Import(AspectJAutoProxyRegistrar.class)引入了AspectJAutoProxyRegistrar,那这又是什么?

  1. class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
  2.  
  3. /**
  4. * Register, escalate, and configure the AspectJ auto proxy creator based on the value
  5. * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
  6. * {@code @Configuration} class.
  7. */
  8. @Override
  9. public void registerBeanDefinitions(
  10. AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  11.  
  12. AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
  13.  
  14. AnnotationAttributes enableAspectJAutoProxy =
  15. AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
  16. if (enableAspectJAutoProxy != null) {
  17. if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
  18. AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
  19. }
  20. if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
  21. AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
  22. }
  23. }
  24. }
  25.  
  26. }

  看到这里,是不是有点眼熟了呢?是的,其实它也是和上面说的xml配置使用的方式一样,通过AopConfigUtils来完成AnnotationAwareAspectJAutoProxyCreator类的注册。是不是比xml配置文件的方式方便许多呢。

4. 开启aop的魔法

  通过前面的学习我们了解了可以通过Spring自定义配置完成对AnnotationAwareAspectJAutoProxyCreator类型的自动注册,而这个类到底是做了什么工作来实现AOP的操作呢?这里还是先来看一下AnnotationAwareAspectJAutoProxyCreator的类层次结构:

  这里有一个很重要的点,就是AnnotationAwareAspectJAutoProxyCreator实现了BeanPostProcessor接口。在IOC部分的文章中有详细说过,Spring在加载Bean的过程中会在实例化bean前后调用BeanPostProcessor的相关方法(相关逻辑是在initializeBean方法中,调用postProcessBeforeInitialization、postProcessAfterInitialization方法),而AOP的魔法就是从这里开始的。

  每次看到这里,我内心对spring的软件架构设计都是涌现出无比的佩服,通过后处理器的方式来做扩展,对原有模块是没有任何改动,也不会产生耦合,spring亲自践行着对修改关闭,对扩展开放的原则。

3. 总结

  本文我们学习了spring是如何开启aop功能的,无论是通过xml配置文件方式,还是通过Java config这种注解的方式,其最终都是完成了将AnnotationAwareAspectJAutoProxyCreator这个类注册到spring容器当中,那这个类又有什么魔法,可以达到将其注册到容器即达到开启aop的功效,其实其继承自BeanPostProcessor接口,通过后处理器的方式扩展出了开启spring aop的功能。

Spring AOP学习笔记02:如何开启AOP的更多相关文章

  1. Spring MVC学习笔记02

    1.常用注解 1.@Autowired,它可以对类成员变量.方法及构造函数进行标注,完成自动装配的工作. <!-- 该 BeanPostProcessor 将自动起作用,对标注 @Autowir ...

  2. Spring入门IOC和AOP学习笔记

    Spring入门IOC和AOP学习笔记 概述 Spring框架的核心有两个: Spring容器作为超级大工厂,负责管理.创建所有的Java对象,这些Java对象被称为Bean. Spring容器管理容 ...

  3. Spring AOP学习笔记01:AOP概述

    1. AOP概述 软件开发一直在寻求更加高效.更易维护甚至更易扩展的方式.为了提高开发效率,我们对开发使用的语言进行抽象,走过了从汇编时代到现在各种高级语言繁盛之时期:为了便于维护和扩展,我们对某些相 ...

  4. Spring AOP学习笔记

      Spring提供了一站式解决方案:          1) Spring Core  spring的核心功能: IOC容器, 解决对象创建及依赖关系          2) Spring Web ...

  5. AOP 学习笔记

    代理是一个设计模式,提供了对目标对象另外的访问方式:即通过代理访问目标对象. 好处:可以在目标对象实现的基础上,增强额外的功能操作. Cglib 代理,也叫作 子类代理. JDK的动态代理有一个限制, ...

  6. 【转】Spring.NET学习笔记——目录

    目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...

  7. Spring MVC 学习笔记12 —— SpringMVC+Hibernate开发(1)依赖包搭建

    Spring MVC 学习笔记12 -- SpringMVC+Hibernate开发(1)依赖包搭建 用Hibernate帮助建立SpringMVC与数据库之间的联系,通过配置DAO层,Service ...

  8. Spring.NET学习笔记——目录(原)

    目录 前言 Spring.NET学习笔记——前言 第一阶段:控制反转与依赖注入IoC&DI Spring.NET学习笔记1——控制反转(基础篇) Level 200 Spring.NET学习笔 ...

  9. Spring Boot 学习笔记(六) 整合 RESTful 参数传递

    Spring Boot 学习笔记 源码地址 Spring Boot 学习笔记(一) hello world Spring Boot 学习笔记(二) 整合 log4j2 Spring Boot 学习笔记 ...

随机推荐

  1. 转帖 支撑4.5亿活跃用户的WhatsApp架构概览

    http://www.csdn.net/article/2014-02-27/2818559-an-overview-at-whatsapp's-19b-architecture/2 写的很好,确实牛 ...

  2. 【雕爷学编程】Arduino动手做(3)---微波雷达感应开关模块

    37款传感器和模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器与模块,依照实践(动手试试)出真知的理念,以学习和交流为目的,这里准备 ...

  3. ORACLE重做日志小结

    1.Redo log特点 重做日志以磁盘I/O为主,将数据库操作记录到日志文件.(磁盘I\O性能有可能成为瓶颈) 每个实例只有一个活动的LGWR(log writer)进程,至少有两个日志组(logf ...

  4. 【python----发轫之始】【基础知识总结】

    python基础知识总结 一.自学感受 学完之后,,,感觉脑子里全是乱的,单词这么多,都要分不清什么时候该用什么,他到底属于哪一个数据类型里的函数,,,,,, 所以,我想着把笔记整理一下,方便自己和需 ...

  5. [原创][开源] SunnyUI.Net 开发日志:ListBox 增加跟随鼠标滑过高亮

    QQ群里,寸目说,ListBox鼠标移动时,当前行需要焦点,我想了想,不难实现啊 不就是在鼠标移动时重绘Item嘛,何况选中的Item已经改了颜色了. 见UIListBox代码: protected ...

  6. java遇到的error解决

    解决Cannot change version of project facet Dynamic web module to 2.5 maven 不能设置为web3.0人解决方法 http://www ...

  7. MySQL InnoDB索引介绍以及在线添加索引实例分析

    引言:MySQL之所以能成为经典,不是没有道理的,B+树足矣! 一.索引概念 InnoDB引擎支持三种常见的索引:B+树索引,全文索引和(自适应)哈希索引.B+树索引是传统意义上的索引,构造类似二叉树 ...

  8. HashMap的源码浅析

    一.HashMap 的数据结构 Java7 及之前主要是"数组+链表",到了 Java8 之后,就变成了"数组+链表+红黑树". 二.Java7 源码浅析: 在 ...

  9. POJ1733

    题目链接:https://vjudge.net/problem/POJ-1733 解题思路:并查集+离散化 AC代码: #include <iostream> #include <c ...

  10. tomcat启动失败怎么回事?

    1.系统环境没有配置好 2.web.xml文件里有错误拼写