原理簡析
1. 背景知識:org.springframework.web.ServletContainerInitializer接口
在基于注解的servlet開發中,ServletContainerInitializer接口用于代替web.xml。它隻有一個方法:onStartup,可以在其中注冊servlet、攔截器(Filter)、監聽器(Listener)這三大元件。另外,ServletContainerInitializer還可以使用@HandlesTypes在onStartup方法的參數清單中注入感興趣的類。servlet容器啟動時,會掃描每個jar包的項目根目錄下的/META-INF/services/javax.servlet.ServletContainerInitializer檔案,執行這個檔案中指定的ServletContainerInitializer接口的實作類的onStartup方法。
2. org.springframework.web包提供的ServletContainerInitializer實作類
org.springframework.web包的/META-INF/services/javax.servlet.ServletContainerInitializer檔案指定了ServletContainerInitializer接口的實作類:SpringServletContainerInitializer,首先來看一下這個類的spring源碼:
package org.springframework.web;
@HandlesTypes(WebApplicationInitializer.class) //(1)
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()); //(2)
}
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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext); //(3)
}
}
}
(1) 通過@HandlesType注解在onStartup方法的參數清單中注入感興趣的類,即WebApplicationInitializer;
(2) 将WebApplicationInitializer的每個實作類,都建立一個執行個體,并放入initializers清單中;
(3) 周遊initializers清單,對每個WebApplicationInitializer執行個體執行其onStartup方法。
那麼問題來了:WebApplicationInitializer有哪些實作類,是用來幹什麼的?
3. WebApplicationInitializer的實作類及其功能
WebApplicationInitializer的實作類有很多,重點看一下AbstractAnnotationConfigDispatcherServletInitializer
package org.springframework.web.servlet.support;
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
return null;
}
}
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
context.register(configClasses);
}
return context;
}
@Nullable
protected abstract Class<?>[] getRootConfigClasses();
@Nullable
protected abstract Class<?>[] getServletConfigClasses();
}
這個類提供了兩個方法的實作,以及兩個抽象方法供子類繼承
(1) createRootApplicationContext:建立根容器;
(2) createServletApplicationContext:建立servlet容器;
(3) getRootConfigClasses:抽象類,用于注冊根容器的配置類,相當于spring.xml;
(4) getServletConfigClasses:抽象的類,用于注冊servlet容器的配置類,相當于springmvc.xml;
另外,它還從AbstractDispatcherServletInitializer類繼承了getServletMappings方法,用于注冊servlet的映射。
是以,我們可以自定義一個WebApplicationInitializer的實作類,繼承AbstractAnnotationConfigDispatcherServletInitializer;在servlet容器啟動時,會建立spring根容器和servlet容器,代替web.xml配置檔案。同時,我們可以看到,在基于注解的springmvc開發中,真正用于代替web.xml的是WebApplicationInitializer,而并不是ServletContainerInitializer,這與本文開頭提到的基于注解的servlet開發有些差別。
4. 根容器和servlet容器
根容器用于管理@Service、@Repository等業務邏輯層和資料庫互動層元件;
servlet容器用于管理@Controller、視圖解析器、攔截器等跟頁面處理有關的元件。
使用步驟
0. 導包或添加依賴:spring-web、spring-webmvc
1. 編寫資料庫通路層、業務邏輯層、控制層等元件,這個跟基于配置檔案的springmvc沒有差別;
2. 編寫根容器和servlet容器的配置類,這裡不需要添加@Configuration注解;
3. 自定義WebApplicationInitializer,繼承AbstractAnnotationConfigDispatcherServletInitializer;
4. 在3的實作類中注冊根容器和servlet容器的配置類,以及servlet映射;
5. 在servlet容器中中注冊視圖解析器、攔截器等元件,用法是使servlet容器配置類實作WebMvcConfigurer接口,
然後選擇相應的方法進行注冊,詳見示例demo。
示例Demo
pom檔案
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.1.6-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.1.6-SNAPSHOT</version>
</dependency>
業務邏輯層元件
package cn.monolog.annabelle.springmvc.service;
import org.springframework.stereotype.Service;
/**
* 業務邏輯層元件
* created on 2019-05-04
*/
@Service
public class BusinessService {
public String resolve(String source) {
return "hello " + source;
}
}
控制層元件
package cn.monolog.annabelle.springmvc.controller;
import cn.monolog.annabelle.springmvc.service.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 控制層元件
* created on 2019-05-04
*/
@Controller
@RequestMapping(value = "/business")
public class BusinessController {
//從容器中自動裝配元件
@Autowired
private BusinessService businessService;
@GetMapping(value = "/resolve")
@ResponseBody
public String resolve(String source) {
String result = this.businessService.resolve(source);
return result;
}
}
自定義springmvc攔截器
package cn.monolog.annabelle.springmvc.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* 自定義的springmvc攔截器
* created on 2019-05-12
*/
public class CustomedInterceptor implements HandlerInterceptor {
/**
* 重寫preHandle方法
* @param request
* @param response
* @param handler
* @return
* @throws Exception
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle正在執行...");
return true;
}
/**
* 重寫postHandle方法
* @param request
* @param response
* @param handler
* @param modelAndView
* @throws Exception
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle正在執行...");
}
/**
* 重寫afterCompletion方法
* @param request
* @param response
* @param handler
* @param ex
* @throws Exception
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion正在執行...");
}
}
根容器的配置類,用于管理資料庫通路層、業務邏輯層等元件,相當于spring.xml
package cn.monolog.annabelle.springmvc.config;
import org.springframework.context.annotation.ComponentScan;
/**
* 根容器配置類
* created on 2019-05-04
*/
@Configuration
@ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.service"})
public class RootConfig {
}
servlet容器的配置類,用于管理控制層、視圖解析器等元件,相當于springmvc.xml
可以在其中配置視圖解析器、靜态資源解析器、攔截器等元件
package cn.monolog.annabelle.springmvc.config;
import cn.monolog.annabelle.springmvc.interceptor.CustomedInterceptor;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.*;
/**
* servlet容器配置類
* @EnableWebMvc相當于配置檔案中的<mvc:annotation-drivern />
* created on 2019-05-04
*/
@Configuration
@ComponentScan(basePackages = {"cn.monolog.annabelle.springmvc.controller"})
@EnableWebMvc
public class ServletConfig implements WebMvcConfigurer {
/**
* 注冊視圖解析器
* @param registry
*/
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/views/", ".jsp");
}
/**
* 注冊靜态資源解析器
* 将springmvc處理不了的請求交給tomcat
* @param configurer
*/
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
/**
* 注冊攔截器
* @param registry
*/
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new CustomedInterceptor());
}
}
自定義的WebApplicationInitializer,用于注冊根容器、servlet容器、servlet映射、攔截器、監聽器等,相當于web.xml
package cn.monolog.annabelle.springmvc.initializer;
import cn.monolog.annabelle.springmvc.config.RootConfig;
import cn.monolog.annabelle.springmvc.config.ServletConfig;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
/**
* 自定義web容器啟動器
* created on 2019-05-04
*/
public class CustomerServletInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
/**
* 注冊根容器配置類
* @return
*/
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class[]{RootConfig.class};
}
/**
* 注冊servlet容器配置類
* @return
*/
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class[]{ServletConfig.class};
}
/**
* 注冊servlet的映射
* @return
*/
@Override
protected String[] getServletMappings() {
return new String[]{"/"};
}
}
測試代碼
<html>
<head>
<title>homepage</title>
<style type="text/css">
a {
color: blueviolet;
font-size: 20px;
}
</style>
</head>
<body>
<a href="/annabelle/business/resolve?source=violet">start...</a>
</body>
</html>