​ spring在早起的是时候是通过xml进行配置的bean的,但是发现所有的bean都放到xml中的时候,密密麻麻的xml配置非常混乱,乍眼一看一定很头晕。之后,spring引入了注解,只是需要在类上加上注解就可以了,非常的方便,但是这些注解又是如何解析的呢?spring是如何做到如此的方便的呢?注解解析的位置不同,这里只介绍@Controller,@Service,@Autowired等注解的解析过程。

文章目录

  • 1. xml文件解析过程
  • 2. BeanDefinition解析
  • 3. Autowired等注解解析

​ 那么什么是 BeanDefinition呢? BeanDefinition可以看作是一个内部的配置文件, spring通过注解或者xml等,把bean的信息存储到 BeanDefinition中,包括: 类名scope属性构造函数参数列表依赖的bean是否是单例类是否是懒加载等等。

1. xml文件解析过程

​ 首先是要配置包的路径。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context" xmlns:p="http://www.springframework.org/schema/p"xmlns:mvc="http://www.springframework.org/schema/mvc" 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/context  http://www.springframework.org/schema/context/spring-context-3.1.xsd  http://www.springframework.org/schema/mvc  http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop-3.1.xsd">  <!-- 配置要扫描的路径--><context:component-scan base-package="com.controller"/>
</beans>

​ 这段是说明spring要扫描com.controller路径下所有的类,然后把配置注解(spring管理的注解)类进行统一处理。

