SpringMVC原理剖析——在代码中配置DispatcherServlet

一、如何在代码中配置DispatcherServlet以及其中的原理

DispatcherServlet是SpringMVC的核心,担任着请求分发的责任。

在SpringMVC中配置DispatcherServlet有两种方式:

1. 在Servlet容器的web.xml文件中配置;

2. 用Java代码将DispatcherServlet配置到Servlet容器中

文本主要讲解一下第二种配置方法

首先直接看在代码中如何配置DispatcherServlet

package com.windcloud.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class FyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class[] getRootConfigClasses() {
        return new Class[] { RootConfig.class };
    }

    //指定配置类
    @Override
    protected Class[] getServletConfigClasses() {
        return new Class[] { WebConfig.class };
    }

    //将DispatcherServlet映射到“/”
    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }
}

通过扩展AbstractAnnotationConfigDispatcherServletInitializer抽象类,可以将DispatcherServletSpring应用上下文配置到Servlet容器中。其原理如下:

Servlet3.0的环境中,即如果Servlet容器遵循的是Servlet3.0接口,那么Servlet容器会在类路径中寻找实现javax.servlet.ServletContainerInitializer接口的类,然后用它来配置Servlet容器。

幸运的是Spring已经为我们提供了这个接口的实现类,名为SpringServletContainerInitializer,在这个类中又会查找WebApplicationInitializer接口的实现类,然后将配置的任务交给这个接口的实现类来完成。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

    @Override
    public void onStartup(@Nullable Set> webAppInitializerClasses, ServletContext servletContext) throws ServletException {
    
        List initializers = new LinkedList<>();

                            ......//查找WebApplicationInitializer的实现类,然后放入initializers中

        AnnotationAwareOrderComparator.sort(initializers);
	for (WebApplicationInitializer initializer : initializers) {
	    initializer.onStartup(servletContext);
	}
    }
}

从上面的源码中可以看到,在WebApplicationInitializer接口的onStartup方法中实现在servletContext中加载Servlet.


Spring3.2中引入了WebApplicationInitializer接口的基础实现类(这是一个抽象类),就是AbstractAnnotationConfigDispatcherServletInitializer.但是AbstractAnnotationConfigDispatcherServletInitializer并不是直接实现WebApplicationInitializer接口的,它的实现路径如下:

WebApplicationInitializer ---> AbstractContextLoaderInitializer ---> AbstractDispatcherServletInitializer ---> AbstractAnnotationConfigDispatcherServletInitializer

AbstractAnnotationConfigDispatcherServletInitializer中,给我们留下了三个抽象方法要求我们去实现:

  • protected abstract String[] getServletMappings();    这个方法在AbstractDispatcherServletInitializer 中,这个方法可以将一个或多个路径映射到DispatcherServlet上,如果路径设置为“/”,则所有的请求都会由DispatcherServlet处理。
  • protected abstract Class[] getRootConfigClasses();    这两个方法在AbstractAnnotationConfigDispatcherServletInitializer
  • protected abstract Class[] getServletConfigClasses();


所以我们需要写一个继承AbstractAnnotationConfigDispatcherServletInitializer的配置类,如开头的FyWebAppInitializer所示。

下面着重解释一下getRootConfigClasses()和getServletConfigClasses()方法。

在解释这两个方法之前,我们需要知道Spring应用上下文的一些基础知识。

Spring中通常由两个应用上下文:

  • 一个是DispatcherServlet启动的时候创建的上下文(上下文一)

在创建这个上下文的时候会加载配置文件或配置类中声明的bean,getServletConfigClasses返回的配置类(如案例中的WebConfig类)中配置的bean会在这个时候被加载到该上下文中

  • 另一个上下文是由ContextLoaderListener创建的(上下文二)

方法getRootConfigClasses返回的配置类(如案例中的RootConfig类)中配置的bean会被加载到这个上下文中

上面两个上下文的区别是:上下文一中加载的通常是Web组件的bean,如控制器、视图解析器、处理器映射器等。而上下文二中加载的往往是一些中间层和数据层的组件。

其实,在AbstractAnnotationConfigDispatcherServletInitializer中会同时创建DispatcherServletContextLoaderListener.

