无配置文件的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,因此会被发现作为参数在执行SpringServletContainerInitializer的onStartup方法时传入。由SpringServletContainerInitializer调用WebApplicationInitializer的onStartup方法进行一系列的初始化。MyWebAppInitializer除了需要实现的抽象方法外从父类继承的变量与方法如下:
AbstractAnnotationConfigDispatcherServletInitializer在初初始化过程实际就是原web.xml中创建ContextLoaderListener与DispatcherServlet的过程。
<!-- 利用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{
}
注意:
- 在WebConfig上加与不加注解@Configuration,预期结果是有不同的。如果加上@Configuration,Spring会将WebConfig也作为配置文件,因在WebConfig上配置了@ComponentScan,因此,Spring也会按照这个注解的扫描路径进行扫描。其结果就是本来不想让Spring容器去扫描Controller相关的类结果会因此扫描到。
- 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