​ 流程比较复杂,直接上图。。

  1. 此以ClassPathXmlApplicationContext的构造方法为入口,其实不管以哪为入口,容器初始化的时候一定会走refresh方法,SpringMVC的初始化也是以refresh来进行初始化。

  2. obtainFreshBeanFactory方法中包括有ioc容器的定位,加载,注册功能。spring扫描可进行管理的类,也是在这里。

  3. obtainFreshBeanFactory方法中,使用委派模式,使IOC的初始化在子类中进行实现。

  4. 接下来一致按照流程走,在AbstractXmlApplicationContext中的loadBeanDefinitions方法中获取Resource对象。

    1.    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {Resource[] configResources = getConfigResources();if (configResources != null) {reader.loadBeanDefinitions(configResources);}// 配置文件这里根据路径进行处理String[] configLocations = getConfigLocations();if (configLocations != null) {//有时候配置文件路径要前缀有 classpath ,// 从这里进去 ,再进入((ResourcePatternResolver) resourceLoader).getResources(location); 方法// 找到 PathMatchingResourcePatternResolver.getResources方法,即可看到对此前缀的处理。reader.loadBeanDefinitions(configLocations);}}
      
  5. xml文件加载完成之后,就要进行读取了,XmlBeanDefinitionReader.doLoadBeanDefinitions方法获取Document对象,然后进行解析。

​ 至此,xml读取完成,接下来进行解析。

2. BeanDefinition解析

  1. registerBeanDefinitions方法进入之后,就是获取doc的根节点进行遍历。

  2. 接着向下走进入parseBeanDefinitions这个方法还是有点意思。如下

    1.  protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {//Bean 定义的 Document 对象使用了 Spring 默认的 XML 命名空间// 那么啥是默认的呢?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;
      //                  这个主要是看xml是否又 http://www.springframework.org/schema/beans 这段,可以留意下,一般spring的xml都有这个链接// 默认的是bean这个标签if (delegate.isDefaultNamespace(ele)) {parseDefaultElement(ele, delegate);}else {// 如果不是默认的,比如 xml 中有一段 <context:component-scan base-package="com.controller"/> ,则就走这段delegate.parseCustomElement(ele);}}}}else {delegate.parseCustomElement(root);}}
      

      就如上的xml为例,isDefaultNamespace方法进行判断是当前标签是普通bean标签。

  3. 如下,这里通过uri获取对应的命名空间处理器,然后进行解析

  4.     public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {String namespaceUri = getNamespaceURI(ele);if (namespaceUri == null) {return null;}// 根据命名空间,找到对应的处理器// 这里应该是通过策略模式吧。。 省去了if /elseNamespaceHandler 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));}
    
  5. 顺着流程向下走,进入ComponentScanBeanDefinitionParser.parse方法。

    1.     public BeanDefinition parse(Element element, ParserContext parserContext) {String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);basePackage = parserContext.getReaderContext().getEnvironment().resolvePlaceholders(basePackage);String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);// Actually scan for bean definitions and register them.// 这里面包括 设置上要扫描的注解 @Component 等等// 从ClassPathBeanDefinitionScanner类的构造方法进行设置注解的ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);// doscan 进行解析,扫描路径下所有的类,判断是否包括要过滤的注解Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);// BeanDefinition是获取到了,但是类里面的@AutoWrite呢? @Value注解呢?// 就是这里进行处理的,这里稍后说registerComponents(parserContext.getReaderContext(), beanDefinitions, element);return null;}
      
  6. 最后 isCandidateComponent方法有点意思了,这里判断是否有交给spring管理的注解,如下:

    1.     protected boolean isCandidateComponent(MetadataReader metadataReader) throws IOException {for (TypeFilter tf : this.excludeFilters) {if (tf.match(metadataReader, getMetadataReaderFactory())) {return false;}}// 一般情况下,此时includeFilters中有三个注解 @Component,@ManagedBean,@Namedfor (TypeFilter tf : this.includeFilters) {// 这里判断是否有注解if (tf.match(metadataReader, getMetadataReaderFactory())) {// 这里是是否跳过的,啥时候跳过呢? 为什么要跳过呢? 可以搜索下@Conditional 注解,在spring boot 使用的特别多。// 参考 https://blog.csdn.net/qq_30285985/article/details/101637212return isConditionMatch(metadataReader);}}return false;}
      

      在ClassPathScanningCandidateComponentProvider.registerDefaultFilters进去,则看到includeFilters集合中如何进行设置值。

      protected void registerDefaultFilters() {// 这里加载到这个 集合中  一共有三个注解 @Component,@ManagedBean,@Named ,也就是说,只要有这三个注解的类都会被spring所管理。// @Component 不用多说// @ManagedBean 是JSF中用到的// @Named 和@Commponent类似   说实话, 也没有百度出啥区别,总之很少用。this.includeFilters.add(new AnnotationTypeFilter(Component.class));ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));logger.debug("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.}try {this.includeFilters.add(new AnnotationTypeFilter(((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));logger.debug("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch (ClassNotFoundException ex) {// JSR-330 API not available - simply skip.}}
      

    但是到这里有些疑问,这里的注解,在平时开发的时候,只用到了@Component呀?那@Controller,@Service这些配置的类就不处理了么?后来发现,其实@Controller包含了@Component注解

    @Target({ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Component  // 这里包含了
    public @interface Controller {/*** The value may indicate a suggestion for a logical component name,* to be turned into a Spring bean in case of an autodetected component.* @return the suggested component name, if any (or empty String otherwise)*/@AliasFor(annotation = Component.class)String value() default "";}
    

到此,spring扫描包,然后判断包上是否设置了对应的注解,生成了BeanDefinition,但是对应类下的属性,还需要解析。

3. Autowired等注解解析

  1. ComponentScanBeanDefinitionParserr.egisterComponents方法是对@Autowired等注解进行处理的。

  2. 进入AnnotationConfigUtils.registerAnnotationConfigProcessors方法,如下:

    1.     public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(BeanDefinitionRegistry registry, @Nullable Object source) {//....省略前面代码if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {//这里就是对Autowire注解进行处理的。RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);def.setSource(source);beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));}//....省略后面代码return beanDefs;}
      

      如上,刚刚开始看到,我也有些疑问,AutowiredAnnotationBeanPostProcessor这个类是干啥的?看着好像是和@Autowired注解有一定的关系。但是这里又不是直接对@Autowired注解做处理。

      这里要对InstantiationAwareBeanPostProcessor接口和BeanPostProcessor有一定的了解。不了解可以进去看下两个接口的介绍。

      InstantiationAwareBeanPostProcessor接口介绍

      BeanPostProcessor接口介绍

  3. 了解了两个接口的作用之后,就可以着重看下这两个接口主要实现了什么了。

         public AutowiredAnnotationBeanPostProcessor() {this.autowiredAnnotationTypes.add(Autowired.class);this.autowiredAnnotationTypes.add(Value.class);ClassLoader cl = AutowiredAnnotationBeanPostProcessor.class.getClassLoader();try {this.autowiredAnnotationTypes.add(cl.loadClass("javax.inject.Inject"));this.logger.info("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");} catch (ClassNotFoundException var3) {;}}
    

    AutowiredAnnotationBeanPostProcessor在初始化的时候,就会加载@Autowired@Value@Inject这三个注解。

  4. 当bean设置值的时候,进行走如下方法:

    1.     public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {//检索出带有注解的属性,这里的注解是初始化时加载的那三个注解InjectionMetadata metadata = this.findAutowiringMetadata(beanName, bean.getClass());try {// 进行注入metadata.inject(bean, beanName, pvs);return pvs;} catch (Throwable var7) {throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", var7);}}
      

      最后进入AutowiredAnnotationBeanPostProcessor.inject

    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {Field field = (Field) this.member;Object value;// 判断是否有缓存,// 就比如,有一个service,在多个地方进行自动注入的话,那么第二次注入的话,就是走缓存了。if (this.cached) {value = resolvedCachedArgument(beanName, this.cachedFieldValue);}else {DependencyDescriptor desc = new DependencyDescriptor(field, this.required);desc.setContainingClass(bean.getClass());Set<String> autowiredBeanNames = new LinkedHashSet<>(1);Assert.state(beanFactory != null, "No BeanFactory available");TypeConverter typeConverter = beanFactory.getTypeConverter();try {// 找到属性的值value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);}catch (BeansException ex) {throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);}// 进行缓存synchronized (this) {if (!this.cached) {if (value != null || this.required) {this.cachedFieldValue = desc;registerDependentBeans(beanName, autowiredBeanNames);if (autowiredBeanNames.size() == 1) {String autowiredBeanName = autowiredBeanNames.iterator().next();if (beanFactory.containsBean(autowiredBeanName) &&beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {this.cachedFieldValue = new ShortcutDependencyDescriptor(desc, autowiredBeanName, field.getType());}}}else {this.cachedFieldValue = null;}this.cached = true;}}}if (value != null) {// 通过反射,进行赋值。ReflectionUtils.makeAccessible(field);field.set(bean, value);}}}
    

更多源码中文注释,有兴趣的直接拉去代码看把。

git地址:https://gitee.com/likuoblog/spring_chinese_notes

【spring系列】spring注解解析原理相关推荐

  1. return error怎么定义_这一次搞懂Spring自定义标签以及注解解析原理

    自定义标签解析原理 在上一篇分析默认标签解析时看到过这个类DefaultBeanDefinitionDocumentReader的方法parseBeanDefinitions:拉勾IT课小编为大家分解 ...

  2. Dubbo学习记录(八) -- Spring整合Dubbo中@Reference注解解析原理

    Spring整合Dubbo中@Reference注解解析原理 @Reference: 可以用在属性或者方法, 意味着需要引用某个Dubbo服务, 那么Dubbo整合Spring后, 我很好奇怎么把这个 ...

  3. Spring自定义命名空间的解析原理与实现

    Spring自定义命名空间的解析原理与实现 原理 由上篇文章refresh() -> obtainFreshBeanFactory()跟踪源码可知Spring在解析除默认命名空间import.a ...

  4. Spring系列——Spring MVC配置文件

    Spring系列--Spring MVC配置文件 一.前端控制器DispatcherServlet 二.RequestMapping注解 1.使用方法 1.1. 标注在方法上 1.2. 标注在类上 2 ...

  5. Spring 系列: Spring 框架

    第一部分:Spring 框架简介 Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序 ...

  6. Spring 系列: Spring 框架简介

    Spring 是一个开源框架,是为了解决企业应用程序开发复杂性而创建的.框架的主要优势之一就是其分层架构,分层架构允许您选择使用哪一个组件,同时为 J2EE 应用程序开发提供集成的框架. 在这篇由三部 ...

  7. spring中@Transaction注解解析

    首先是会自动创建事务的Advisor 1.创建事务Advisor是如何开始的? 在Spring boot的TransactionAutoConfiguration中有开启@EnableTransact ...

  8. 8.spring系列- java注解

    问题 注解是干什么的? 一个注解可以使用多次吗?如何使用? @Inherited是做什么的? @Target中的TYPE_PARAMETER和TYPE_USER用在什么地方? 泛型中如何使用注解? 注 ...

  9. Spring系列第20篇:@Conditional通过条件来控制bean的注册

    面试阿里p7被问到的问题(当时我只知道第一个): @Conditional是做什么的? @Conditional多个条件是什么逻辑关系? 条件判断在什么时候执行? ConfigurationCondi ...

  10. Spring系列第10篇:primary可以解决什么问题?

    存在的问题以及解决方案 直接上案例,通过案例来看技术是如何使用的: package com.javacode2018.lesson001.demo8;public class NormalBean { ...

最新文章

  1. 如何成为一名优秀的软件架构师?
  2. python 百分比数据_如何使用python计算数据列相对于另一列的百分比排名
  3. python动态映射_Python Django框架url反向解析实现动态生成对应的url链接示例
  4. python随机选取0到100间的奇数_python random模块(随机数)详解
  5. 游标sql server_使用SQL Server游标–优点和缺点
  6. python elasticsearch模块_Python3 操作 elasticsearch
  7. 开根号的笔算算法图解_开根号手算方法
  8. 3D GAME PROGRAMMING WITH DIRECTX11 (1)
  9. ElasticSearch、ki、head、kibana安装与基本使用
  10. C#递归算法使用案例——画树
  11. sql 远程过程调用失败
  12. cout与cerr区别
  13. 2019年软件QA与测试八大关键词
  14. Transformer模型学习笔记
  15. 碧水风荷录-第一章(未完,正在整理中……)
  16. autogen.sh出错
  17. linux界面赶不上微软,忘掉微软!其实Linux桌面也能如此美
  18. linux查看riak版本,Riak学习(一):Linux Centos 下安装 Riak 服务
  19. 计算机应用基础实训项目三excel,计算机应用基础实训项目三 Excel 综合应用
  20. HpM351a激光打印机加粉后,提示remove shipping lock from black cartridge的解决办法

热门文章

  1. GitHub中文社区
  2. 微信小程序如何上传图片
  3. java date 格式化 yyyymmdd_如何将LocalDate格式化为yyyyMMDD(不含JodaTime)
  4. es java api 获取总数_java Es Api --解决大量数据查询
  5. 文件名变乱码. 不知道如何恢复呢?
  6. 【电子设计】AD15快捷键整理
  7. 基于STM32的四位TM1637完整程序
  8. 好工具推荐系列:Feem和Send-anywhere,跨平台局域网传输工具,文件共享工具
  9. 使用微信企业号发送工资条教程
  10. 腾达u12如何安装linux驱动,Centos7 安装腾达U12驱动无线网卡