springmvc是用户和服务沟通的桥梁,官网提供了springmvc的全面使用和解释:DispatcherServlet :: Spring Framework
1.Tomcat启动
2.解析web.xml文件,根据servlet-class找到DispatcherServlet,根据init-param来获取spring的配置文件,spring的配置文件配置的主要内容就是参数的扫描路径(扫描Bean)和自定义Bean
- <servlet>
- <servlet-name>mvc_shine_aaservlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
- <init-param>
- <param-name>contextConfigLocationparam-name>
- <param-value>classpath:mvc.xmlparam-value>
- init-param>
-
- <load-on-startup>1load-on-startup>
- servlet>
- <servlet-mapping>
- <servlet-name>mvc_shine_aaservlet-name>
-
- <url-pattern>/url-pattern>
- servlet-mapping>
mvc.xml文件
- <context:component-scan base-package="com.qf.web"/>
-
- <bean class="com.yuyu.AService">
- <property name="id" value="2">property>
- bean>
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
- <servlet>
- <servlet-name>app1servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
- <init-param>
- <param-name>contextConfigLocation1param-name>
- <param-value>spring1.xmlparam-value>
- init-param>
- <load-on-startup>1load-on-startup>
- servlet>
- <servlet-mapping>
- <servlet-name>app1servlet-name>
- <url-pattern>/app1/*url-pattern>
- servlet-mapping>
-
- <servlet>
- <servlet-name>app2servlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
- <init-param>
- <param-name>contextConfigLocation2param-name>
- <param-value>spring2.xmlparam-value>
- init-param>
- <load-on-startup>1load-on-startup>
- servlet>
- <servlet-mapping>
- <servlet-name>app2servlet-name>
- <url-pattern>/app2/*url-pattern>
- servlet-mapping>
springmvc为了解决这个问题就创造了一个父容器的概念,在springmvc官方提供的配置文件中就有一个
- <web-app>
-
- <listener>
- <listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
- listener>
-
- <context-param>
- <param-name>contextConfigLocationparam-name>
- <param-value>/WEB-INF/app-context.xmlparam-value>
- context-param>
-
- <servlet>
- <servlet-name>appservlet-name>
- <servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
- <init-param>
- <param-name>contextConfigLocationparam-name>
- <param-value>param-value>
- init-param>
- <load-on-startup>1load-on-startup>
- servlet>
-
- <servlet-mapping>
- <servlet-name>appservlet-name>
- <url-pattern>/app/*url-pattern>
- servlet-mapping>
-
- web-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/*");
- }
- }