SpringMVC的JavaConfig配置分析

无配置文件的Web项目基础知识

Servlet 3.0 规范和 Spring DispatcherServlet 配置

在Servlet 3.0 的环境中,容器会在 classpath 中寻找继承了 javax.servlet.ServletContainerInitializer接口的类,用它来配置 servlet 容器。

Spring 提供了一个继承这个接口的类 SpringServletContainerInitializer,在这个类中,它会寻找(其实是使用了注解@HandlesTypes(WebApplicationInitializer.class)
由容器负责寻找在调用onStartup方法时作为参数传入)任何继承了 WebApplicationInitializer接口的类并
用其来配置 servlet 容器。
Spring 3.2 提供了一个继承了 WebApplicationInitializer接口的基类 AbstractAnnotationConfigDispatcherServletInitializer
所以,你的 servlet 配置类只需要继承 AbstractAnnotationConfigDispatcherServletInitializer,就会被发现而用于 servlet 容器的配置。

Spring MVC的Java配置

我们先来看一下AbstractAnnotationConfigDispatcherServletInitializer的继承关系

public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer 
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer 
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer 

继承AbstractAnnotationConfigDispatcherServletInitializer需要实现三个方法:

package web.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer  {
	
	/**获取Spring应用容器的配置文件*/
	@Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] {RootConfig.class};
    }
	
	/**负责获取Spring MVC应用容器,这里传入预先定义好的MyWebConfig.class*/
	@Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] {WebConfig.class};
    }
	
	/**
	* 负责指定需要由DispatcherServlet映射的路径,
	* 这里给定的是"/",意思是由DispatcherServlet处理所有向该应用发起的请求
	* */
	@Override
    protected String[] getServletMappings() {
        return new String[] {"/"};
    }
}
	

在容器启动时因MyWebAppInitializer最终实现了接口WebApplicationInitializer,因此会被发现作为参数在执行SpringServletContainerInitializeronStartup方法时传入。由SpringServletContainerInitializer调用WebApplicationInitializeronStartup方法进行一系列的初始化。MyWebAppInitializer除了需要实现的抽象方法外从父类继承的变量与方法如下:

这里写图片描述

AbstractAnnotationConfigDispatcherServletInitializer在初初始化过程实际就是原web.xml中创建ContextLoaderListenerDispatcherServlet的过程。

<!-- 利用Spring提供的ContextLoaderListener监听器去监听ServletContext对象的创建,并初始化WebApplicationContext对象 -->
<listener>
	<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
	
<!-- 配置Spring MVC的前端控制器:DispatchcerServlet -->
<servlet>
	<servlet-name>dispatchcerServlet</servlet-name>
	<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
	<init-param>
	      <param-name>contextConfigLocation</param-name>
	      <param-value>classpath:spring-mvc.xml</param-value>
	</init-param>
	<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
	  <servlet-name>dispatchcerServlet</servlet-name>
	  <url-pattern>/</url-pattern>
</servlet-mapping>

在初始化过程中会先创建ContextLoaderListener,并将其注册到ServletContext中(servletContext.addListener(listener)),代码如下:

public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
	
    /**
      * Register a {@link ContextLoaderListener} against the given servlet context. The
      * {@code ContextLoaderListener} is initialized with the application context returned
      * from the {@link #createRootApplicationContext()} template method.
      * @param servletContext the servlet context to register the listener against
      */
     protected void registerContextLoaderListener(ServletContext servletContext) {
             //在createRootApplicationContext方法中会调用getRootConfigClasses方法获取配置类
             WebApplicationContext rootAppContext = createRootApplicationContext();
             if (rootAppContext != null) {
                     ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
                     listener.setContextInitializers(getRootApplicationContextInitializers());
                     servletContext.addListener(listener);
             }
             else {
                     logger.debug("No ContextLoaderListener registered, as " +
                             "createRootApplicationContext() did not return an application context");
             }
	     }
	
	}
	

然后再创建DispatcherServlet,并将其注册到ServletContext中(servletContext.addServlet(servletName, dispatcherServlet)),代码如下:

