一.基于XML的方式

在Spring的早些版本,流行xml的配置方式。只要在xml中配置如下的标签以及属性,Spring就会帮我们解析。

    <!--开启自动扫描--><context:component-scan base-package="com.seaway.curatorframework"/><!--开启自动代理--><aop:aspectj-autoproxy/>

为什么配置了<aop:aspectj-autoproxy>就能实现AOP功能呢?这里就要涉及到Spring的另一个知识点了,即Spring自定义标签解析。我在另一篇文章有详细讲解过,大家有兴趣的可以去看看细说Spring自定义标签,这里就不再赘述。大概意思就是这一类自定义标签,Spring有它独特的解析方式,<aop:aspectj-autoproxy>的解析类为AopNamespaceHandler

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

这里先重点关注aspectj-autoproxy的解析,进入registerBeanDefinitionParser方法看看都做了什么事情。

private final Map<String, BeanDefinitionParser> parsers = new HashMap();protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {this.parsers.put(elementName, parser);
}

就是将当前解析到的标签封装成一个parser,存入map中,待后面进行解析。那什么时候会执行AopNamespaceHandler中的init方法呢?这里有一个逻辑所有的spring的配置文件,最终会被解析成BeanDefinitionMap存储起来,便于以后进行bean的创建等过程。

protected void doRegisterBeanDefinitions(Element root) {//具体的解析过程由BeanDefinitionParserDelegate实现,//BeanDefinitionParserDelegate中定义了Spring Bean定义XML文件的各种元素BeanDefinitionParserDelegate parent = this.delegate;this.delegate = createDelegate(getReaderContext(), root, parent);if (this.delegate.isDefaultNamespace(root)) {String profileSpec = root.getAttribute(PROFILE_ATTRIBUTE);if (StringUtils.hasText(profileSpec)) {String[] specifiedProfiles = StringUtils.tokenizeToStringArray(profileSpec, BeanDefinitionParserDelegate.MULTI_VALUE_ATTRIBUTE_DELIMITERS);if (!getReaderContext().getEnvironment().acceptsProfiles(specifiedProfiles)) {if (logger.isInfoEnabled()) {logger.info("Skipped XML bean definition file due to specified profiles [" + profileSpec +"] not matching: " + getReaderContext().getResource());}return;}}}//在解析Bean定义之前,进行自定义的解析,增强解析过程的可扩展性preProcessXml(root);//从Document的根元素开始进行Bean定义的Document对象parseBeanDefinitions(root, this.delegate);//在解析Bean定义之后,进行自定义的解析,增加解析过程的可扩展性postProcessXml(root);this.delegate = parent;
}

这里是DefaultBeanDefinitionDocumentReader的方法,逻辑便是将解析到的xml的document对象传入,然后转义成BeanDefinition的过程。而调用parseBeanDefinitions进行解析时,会分别对默认标签和自定义标签进行单独解析。默认标签即<bean>之类的。

//Bean定义的Document的元素节点使用的是Spring默认的XML命名空间
if (delegate.isDefaultNamespace(ele)) {//使用Spring的Bean规则解析元素节点parseDefaultElement(ele, delegate);
}
else {//没有使用Spring默认的XML命名空间,则使用用户自定义的解//析规则解析元素节点delegate.parseCustomElement(ele);
}

进入parseCustomElement方法,获取命名空间,并调用init方法的就是这里面的逻辑。

public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {//获取节点的命名空间String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}//这个地方是重点,先获取到命名空间解析器,然后进行解析NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);if (handler == null) {error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);return null;}//调用命名空间的parse方法进行解析,转义成BeanDefinitionreturn handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}

此处的resolver是DefaultNamespaceHandlerResolver,所以会调用该类的resolve方法,继续跟下去。

public NamespaceHandler resolve(String namespaceUri) {//获取所有的handler文件中的属性保存在map中Map<String, Object> handlerMappings = getHandlerMappings();//通过命名空间从map中取Object handlerOrClassName = handlerMappings.get(namespaceUri);if (handlerOrClassName == null) {return null;}else if (handlerOrClassName instanceof NamespaceHandler) {return (NamespaceHandler) handlerOrClassName;}else {String className = (String) handlerOrClassName;//获取对应的class对象Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");}//通过初始化策略生成对应的handler实例NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);//调用init方法,将对应的parser注册到map中,便于后续解析namespaceHandler.init();//存入缓存,防止重复解析配置文件handlerMappings.put(namespaceUri, namespaceHandler);return namespaceHandler;}
}

此方法首先会获取扫描所有jar下的META-INF/spring.handlers文件,并对其进行解析,以键值对的形式存于缓存中。

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

这是aop包下的spring.handlers文件的内容,此处就获取到了AopNamespaceHandler。通过反射创建实例,然后调用了init方法,将对应的parser注册到map中,便于后续的解析。所以到了这里,最初的逻辑也得到了验证。接下来继续看是如何进行解析的,又是将什么东西解析到了BeanDefinitionMap中?让我们进入对应parse方法一探究竟。

registerBeanDefinitionParser("aspectj-autoproxy", new AspectJAutoProxyBeanDefinitionParser());

此处加入map中的parser是AspectJAutoProxyBeanDefinitionParser,因此进入该类的parse方法进行解析。

public BeanDefinition parse(Element element, ParserContext parserContext) {//向ioc容器注册一个AnnotationAwareAspectJAutoProxyCreator类型的BeanDefinitionAopNamespaceUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext, element);//继续解析element的子节点如<aop:include/>,将当前的BeanDefnition的属性完善extendBeanDefinition(element, parserContext);return null;
}

此处主要是向ioc容器注册一个AnnotationAwareAspectJAutoProxyCreator类型的BeanDefinition,第二部逻辑就是对属性的完善,让我们重点看下第一步的逻辑。

public static void registerAspectJAnnotationAutoProxyCreatorIfNecessary(ParserContext parserContext, Element sourceElement) {//此处才是真正的注册逻辑BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(parserContext.getRegistry(), parserContext.extractSource(sourceElement));//如果设置了proxy-target-class和expose-proxy属性,则在此进行赋值useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);//向容器中注入组件registerComponentIfNecessary(beanDefinition, parserContext);
}

真正的注册逻辑其实在第一句,后面的两句代码只是对第一行的BeanDefinition进行属性的修改,继续跟进。

public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry,@Nullable Object source) {//注册或者升级该代理创建器return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
}private static BeanDefinition registerOrEscalateApcAsRequired(Class<?> cls, BeanDefinitionRegistry registry,@Nullable Object source) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");//如果beanDefintionMap中有则不需要再构建了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;}//如果beanDefintionMap中没有则创建一个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;
}

