开始之前扯一些题外话。
有时候也在想,学源码到底是为了什么?不学似乎也没事,反正工作也用不到,似乎只有面试有可能被问到(背一背也能应付过去)。
但是,在反复阅读一些源码后,可以发现能够更容易理解一些技术实现原理,能够更自信更从容的应付面试,最终要的是能够让自己的代码看起来尽量的美观。
看源码只是为了能够更好理解技术,而不是单纯的死记硬背。只有理解了,才能建立自身对技术的认知,而非人云亦云。
为什么要分析源码
理解其内部实现原理。(任何技术从外层去看这么多功能,这么好用,这么牛。再一看源码,似乎也这么回事)
学习其设计思路,好处优点。
知识储备。
自我认知。
开始
学会看堆栈
看源码一定要注意堆栈信息。里边每一步都很清楚,一边调试,一边理解会轻松很多。如下图 Spring IOC 步骤图。
Spring IOC 核心流程 (基于XML)
Spring的IOC主要是为了实现对Bean的统一管理,将Bean的初始化、创建,销毁都交由Spring的IOC容器来实现。
上边是Spring IOC的核心功能,完成Bean的初始化创建,依赖注入,管理Bean的生命周期。
那么可以想两个问题。
-
我们再项目中使用Spring都做了哪些功能?
a. 定义并配置Spring的XML文件
b. 在Web.xml中配置解析SpringXml文件
-
如果要完成bean的创建我们应该如何做?
a. 知道要创建哪些bean。(扫描bean)
b. 创建完了之后要有容器来存放。(加载bean)
忘记了Spring XML配置的同学,可以参考这个文档,回忆一下。 Spring MVC 配置
简单来说分为以下几步。
jar包引入。
配置Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<!-- 全局初始化方式,正常只需要配置 DispatcherServlet 初始化方式即可 -->
<context-param>
<!-- 初始化过程中回加载 contextConfigLocation 这个参数 路径 -->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>
<listener>
<!-- web 容器初始化监听 实际事从这里开始 初始化 ApplicationContext -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- DispatcherServlet 初始化方式 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<!-- 这个参数可以省略,Spring 默认加载 WEB-INF下servlet名称-servlet.xml。即dispatcher-servlet.xml -->
<init-param>
<!--配置 dispatcher-servlet.xml作为mvc的配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>*.form</url-pattern>
</servlet-mapping>
</web-app>
这里简单做一个说明:
初始化有两种方式,一种是ContextLoaderListener,一种是DispatcherServlet ,这两种建议使用DispatcherServlet。因为已经是老技术了(现在没人用这两种初始化方式了),这里不做深究,有兴趣的同学可以自行百度一下区别。目前了解到的是,配置两种方式,会进行两次初始化。如下方日志,initialization started 出现两次。一般单独使用DispatcherServlet即可。下边分析源码也会以DispatcherServlet启动过程为主。
[2021-11-14 04:03:15,384] Artifact fishbone-springmvc-study:war: Artifact is being deployed, please wait...
14-Nov-2021 16:03:15.861 警告 [RMI TCP Connection(3)-127.0.0.1] org.apache.tomcat.util.descriptor.web.WebXml.setVersion Unknown version string [4.0]. Default version will be used.
14-Nov-2021 16:03:18.019 信息 [RMI TCP Connection(3)-127.0.0.1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
14-Nov-2021 16:03:18.118 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization started
14-Nov-2021 16:03:23.148 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.context.support.AbstractApplicationContext.prepareRefresh Refreshing Root WebApplicationContext: startup date [Sun Nov 14 16:03:23 CST 2021]; root of context hierarchy
14-Nov-2021 16:03:25.468 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory [D:\Program Files (x86)\apache-tomcat-8.5.35\webapps\manager]
14-Nov-2021 16:03:25.797 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/applicationContext.xml]
14-Nov-2021 16:03:28.337 信息 [localhost-startStop-1] org.apache.jasper.servlet.TldScanner.scanJars At least one JAR was scanned for TLDs yet contained no TLDs. Enable debug logging for this logger for a complete list of JARs that were scanned but no TLDs were found in them. Skipping unneeded JARs during scanning can improve startup time and JSP compilation time.
14-Nov-2021 16:03:28.756 信息 [localhost-startStop-1] org.apache.catalina.startup.HostConfig.deployDirectory Deployment of web application directory [D:\Program Files (x86)\apache-tomcat-8.5.35\webapps\manager] has finished in [3,287] ms
14-Nov-2021 16:03:33.519 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.context.ContextLoader.initWebApplicationContext Root WebApplicationContext: initialization completed in 15400 ms
14-Nov-2021 16:03:35.161 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean FrameworkServlet 'dispatcher': initialization started
14-Nov-2021 16:03:36.865 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.context.support.AbstractApplicationContext.prepareRefresh Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:38.218 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.beans.factory.xml.XmlBeanDefinitionReader.loadBeanDefinitions Loading XML bean definitions from ServletContext resource [/WEB-INF/dispatcher-servlet.xml]
14-Nov-2021 16:03:50.068 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$MappingRegistry.register Mapped "{[/test/test],methods=[GET]}" onto public java.lang.String com.fishbone.spring.TestController.test()
14-Nov-2021 16:03:50.368 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:50.507 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.initControllerAdviceCache Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Sun Nov 14 16:03:36 CST 2021]; parent: Root WebApplicationContext
14-Nov-2021 16:03:50.662 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/css/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#0'
14-Nov-2021 16:03:50.667 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/js/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#1'
14-Nov-2021 16:03:50.678 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/image/**] onto handler 'org.springframework.web.servlet.resource.ResourceHttpRequestHandler#2'
14-Nov-2021 16:03:50.694 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.handler.AbstractUrlHandlerMapping.registerHandler Mapped URL path [/**] onto handler 'org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler#0'
14-Nov-2021 16:03:50.977 信息 [RMI TCP Connection(3)-127.0.0.1] org.springframework.web.servlet.FrameworkServlet.initServletBean FrameworkServlet 'dispatcher': initialization completed in 15815 ms
[2021-11-14 04:03:50,996] Artifact fishbone-springmvc-study:war: Artifact is deployed successfully
[2021-11-14 04:03:50,997] Artifact fishbone-springmvc-study:war: Deploy took 35,613 milliseconds
- 配置 dispatcher-servlet.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:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">
<!--此文件负责整个mvc中的配置-->
<!-- 自动扫描装配 -->
<context:component-scan base-package="com.fishbone.spring"/>
<!--启用spring的一些annotation -->
<context:annotation-config/>
<!-- 配置注解驱动 可以将request参数与绑定到controller参数上 -->
<mvc:annotation-driven/>
<!--静态资源映射-->
<!--本项目把静态资源放在了webapp的statics目录下,资源映射如下-->
<mvc:resources mapping="/css/**" location="/static/css/"/>
<mvc:resources mapping="/js/**" location="/static/js/"/>
<mvc:resources mapping="/image/**" location="/static/images/"/>
<mvc:default-servlet-handler /> <!--这句要加上,要不然可能会访问不到静态资源,具体作用自行百度-->
<!-- 对模型视图名称的解析,即在模型视图名称添加前后缀(如果最后一个还是表示文件夹,则最后的斜杠不要漏了) 使用JSP-->
<!-- 默认的视图解析器 在上边的解析错误时使用 (默认使用html)- -->
<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/views/"/><!--设置JSP文件的目录位置-->
<property name="suffix" value=".jsp"/>
<property name="exposeContextBeansAsAttributes" value="true"/>
</bean>
</beans>
- 最后就是在 test.SpringMVC包下边创建自己的Controller、Service 等类。
以上就是我们再使用Spring框架时的步骤。总结一下有几个点:
通过web.xml配置DispatcherServlet作为入口,由于这里初始化参数配置了Spring的xml文件地址,因此这里启动后,一定会去加载Spring的配置文件。
Spring 的配置文件中,定义了Spring要管理的Bean位置(包路径),因此Spring会扫描加载包下边的所有类文件,找到Spring需要解析的类(@Controller、@Service等Spring相关的注解)
扫描到以后,根据其注解类型采用不同的解析方式。完成bean的解析,并存放到IOC容器中。
接下来我们就按照这三个步骤,一步一步的来看其加载过程。
流程图
贴代码实在太费劲了,直接搞一个流程图比较方便。下边会对一些关键部分代码做一下解析。主要会涉及到一些比较容易迷路的地方。
建议先根据流程图点一遍源码。
DispatcherServlet
首先我们都知道,这里会调用init()方法,根据继承关系,我们可以找到init()方法是在DispatcherServlet的父类,HttpSevletBean中。
@Override
public final void init() throws ServletException {
// 其实前边这一堆东西 都没什么用, pvs 正常情况都是 null 直接看 initServletBean
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
if (!pvs.isEmpty()) {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
initBeanWrapper(bw);
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
// 调用FrameWork初始化Bean的过程,完成Spring的初始化
initServletBean();
}
FrameworkServlet
protected WebApplicationContext initWebApplicationContext() {
// 这里会初始化ApplicationContext,实际返回的是Null
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
// 如果在web.xml 中配置了 ContextLoadListener 此处会不为Null
if (this.webApplicationContext != null) {
// A context instance was injected at construction time -> use it
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
// The context has not yet been refreshed -> provide services such as
// setting the parent context, setting the application context id, etc
if (cwac.getParent() == null) {
// The context instance was injected without an explicit parent -> set
// the root application context (if any; may be null) as the parent
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);
}
}
}
if (wac == null) {
// No context instance was injected at construction time -> see if one
// has been registered in the servlet context. If one exists, it is assumed
// that the parent context (if any) has already been set and that the
// user has performed any initialization such as setting the context id
wac = findWebApplicationContext();
}
if (wac == null) {
// No context instance is defined for this servlet -> create a local one
// 单配置DispatcherServlet情况会走这里
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
// Either the context is not a ConfigurableApplicationContext with refresh
// support or the context injected at construction time had already been
// refreshed -> trigger initial onRefresh manually here.
onRefresh(wac);
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 这里会返回一个 XmlWebApplicationContext
Class<?> contextClass = getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + getServletName() +
"' will try to create custom WebApplicationContext context of class '" +
contextClass.getName() + "'" + ", using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException(
"Fatal initialization error in servlet with name '" + getServletName() +
"': custom WebApplicationContext class [" + contextClass.getName() +
"] is not of type ConfigurableWebApplicationContext");
}
// 进行实例化 XmlWebApplicationContext
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
wac.setParent(parent);
// 如果web.xml DispatcherServlet 标签下 配置了 <init-param> 且<param-name> 为contextConfigLocation
// 那么此处可以获取到 配置的值,我们的代码中没有配置因此此处为null
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 准备开始调用 refresh 方法
configureAndRefreshWebApplicationContext(wac);
return wac;
}
AbstractApplicationContext
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//1、调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
// 2、告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的载入从
// 子类的refreshBeanFactory()方法启动, IOC 容器初始化
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
//3、为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
// Allows post-processing of the bean factory in context subclasses.
//4、为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
//5、调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
//6、为BeanFactory注册BeanPost事件处理器.
//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
//7、初始化信息源,和国际化相关.
initMessageSource();
// Initialize event multicaster for this context.
//8、初始化容器事件传播器.
initApplicationEventMulticaster();
// Initialize other special beans in specific context subclasses.
//9、调用子类的某些特殊Bean初始化方法
onRefresh();
// Check for listener beans and register them.
//10、为事件传播器注册事件监听器.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
//11、初始化所有剩余的单例Bean
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
//12、初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Exception encountered during context initialization - " +
"cancelling refresh attempt: " + ex);
}
// Destroy already created singletons to avoid dangling resources.
//13、销毁已创建的Bean
destroyBeans();
// Reset 'active' flag.
//14、取消refresh操作,重置容器的同步标识。
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
finally {
// Reset common introspection caches in Spring's core, since we
// might not ever need metadata for singleton beans anymore...
//15、重设公共缓存
resetCommonCaches();
}
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
// 关于此处应该到哪个类 可以查看类关系图 使用快捷键 ctrl + H
// 由于上放 FrameworkServlet#createWebApplicationContext方法中得知返回的对象为 XmlWebApplicationContext
// 因此结合类关系图得知 这里应该为 AbstractRefreshableApplicationContext 如下图
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
AbstractRefreshableApplicationContext
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
// 关注一下这里,返回了一个 DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
// 这里同上查询类关系图可知 应该走 XmlWebApplicationContext
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
XmlWebApplicationContext
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
// 关注一下这里,直接将 DefaultListableBeanFactory 作为 AbstractBeanDefinitionReader 类的 registry
// PathMatchingResourcePatternResolver 作为 resourceLoader 。StandardEnvironment 作为 environment
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(getEnvironment());
// 重新设置 resourceLoader值
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws IOException {
// 这里获取 默认需要加载的配置文件 也就是 /WEB-INF/dispatcher-servlet.xml
// 这里点进去之后 不要迷路哈,先调用父类,实际最后调用的还是 子类的实现。XmlWebApplicationContext
// 支持配置多个配置文件
String[] configLocations = getConfigLocations();
if (configLocations != null) {
for (String configLocation : configLocations) {
reader.loadBeanDefinitions(configLocation);
}
}
}
XmlBeanDefinitionReader
public int registerBeanDefinitions(Document doc, Resource resource) throws BeanDefinitionStoreException {
// 创建并实例化 DefaultBeanDefinitionDocumentReader 类
BeanDefinitionDocumentReader documentReader = createBeanDefinitionDocumentReader();
int countBefore = getRegistry().getBeanDefinitionCount();
// 初始化 标签的解析类,createReaderContext() 方法 需要点进去看一下
documentReader.registerBeanDefinitions(doc, createReaderContext(resource));
return getRegistry().getBeanDefinitionCount() - countBefore;
}
public XmlReaderContext createReaderContext(Resource resource) {
// 初始化 XmlReaderContext 类
return new XmlReaderContext(resource, this.problemReporter, this.eventListener,
this.sourceExtractor, this, getNamespaceHandlerResolver());
}
/**
* Lazily create a default NamespaceHandlerResolver, if not set before.
* @see #createDefaultNamespaceHandlerResolver()
*/
public NamespaceHandlerResolver getNamespaceHandlerResolver() {
if (this.namespaceHandlerResolver == null) {
// 记一下这个变量,解析的时候会用到这个变量
this.namespaceHandlerResolver = createDefaultNamespaceHandlerResolver();
}
return this.namespaceHandlerResolver;
}
protected NamespaceHandlerResolver createDefaultNamespaceHandlerResolver() {
ClassLoader cl = (getResourceLoader() != null ? getResourceLoader().getClassLoader() : getBeanClassLoader());
// 注意这里 初始化了 DefaultNamespaceHandlerResolver 类 后续解析context 标签会用到
// 同时设置了 handlerMappingsLocation 属性值为 META-INF/spring.handlers
// 后边解析标签时会扫描这个文件,解析实例化其中的类作为标签解析类
// Spring XML配置方式的一个关键扩展点,包括Dubbo等整合Spring通过配置文件方式实现场景都会用到这个扩展点 NamespaceHandler
return new DefaultNamespaceHandlerResolver(cl);
}
// 这里 DEFAULT_HANDLER_MAPPINGS_LOCATION = META-INF/spring.handlers
public DefaultNamespaceHandlerResolver(@Nullable ClassLoader classLoader) {
this(classLoader, DEFAULT_HANDLER_MAPPINGS_LOCATION);
}
DefaultBeanDefinitionDocumentReader
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
// 这里获取的是配置文件的标签对象 比如 <context:component-scan>
// dispatcher-servlet.xml 标签是以<beans> 开头的 因此会往下走
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) {
// 这里获取的是配置文件的标签对象 比如 <context:component-scan>
Element ele = (Element) node;
// 这里会判断是否为bean标签, <bean>标签直接解析,其他的通过委托类解析
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
// <context:component-scan> 会走这里
delegate.parseCustomElement(ele);
}
}
}
}
else {
// 非<beans> 标签,比如dubbo、mybatis整合spring后使用的 <dubbo> 等标签就会走这里
delegate.parseCustomElement(root);
}
}
BeanDefinitionParserDelegate
public BeanDefinition parseCustomElement(Element ele, @Nullable BeanDefinition containingBd) {
// 这里获取的 nameSpaceURI 就是配置文件 头信息中的定义URI
// 比如 xmlns:context="http://www.springframework.org/schema/context"
// context 标签的URI 为 http://www.springframework.org/schema/context
String namespaceUri = getNamespaceURI(ele);
if (namespaceUri == null) {
return null;
}
// 这里会通过URI 获取对应的解析类 并实例化
// 还记得前边 namespaceHandlerResolver = new DefaultNamespaceHandlerResolver() 如果忘记往下看 DefaultNamespaceHandlerResolver 类
// 这里还是以<context:component-scan> 标签为例,获取的 NamespaceHandler 为 ContextNamespaceHandler
NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri);
if (handler == null) {
error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele);
return null;
}
// 调用 NamespaceHandlerSupport 的解析方法
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
DefaultNamespaceHandlerResolver
public NamespaceHandler resolve(String namespaceUri) {
// 尝试获取缓存中的映射关系 URI跟实体类
// 点进去看一下吧。初始化这里一般都没有实例化处理类,存的是类的全路径,下边会进行实例化
// org.springframework.context.config.ContextNamespaceHandler
Map<String, Object> handlerMappings = getHandlerMappings();
Object handlerOrClassName = handlerMappings.get(namespaceUri);
if (handlerOrClassName == null) {
return null;
}
else if (handlerOrClassName instanceof NamespaceHandler) {
return (NamespaceHandler) handlerOrClassName;
}
else {
// 注意初次加载Value 都是String类型的 全路径类名。
String className = (String) handlerOrClassName;
try {
// 直接通过classforname 完成实例化 (ContextNamespaceHandler)
Class<?> handlerClass = ClassUtils.forName(className, this.classLoader);
if (!NamespaceHandler.class.isAssignableFrom(handlerClass)) {
throw new FatalBeanException("Class [" + className + "] for namespace [" + namespaceUri +
"] does not implement the [" + NamespaceHandler.class.getName() + "] interface");
}
NamespaceHandler namespaceHandler = (NamespaceHandler) BeanUtils.instantiateClass(handlerClass);
// 实例化完成以后调用 init 方法 (ContextNamespaceHandler)
namespaceHandler.init();
handlerMappings.put(namespaceUri, namespaceHandler);
return namespaceHandler;
}
catch (ClassNotFoundException ex) {
throw new FatalBeanException("NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "] not found", ex);
}
catch (LinkageError err) {
throw new FatalBeanException("Invalid NamespaceHandler class [" + className + "] for namespace [" +
namespaceUri + "]: problem with handler class file or dependent class", err);
}
}
}
// 加载NamespaceHandler扩展类
private Map<String, Object> getHandlerMappings() {
Map<String, Object> handlerMappings = this.handlerMappings;
// 初次加载这里肯定为空
if (handlerMappings == null) {
synchronized (this) {
handlerMappings = this.handlerMappings;
if (handlerMappings == null) {
try {
// 这里使用的路径 handlerMappingsLocation = META-INF/spring.handlers
// 因此这里会扫描所有的 META-INF/spring.handlers 文件读取里边的属性值 key/Value结构。
// 以<context>标签URI为例,如下图
Properties mappings =
PropertiesLoaderUtils.loadAllProperties(this.handlerMappingsLocation, this.classLoader);
if (logger.isDebugEnabled()) {
logger.debug("Loaded NamespaceHandler mappings: " + mappings);
}
Map<String, Object> mappingsToUse = new ConcurrentHashMap<>(mappings.size());
CollectionUtils.mergePropertiesIntoMap(mappings, mappingsToUse);
handlerMappings = mappingsToUse;
this.handlerMappings = handlerMappings;
}
catch (IOException ex) {
throw new IllegalStateException(
"Unable to load NamespaceHandler mappings from location [" + this.handlerMappingsLocation + "]", ex);
}
}
}
}
return handlerMappings;
}
ContextNamespaceHandler
public class ContextNamespaceHandler extends NamespaceHandlerSupport {
@Override
public void init() {
// 不同的标签 使用不同的解析处理类
// <context:property-override> 使用 PropertyOverrideBeanDefinitionParser
// <context:component-scan> 使用 ComponentScanBeanDefinitionParser
// 调用 NamespaceHandlerSupport#registerBeanDefinitionParser()方法
registerBeanDefinitionParser("property-placeholder", new PropertyPlaceholderBeanDefinitionParser());
registerBeanDefinitionParser("property-override", new PropertyOverrideBeanDefinitionParser());
registerBeanDefinitionParser("annotation-config", new AnnotationConfigBeanDefinitionParser());
registerBeanDefinitionParser("component-scan", new ComponentScanBeanDefinitionParser());
registerBeanDefinitionParser("load-time-weaver", new LoadTimeWeaverBeanDefinitionParser());
registerBeanDefinitionParser("spring-configured", new SpringConfiguredBeanDefinitionParser());
registerBeanDefinitionParser("mbean-export", new MBeanExportBeanDefinitionParser());
registerBeanDefinitionParser("mbean-server", new MBeanServerBeanDefinitionParser());
}
}
NamespaceHandlerSupport
protected final void registerBeanDefinitionParser(String elementName, BeanDefinitionParser parser) {
this.parsers.put(elementName, parser);
}
public BeanDefinition parse(Element element, ParserContext parserContext) {
// 找到对应的标签解析类,即ContextNamespaceHandler 中注册的 ComponentScanBeanDefinitionParser
BeanDefinitionParser parser = findParserForElement(element, parserContext);
return (parser != null ? parser.parse(element, parserContext) : null);
}
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) {
// 这里localName为 component-scan
String localName = parserContext.getDelegate().getLocalName(element);
// ContextNamespaceHandler#init()方法完成类的注册
BeanDefinitionParser parser = this.parsers.get(localName);
if (parser == null) {
parserContext.getReaderContext().fatal(
"Cannot locate BeanDefinitionParser for element [" + localName + "]", element);
}
// 返回类为 ComponentScanBeanDefinitionParser
return parser;
}