Spring的初始化和XML解析的实现

网友投稿 312 2023-01-11


Spring的初始化和XML解析的实现

前言

Spring是什么?它是一个应用程序框架,为应用程序的开发提供强大的支持,例如对事务处理和持久化的支持等;它也是一个bean容器,管理bean对象的整个生命周期,维护bean的各种存在状态,例如bean对象的实例化、销毁、bean的单实例和多实例状态等。

Spring作为java发展史上不可忽视的存在,说他重新定义了Java也不为过。它功能强大,着实为日常开发提供了大大的便利。表面越简单的东西,背后越复杂。

从本章节开始,我们一起分析Spring的源码,看它到底是怎么样来实现我们常说常用的诸如IOC、Annotation、AOP、事务等功能的。

1、Spring的入口

在我们的项目中,web.xml必不可少,其中就定义了Spring的监听器。

org.springframework.web.context.ContextLoaderListener

我们来看ContextLoaderListener类,可以看到它实现了ServletContextListener接口,

contextInitialized就是Spring初始化的入口方法。

Spring还有一个入口,叫做org.springframework.web.servlet.DispatcherServlet,它们之间是父子容器的关系,最终都会调用到同一个方法org.springframework.context.support.AbstractApplicationContext.refresh()。

2、初始化

Spring的初始化第一步就是要加载配置文件OuksZtz,然后解析里面的配置项。

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这个方法解析这个配置文件。

3、解析XML配置

首先把一个配置文件封装成一个Resource对象,然后获取Resource对象的输入流,转换成InputSource对象,最后解析成Document对象。下面代码只保留了主要部分。

public int loadBeanDefinitions(String location, Set actualResources)

throws BeanDefinitionStoreException {

ResourceLoader resourceLoader = getResourceLoader();

if (resourceLoader instanceof ResourcePatternResolver) {

// Resource pattern matching available.

try {

//这里的location就是配置文件-applicationContext.xml,转成Resource对象

Resource[] resources=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: ]

]

4、加载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);

}

}

4.1 component-scan的解析

首先定位到自定义解析方法delegate.parseCustomElement(ele);

最终调用了org.springframework.context.annotation.ComponentScanBeanDefinitionParser.parse(Element element, ParserContext parserContext),不过它是怎么调用到这个类的呢?说起来就比较有意思了。

我们先来看Spring里面的一个配置文件,/META-INF/spring.handlers

http\://springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler

http\://springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler

http\://springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler

http\://springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler

http\://springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler

这里面配置了不同标签的处理类,比如context标签处理类就是ContextNamespaceHandler,然后通过反射实例化这个处理类,调用它的init()方法。init()方法里面它又注册了一堆处理类,其中就有我们很感兴趣的component-scan。

public NamespaceHandler resolve(String namespaceUri) {

//handlerMappings里有个方法loadAllProperties(),获取Spring所有的配置项

Map 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();

http:// 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 beanDefinitions = scanner.doScan(basePackages);

registerComponents(parserContext.getReaderContext(), beanDefinitions, element);

return null;

}

4.1.1 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.

}

//...未完

}

}

4.1.2 doScan扫描

doScan分为三个步骤。

findCandidateComponents 扫描包路径下的所有class文件,过滤有Component注解的类,转换成BeanDefinition对象,加入一个LinkedHashSet中。

循环上一步返回的LinkedHashSet,设置基本属性,比如setLazyInit、setScope。

注册BeanDefinition对象,向Map容器中缓存beanName和BeanDefinition,向List中加入beanName。

protected Set doScan(String... basePackages) {

Set beanDefinitions = new LinkedHashSet();

for (String basePackage : basePackages) {

//findCandidateComponents方法扫描class文件,判断Component注解,转成BeanDefinition对象返回。

//值得注意的是,Component不止是@Component,还有

//@Controller、@Service、@Repository,因为在这三个注解上面还有个@Component。

//这就相当于它们都是Component的子注解。

Set 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]

]

4.1.3 对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 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 beanDefs = new LinkedHashSet(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标签的解析

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属性

parsePropertyElements(ele, bd);

return bd;

}

return null;

}

注册BeanDefinition对象,和component-scan扫描的bean注册一样。向容器中填充对象。

不管是XML配置的Bean,还是通过component-scan扫描注册的Bean它们最后都是殊途同归的,会转换成一个BeanDefinition对象。记录着这个Bean对象的属性和方法,最后都注册到容器中,等待在实例化和IOC的时候遍历它们。


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:Java实现的动态数字时钟功能示例【显示世界时间】
下一篇:装修完怎么做网线接口测试(网线接好后怎么测试)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~