最后会执行如下代码this.beanDefinitionMap.put(beanName, beanDefinition);向IOC容器注册了该bean,beanName为org.springframework.aop.config.internalAutoProxyCreator。如果设置了proxy-target-classexpose-proxy属性,则会修改BeanDefinition的属性,相当于打个标记,是否需要强制使用Cglib代码,是否需要暴露代理对象。对于XML形式的解析就讲解到这了,至于为什么要注册这么一个bean,在下一篇博文我会做出解答。继续看下基于注解方式是如何开启AOP的吧!

二.基于Annotation的方式

接下来我会基于SpringBoot是实现AOP,首先引入相关jar包。

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.9</version>
</dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.8.9</version>
</dependency>

然后在启动类上配置@EnableAspectJAutoProxy注解即可开启,因为是SpringBoot的缘故,会帮我们自动装配,默认是开启AOP的,也可以不加该注解。接下来着重分析这个注解到底干了什么事情。

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {boolean proxyTargetClass() default false;boolean exposeProxy() default false;
}

当前注解中有一个@Import注解,就是讲有关类加入到Spring容器中。该注解的用法有三种,第一种是直接在后面添加一个或多个类,如下形式。

@Import({HelloRequest.class, HelloResponse.class})
public class imp {
}

第二种,则是在@Import注解上配置选择器ImportSelector,配置这个选择器的作用就是有选择性的注入,会进行筛选。

