Spring 源碼梳理(七) 註解源碼

註解源碼

1.Spring的註解可以分爲兩類,一個是類上的註解,如@Component; 一個是類內部的註解,如@Autowired;Spring對兩種形式的註解的處理是不同的,在Spring的初始化週期中註解生效的時間也不同。

2.使用實例來分析一下(至於項目搭建的步驟見系列第一部分,源碼梳理(一))

App.java

package com.mycompany.app;

import org.springframework.stereotype.Component;

/**
 * Hello world!
 *
 */
@Component
public class App 
{
	public String appString = "This is App";
}

God.java

package com.mycompany.app;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.stereotype.Component;

@Component
public class God {
	@Autowired
	public App app;
	
	public void godSay(){
		System.out.println("God.godSay():"+app.appString);
	}
	@SuppressWarnings("resource")
	public static void main(String[] args) {
	     ApplicationContext applicationContext = new ClassPathXmlApplicationContext("SpringBean.xml");   
	     God god = (God)applicationContext.getBean("god");   
	     god.godSay();
	}
	
}

SpringBean.xml

<?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:context="http://www.springframework.org/schema/context"  
    xmlns:mybatis-spring="http://mybatis.org/schema/mybatis-spring"  
    xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd  
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd  
        http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd  
        http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring-1.2.xsd  
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd">  
   
    <context:component-scan base-package="com.mycompany.app" />
    
</beans>

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.mycompany.app</groupId>
  <artifactId>myapp</artifactId>
  <packaging>jar</packaging>
  <version>1.0-SNAPSHOT</version>
  <name>myapp</name>
  <url>http://maven.apache.org</url>
  
  <properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>4.3.0.RELEASE</spring.version>
  </properties>
  
  <dependencies>
   	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-core</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-web</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context-support</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-webmvc</artifactId>
		<version>${spring.version}</version>
	</dependency>

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.version}</version>
	</dependency>
  </dependencies>
  
</project>

運行God.java,得到的結果是:

3.首先可以做以下猜想

1)對於上面的兩種註解,Spring讀取相關的字節碼文件,然後找到相關的關鍵字;針對關鍵字做處理。

2)對於類註解,直接註冊類信息,放入DefinitionMap;對於類內部的註解,如何把信息注入進去的?有兩種猜測

A:之前的源碼分析中提到了,BeanFactoryPostProcessor,它也被稱作容器的後置處理器,因爲它是在Spring容器加載完所有的Bean信息之後(是加載Bean,不是初始化)調用的處理器。 所以猜測註解可能是由BeanFactoryPostProcessor來實現的。

B:之前的源碼分析中也提到了BeanPostProcessor,它包括了兩個方法:postProcessBeforeInitialization和postProcessAfterInitialization;它們分別被稱爲Bean的前置和後置處理器;因爲它們分別在Bean的初始化前後調用。所以猜測註解也可能是這兩個方法來實現的。

4.調試,對代碼進行分析

1)首先通過加載配置文件然後解析相關類的過程是在BeanFacroty的初始化過程中;所以我們分析的是obtainFreshBeanFactory方法:


一直debug到前面文章講到過的parseBeanDefinitions方法:


我們的context節點屬於一般性的配置節點,進入parseCustomElement方法:


然後進入parse方法:


它有多個實現類,我們的配置文件中的一個配置是<context: componentScan/>,所以我們進入ComponentScanBeanDefinitionParser方法:


basePackage指的是我們要掃描package,basepackages指的是package下面的java類; doScan方法對其中每一個java類進行掃描(包括類上的和類內部的註解),進入doScan方法:


2)然後順着findCandidateComponents方法






3)通過一點點分析postProcessBeanDefinition方法,發現實際上它是在beanDefinition中設置相關的屬性, 通過設置自動注入的屬性(從而在註冊bean處理器時發現它,使它繼承AutowiredAnnotationBeanPostProcessor接口),然後在初始化時,在Bean的後置處理方法中開始注入。這就說明猜想2中的B是正確的,而且注入實際上是bean後置處理器實現的,既postProcessAfterInitialization。

至於processCommonDefinitionAnnotations方法,就是對類上的bean進行處理, 然後設置beanDefinition中的屬性, 最後在初始化的時候對類進行初始化(沒有註冊BeanPostProcessor的過程),也證實猜想1是正確的。

總結如圖:


類內的註解經過了1,2,3三個方法,而類上的註解經過了1,3兩個方法。 分析到此,註解生效的整個生命週期就很清楚了。

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