IDEA搭建纯注解版本SpringMVC的web开发环境全过程并分析启动原理
作者:莫失莫忘hh
现在spring开发的项目,越来越多的用到注解开发了,所以这里就记录一下,存注解开发搭建sping的web开发。
创建一个maven工程
直接点next,这里不添加原型插件,(到创建后面在添加web环境)
设置groupId 和artifactid 信息。
然后next.
点击finish创建项目工程
然后进入这个页面,配置文件结构的属性,点击加号。来添加web环境
点击web,添加web环境
选中这个之后出现这个页面(设置web资源的目录,也就是我们属性的webapp所在的位置,它默认的位置并不正确,所有我们要修改一下)
修改为 src\main\webapp
下面这个是设置web.xml的因为我们现在搭建的注解版,所以就不需要,(点击-号把默认的删除)
然后点击ok.
这里就有了一个web环境的标识了,说明可以了
然后查看文件目录结构:
到这里项目的结构就搭建好了
配置pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <!--注意默认情况下 创建出的maven 项目并没有设置打包的类型,这里设置成war包--> <packaging>war</packaging> <groupId>com.kuake</groupId> <artifactId>springmvc-web</artifactId> <version>1.0-SNAPSHOT</version> <!--配置依赖 --> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>4.3.23.RELEASE</version> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>javax.servlet-api</artifactId> <version>3.0.1</version> <scope>provided</scope> </dependency> <!--导入jackson包,使用@responseBody与@requestBody所需要--> <!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-databind --> <dependency> <groupId>com.fasterxml.jackson.core</groupId> <artifactId>jackson-databind</artifactId> <version>2.9.8</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.4</version> <configuration> <!--设置忽略没有web.xml文件--> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> <!--这里就不使用外部的tomcat,使用maven的tomcat插件--> <plugin> <groupId>org.apache.tomcat.maven</groupId> <artifactId>tomcat7-maven-plugin</artifactId> <version>2.2</version> <configuration> <port>8080</port> <path>/</path> <uriEncoding>UTF-8</uriEncoding> <server>tomcat7</server> </configuration> </plugin> <!--如果不指定,maven指定maven编译的jdk版本,如果不指定,maven3默认用jdk 1.5 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.2</version> <configuration> <source>1.8</source> <target>1.8</target> <encoding>UTF-8</encoding> </configuration> </plugin> </plugins> </build> </project>
(1)创建一个RootConfig根容器
package com.kuake.config; import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; /** * @author hao * @create 2019-06-14 ${TIM} */ /** * 这个是跟容器,相当于ApplicationContxt.xml,包扫描的时候要排除@Controller注解,避免重复扫描 */ @ComponentScan(value ={"com.kuake"}, excludeFilters={@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {Controller.class})}) @Configuration//声明这是一个配置类 public class RootConfig { }
(2)创建一个AppConfig
package com.kuake.config; /** * @author hao * @create 2019-06-14 ${TIM} */ import org.springframework.context.annotation.ComponentScan; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.FilterType; import org.springframework.stereotype.Controller; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.ViewResolverRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; /** * Springweb应用的配置 相当于配置文件SpringMVC.xml * */ @Configuration @EnableWebMvc//开启全面接管spingmvc //配置只会扫描Controller注解 @ComponentScan(value = {"com.kuake"},includeFilters={@ComponentScan.Filter(type= FilterType.ANNOTATION,value={Controller.class})},useDefaultFilters = false) public class AppConfig extends WebMvcConfigurerAdapter { //配置视图解析器 @Override public void configureViewResolvers(ViewResolverRegistry registry) { //默认前缀 /WEB-INF/ 后缀.jsp registry.jsp(); } //可以自定义添加各种组件 通过重写方法 }
(3)创建一个容器的初始化器MyWebAppInitializer
package com.kuake.config; import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer; /** * @author hao * @create 2019-06-14 ${TIM} */ public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { //加载容器 @Override protected Class<?>[] getRootConfigClasses() { return new Class<?>[] { RootConfig.class }; } //加载webapp容器 @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { AppConfig.class }; } //设置DispatcherServlet的拦截规则 / 代表拦截所有但是不包括jsp @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
(4)创建一个helloController
package com.kuake.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; /** * @author hao * @create 2019-06-14 ${TIM} */ @Controller public class UserController { @GetMapping("/hello") public String hello(){ return "hello"; } }
在WEB-INF下创建一个hello.jsp
<%-- Created by IntelliJ IDEA. User: Administrator Date: 2019/6/14 Time: 18:48 To change this template use File | Settings | File Templates. --%> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>Title</title> </head> <body> hello world! </body> </html>
(5)启动web应用,使用maven的tomcat插件
控制台打印日志:
[INFO] --- tomcat7-maven-plugin:2.2:run (default-cli) @ springmvc --- [INFO] Running war on http://localhost:8080/ [INFO] Using existing Tomcat server configuration at F:\ideaworkplace\spring-test\springmvc\target\tomcat [INFO] create webapp with contextPath: 六月 14, 2019 9:09:40 下午 org.apache.coyote.AbstractProtocol init 信息: Initializing ProtocolHandler ["http-bio-8080"] 六月 14, 2019 9:09:40 下午 org.apache.catalina.core.StandardService startInternal 信息: Starting service Tomcat 六月 14, 2019 9:09:40 下午 org.apache.catalina.core.StandardEngine startInternal 信息: Starting Servlet Engine: Apache Tomcat/7.0.47 六月 14, 2019 9:09:44 下午 org.apache.catalina.core.ApplicationContext log 信息: 1 Spring WebApplicationInitializers detected on classpath 六月 14, 2019 9:09:44 下午 org.apache.catalina.core.ApplicationContext log 信息: Initializing Spring root WebApplicationContext 六月 14, 2019 9:09:44 下午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization started 六月 14, 2019 9:09:44 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext prepareRefresh 信息: Refreshing Root WebApplicationContext: startup date [Fri Jun 14 21:09:44 CST 2019]; root of context hierarchy 六月 14, 2019 9:09:44 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext loadBeanDefinitions 信息: Registering annotated classes: [class com.kuake.config.RootConfig] 六月 14, 2019 9:09:45 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init> 信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 六月 14, 2019 9:09:45 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register 信息: Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.kuake.controller.UserController.hello() 六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache 信息: Looking for @ControllerAdvice: Root WebApplicationContext: startup date [Fri Jun 14 21:09:44 CST 2019]; root of context hierarchy 六月 14, 2019 9:09:49 下午 org.springframework.web.context.ContextLoader initWebApplicationContext 信息: Root WebApplicationContext: initialization completed in 5098 ms 六月 14, 2019 9:09:49 下午 org.apache.catalina.core.ApplicationContext log 信息: Initializing Spring FrameworkServlet 'dispatcher' 六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.DispatcherServlet initServletBean 信息: FrameworkServlet 'dispatcher': initialization started 六月 14, 2019 9:09:49 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext prepareRefresh 信息: Refreshing WebApplicationContext for namespace 'dispatcher-servlet': startup date [Fri Jun 14 21:09:49 CST 2019]; parent: Root WebApplicationContext 六月 14, 2019 9:09:49 下午 org.springframework.web.context.support.AnnotationConfigWebApplicationContext loadBeanDefinitions 信息: Registering annotated classes: [class com.kuake.config.AppConfig] 六月 14, 2019 9:09:49 下午 org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor <init> 信息: JSR-330 'javax.inject.Inject' annotation found and supported for autowiring 六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping register 信息: Mapped "{[/hello],methods=[GET]}" onto public java.lang.String com.kuake.controller.UserController.hello() 六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter initControllerAdviceCache 信息: Looking for @ControllerAdvice: WebApplicationContext for namespace 'dispatcher-servlet': startup date [Fri Jun 14 21:09:49 CST 2019]; parent: Root WebApplicationContext 六月 14, 2019 9:09:49 下午 org.springframework.web.servlet.DispatcherServlet initServletBean 信息: FrameworkServlet 'dispatcher': initialization completed in 359 ms 六月 14, 2019 9:09:49 下午 org.apache.coyote.AbstractProtocol start 信息: Starting ProtocolHandler ["http-bio-8080"]
进入浏览器:
到这里说明我们的环境搭建成功
相比传统搭建传统的web环境,要在web.xml当中配置前端控制器组件DispatcherServlet
,需要在springmvc.xml当中配置视图解析器,…等等,那么在这种纯注解的情况下,他是如何创建出ioc容器的呢,如何添加DispatcherServlet这个组件的呢。现在来一探究竟。
我们MyWebAppInitializer
这个类继承了一个AbstractAnnotationConfigDispatcherServletInitializer
就从这个类入手。
1、AbstractAnnotationConfigDispatcherServletInitializer
/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.servlet.support; import org.springframework.util.ObjectUtils; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.AnnotationConfigWebApplicationContext; public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer { @Override protected WebApplicationContext createRootApplicationContext() { //获得rootConfig.class Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { //创建一个跟容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); rootAppContext.register(configClasses); return rootAppContext; } else { return null; } } @Override protected WebApplicationContext createServletApplicationContext() { //创建一个web的ioc容器 AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext(); Class<?>[] configClasses = getServletConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { servletAppContext.register(configClasses); } return servletAppContext; } protected abstract Class<?>[] getRootConfigClasses(); protected abstract Class<?>[] getServletConfigClasses(); }
这个类 有两个主要的方法createRootApplicationContext()和createServletApplicationContext(),分别用来创建根容器
和子容器
。
因为定义了两个抽象方法getRootConfigClasses()和getServletConfigClasses(),在创建的过程中也都调用了这俩个方法,如果我们的子类重写了这两个抽象方法,那么父类在创建的时候,就会回调子类的方法 (这其实符合一个设计模式,模板方法)
(2)接着看他的父类:AbstractDispatcherServletInitializer
/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.servlet.support; import java.util.EnumSet; import javax.servlet.DispatcherType; import javax.servlet.Filter; import javax.servlet.FilterRegistration; import javax.servlet.FilterRegistration.Dynamic; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletRegistration; import org.springframework.context.ApplicationContextInitializer; import org.springframework.core.Conventions; import org.springframework.util.Assert; import org.springframework.util.ObjectUtils; import org.springframework.web.context.AbstractContextLoaderInitializer; import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.DispatcherServlet; import org.springframework.web.servlet.FrameworkServlet; /** * Base class for {@link org.springframework.web.WebApplicationInitializer} * implementations that register a {@link DispatcherServlet} in the servlet context. * * <p>Concrete implementations are required to implement * {@link #createServletApplicationContext()}, as well as {@link #getServletMappings()}, * both of which get invoked from {@link #registerDispatcherServlet(ServletContext)}. * Further customization can be achieved by overriding * {@link #customizeRegistration(ServletRegistration.Dynamic)}. * * <p>Because this class extends from {@link AbstractContextLoaderInitializer}, concrete * implementations are also required to implement {@link #createRootApplicationContext()} * to set up a parent "<strong>root</strong>" application context. If a root context is * not desired, implementations can simply return {@code null} in the * {@code createRootApplicationContext()} implementation. * * @author Arjen Poutsma * @author Chris Beams * @author Rossen Stoyanchev * @author Juergen Hoeller * @author Stephane Nicoll * @since 3.2 */ public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer { /** * The default servlet name. Can be customized by overriding {@link #getServletName}. */ public static final String DEFAULT_SERVLET_NAME = "dispatcher"; @Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); } /** * Register a {@link DispatcherServlet} against the given servlet context. * <p>This method will create a {@code DispatcherServlet} with the name returned by * {@link #getServletName()}, initializing it with the application context returned * from {@link #createServletApplicationContext()}, and mapping it to the patterns * returned from {@link #getServletMappings()}. * <p>Further customization can be achieved by overriding {@link * #customizeRegistration(ServletRegistration.Dynamic)} or * {@link #createDispatcherServlet(WebApplicationContext)}. * @param servletContext the context to register the servlet against */ protected void registerDispatcherServlet(ServletContext servletContext) { String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return empty or null"); WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() did not return an application " + "context for servlet [" + servletName + "]"); FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'." + "Check if there is another servlet registered under the same name."); registration.setLoadOnStartup(1); registration.addMapping(getServletMappings()); registration.setAsyncSupported(isAsyncSupported()); Filter[] filters = getServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } customizeRegistration(registration); } /** * Return the name under which the {@link DispatcherServlet} will be registered. * Defaults to {@link #DEFAULT_SERVLET_NAME}. * @see #registerDispatcherServlet(ServletContext) */ protected String getServletName() { return DEFAULT_SERVLET_NAME; } /** * Create a servlet application context to be provided to the {@code DispatcherServlet}. * <p>The returned context is delegated to Spring's * {@link DispatcherServlet#DispatcherServlet(WebApplicationContext)}. As such, * it typically contains controllers, view resolvers, locale resolvers, and other * web-related beans. * @see #registerDispatcherServlet(ServletContext) */ protected abstract WebApplicationContext createServletApplicationContext(); /** * Create a {@link DispatcherServlet} (or other kind of {@link FrameworkServlet}-derived * dispatcher) with the specified {@link WebApplicationContext}. * <p>Note: This allows for any {@link FrameworkServlet} subclass as of 4.2.3. * Previously, it insisted on returning a {@link DispatcherServlet} or subclass thereof. */ protected FrameworkServlet createDispatcherServlet(WebApplicationContext servletAppContext) { return new DispatcherServlet(servletAppContext); } /** * Specify application context initializers to be applied to the servlet-specific * application context that the {@code DispatcherServlet} is being created with. * @since 4.2 * @see #createServletApplicationContext() * @see DispatcherServlet#setContextInitializers * @see #getRootApplicationContextInitializers() */ protected ApplicationContextInitializer<?>[] getServletApplicationContextInitializers() { return null; } /** * Specify the servlet mapping(s) for the {@code DispatcherServlet} — * for example {@code "/"}, {@code "/app"}, etc. * @see #registerDispatcherServlet(ServletContext) */ protected abstract String[] getServletMappings(); /** * Specify filters to add and map to the {@code DispatcherServlet}. * @return an array of filters or {@code null} * @see #registerServletFilter(ServletContext, Filter) */ protected Filter[] getServletFilters() { return null; } /** * Add the given filter to the ServletContext and map it to the * {@code DispatcherServlet} as follows: * <ul> * <li>a default filter name is chosen based on its concrete type * <li>the {@code asyncSupported} flag is set depending on the * return value of {@link #isAsyncSupported() asyncSupported} * <li>a filter mapping is created with dispatcher types {@code REQUEST}, * {@code FORWARD}, {@code INCLUDE}, and conditionally {@code ASYNC} depending * on the return value of {@link #isAsyncSupported() asyncSupported} * </ul> * <p>If the above defaults are not suitable or insufficient, override this * method and register filters directly with the {@code ServletContext}. * @param servletContext the servlet context to register filters with * @param filter the filter to be registered * @return the filter registration */ protected FilterRegistration.Dynamic registerServletFilter(ServletContext servletContext, Filter filter) { String filterName = Conventions.getVariableName(filter); Dynamic registration = servletContext.addFilter(filterName, filter); if (registration == null) { int counter = -1; while (counter == -1 || registration == null) { counter++; registration = servletContext.addFilter(filterName + "#" + counter, filter); Assert.isTrue(counter < 100, "Failed to register filter '" + filter + "'." + "Could the same Filter instance have been registered already?"); } } registration.setAsyncSupported(isAsyncSupported()); registration.addMappingForServletNames(getDispatcherTypes(), false, getServletName()); return registration; } private EnumSet<DispatcherType> getDispatcherTypes() { return (isAsyncSupported() ? EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE, DispatcherType.ASYNC) : EnumSet.of(DispatcherType.REQUEST, DispatcherType.FORWARD, DispatcherType.INCLUDE)); } /** * A single place to control the {@code asyncSupported} flag for the * {@code DispatcherServlet} and all filters added via {@link #getServletFilters()}. * <p>The default value is "true". */ protected boolean isAsyncSupported() { return true; } /** * Optionally perform further registration customization once * {@link #registerDispatcherServlet(ServletContext)} has completed. * @param registration the {@code DispatcherServlet} registration to be customized * @see #registerDispatcherServlet(ServletContext) */ protected void customizeRegistration(ServletRegistration.Dynamic registration) { } }
主要方法是registerDispatcherServlet(ServletContext servletContext)
他的作用是:
- 1、创建一个web的ioc容器【createServletApplicationContext()】
- 2、创建了DispatcherServlet【createDispatcherServlet()】
- 3、将创建的DispatcherServlet添加到ServletContext中;
(3)接着再上一个父类:AbstractContextLoaderInitializer
/* * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web.context; import javax.servlet.ServletContext; import javax.servlet.ServletException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContextInitializer; import org.springframework.web.WebApplicationInitializer; /** * Convenient base class for {@link WebApplicationInitializer} implementations * that register a {@link ContextLoaderListener} in the servlet context. * * <p>The only method required to be implemented by subclasses is * {@link #createRootApplicationContext()}, which gets invoked from * {@link #registerContextLoaderListener(ServletContext)}. * * @author Arjen Poutsma * @author Chris Beams * @author Juergen Hoeller * @since 3.2 */ public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } /** * Register a {@link ContextLoaderListener} against the given servlet context. The * {@code ContextLoaderListener} is initialized with the application context returned * from the {@link #createRootApplicationContext()} template method. * @param servletContext the servlet context to register the listener against */ protected void registerContextLoaderListener(ServletContext servletContext) { WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context"); } } /** * Create the "<strong>root</strong>" application context to be provided to the * {@code ContextLoaderListener}. * <p>The returned context is delegated to * {@link ContextLoaderListener#ContextLoaderListener(WebApplicationContext)} and will * be established as the parent context for any {@code DispatcherServlet} application * contexts. As such, it typically contains middle-tier services, data sources, etc. * @return the root application context, or {@code null} if a root context is not * desired * @see org.springframework.web.servlet.support.AbstractDispatcherServletInitializer */ protected abstract WebApplicationContext createRootApplicationContext(); /** * Specify application context initializers to be applied to the root application * context that the {@code ContextLoaderListener} is being created with. * @since 4.2 * @see #createRootApplicationContext() * @see ContextLoaderListener#setContextInitializers */ protected ApplicationContextInitializer<?>[] getRootApplicationContextInitializers() { return null; } }
主要的方法createRootApplicationContext()
创建一个根容器。
(4)最顶层接口WebApplicationInitializer
public interface WebApplicationInitializer { /** * Configure the given {@link ServletContext} with any servlets, filters, listeners * context-params and attributes necessary for initializing this web application. See * examples {@linkplain WebApplicationInitializer above}. * @param servletContext the {@code ServletContext} to initialize * @throws ServletException if any call against the given {@code ServletContext} * throws a {@code ServletException} */ void onStartup(ServletContext servletContext) throws ServletException; }
那么容器启动时候,为什么这么MyWebAppInitializer
这个类会被加载呢,进而创建根容器,创建web的ioc容器呢
来看这个类的介绍:
- WebApplicationInitializer是Spring MVC提供的一个接口,它确保检测到您的实现并自动用于初始化Servlet 3容器。
- WebApplicationInitializer的抽象基类实现AbstractDispatcherServletInitializer通过重写方法来指定servlet映射和DispatcherServlet配置的位置,使得注册DispatcherServlet更加容易。
这里提到了初始化servlet3.0容器,那就有必要了解一下sevrvlet3.0的一个初始化规范,根据官方文档的解释,用自己的话总结一下有关的重要几点,大概意思就是:
- web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- 加载这个文件制定的类,并且可以通过@HandlesTypes注解,
- 把感兴趣的类的信息,注入到
onStartup(Set<Class<?>> var1, ServletContext var2)
var1当中
经过查看源码,以及debug调试发现,这个ServletContainerInitializer其实就是SpringServletContainerInitializer
,来看SpringServletContainerInitializer所在在包下的目录结构。
根据上面介绍的servlet3.0规范,当servlet容器启动的时候,就会加载javax.servlet.ServletContainerInitializer文件中指定的类
其内容就是org.springframework.web.SpringServletContainerInitializer
,所有也就会在容器启动的时候,加载SpringServletContainerInitializer
SpringServletContainerInitializer代码如下:
/* * Copyright 2002-2016 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.springframework.web; import java.lang.reflect.Modifier; import java.util.LinkedList; import java.util.List; import java.util.ServiceLoader; import java.util.Set; import javax.servlet.ServletContainerInitializer; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.annotation.HandlesTypes; import org.springframework.core.annotation.AnnotationAwareOrderComparator; @HandlesTypes(WebApplicationInitializer.class) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup(Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<WebApplicationInitializer>(); if (webAppInitializerClasses != null) { for (Class<?> waiClass : webAppInitializerClasses) { // Be defensive: Some servlet containers provide us with invalid classes, // no matter what @HandlesTypes says... if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class.isAssignableFrom(waiClass)) { try { initializers.add((WebApplicationInitializer) waiClass.newInstance()); } catch (Throwable ex) { throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex); } } } } if (initializers.isEmpty()) { servletContext.log("No Spring WebApplicationInitializer types detected on classpath"); return; } servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath"); AnnotationAwareOrderComparator.sort(initializers); //遍历容器中所有的WebApplicationInitializer#onStartup方法 for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
这段代码的简单的介绍一下:因为这个类上标注了@HandlesTypes(WebApplicationInitializer.class)
,所以会加载所有WebApplicationInitializer
信息都会被注入到onStartup()
方法的形参webAppInitializerClasses
上,然后遍历,判断如果不是接口【!waiClass.isInterface()】,不是抽象类【!Modifier.isAbstract(waiClass.getModifiers()】,那么就实例化,并且添加在集合当中。最后遍历集合initializers
,代用每一个对象的#onStartup(servletContext)
方法。在这个遍历上,打上一个断点,看一下initializers
中有哪些对象。
for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); }
查看结果如下:(加载的感兴趣的类信息一共有4个,因为其他三个都是抽象类,不符合实例化的条件,所有集合中也就一个类,就是我们定义的MyWebAppInitializer
)
经过上面的分析,可以大概小结一下:
因为servlet3.0容器加载规范,会加载特定位置的文件中指定的类,在这里也就是SpringServletContainerInitializer,然后加载@HandleType注解标注的感兴趣的类,然后根据条件实例化这些类,添加到集合中,遍历集合然后调用他们的onStartup方法
有了这些基础,那么就可以来看一下,他的执行调用过程,来看一下容器如何被创建的,核心控制器DispatcherServlet是如何被添加到容器中…
执行initializer.onStartup(servletContext)
,所以来到MyWebAppInitializer#onStartup
的方法,因为他本身没有重写这个方法,所以往上找他的父类AbstractAnnotationConfigDispatcherServletInitializer
,但是这里也
没有那么就接着再找父类AbstractDispatcherServletInitializer
,在这个类中onStartup
方法代码如下:
@Override public void onStartup(ServletContext servletContext) throws ServletException { super.onStartup(servletContext); registerDispatcherServlet(servletContext); }
他分为两步,先执行super.onStartup(servletContext)
,所以接着来到他的父类AbstractContextLoaderInitializer
中这个方法的实现,
@Override public void onStartup(ServletContext servletContext) throws ServletException { registerContextLoaderListener(servletContext); } protected void registerContextLoaderListener(ServletContext servletContext) { //调用#createRootApplicationContext()方法,因为自己没有实现,调用子类的方法 //执行AbstractAnnotationConfigDispatcherServletInitializer这个类的createRootApplicationContext() WebApplicationContext rootAppContext = createRootApplicationContext(); if (rootAppContext != null) { //添加容器监听事件 ContextLoaderListener listener = new ContextLoaderListener(rootAppContext); listener.setContextInitializers(getRootApplicationContextInitializers()); servletContext.addListener(listener); } else { logger.debug("No ContextLoaderListener registered, as " + "createRootApplicationContext() did not return an application context"); } }
进入AbstractAnnotationConfigDispatcherServletInitializer
@Override protected WebApplicationContext createRootApplicationContext() { //getRootConfigClasses()方法,这个类自己没有实现,调用的其实是MyWebAppInitializer#getRootConfigClasses方法 Class<?>[] configClasses = getRootConfigClasses(); if (!ObjectUtils.isEmpty(configClasses)) { //创建一个根容器 AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext(); //把我们的配置类注册进去 rootAppContext.register(configClasses); return rootAppContext; } else { return null; } }
执行到这里,super.onStartup(servletContext),方法执行完,根容器已经被创建出来了,接着调用registerDispatcherServlet(servletContext)这个方法,方法如下:
protected void registerDispatcherServlet(ServletContext servletContext) { //获得名字 默认是dispatcher String servletName = getServletName(); Assert.hasLength(servletName, "getServletName() must not return empty or null"); // 因为本类没有实现,调动子类AbstractAnnotationConfigDispatcherServletInitializer的 //createServletApplicationContext方法,创建出web的ioc容器 这个方法的代码与createRootApplicationContext()的执行过程类似 /*调用子类MyWebAppInitializer 的getServletConfigClasses方法,加载AppConfig.class 然后创建出一个容器。 */ WebApplicationContext servletAppContext = createServletApplicationContext(); Assert.notNull(servletAppContext, "createServletApplicationContext() did not return an application " + "context for servlet [" + servletName + "]"); //其实就是new DispatcherServlet(servletAppContext),创建出一个DispatcherServlet FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext); dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers()); // 将创建出来的DispatcherServlet添加到容器中 ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet); Assert.notNull(registration, "Failed to register servlet with name '" + servletName + "'." + "Check if there is another servlet registered under the same name."); //容器加载,就创建 registration.setLoadOnStartup(1); //设置拦截的mapping 回调子类的getServletMappings()方法 也就是MyWebAppInitializer#getServletMappings方法 registration.addMapping(getServletMappings()); //设置异步支持 默认是true registration.setAsyncSupported(isAsyncSupported()); //添加过滤器,可以通过重写ServletFilters方法 ,来添加过滤器 Filter[] filters = get ServletFilters(); if (!ObjectUtils.isEmpty(filters)) { for (Filter filter : filters) { registerServletFilter(servletContext, filter); } } customizeRegistration(registration); }
执行到这里,作用是创建出web的ioc容器,并且创建出DispatcherServlet,设置了他的启动机制,设置了ServletMapping,所有就不需要的web.xml配置,就能成功启动容器。
(其实根容器和web容器其实是一个父子关系),每一个容器,装载一些特定的组件。
DispatcherServlet需要一个WebApplicationContext(一个普通ApplicationContext的扩展)来进行自己的配置。
WebApplicationContext有一个指向它关联的ServletContext和Servlet的链接。
它还绑定到ServletContext,以便应用程序可以在requestcontext tutils上使用静态方法来查找需要访问的WebApplicationContext。
对于许多只有一个WebApplicationContext的应用程序来说,这是简单而充分的。
还可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。
有关上下文层次结构特性的更多信息,根WebApplicationContext通常包含基础设施bean,比如需要跨多个Servlet实例共享的数据存储库和业务服务。
这些bean是有效继承的,可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地
总结一下
画了一个uml时序图:
- web容器在启动的时候,会扫描每个jar包下的META-INF/services/javax.servlet.ServletContainerInitializer
- spring的应用一启动会加载感兴趣的WebApplicationInitializer接口的下的所有组件;
- 并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。