Spring的初始化和XML解析的實(shí)現(xiàn)
前言
Spring是什么?它是一個應(yīng)用程序框架,為應(yīng)用程序的開發(fā)提供強(qiáng)大的支持,例如對事務(wù)處理和持久化的支持等;它也是一個bean容器,管理bean對象的整個生命周期,維護(hù)bean的各種存在狀態(tài),例如bean對象的實(shí)例化、銷毀、bean的單實(shí)例和多實(shí)例狀態(tài)等。
Spring作為Java發(fā)展史上不可忽視的存在,說他重新定義了Java也不為過。它功能強(qiáng)大,著實(shí)為日常開發(fā)提供了大大的便利。表面越簡單的東西,背后越復(fù)雜。
從本章節(jié)開始,我們一起分析Spring的源碼,看它到底是怎么樣來實(shí)現(xiàn)我們常說常用的諸如IOC、Annotation、AOP、事務(wù)等功能的。
1、Spring的入口
在我們的項(xiàng)目中,web.xml必不可少,其中就定義了Spring的監(jiān)聽器。
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
我們來看ContextLoaderListener類,可以看到它實(shí)現(xiàn)了ServletContextListener接口,
contextInitialized就是Spring初始化的入口方法。
Spring還有一個入口,叫做org.springframework.web.servlet.DispatcherServlet,它們之間是父子容器的關(guān)系,最終都會調(diào)用到同一個方法org.springframework.context.support.AbstractApplicationContext.refresh()。
2、初始化
Spring的初始化第一步就是要加載配置文件,然后解析里面的配置項(xiàng)。
ok,我們來到XmlWebApplicationContext類的loadBeanDefinitions方法。
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
可以看到,configLocations是一個數(shù)組,它獲取的就是配置文件。在筆者的項(xiàng)目中,只有一個配置文件,名字是applicationContext.xml。下一步就是通過loadBeanDefinitions這個方法解析這個配置文件。
3、解析XML配置
首先把一個配置文件封裝成一個Resource對象,然后獲取Resource對象的輸入流,轉(zhuǎn)換成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,轉(zhuǎn)成Resource對象
Resource[] resources=resourceLoader).getResources(location);
//獲取resources對象的輸入流 再轉(zhuǎn)成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節(jié)點(diǎn)信息如下:
[
[#text:],
[context:component-scan: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[bean: null],
[#text:],
[#comment: 指定了表現(xiàn)層資源的前綴和后綴
viewClass:JstlView表示JSP模板頁面需要使用JSTL標(biāo)簽庫
prefix 和suffix:查找視圖頁面的前綴和后綴,比如傳進(jìn)來的邏輯視圖名為hello,則該該
jsp視圖頁面應(yīng)該存放在“WEB-INF/jsp/hello.jsp”],
[#text:],
[bean: null],
[#text: ]
]
4、加載Bean信息
上一步我們看到Spring已經(jīng)把a(bǔ)pplicationContext.xml這個配置文件解析成了Document對象,接下來就是關(guān)鍵的一步。先看源碼
//這里拿到的是Document對象的根節(jié)點(diǎn),根節(jié)點(diǎn)信息參考上圖
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;
//這里有兩個分支。
//一個是處理默認(rèn)的節(jié)點(diǎn)(import、alias、bean、beans)
//一個是處理自定義的節(jié)點(diǎn)(context:component-scan)
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
4.1 component-scan的解析
首先定位到自定義解析方法delegate.parseCustomElement(ele);
最終調(diào)用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不過它是怎么調(diào)用到這個類的呢?說起來就比較有意思了。
我們先來看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
這里面配置了不同標(biāo)簽的處理類,比如context標(biāo)簽處理類就是ContextNamespaceHandler,然后通過反射實(shí)例化這個處理類,調(diào)用它的init()方法。init()方法里面它又注冊了一堆處理類,其中就有我們很感興趣的component-scan。
public NamespaceHandler resolve(String namespaceUri) {
//handlerMappings里有個方法loadAllProperties(),獲取Spring所有的配置項(xiàng)
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
//通過反射,實(shí)例化這個ContextNamespaceHandler,然后調(diào)用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這個標(biāo)簽,拿到ComponentScanBeanDefinitionParser類,調(diào)用它的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) {
//獲取包掃描路徑,對應(yīng)配置文件中的base-package="com.viewscenes.netsupervisor"
String basePackage = element.getAttribute(BASE_PACKAGE_ATTRIBUTE);
basePackage = parserContext.getReaderContext().getEnvironment().
resolvePlaceholders(basePackage);
//這里可能有多個包路徑,分割成數(shù)組
String[] basePackages = StringUtils.tokenizeToStringArray(basePackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
/**
* configureScanner 配置掃描器。
* scanner.doScan 掃描執(zhí)行
* registerComponents 這里重點(diǎn)是對registerComponents的支持
*
* @return
*/
ClassPathBeanDefinitionScanner scanner = configureScanner(parserContext, element);
Set<BeanDefinitionHolder> beanDefinitions = scanner.doScan(basePackages);
registerComponents(parserContext.getReaderContext(), beanDefinitions, element);
return null;
}
4.1.1 configureScanner 配置掃描器
這里面重點(diǎn)就是注冊了默認(rèn)的過濾器。use-default-filters,默認(rèn)值是true,如果配置文件配置了此屬性的值為false,有些注解就加不進(jìn)來,到下一步掃描的時候就注冊不了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.
}
//...未完
}
}
4.1.2 doScan掃描
doScan分為三個步驟。
- findCandidateComponents 掃描包路徑下的所有class文件,過濾有Component注解的類,轉(zhuǎn)換成BeanDefinition對象,加入一個LinkedHashSet中。
- 循環(huán)上一步返回的LinkedHashSet,設(shè)置基本屬性,比如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注解,轉(zhuǎn)成BeanDefinition對象返回。
//值得注意的是,Component不止是@Component,還有
//@Controller、@Service、@Repository,因?yàn)樵谶@三個注解上面還有個@Component。
//這就相當(dāng)于它們都是Component的子注解。
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.
resolveScopeMetadata(candidate);
//設(shè)置屬性,沒有配置的都是默認(rèn)值
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] ]
4.1.3 對annotation-config的支持
我們知道,在Spring配置文件有個配置是context:annotation-config 但如果配置了context:component-scan 就不必再配置config,這是因?yàn)樵诮馕鯿omponent-scan的時候已經(jīng)默認(rèn)添加了annotation-config的支持,除非你手動設(shè)置了annotation-config="false",不過這可不太妙,因?yàn)樵贗OC的時候就沒辦法支持@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));
}
......未完
}
}
4.2 bean標(biāo)簽的解析
bean標(biāo)簽的解析,就是默認(rèn)的處理方法。
獲取bean標(biāo)簽的id,并且把beanName賦值為id,設(shè)置別名。新建AbstractBeanDefinition對象,通過反射設(shè)置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);
//最后返回已經(jīng)包含了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 {
//根據(jù)className反射設(shè)置setBeanClass和setBeanClassName
AbstractBeanDefinition bd = createBeanDefinition(className, parent);
//設(shè)置默認(rèn)方法 setScope、setLazyInit、setAutowireMode...
parseBeanDefinitionAttributes(ele, beanName, containingBean, bd);
//設(shè)置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它們最后都是殊途同歸的,會轉(zhuǎn)換成一個BeanDefinition對象。記錄著這個Bean對象的屬性和方法,最后都注冊到容器中,等待在實(shí)例化和IOC的時候遍歷它們。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Mac OS X 下 IntelliJ IDEA、jEdit 等 Java 程序中文標(biāo)點(diǎn)輸入無效的完美解決方法
Mac OS X 下基于 Java 的程序會出現(xiàn)中文標(biāo)點(diǎn)輸入無效的問題,在中文輸入法狀態(tài),可以輸入中文字,但輸入中文標(biāo)點(diǎn)最后上去的是英文標(biāo)點(diǎn).這篇文章主要介紹了Mac OS X 下 IntelliJ IDEA、jEdit 等 Java 程序中文標(biāo)點(diǎn)輸入無效的完美解決方法,需要的朋友可以參考下2016-10-10
spring cloud gateway使用 uri: lb://方式配置時,服務(wù)名的特殊要求
這篇文章主要介紹了spring cloud gateway使用 uri: lb://方式配置時,服務(wù)名的特殊要求,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
Java使用Redisson分布式鎖實(shí)現(xiàn)原理
Redisson分布式鎖 之前的基于注解的鎖有一種鎖是基本redis的分布式鎖,這篇文章主要介紹了Java使用Redisson分布式鎖實(shí)現(xiàn)原理,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2018-10-10
Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)
這篇文章主要給大家介紹了關(guān)于Java如何使用Optional與Stream取代if判空邏輯(JDK8以上)的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家學(xué)習(xí)或者使用Java具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來一起學(xué)習(xí)學(xué)習(xí)吧2019-09-09
@FeignClient注解中屬性contextId的使用說明
這篇文章主要介紹了@FeignClient注解中屬性contextId的使用說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-06-06
Springboot 整合通用mapper和pagehelper展示分頁數(shù)據(jù)的問題(附github源碼)
這篇文章主要介紹了Springboot 整合通用mapper和pagehelper展示分頁數(shù)據(jù)(附github源碼),本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-09-09

