一、Spring整合SpringMVC
Spring利用父子容器整合SpringMVC。(父容器:Spring,子容器:mvc)。
父容器管理Service、Dao层实例对象;子容器管理Controller实例对象。
子容器可以访问父容器Bean。反之不可。
1.利用web.xml整合
本质:Web容器解析web.xml。启动Spring容器,加载DispatcherServlet。
重点:注册ContextLoaderListener、DispatcherServlet
<!--spring 基于web应用的启动-->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!--全局参数:spring配置文件-->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-core.xml</param-value>
</context-param>
<!--前端调度器servlet-->
<servlet>
<servlet-name>dispatcherServlet</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>
<!--dispatcherServlet管理的请求路径-->
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
<servlet-mapping>
2.零配置整合
本质:把配置利用代码实现,并且利用SPI机制机制,在WEB容器启动时进行加载。
核心类:SpringServletContainerInitializer,重点完成:注册ContextLoaderListener、DispatcherServlet
执行流程
- Tomcat在启动时会通过SPI注册 ContextLoaderListener和DispatcherServlet对象
- 同时创建父子容器
- 分别创建在ContextLoaderListener初始化时创建父容器设置配置类
- 在DispatcherServlet初始化时创建子容器 即2个ApplicationContext实例设置配置类
- 同时创建父子容器
- Tomcat在启动时执行ContextLoaderListener和DispatcherServlet对象的初始化方法, 执行容器refresh进行加载
- 在子容器加载时 创建SpringMVC所需的Bean和预准备的数据:(通过配置类+@EnableWebMvc配置(DelegatingWebMvcConfiguration)——可实现WebMvcConfigurer进行定制扩展)
- RequestMappingHandlerMapping,它会处理@RequestMapping 注解
- RequestMappingHandlerAdapter,则是处理请求的适配器,确定调用哪个类的哪个方法,并且构造方法参数,返回值。
- HandlerExceptionResolver 错误视图解析器
- addDefaultHttpMessageConverters 添加默认的消息转换器(解析json、解析xml)
- 子容器需要注入父容器的Bean时(比如Controller中需要@Autowired Service的Bean); 会先从子容器中找,没找到会去父容器中找
Web容器利用SPI机制,调用SpringServletContainerInitializer。取代在xml中配置的操作。具体流程
- tomcat启动时,利用SPI机制的ServiceLoader#load加载接口(ServletContainerInitializer)注册的所有实现类。
- SpringMVC在实现类SpringServletContainerInitializer上标注@HandlesTypes({WebApplicationInitializer.class})。
- tomcat会从classpath下找到所有的WebApplicationInitializer实现类,将所有的WebApplicationInitializer实现类作为第一个参数传入SpringServletContainerInitializer#onStartup方法,调用方法。
- 在SpringServletContainerInitializer#onStartup方法中,调用所有的WebApplicationInitializer实现类的onStartup方法。
@HandlesTypes注解由Servlet容器提供支持(实现),参数中指定的所有实现类。WEB容器(例如tomcat)利用字节码扫描框架(例如ASM、BCEL)从classpath中扫描出来,放入集合,传给回调方法onStartup的第一个参数。
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
/**
* 容器启动时,调用接口方法。并将@HandlesTypes标记的类型,传入方法。
*
* 调用onStartup前,web容器会查找到@HandlesTypes标记的类
* @param webAppInitializerClasses
* @param servletContext web容器(例如tomcat)传入。可以通过servlet上下文对象动态注册三大组件
* @throws ServletException
*/
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
// 1.tomcat通过SPI机制,调用到此方法中
// 2.tomcat启动后 ,传入实现WebApplicationInitializer接口的类
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
//排除接口和抽象类。(Springmvc中实现WebApplicationInitializer接口的都是抽象类)
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
//进行反射利用实例化后,添加到集合中。为调用做准备
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(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);
}
}
AbstractDispatcherServletInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//以下步骤,可以在web.xml配置实现
//注册ContextLoaderListener(创建父容器)
super.onStartup(servletContext);
//注册DispatcherServlet(创建子容器)
registerDispatcherServlet(servletContext);
}
AbstractContextLoaderInitializer
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
//创建父容器。方法实现类:AbstractAnnotationConfigDispatcherServletInitializer
// 1.调用getRootConfigClasses();方法获取父容器配置类(此抽象方法在我们自定义的子类中实现提供我们自定义的映射路径 )
// 2.创建父容器,注册配置类
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//将监听器加入上下文中。监听mvc启动完成。完成后开始初始化DispatchSevlet
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
protected void registerDispatcherServlet(ServletContext servletContext) {
//获取DispatchServlet名称(默认:dispatcher)
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//创建子容器。实现类:AbstractAnnotationConfigDispatcherServletInitializer
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//实例化DispatcherServlet。tomcat会管理DispatchServlet生命周期
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
//初始化器
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//注册dispatcherServlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("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);
}
初始化ContextLoaderListener
tomcat会调用ContextLoaderListener#contextInitialized 进行初始化
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException(
"Cannot initialize context because there is already a root application context present - " +
"check whether you have multiple ContextLoader* definitions in your web.xml!");
}
servletContext.log("Initializing Spring root WebApplicationContext");
Log logger = LogFactory.getLog(ContextLoader.class);
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
long startTime = System.currentTimeMillis();
try {
// Store context in local instance variable, to guarantee that
// it is available on ServletContext shutdown.
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
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 ->
// determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
//配置和刷线根容器对象。调用Spring容器refresh方法
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//将Spring上下文,保存到web应用上下文中
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
}
else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
}
return this.context;
}
catch (RuntimeException | Error ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
}
初始化DispatcherServlet
tomcat会调用DispatcherServlet#init()
1.注册监听
2.刷新子容器