@Import(selector.class)
public class imp {
}public class selector implements ImportSelector {@Overridepublic String[] selectImports(AnnotationMetadata annotationMetadata) {//在注入前,进行筛选return new String[]{"com.seaway.mibank.request.HelloRequest","com.seaway.mibank.response.HelloResponse"};}
}

第三种,则是在@Import注解上配置注册器ImportBeanDefinitionRegistrar,这个接口中有默认实现registerBeanDefinitions方法,其中的一个入参为BeanDefinitionRegistry,那就意味着我们可以自己自定义的向容器注入自己想要注入的Bean。

@Import(Registar.class)
public class imp {
}
public class Registar implements ImportBeanDefinitionRegistrar {@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {RootBeanDefinition beanDefinition = new RootBeanDefinition(HelloRequest.class);registry.registerBeanDefinition("helloRequest", beanDefinition);}
}

此处注解传入的是一个Registar,所以我们看下AspectJAutoProxyRegistrar的registerBeanDefinitions中的逻辑。

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {//像容器中注册一个beanAopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);//获取到注解的属性AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);if (enableAspectJAutoProxy != null) {if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {//如果为true,则强制使用Cglib代理AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);}if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {//如果为true,则会暴露代理对象AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);}}
}

这里面的逻辑可以说是跟XML形式一模一样啊,this.beanDefinitionMap.put(beanName, beanDefinition);向IOC容器注册了该bean,beanName为org.springframework.aop.config.internalAutoProxyCreator。如果设置了proxy-target-classexpose-proxy属性,则会修改BeanDefinition的属性,相当于打个标记,是否需要强制使用Cglib代码,是否需要暴露代理对象。

总结

本文讲解了两种开启AOP的方式,核心逻辑就是在beanDefinitionMap中注册AnnotationAwareAspectJAutoProxyCreator。那么我们是否有疑问,为什么注册此Bean呢?它有什么用,有了它就能实现AOP吗?我只能说着仅仅只是一个开始,下一篇文章,我会详细解答。如感兴趣,请持续关注AOP系列博文,谢谢!

Spring源码解析之AOP篇(三)----Spring开启AOP的两种方式相关推荐

  1. Spring源码解析-bean实例化

    Spring源码解析-bean实例化 ​ 本文介绍Spring创建 bean 过程中的第一个步骤:实例化 bean. 1. Bean实例化源码 ​ 虽然实例化Bean有多种方式(包括静态工厂和工厂实例 ...

  2. Spring 源码解析 - Bean创建过程 以及 解决循环依赖

    一.Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析,我们可以得到结论,资源文件中的 bean 定义信息,被组装成了 BeanDef ...

  3. Spring源码解析:自定义标签的解析过程

    2019独角兽企业重金招聘Python工程师标准>>> spring version : 4.3.x Spring 中的标签分为默认标签和自定义标签两类,上一篇我们探究了默认标签的解 ...

  4. Spring 源码解析 -- SpringWeb过滤器Filter解析

    简介 在上几篇文章中探索了请求处理相关的代码,本篇开始探索请求处理前的一些操作代码,如Filter.本篇探索Filter初始化.请求处理等相关代码. 前言 说先简单的定义相关的测试代码: 启动类: i ...

  5. Spring源码解析【完整版】--【bilibili地址:https://www.bilibili.com/video/BV1oW41167AV】

    [本文为bilibili视频雷丰阳的Spring源码解析的完整版总结文章,其中文章前面大部分为他人博文的搬运,后面补充了其未总结的部分] 一.Java的注解 1. 注解的概念 注释:用文字描述程序,给 ...

  6. 人人都能看懂的Spring源码解析,Spring如何解决循环依赖

    人人都能看懂的Spring源码解析,Spring如何解决循环依赖 原理解析 什么是循环依赖 循环依赖会有什么问题? 如何解决循环依赖 问题的根本原因 如何解决 为什么需要三级缓存? Spring的三级 ...

  7. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    2019独角兽企业重金招聘Python工程师标准>>> 我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegi ...

  8. Spring源码解析 -- SpringWeb请求参数获取解析

    Spring源码解析 – SpringWeb请求参数获取解析 简介 在文章:Spring Web 请求初探中,我们看到最后方法反射调用的相关代码,本篇文章就探索其中的参数是如何从请求中获取的 概览 方 ...

  9. Spring源码解析 -- SpringWeb请求映射Map初始化

    简介 在上篇文章中,大致解析了Spring如何将请求路径与处理方法进行映射,但映射相关的初始化对于我们来说还是一团迷雾 本篇文章就来探索下,请求路径和处理方法的映射,是如何进行初始化的 概览 基于上篇 ...

  10. Spring 源码解析 -- SpringWeb请求映射解析

    Spring 源码解析 – SpringWeb请求映射解析 简介 基于上篇请求路径初步探索,了解到了一个请求到具体处理方法的大致路径,本篇就继续探索,看下路径是如何匹配到处理方法的 概览 基于上篇:S ...

最新文章

  1. python 服务端渲染_客户端渲染和服务器渲染的区别
  2. 【BZOJ1406】【codevs2478】密码箱,数论练习
  3. Python学习week4-set集合
  4. mysql必知必会的数据_MySQL必知必会---数据过滤
  5. 4 拼接_3个孩子,64㎡小户型内“镶嵌”4室一厅,餐桌还能随意拼接
  6. k2p 官方固件纯净版
  7. 微信网页第三方登录原理
  8. linux libodbc.so.1,关于C#:Testprintenv:加载共享库时出错:libodbc.so.1:无法打开共享对象文件...
  9. 常用软件国内源镜像地址大全
  10. 2020南京市(徐庄)高层次创业人才引进计划开启申报
  11. Markdown合并表格单元格
  12. 我那么爱你为什么?伤感爱情日志
  13. 常用软件安装及破解——IntelliJ IDEA 2019.1
  14. 如何避免渠道商的“养卡”和“劝弃卡”行为的发生
  15. 记录-关于网站的欢迎页,初次进入可见欢迎页,再次进入就直接显示主页了
  16. 数据分析-numpy-pandas-matplotlib
  17. 北京联通KD-YUN-811E改桥接
  18. 向U盘中安装Linux系统的经验(不是制作安装盘)
  19. iPhone无法连接电脑原因分析及解决
  20. SQL Server 2016 快照代理过程分析

热门文章

  1. ios学习路线图_iOS开发学习路线 +技巧整理
  2. python爬虫获取城市天气信息
  3. 流程图制作原则与示例
  4. opencv图像对比度
  5. 给你一个字符串,删除其中的不是英文字母的符号,也就是说除了英文字母之外的字符都应该删除,请你输出删除后的字符串。
  6. 给网页添加背景图片 html+css
  7. matlab如何找出相似的图,图像相似性搜索的MATLAB实现
  8. python中str类型_python中str指的是什么类型
  9. 将pdf转成图片时,文字没法显示
  10. 手机之家签名工具_自签工具更新 | 手机端自签,无需电脑,支持iOS 14 !