原理簡析
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>
