spring boot 是基于spring 4 的基础上的一个框架,spring 4 有一个新特效–>基于java config 实现零配置.而在企业的实际工作中,spring 都是和spring mvc 紧密结合的,这篇文章从以下4个方面进行阐述:
项目结构图如下:
这里需要设定project Facets 中的 web 版本为 3.0 以上.如图:
这是因为spring mvc 零配置 是基于servlet 3.0 规范的.
在Servlet3.0规范,支持将web.xml相关配置也硬编码到代码中[servlet,filter,listener,等等],并由javax.servlet.ServletContainerInitializer的实现类负责在容器启动时进行加载.
spring提供了一个实现类org.springframework.web.SpringServletContainerInitializer,
该类会调用所有org.springframework.web.WebApplicationInitializer的实现类的onStartup(ServletContext servletContext)方法,将相关的组件注册到服务器.
spring同时提供了一些WebApplicationInitializer的实现类供我们继承,以简化相关的配置,比如:org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer : 注册spring DispatcherServlet.
因此我们只需继承AbstractAnnotationConfigDispatcherServletInitializer 即可.
代码如下:
package com.demo.config;
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer{
// 指定spring application 的配置
@Override
protected Class>[] getRootConfigClasses() {
return new Class>[] { RootConfig.class };
}
// 指定spring web 配置
@Override
protected Class>[] getServletConfigClasses() {
return new Class>[] { WebConfig.class };
}
// 配置dispatcherServlet的映射路径
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
}
RootConfig,用来指定扫描除去@Controller注解的类, 代码如下,
@Configuration
@ComponentScan(basePackages = { "com.demo" },
excludeFilters={@Filter(type=FilterType.ANNOTATION,classes=Controller.class)})
public class RootConfig {
}
其中,@Configuration 声明该类是一个配置类,@ComponentScan 声明了扫描包的范围,可配置多个, excludeFilters 用来去除扫描@Controller注解的类.
WebConfig用来配置Spring mvc 相关的配置,这里我们继承WebMvcConfigurerAdapter,用来简化配置.代码如下:
package com.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.stereotype.Controller;
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
@ComponentScan(basePackages = "com.demo.controller", useDefaultFilters = false, includeFilters = {
@ComponentScan.Filter(type = FilterType.ANNOTATION, value = { Controller.class }) })
public class WebConfig extends WebMvcConfigurerAdapter {
@Bean
public ViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("/WEB-INF/views/jsp/function/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
其中@Configuration 声明该类是一个配置类,@EnableWebMvc 用来导入WebMvcConfigurationSupport中对于spring mvc的配置.@ComponentScan 用来指定扫描com.demo.controller包下被@Controller注解的类.
此外,还声明了对于jsp的ViewResolver,和覆盖了configureDefaultServletHandling来实例化DefaultServletHttpRequestHandler,该配置相当于在xml中配置的
default-servlet-handler />
DefaultServletHttpRequestHandler的作用是它会像一个检查员,对进入DispatcherServlet的URL进行筛查,如果发现是静态资源的请求,就将该请求转由Web应用服务器默认的Servlet处理,如果不是静态资源的请求,才由DispatcherServlet继续处理
package com.demo.service;
public interface TestService {
String sayHello();
}
实现类:
package com.demo.service;
import org.springframework.stereotype.Service;
@Service
public class TestServiceImpl implements TestService {
@Override
public String sayHello() {
return "hi ,spring 4 ";
}
}
package com.demo.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.demo.service.TestService;
@Controller
public class TestController {
@Autowired
private TestService testService;
@RequestMapping(value = "/test", method = RequestMethod.GET)
@ResponseBody
public String test() {
return testService.sayHello();
}
}
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0modelVersion>
<groupId>com.jihegupiao.demogroupId>
<artifactId>spring4artifactId>
<version>0.0.1-SNAPSHOTversion>
<packaging>warpackaging>
<properties>
<maven-compiler-plugin.version>3.1maven-compiler-plugin.version>
<project.compiler.version>1.8project.compiler.version>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>javax.servletgroupId>
<artifactId>javax.servlet-apiartifactId>
<version>3.0.1version>
<scope>providedscope>
dependency>
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-webmvcartifactId>
<version>4.2.8.RELEASEversion>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjrtartifactId>
<version>1.7.3version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.7.3version>
dependency>
dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-war-pluginartifactId>
<version>2.6version>
<configuration>
<failOnMissingWebXml>falsefailOnMissingWebXml>
configuration>
plugin>
<plugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-compiler-pluginartifactId>
<version>${maven-compiler-plugin.version}version>
<configuration>
<source>${project.compiler.version}source>
<target>${project.compiler.version}target>
<encoding>${project.build.sourceEncoding}encoding>
configuration>
plugin>
<plugin>
<groupId>org.apache.tomcat.mavengroupId>
<artifactId>tomcat7-maven-pluginartifactId>
<version>2.2version>
<configuration>
<url>http://localhost:8080/manager/texturl>
<server>Tomcat7server>
<username>adminusername>
<password>adminpassword>
<port>8082port>
<uriEncoding>UTF-8uriEncoding>
<path>/path>
<warFile>${basedir}/target/${project.build.finalName}.warwarFile>
configuration>
plugin>
plugins>
build>
project>
前言部分已经有提到,spring mvc 4 零配置是基于 servlet 3.0 规范的,在该规范中,是通过ServletContainerInitializer进行配置的,而在spring mvc中有一个唯一的实现–>SpringServletContainerInitializer,它就是打开宝箱的钥匙.代码如下:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List initializers = new LinkedList();
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);
}
}
}
由于该类声明了@HandlesTypes(WebApplicationInitializer.class),则实现了Servlet 3.0 +规范的容器会依次扫描类路径下实现了WebApplicationInitializer接口的类传递到onStartup中.处理逻辑如下:
如果initializers 为空集合,则打印一条日志后直接return.如下:
No Spring WebApplicationInitializer types detected on classpath
否则,排序后(如果它们存在@Order 注解,或者实现了Ordered 接口),依次调用其onStartup方法,进行启动.
WebApplicationInitializer 继承结构如下:
因此会最终调用AbstractDispatcherServletInitializer#onStartup方法,代码如下:
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);
registerDispatcherServlet(servletContext);
}
AbstractContextLoaderInitializer#onStartup 代码如下:
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
调用
protected void registerContextLoaderListener(ServletContext servletContext) {
// 1. 创建WebApplicationContext 上下文
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
// 2. 实例化ContextLoaderListener
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
// 3. 添加到ServletContext 中
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
3件事:
调用抽象方法createRootApplicationContext来创建WebApplicationContext.该方法的实现在AbstractAnnotationConfigDispatcherServletInitializer中,代码如下:
protected WebApplicationContext createRootApplicationContext() {
// 1. 获得RootConfigClasses
Class>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 2. 初始化AnnotationConfigWebApplicationContext,并进行注册
AnnotationConfigWebApplicationContext rootAppContext = new AnnotationConfigWebApplicationContext();
rootAppContext.register(configClasses);
return rootAppContext;
}
else {
return null;
}
}
AbstractDispatcherServletInitializer#registerDispatcherServlet 代码如下:
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return empty or null");
// 1. 初始化WebApplicationContext
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext,
"createServletApplicationContext() did not return an application " +
"context for servlet [" + servletName + "]");
// 2. 舒适化DispatcherServlet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
// 3. 向servletContext 添加Servlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
Assert.notNull(registration,
"Failed to register servlet with name '" + servletName + "'." +
"Check if there is another servlet registered under the same name.");
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());// 默认支持异步
// 4. 获得Filter的配置,并依次进行注册
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
// 5. 自定义初始化
customizeRegistration(registration);
}
5件事:
调用createServletApplicationContext直接初始化WebApplicationContext,代码如下:
@Override
protected WebApplicationContext createServletApplicationContext() {
// 1. 初始化AnnotationConfigWebApplicationContext
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
// 2. 获得ServletConfigClasses
Class>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
// 3. 向AnnotationConfigWebApplicationContext 注册
servletAppContext.register(configClasses);
}
return servletAppContext;
}
WebConfig声明了@EnableWebMvc,该注解通过@Import(DelegatingWebMvcConfiguration.class)的方式导入了DelegatingWebMvcConfiguration的配置,通过前几篇文章可以知道,此时spring 将加载DelegatingWebMvcConfiguration的配置. DelegatingWebMvcConfiguration类图如下:
通过查看源码可知,真正生效的配置是在WebMvcConfigurationSupport中,该类配置了如下几个bean:
RequestMappingHandlerMapping order 为0,用来处理被@controller注解的类的方法.代码如下:
@Bean
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
// 1. 实例化RequestMappingHandlerMapping
RequestMappingHandlerMapping handlerMapping = createRequestMappingHandlerMapping();
handlerMapping.setOrder(0);
// 2. 设置拦截器,默认注册的有ConversionServiceExposingInterceptor,ResourceUrlProviderExposingInterceptor
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setContentNegotiationManager(mvcContentNegotiationManager());
// 跨域设置
handlerMapping.setCorsConfigurations(getCorsConfigurations());
// 3. 实例化PathMatchConfigurer,该类是用来配置路径匹配的
PathMatchConfigurer configurer = getPathMatchConfigurer();
if (configurer.isUseSuffixPatternMatch() != null) {
handlerMapping.setUseSuffixPatternMatch(configurer.isUseSuffixPatternMatch());
}
if (configurer.isUseRegisteredSuffixPatternMatch() != null) {
handlerMapping.setUseRegisteredSuffixPatternMatch(configurer.isUseRegisteredSuffixPatternMatch());
}
if (configurer.isUseTrailingSlashMatch() != null) {
handlerMapping.setUseTrailingSlashMatch(configurer.isUseTrailingSlashMatch());
}
UrlPathHelper pathHelper = configurer.getUrlPathHelper();
if (pathHelper != null) {
handlerMapping.setUrlPathHelper(pathHelper);
}
PathMatcher pathMatcher = configurer.getPathMatcher();
if (pathMatcher != null) {
handlerMapping.setPathMatcher(pathMatcher);
}
return handlerMapping;
}
HandlerMapping,order 为1,用来处理URL path 直接映射到view 的名称,代码如下:
@Bean
public HandlerMapping viewControllerHandlerMapping() {
// 注册HandlerMapping
ViewControllerRegistry registry = new ViewControllerRegistry();
registry.setApplicationContext(this.applicationContext);
addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
handlerMapping = (handlerMapping != null ? handlerMapping : new EmptyHandlerMapping());
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(getInterceptors());
handlerMapping.setCorsConfigurations(getCorsConfigurations());
return handlerMapping;
}
该类的具体使用可以参考如下链接:
BeanNameUrlHandlerMapping,order 为2,用来处理URL path 直接映射到controller的名字.代码如下:
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping() {
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
mapping.setOrder(2);
mapping.setInterceptors(getInterceptors());
mapping.setCorsConfigurations(getCorsConfigurations());
return mapping;
}
HandlerMapping,order 为Integer.MAX_VALUE-1,用来处理静态资源.代码如下:
@Bean
public HandlerMapping resourceHandlerMapping() {
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, mvcContentNegotiationManager());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
if (handlerMapping != null) {
handlerMapping.setPathMatcher(mvcPathMatcher());
handlerMapping.setUrlPathHelper(mvcUrlPathHelper());
handlerMapping.setInterceptors(new ResourceUrlProviderExposingInterceptor(mvcResourceUrlProvider()));
handlerMapping.setCorsConfigurations(getCorsConfigurations());
}
else {
handlerMapping = new EmptyHandlerMapping();
}
return handlerMapping;
}
对HandlerMapping进行配置.
声明该bean,相当于在xml时代的配置:
<mvc:resources location="/html/" mapping="/html/**" />
HandlerMapping,order 为Integer.MAX_VALUE,用来处理转发请求到DispatcherServlet.代码如下:
@Bean
public HandlerMapping defaultServletHandlerMapping() {
DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(servletContext);
configureDefaultServletHandling(configurer);
AbstractHandlerMapping handlerMapping = configurer.getHandlerMapping();
handlerMapping = handlerMapping != null ? handlerMapping : new EmptyHandlerMapping();
return handlerMapping;
}
RequestMappingHandlerAdapter,处理请求通过被@controller注解的类的方法.
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(
Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(
Collections.>singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
HttpRequestHandlerAdapter,处理请求通过HttpRequestHandler.调用具体的方法对用户发来的请求来进行处理。当handlerMapping获取到执行请求的controller时,DispatcherServlte会根据controller对应的controller类型来调用相应的HandlerAdapter来进行处理。代码如下:
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
SimpleControllerHandlerAdapter,处理请求通过Controller的实现类.代码如下:
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
HandlerExceptionResolverComposite.代码如下:
@Bean
public HandlerExceptionResolver handlerExceptionResolver() {
List exceptionResolvers = new ArrayList();
configureHandlerExceptionResolvers(exceptionResolvers);
if (exceptionResolvers.isEmpty()) {
addDefaultHandlerExceptionResolvers(exceptionResolvers);
}
extendHandlerExceptionResolvers(exceptionResolvers);
HandlerExceptionResolverComposite composite = new HandlerExceptionResolverComposite();
composite.setOrder(0);
composite.setExceptionResolvers(exceptionResolvers);
return composite;
}
AntPathMatcher,代码如下:
@Bean
public PathMatcher mvcPathMatcher() {
// 注册PathMatcher
PathMatcher pathMatcher = getPathMatchConfigurer().getPathMatcher();
return (pathMatcher != null ? pathMatcher : new AntPathMatcher());
}
默认配置的是AntPathMatcher
UrlPathHelper,代码如下:
@Bean
public UrlPathHelper mvcUrlPathHelper() {
// 注册UrlPathHelper
UrlPathHelper pathHelper = getPathMatchConfigurer().getUrlPathHelper();
return (pathHelper != null ? pathHelper : new UrlPathHelper());
}
默认配置的是UrlPathHelper
ContentNegotiationManager,该类的作用是根据请求规则决定返回什么样的内容类型。后缀规则、参数规则、Accept头规则、固定的内容类型等。注意,这里只是决定,不是具体提供内容类型的地方.代码如下:
@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
// 注册ContentNegotiationManager
if (this.contentNegotiationManager == null) {
ContentNegotiationConfigurer configurer = new ContentNegotiationConfigurer(this.servletContext);
configurer.mediaTypes(getDefaultMediaTypes());
configureContentNegotiation(configurer);
try {
this.contentNegotiationManager = configurer.getContentNegotiationManager();
}
catch (Exception ex) {
throw new BeanInitializationException("Could not create ContentNegotiationManager", ex);
}
}
return this.contentNegotiationManager;
}
lazy-init风格,会在实例化RequestMappingHandlerMapping时进行初始化.
配置mediaTypes为:
FormattingConversionService,用来格式化的.代码如下:
@Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
Validator,用来mvc校验的.代码如下:
@Bean
public Validator mvcValidator() {
// 1. 该方法默认返回null,可以复写该方法
Validator validator = getValidator();
if (validator == null) {
// 2. 如果存在javax.validation.Validator,则尝试加载org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean
if (ClassUtils.isPresent("javax.validation.Validator", getClass().getClassLoader())) {
Class> clazz;
try {
String className = "org.springframework.validation.beanvalidation.OptionalValidatorFactoryBean";
clazz = ClassUtils.forName(className, WebMvcConfigurationSupport.class.getClassLoader());
}
catch (ClassNotFoundException ex) {
throw new BeanInitializationException("Could not find default validator class", ex);
}
catch (LinkageError ex) {
throw new BeanInitializationException("Could not load default validator class", ex);
}
validator = (Validator) BeanUtils.instantiateClass(clazz);
}
else {
// 否则返回NoOpValidator
validator = new NoOpValidator();
}
}
return validator;
}
CompositeUriComponentsContributor,MvcUriComponentsBuilder会用到,代码如下:
@Bean
public CompositeUriComponentsContributor mvcUriComponentsContributor() {
return new CompositeUriComponentsContributor(
requestMappingHandlerAdapter().getArgumentResolvers(), mvcConversionService());
}
ViewResolver,代码如下:
@Bean
public ViewResolver mvcViewResolver() {
ViewResolverRegistry registry = new ViewResolverRegistry();
registry.setContentNegotiationManager(mvcContentNegotiationManager());
registry.setApplicationContext(this.applicationContext);
configureViewResolvers(registry);
if (registry.getViewResolvers().isEmpty()) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, ViewResolver.class, true, false);
if (names.length == 1) {
registry.getViewResolvers().add(new InternalResourceViewResolver());
}
}
ViewResolverComposite composite = new ViewResolverComposite();
composite.setOrder(registry.getOrder());
composite.setViewResolvers(registry.getViewResolvers());
composite.setApplicationContext(this.applicationContext);
composite.setServletContext(this.servletContext);
return composite;
}