Spring MVC 源码解析(二)— 容器初始化

一、什么是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容器。java

二、Servlet 容器初始化时 Spring Mvc 都作了什么

在web.xml的文件中可看到以下配置:web

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener> 
复制代码

这个配置的意思是在servlet 容器中增长一个监听器,当容器初始化的时候会抛出ServletContextEvent事件,监听器监听到该事件就会调用 ContextLoaderListener.contextInitialized(ServletContextEvent event)方法来初始化Spring mvc rootContextspring

/**
	 * Initialize the root web application context.
	 */
	@Override
	public void contextInitialized(ServletContextEvent event) {
		initWebApplicationContext(event.getServletContext());
	}
复制代码

三、Spring Mvc 容器结构

Spring Mvc 的容器是分层的,当咱们的web应用有多个servlet的时候,一些公共的资源(bean)就能够放在root WebApplicationContext 中 好比dataSourceservice等,在Spring Mvc中 每一servlet 都有对应的属于本身的一个servlet WebApplicationContext 这些ControllersViewResolver等就能够放到与之相关连的WebapplicationContext 中:json

当咱们只有一个 servlet 时,固然能够把因此的 bean 都交由 root WebApplicationContext 来管理,这样 Spring Mvc 就会经过代理的形式生成一个空的与之对应的容器。

四、java 配置容器

servlet 3.0 之后支持用过java 代码来配置容器,servlet提供了一个接口:spring-mvc

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 分装了一层代理实现了这个接口:tomcat

@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 为咱们提供的接口便可;bash

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 容器的初始化工做,这样作就能够将项目中的配置文件完全消灭掉。服务器

五、如何消灭配置文件

去除 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 配置mvc

@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源码。

相关文章
相关标签/搜索