【spring系列】spring註解解析原理

​ spring在早起的是時候是通過xml進行配置的bean的,但是發現所有的bean都放到xml中的時候,密密麻麻的xml配置非常混亂,乍眼一看一定很頭暈。之後,spring引入了註解,只是需要在類上加上註解就可以了,非常的方便,但是這些註解又是如何解析的呢?spring是如何做到如此的方便的呢?註解解析的位置不同,這裏只介紹@Controller,@Service,@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.xsd
      http://www.springframework.org/schema/aop
      http://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 /else
       		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));
       	}
    
  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,@Named
         		for (TypeFilter tf : this.includeFilters) {
         			// 這裏判斷是否有註解
         			if (tf.match(metadataReader, getMetadataReaderFactory())) {
         				// 這裏是是否跳過的,啥時候跳過呢? 爲什麼要跳過呢? 可以搜索下@Conditional 註解,在spring boot 使用的特別多。
         				// 參考 https://blog.csdn.net/qq_30285985/article/details/101637212
         				return 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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章