springmvc是用户和服务沟通的桥梁,官网提供了springmvc的全面使用和解释:DispatcherServlet :: Spring Framework
1.Tomcat启动
2.解析web.xml文件,根据servlet-class找到DispatcherServlet,根据init-param来获取spring的配置文件,spring的配置文件配置的主要内容就是参数的扫描路径(扫描Bean)和自定义Bean
mvc_shine_aa
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:mvc.xml
1
mvc_shine_aa
/
mvc.xml文件
3.创建DispatcherServlet实例,创建实例后会执行父类(FrameworkServlet)的父类(HttpServletBean)的init方法,HttpServletBean 在执行init()方法最后会执行initServletBean()方法,该方法在HttpServletBean是一个空方法,目的是让继承他的子类自己去完成接下来的初始化操作。
@Override
public final void init() throws ServletException {
// Set bean properties from init parameters.
// 进行一些初始化操作
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;
}
}
// Let subclasses do whatever initialization they like.
// 核心代码,将剩余的初始化操作交给子类自己去实现
initServletBean();
}
接下来程序就走到了FrameworkServlet的initServletBean()方法,
@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 {
// 核心代码
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");
}
}
该方法的核心就是
this.webApplicationContext = initWebApplicationContext();
这一步就是创建一个spring容器,该方法首先会尝试获取父容器,然后判断之前是否有创建过webApplicationContext容器。
protected WebApplicationContext initWebApplicationContext() {
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);
}
}
}
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
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;
}
如果之前没有创建容器,程序会走到
if (wac == null) {
// 核心代码,创建本地容器实例
// No context instance is defined for this servlet -> create a local one
wac = createWebApplicationContext(rootContext);
}
通过createWebApplicationContext(rootContext),之后就会成功创建并初始化一个spring容器了
protected WebApplicationContext createWebApplicationContext(@Nullable ApplicationContext parent) {
Class> contextClass = getContextClass();
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;
}
之后spring容器创建完成,就可以给外部提供访问了。
在前文中发现,DispatcherServlet在生成容器之前会去找一个rootContext父容器,那么这个父容器是什么呢?为什么要找父容器呢?
现在如果在配置文件中声明了两个servlet,并且对应的spring配置文件配置了不同的bean,但是扫描的bean路径都相同的话,就会出现两个DispatcherServlet容器里会有一部分重复的bean
app1
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation1
spring1.xml
1
app1
/app1/*
app2
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation2
spring2.xml
1
app2
/app2/*
springmvc为了解决这个问题就创造了一个父容器的概念,在springmvc官方提供的配置文件中就有一个
org.springframework.web.context.ContextLoaderListener
contextConfigLocation
/WEB-INF/app-context.xml
app
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
1
app
/app/*
spring官方不止提供了通过配置文件来配置springMvc,还提供了配置类的形式来配置,这样就可以省略掉xml配置文件了。
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
// AppConfig对应之前的spring.xml配置文件配置包扫描路径
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
@ComponentScan("com.yuyu")
public class AppConfig {
}
使用代码的方式来实现父子容器也非常简单
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// 创建子容器,将context 父容器注入子容器
AnnotationConfigWebApplicationContext context2 = new AnnotationConfigWebApplicationContext();
context2.register(AppConfig.class);
context2.setParent(context);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}