源码注释码云地址:spring源码解读地址:https://gitee.com/oushiyou/spring-framework-5.2.8.RELEASE
这里首先附上一张整体流程图,以供解读的时候可以进行参考:
精华提取:java多态性、委派模式、父子容器
我们在使用springMVC的时候,通常的做法是在web.xml中配置DispatcherServlet, 然后传入springMVC的配置文件位置。比如:
<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
项目在启动的时候,会对我们配置的Servlet进行初始化,然后调用其init方法,并且将我们配置的init-param初始化参数传递给ServletConfig。
如上配置:我们配置的Servlet是org.springframework.web.servlet.DispatcherServlet,
那么就会调用其init方法进行初始化servlet。
由于DispatcherServlet没有init方法,根据java多态的特性,那么他们去调用父类的init方法。
–> DispatcherServlet的直接父类是:FrameworkServlet,FrameworkServlet类中也没有init方法 --> FrameworkServlet的直接父类是:HttpServletBean --> HttpServletBean类中有init方法,所以最终调用的是: HttpServletBean的init()。
此方法主要是设置一些配置参数到当前Servlet的属性上,以便后续获取,
比如我们配置的contextConfigLocation,他就会赋值到contextConfigLocation的contextConfigLocation属性上。
这里利用了委派模式,将初始化容器委派给了FrameworkServlet来实现,将其职责进行拆分,互不影响和干扰。
/**
* 二、将配置参数映射到该servlet的bean属性上,并调用子类初始化。
* 具体做初始化工作的是{@link HttpServletBean#initServletBean()}方法
* 而此类的initServletBean方法是一个空的方法,所有初始化bean的工作交给其子类做的:{@link FrameworkServlet#initServletBean()}
*
*/
@Override
public final void init() throws ServletException {
/**
* 从init参数设置bean属性。
* 将初始化参数设置进入{@link org.springframework.beans.MutablePropertyValues }中
*/
PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
// 如果PropertyValues不为空:当前环境中有key-value的配置
if (!pvs.isEmpty()) {
try {
// 获取HttpServletBean的BeanWrapper,以JavaBeans样式访问属性。
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
// 资源加载器
ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
// 向HttpServletBean注册一个ResourceEditor
// PropertyEditor:主要方法有四个
// void setValue(Object value); 设置属性值
// Object getValue(); 获取属性值
// String getAsText(); 把属性值转换成 String
// void setAsText(String text); 把 String 转换成属性值
// 而 Java 也为我们提供了一个默认的实现类 java.beans.PropertyEditorSupport
bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
// 目前此方法是空的方法,提供给子类进行扩展的,如果我们想要扩展,可以通过这里来进行扩展
// todo 可以使用自定义编辑器初始化此HttpServletBean的BeanWrapper。
initBeanWrapper(bw);
// 设置初始化参数到configLocation中
bw.setPropertyValues(pvs, true);
}
catch (BeansException ex) {
if (logger.isErrorEnabled()) {
logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
}
throw ex;
}
}
/**
* 这里的具体实现是{@link org.springframework.web.servlet.FrameworkServlet#initServletBean()}
*/
initServletBean();
}
然后我们看FrameworkServlet的initServletBean方法:此方法是HttpServletBean 委派下来初始化springMVC容器的,此方法中,主要还是交给initWebApplicationContext方法。
这个方法主要就是创建当前的web容器,在创建的时候,尝试获取到父容器,也就是spring容器,如果存在,那么将其设置为parent父容器。
/**
* 三、{@link HttpServletBean9}的重写方法,在设置任何bean属性后调用。创建此Servlet的WebApplicationContext。
* 在此处设置父容器的(如果父容器存在的话),
*/
@Override
protected final void initServletBean() throws ServletException {
// 打个日志:表示初始化中...
getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
if (logger.isInfoEnabled()) {
logger.info("Initializing Servlet '" + getServletName() + "'");
}
// 记录初始化开始时间
long startTime = System.currentTimeMillis();
try {
// 初始化并发布此Servlet的WebApplicationContext(这里的容器为子容器)。
this.webApplicationContext = initWebApplicationContext();
// 这里是一个空方法,至于用途,目前还未知
initFrameworkServlet();
}
catch (ServletException | RuntimeException ex) {
logger.error("Context initialization failed", ex);
throw ex;
}
if (logger.isDebugEnabled()) {
String value = this.enableLoggingRequestDetails ?
"shown which may lead to unsafe logging of potentially sensitive data" :
"masked to prevent unsafe logging of potentially sensitive data";
logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
"': request parameters and headers will be " + value);
}
// 输出总共使用了多长时间
if (logger.isInfoEnabled()) {
logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
}
}
初始化当前应用上下文核心方法:initWebApplicationContext
获取父容器 --> 创建当前容器 --> 判断我们是否配置了springMVC容器的全限定类名,如果没有指定,那么使用默认的:XmlWebApplicationContext --> 反射实例化容器对象 --> 设置父容器 --> 刷新上下文
protected WebApplicationContext initWebApplicationContext() {
// 获取到父容器,父容器是springContextLoaderListener实现ServletContextListener监听的时候创建的,显然,ServletContextListener监听是在此之前的。
WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = 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);
}
}
}
// 如果为null,则尝试查找一下当前容器
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
// 在构建时没有注入上下文实例
// 看看是否已经在servlet上下文中注册了一个。如果存在,则假定父上下文(如果有的话)已经设置,
// 并且用户已经执行了任何初始化,比如设置上下文id
wac = findWebApplicationContext();
}
// 如果查找不到,那么就进行创建:很显然,初始化的时候得从这里创建的
if (wac == null) {
// 没有为这个servlet定义上下文实例 -> 创建一个本地上下文, 然后进行刷新
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.
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
// 获取上下文类型,默认是:org.springframework.web.context.support.XmlWebApplicationContext
Class<?> contextClass = getContextClass();
// 这里判断传入的上下文类是否实现了ConfigurableWebApplicationContext接口,如果没有实现,那么就会报错
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");
}
// 实例化
ConfigurableWebApplicationContext wac =
(ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(getEnvironment());
// 设置父容器
wac.setParent(parent);
// 设置本地配置文件的位置
String configLocation = getContextConfigLocation();
if (configLocation != null) {
wac.setConfigLocation(configLocation);
}
// 配置并且刷新当前容器上下文
configureAndRefreshWebApplicationContext(wac);
return wac;
}
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
// The wac environment's #initPropertySources will be called in any case when the context
// is refreshed; do it eagerly here to ensure servlet property sources are in place for
// use in any post-processing or initialization that occurs below prior to #refresh
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
// 应用初始化器:调用自定义初始化器
applyInitializers(wac);
// 刷新上下文
wac.refresh();
}
从整个启动流程来看,虽然我们配置的是DispatcherServlet,但是在启动的时候,他什么都没有做,而是交个其两个父类来进行实现的,这种委派这是模式的思想很值得我们进行学习。而他是作为请求中央调度器的,后面我会深度剖析DispatcherServlet以及其使用到的一些设计模式。
其中讲到了将spring的容器作为父容器,这里面蕴含着很高的设计思想,后面我也会深度讲解其父子容器的作用,以及做此设计解决的问题和对项目有着什么样的扩展性。
如果你也喜欢源码,不防点赞评论关注一套流程走完,相互学习吧!
一个相信努力就会有结果的程序员,以兴趣驱动技术! ------ CoderOu