public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
	/**
	 * Register a {@link DispatcherServlet} against the given servlet context.
	 * <p>This method will create a {@code DispatcherServlet} with the name returned by
	 * {@link #getServletName()}, initializing it with the application context returned
	 * from {@link #createServletApplicationContext()}, and mapping it to the patterns
	 * returned from {@link #getServletMappings()}.
	 * <p>Further customization can be achieved by overriding {@link
	 * #customizeRegistration(ServletRegistration.Dynamic)} or
	 * {@link #createDispatcherServlet(WebApplicationContext)}.
	 * @param servletContext the context to register the servlet against
	 */
	protected void registerDispatcherServlet(ServletContext servletContext) {
	        String servletName = getServletName();
	        Assert.hasLength(servletName, "getServletName() must not return null or empty");
	        
	        //在createServletApplicationContext方法中会调用getServletConfigClasses方法获取配置类
	        WebApplicationContext servletAppContext = createServletApplicationContext();
	        Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
	        /**在此处创建DispatcherServlet对象**/
	        FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
	        Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
	        dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
	
	        ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
	        if (registration == null) {
	        throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
	                "Check if there is another servlet registered under the same name.");
	        }
	
	        registration.setLoadOnStartup(1);
	        //在此处调用getServletMappings方法获取配置的映射路径
	        registration.addMapping(getServletMappings());
	        registration.setAsyncSupported(isAsyncSupported());
	        
	        Filter[] filters = getServletFilters();
	        if (!ObjectUtils.isEmpty(filters)) {
		        for (Filter filter : filters) {
		                registerServletFilter(servletContext, filter);
		        }
	        }
	
	        customizeRegistration(registration);
	 }
}
	

我们在实现getRootConfigClasses()方法所返回的RootConfig.class与实现getServletConfigClasses()所返回的WebConfig.class,可以被Spring直接获取当做配置文件来解析,因此这两个类上不必再放置注解@Configuration,同时WebConfig.class会被用来创建DispatcherServlet,因此也不必放置注解@EnableWebMvc,按照约定Spring会知道如何使用这两个配置文件。

SpringMVC使用Java配置实现路径扫描的XML等效配置

在配置spring与springmvc配置文件的时候,一般在配置扫描路径时,我们会配置springmvc仅扫描与Controller有关的类,其他的类由spring去扫描。使用标签<context:component-scan>分别进行配置,一个典型配置样例如下:

spring-mvc.xml:

<!-- 启用注解扫描,并定义组件查找规则 ,mvc层只负责扫描@Controller、@ControllerAdvice -->
<!-- base-package 如果多个,用“,”分隔 -->
<context:component-scan base-package="com.modular" use-default-filters="false">
        <!-- 扫描@Controller -->
        <context:include-filter type="annotation"
                expression="org.springframework.stereotype.Controller" />
        <!-- 扫描@ControllerAdvice  控制器增强,使一个Contoller成为全局的异常处理类,类中用@ExceptionHandler方法注解的方法可以处理所有Controller发生的异常 -->
        <context:include-filter type="annotation"
                expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>

spring-config.xml:

<!-- Spring负责扫描除mvc层负责扫描的@Controller、@ControllerAdvice之外的全部注解 -->
<!-- base-package 如果多个,用“,”分隔 -->
<context:component-scan base-package="com.core,com.modular">
        <context:exclude-filter type="annotation"
                expression="org.springframework.stereotype.Controller" />
        <context:exclude-filter type="annotation"
                expression="org.springframework.web.bind.annotation.ControllerAdvice" />
</context:component-scan>
	

如果我们要在配置类中实现相同的配置可以这样写:

/** 配置扫描路径 **/
@ComponentScan(basePackageClasses = { ComponentScanPackageFlag_Core.class, ComponentScanPackageFlag_Modular.class }, 
        excludeFilters = { @Filter(type = FilterType.ANNOTATION, value = Controller.class), @Filter(type = FilterType.ANNOTATION, value = ControllerAdvice.class) })
public class RootConfig{
}
	
/** 配置扫描路径 **/
@ComponentScan(useDefaultFilters = false,basePackageClasses = { ComponentScanPackageFlag_Modular.class },
        includeFilters = { @Filter(type = FilterType.ANNOTATION, value = Controller.class), @Filter(type = FilterType.ANNOTATION, value = ControllerAdvice.class) } )
public class WebConfig{
}

注意:

  1. 在WebConfig上加与不加注解@Configuration,预期结果是有不同的。如果加上@Configuration,Spring会将WebConfig也作为配置文件,因在WebConfig上配置了@ComponentScan,因此,Spring也会按照这个注解的扫描路径进行扫描。其结果就是本来不想让Spring容器去扫描Controller相关的类结果会因此扫描到。
  2. WebConfig类使用注解@EnableWebMvc来从WebMvcConfigurationSupport中导入Spring MVC配置,如果需要自定义SpringMVC的一些配置,有两种方式:
    方式一:使用@EnableWebMvc注解,WebConfig实现WebMvcConfigurer 接口,然后复写相应的方法。
    方式二:不使用@EnableWebMvc注解,WebConfig继承 WebMvcConfigurationSupport 或者 DelegatingWebMvcConfiguration,然后复写相应的方法。
    详细内容,参照官方文档:
    https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/config/annotation/EnableWebMvc.html

版权声明:本文为zyx322原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。