下面分别给出示例中WebConfigRootConfig的实现

WebConfig 实现如下:

package com.windcloud.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc       //启用SpringMVC
@ComponentScan("com.windcloud.config")   //启用组件扫描
public class WebConfig extends WebMvcConfigurerAdapter {

    //配置JSP视图解析器
    @Bean
    public ViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views");
        resolver.setSuffix(".jsp");
        resolver.setExposeContextBeansAsAttributes(true);
        return resolver;
    }

    //配置静态资源的处理
    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

RootConfig 实现如下:

package com.windcloud.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;

@Configuration
@ComponentScan(basePackages = { "com.windcloud.controller" },
        excludeFilters = {@ComponentScan.Filter(type = FilterType.ANNOTATION, value = EnableWebMvc.class)})
public class RootConfig {

}

以上就是关于DispatcherServlet在代码中的配置过程。

二、总结

这是一个完全通过Java代码将DispatcherServlet和ContextLoaderListener配置到Servlet容器中的方法。

1. 上述的在代码中配置DispatcherServlet只能在支持Servlet3.0及以上版本的服务器中才可以使用,如Tomcat7及以上,如果版本低于Servlet3.0,那只能在web.xml中配置了

2. 配置DispatcherServlet有三个步骤:

  • 编写继承了AbstractAnnotationConfigDispatcherServletInitializer类的初始化类,并实现其中的三个抽象方法
  • 编写一个配置类(如WebConfig),在该类中配置视图解析器等组件
  • 再编写一个配置类(如RootConfig),在该类中配置扫描路径,通过componentScann可以将service层和dao层组件配置到Spring的上下文中(由ContextLoaderListener创建的上下文)

三、拓展配置

上述通过继承Spring已经为我们设计好的抽象类AbstractAnnotationConfigDispatcherServletInitializer类,我们只需要实现其中的三个抽象方法即可。其实在AbstractAnnotationConfigDispatcherServletInitializer类中,Spring已经将DispatcherServletContextLoaderListener加载到了Servlet容器中了。但是如果我们想自己再添加其它的ServletFilterServlet容器中,或者想对DispatcherServlet增加一些初始化配置时该怎么办呢?

下面分别介绍一下:

对DispatcherServlet进行初始化设置

在上文中,我们只对AbstractAnnotationConfigDispatcherServletInitializer类中的三个抽象方法进行了实现,如果想进一步设置DispatcherServlet,可以对AbstractAnnotationConfigDispatcherServletInitializer类中的其它方法进行重写。

例如,其中一个方法就是customizeRegistration(ServletRegistration.Dynamic registration),在AbstractAnnotationConfigDispatcherServletInitializer类将DispatcherServlet注册到Servlet容器中后,会调用customizeRegistration(ServletRegistration.Dynamic registration)方法,假设我们想让DispatcherServlet支持multipart请求,那么可以通过重写customizeRegistration方法来实现。

@Override
protected void customizeRegistration(ServletRegistration.Dynamic registration) {
    //将上传的文件临时存放到/tmp/fy/uploads目录中
    registration.setMultipartConfig(new MultipartConfigElement("/tmp/fy/uploads"));
}

除了上面的配置支持multipart请求,还可以通过registration.setInitParameters(Map var1)来设置DispatcherServlet的初始化参数等。

添加其它的Servlet和Filter

通过学习上面DispatcherServlet的配置,我们可以想到如果想自己配置ServletServlet容器中,我们需要自己写一个WebApplicationInitializer接口的实现类即可(称为spring的初始化器),即自己创建一个初始化器。

如下就是一个将自定义的Servlet加载到Servlet容器中的初始化器:

public class MyServletInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        Dynamic myServlet = servletContext.addServlet("myServlet", MyServlet.class);
	myServlet.addMapping("custom/**");
    }
}

如果需要将自定义的FilterListener加载到Servlet容器中的初始化器与Servlet类似:

public class MyFilterInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        javax.servlet.FilterRegistration.Dynamic filter = servletContext.addFilter("myFilter", MyFilter.class);
	filter.addMappingForUrlPatterns(null, false, "custom/*");
    }
}


你可能感兴趣的:(Spring原理解析)