前言
Spring是什麼?它是一個應用程式框架,為應用程式的開發提供強大的支援,例如對事務處理和持久化的支援等;它也是一個bean容器,管理bean物件的整個生命週期,維護bean的各種存在狀態,例如bean物件的例項化、銷燬、bean的單例項和多例項狀態等。 Spring作為Java發展史上不可忽視的存在,說他重新定義了Java也不為過。它功能強大,著實為日常開發提供了大大的便利。表面越簡單的東西,背後越複雜。 從本章節開始,我們一起分析Spring的原始碼,看它到底是怎麼樣來實現我們常說常用的諸如IOC、Annotation、AOP、事務等功能的。
一、Spring的入口
在我們的專案中,web.xml必不可少,其中就定義了Spring的監聽器。
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
複製程式碼
我們來看ContextLoaderListener類,可以看到它實現了ServletContextListener介面,
contextInitialized就是Spring初始化的入口方法。
Spring還有一個入口,叫做org.springframework.web.servlet.DispatcherServlet
,它們之間是父子容器的關係,最終都會呼叫到同一個方法org.springframework.context.support.AbstractApplicationContext.refresh()
。
二、初始化
Spring的初始化第一步就是要載入配置檔案,然後解析裡面的配置項。 ok,我們來到XmlWebApplicationContext類的loadBeanDefinitions方法。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
複製程式碼
可以看到,configLocations是一個陣列,它獲取的就是配置檔案。在筆者的專案中,只有一個配置檔案,名字是applicationContext.xml。下一步就是通過loadBeanDefinitions這個方法解析這個配置檔案。
三、解析XML配置
首先把一個配置檔案封裝成一個Resource物件,然後獲取Resource物件的輸入流,轉換成InputSource物件,最後解析成Document物件。下面程式碼只保留了主要部分。
public int loadBeanDefinitions(String location, Set<Resource> actualResources)
throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//這裡的location就是配置檔案-applicationContext.xml,轉成Resource物件
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//獲取resources物件的輸入流 再轉成JDK的InputSource物件,最後解析成Document
InputStream inputStream = resources.getInputStream();
InputSource inputSource = new InputSource(inputStream);
Document doc = doLoadDocument(inputSource, resource);
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
}
複製程式碼
applicationContext.xml配置檔案解析成Document物件,它的Root節點資訊如下:
[
[#text:],
[context:component-scan: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[#comment: 指定了表現層資源的字首和字尾
viewClass:JstlView表示JSP模板頁面需要使用JSTL標籤庫
prefix 和suffix:查詢檢視頁面的字首和字尾,比如傳進來的邏輯檢視名為hello,則該該
jsp檢視頁面應該存放在“WEB-INF/jsp/hello.jsp”],
[#text:],
[bean: null],
[#text: ]
]
複製程式碼
四、載入Bean資訊
上一步我們看到Spring已經把applicationContext.xml這個配置檔案解析成了Document物件,接下來就是關鍵的一步。先看原始碼
//這裡拿到的是Document物件的根節點,根節點資訊參考上圖
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
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;
//這裡有兩個分支。
//一個是處理預設的節點(import、alias、bean、beans)
//一個是處理自定義的節點(context:component-scan)
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
複製程式碼
1、 component-scan的解析
首先定位到自定義解析方法delegate.parseCustomElement(ele);
最終呼叫了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext)
,不過它是怎麼呼叫到這個類的呢?說起來就比較有意思了。
我們先來看Spring裡面的一個配置檔案,/META-INF/spring.handlers
http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
複製程式碼
這裡面配置了不同標籤的處理類,比如context標籤處理類就是ContextNamespaceHandler,然後通過反射例項化這個處理類,呼叫它的init()方法。init()方法裡面它又註冊了一堆處理類,其中就有我們很感興趣的component-scan。
public NamespaceHandler resolve(String namespaceUri) {
//handlerMappings裡有個方法loadAllProperties(),獲取Spring所有的配置項
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
String className = (String) handlerOrClassName;
try {
//以context:component-scan舉例
//這裡拿到的className就是org.springframework.context.config.ContextNamespaceHandler
//通過反射,例項化這個ContextNamespaceHandler,然後呼叫init方法
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
NamespaceHandler namespaceHandler = BeanUtils.instantiateClass(handlerClass);
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
}
}
public void init() {
registerBeanDefinitionParser("annotation-config",
new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan",
new ComponentScanBeanDefinitionParser());
//...未完
}
複製程式碼
最終Spring就可以通過component-scan這個標籤,拿到ComponentScanBeanDefinitionParser類,呼叫它的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) {
//獲取包掃描路徑,對應配置檔案中的base-package="com.viewscenes.netsupervisor"
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().
resolvePlaceholders(basePackage);
//這裡可能有多個包路徑,分割成陣列
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
/**
* configureScanner 配置掃描器。
* scanner.doScan 掃描執行
* registerComponents 這裡重點是對registerComponents的支援
*
* @return
*/
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
複製程式碼
configureScanner 配置掃描器
這裡面重點就是註冊了預設的過濾器。use-default-filters
,預設值是true,如果配置檔案配置了此屬性的值為false,有些註解就加不進來,到下一步掃描的時候就註冊不了Bean。
protected void registerDefaultFilters() {
//這個就是配置的use-default-filters,如果配置了false。那麼下面的
// Component、ManagedBean、Named註解都不會被掃描到
if (useDefaultFilters) {
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.
}
//...未完
}
}
複製程式碼
doScan掃描
doScan分為三個步驟。
- findCandidateComponents 掃描包路徑下的所有class檔案,過濾有Component註解的類,轉換成BeanDefinition物件,加入一個LinkedHashSet中。
- 迴圈上一步返回的LinkedHashSet,設定基本屬性,比如setLazyInit、setScope。
- 註冊BeanDefinition物件,向Map容器中快取beanName和BeanDefinition,向List中加入beanName。
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
//findCandidateComponents方法掃描class檔案,判斷Component註解,轉成BeanDefinition物件返回。
//值得注意的是,Component不止是@Component,還有
//@Controller、@Service、@Repository,因為在這三個註解上面還有個@Component。
//這就相當於它們都是Component的子註解。
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.
resolveScopeMetadata(candidate);
//設定屬性,沒有配置的都是預設值
candidate.setScope(scopeMetadata.getScopeName());
candidate.setxxx(scopeMetadata.getxxxName());
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
//registerBeanDefinition方法 註冊BeanDefinition,等同於下面兩句
//this.beanDefinitionMap.put(beanName, beanDefinition);
//this.beanDefinitionNames.add(beanName);
registerBeanDefinition(definitionHolder, this.registry);
}
}
return beanDefinitions;
}
複製程式碼
最後整個方法返回的就是beanDefinition物件的Set集合,以兩個Controller為例。
[
Generic bean: class [com.viewscenes.netsupervisor.controller.IndexController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\IndexController.class],
Generic bean: class [com.viewscenes.netsupervisor.controller.UserController]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null; defined in file [D:\apache-tomcat-7.0.78\webapps\springmvc_dubbo_producer\WEB-INF\classes\com\viewscenes\netsupervisor\controller\UserController.class]
]
複製程式碼
對annotation-config的支援
我們知道,在Spring配置檔案有個配置是context:annotation-config
但如果配置了context:component-scan
就不必再配置config,這是因為在解析component-scan的時候已經預設新增了annotation-config的支援,除非你手動設定了annotation-config="false"
,不過這可不太妙,因為在IOC的時候就沒辦法支援@Autowired等註解了。
protected void registerComponents(XmlReaderContext readerContext,
Set<BeanDefinitionHolder> beanDefinitions, Element element) {
boolean annotationConfig = true;
if (element.hasAttribute(ANNOTATION_CONFIG_ATTRIBUTE)) {
annotationConfig = Boolean.valueOf(element.getAttribute(ANNOTATION_CONFIG_ATTRIBUTE));
}
if (annotationConfig) { //判斷annotation-config屬性的值
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<BeanDefinitionHolder>(4);
if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (!registry.containsBeanDefinition(REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(RequiredAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry, def, REQUIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
}
......未完
}
}
複製程式碼
2、bean標籤的解析
bean標籤的解析,就是預設的處理方法。
- 獲取bean標籤的id,並且把beanName賦值為id,設定別名。新建AbstractBeanDefinition物件,通過反射設定beanClass,解析property屬性名稱和值。
public BeanDefinitionHolder parseBeanDefinitionElement(Element ele, BeanDefinition containingBean) {
//獲取bean_id
String id = ele.getAttribute(ID_ATTRIBUTE);
String beanName = id;
AbstractBeanDefinition beanDefinition =
parseBeanDefinitionElement(ele, beanName, containingBean);
String[] aliasesArray = StringUtils.toStringArray(aliases);
//最後返回已經包含了beanName、class物件和一系列方法的BeanDefinition物件
return new BeanDefinitionHolder(beanDefinition, beanName, aliasesArray);
}
public AbstractBeanDefinition parseBeanDefinitionElement(Element ele,
String beanName, BeanDefinition containingBean) {
String className = ele.getAttribute(CLASS_ATTRIBUTE).trim();
try {
//根據className反射設定setBeanClass和setBeanClassName
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//設定預設方法 setScope、setLazyInit、setAutowireMode...
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//設定property屬性 <bean><property name="id" value="1001"></property></bean>
parsePropertyElements(ele, bd);
return bd;
}
return null;
}
複製程式碼
- 註冊BeanDefinition物件,和component-scan掃描的bean註冊一樣。向容器中填充物件。
- 不管是XML配置的Bean,還是通過component-scan掃描註冊的Bean它們最後都是殊途同歸的,會轉換成一個BeanDefinition物件。記錄著這個Bean物件的屬性和方法,最後都註冊到容器中,等待在例項化和IOC的時候遍歷它們。