1、什麼是servlet
容器
servlet
我們可以把它理解成是基於java
語言的web
元件,每一個servlet
都有自己的生命週期包括init()
、service()
、destroy()
而這些都是通過servlet container
管理的。客戶端通過servlet 容器實現的 request/response paradigm(請求/應答模式)與servlet進行互動。Servlet Container 是 Web 伺服器或者應用伺服器的一部分,用於提供基於請求/響應傳送模式的網路服務,解碼基於 MIME(全稱是"Multipurpose Internet Mail Extensions",中譯為"多用途網際網路郵件擴充套件",指的是一系列的電子郵件技術規範) 的請求,並且格式化基於 MIME 的響應。Servlet 容器可以嵌入到宿主的 Web 伺服器中,或者通過 Web 伺服器的本地擴充套件 API 單獨作為附加元件安裝。Servelt 容器也可能內嵌或安裝到啟用 Web 功能的應用伺服器中。目前 Spring boot 就內嵌了 tomcat、jetty等web容器。
2、Servlet 容器初始化時 Spring Mvc 都做了什麼
在web.xml的檔案中可看到如下配置:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
複製程式碼
這個配置的意思是在servlet
容器中增加一個監聽器,當容器初始化的時候會丟擲ServletContextEvent
事件,監聽器監聽到該事件就會呼叫 ContextLoaderListener.contextInitialized(ServletContextEvent event)
方法來初始化Spring mvc rootContext
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
複製程式碼
3、Spring Mvc 容器結構
Spring Mvc
的容器是分層的,當我們的web
應用有多個servlet
的時候,一些公共的資源(bean)就可以放在root WebApplicationContext
中 比如dataSource
、service
等,在Spring Mvc
中 每一servlet
都有對應的屬於自己的一個servlet WebApplicationContext
這些Controllers
、ViewResolver
等就可以放到與之相關連的WebapplicationContext
中:
servlet
時,當然可以把所以的 bean
都交由 root WebApplicationContext
來管理,這樣Spring Mvc
就會通過代理的形式生成一個空的與之對應的容器。
4、java 配置容器
servlet 3.0
以後支援用過java
程式碼來配置容器,servlet
提供了一個介面:
public interface ServletContainerInitializer {
/**
* Receives notification during startup of a web application of the classes
* within the web application that matched the criteria defined via the
* {@link javax.servlet.annotation.HandlesTypes} annotation.
*
* @param c The (possibly null) set of classes that met the specified
* criteria
* @param ctx The ServletContext of the web application in which the
* classes were discovered
*
* @throws ServletException If an error occurs
*/
void onStartup(Set<Class<?>> c, ServletContext ctx) throws ServletException;
}
複製程式碼
servlet
容器在啟動的時候回到 classpath
下掃描這個介面的實現。spring Mvc
分裝了一層代理實現了這個介面:
@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);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
複製程式碼
在實際使用中只要實現Spring Mvc
為我們提供的介面即可;
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;
}
複製程式碼
我們可以通過這個介面來完成servlet 容器 以及 Spring Mvc 容器的初始化工作,這樣做就可以將專案中的配置檔案徹底消滅掉。
5、如何消滅配置檔案
去除 web.xml
<web-app id="WebApp_ID" version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd
http://java.sun.com/xml/ns/j2ee ">
<servlet>
<servlet-name>demo</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-servlet.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>demo</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<!-- 如果不想用預設的配置檔名,可以在這裡指定. 核心檔名規則:xxx-servlet.xml,xxx是<servlet-name>的名字 -->
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc-servlet.xml</param-value>
</context-param>
<!-- Spring ContextLoaderListener -->
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<!-- Post請求中文亂碼 -->
<filter>
<filter-name>CharacterEncoding</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CharacterEncoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
複製程式碼
通過java 程式碼可以將其改造為:
public class DemoServletinitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//初始化root WebApplicationContext
AnnotationConfigWebApplicationContext rootContext = new AnnotationConfigWebApplicationContext();
rootContext.register(DemoRootApplicationContextConfiguration.class);
servletContext.addListener(new ContextLoaderListener(rootContext));
//初始化 servlet WebApplicationContext
AnnotationConfigWebApplicationContext webApplicationContext = new AnnotationConfigWebApplicationContext();
webApplicationContext.register(DemoWebMvcConfiguration.class);
//註冊 servlet
ServletRegistration.Dynamic registration = servletContext.addServlet("demo", new DispatcherServlet(webApplicationContext));
registration.setLoadOnStartup(1);
registration.addMapping("/");
//註冊 filter
FilterRegistration.Dynamic characterEncoding = servletContext.addFilter("characterEncoding", CharacterEncodingFilter.class);
characterEncoding.setInitParameter("encoding", "UTF-8");
characterEncoding.setInitParameter("forceEncoding", "true");
}
}
複製程式碼
去除 xxx-servlet.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd">
<context:component-scan base-package="demo" />
<mvc:annotation-driven />
<!-- 啟用註解配置 -->
<context:annotation-config />
<bean class="org.springframework.web.servlet.view.ContentNegotiatingViewResolver">
<property name="defaultViews">
<list>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView">
<property name="extractValueFromSingleKeyModel" value="true" />
</bean>
</list>
</property>
</bean>
<mvc:interceptors>
<bean class="demo.DemoInterceptor"/>
</mvc:interceptors>
</beans>
複製程式碼
等價於java 配置: Root WebAppicationContext 配置
@Configuration
@ComponentScan("demo")
public class DemoRootApplicationContextConfiguration {
@Bean
public ContentNegotiatingViewResolver contentNegotiatingViewResolver() {
ContentNegotiatingViewResolver viewResolver = new ContentNegotiatingViewResolver();
viewResolver.setDefaultViews(Lists.<View>newArrayList(mappingJackson2JsonView()));
return viewResolver;
}
@Bean
public MappingJackson2JsonView mappingJackson2JsonView() {
MappingJackson2JsonView jsonView = new MappingJackson2JsonView();
jsonView.setExtractValueFromSingleKeyModel(true);
return jsonView;
}
@Bean
public DataSource dataSource() {
// add data source config
return null;
}
}
複製程式碼
servlet WebApplicationContext 配置
@ComponentScan("demo")
@Configuration
@EnableWebMvc
public class DemoWebMvcConfiguration extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new DemoInterceptor());
}
@Bean
public InternalResourceViewResolver internalResourceViewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
複製程式碼
這樣就可以將web.xml、xxx-servlet去除掉了,再也不用看到煩人的配置檔案了。
當然 spring Mvc 還提供了一些抽象類來簡化配置工作,這裡為了更方便的解釋java 配置的過程所以沒有直接使用。對這一部分有興趣的同學可以自己檢視 AbstractAnnotationConfigDispatcherServletInitializer
原始碼。