Spring MVC 之自动配置
Springmvc 是基于 servlet 规范来完成的一个请求响应模块,也是 spring 中比较大的一个模块,现在基本上都是零 xml 配置了, 采用的是约定 大于配置的方式,所以我们的 springmvc 也是采用这种零 xml 配置的方式。要完成这种过程,要解决两个问题
- 取代 web.xml 配置,
- 取代 springmvc.xml 配置。
取代 web.xml 配置
DispatcherServletAutoConfiguration
spring boot 主要是通过 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 来完成初始化工作。
这里配置了 DispatcherServlet.java
首先,DispatcherServlet 被作为一个普通 Bean 被定义和注册到容器;
然后,又定义了另外一个 DispatcherServletRegistrationBean bean 用来添加该 DispatcherServlet bean 到 ServletContext;
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass(DispatcherServlet.class)
@AutoConfigureAfter(ServletWebServerFactoryAutoConfiguration.class)
public class DispatcherServletAutoConfiguration {
public static final String DEFAULT_DISPATCHER_SERVLET_BEAN_NAME = "dispatcherServlet";
public static final String DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME = "dispatcherServletRegistration";
@Configuration(proxyBeanMethods = false)
@Conditional(DefaultDispatcherServletCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
protected static class DispatcherServletConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
return dispatcherServlet;
}
/**
* 初始化上传文件的解析器
*/
@Bean
@ConditionalOnBean(MultipartResolver.class)
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
public MultipartResolver multipartResolver(MultipartResolver resolver) {
// Detect if the user has created a MultipartResolver but named it incorrectly
return resolver;
}
}
/**
* 该类主要作用是为了将 dispatcherServlet 注册到系统(servlet)里面去。
*/
@Configuration(proxyBeanMethods = false)
@Conditional(DispatcherServletRegistrationCondition.class)
@ConditionalOnClass(ServletRegistration.class)
@EnableConfigurationProperties(WebMvcProperties.class)
@Import(DispatcherServletConfiguration.class)
protected static class DispatcherServletRegistrationConfiguration {
@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
// 通过 ServletRegistrationBean 将 dispatcherServlet 注册到 ServletContext,这样 servlet 才会生效。
DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
webMvcProperties.getServlet().getPath());
// 名称为 dispatcherServlet
registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
multipartConfig.ifAvailable(registration::setMultipartConfig);
return registration;
}
}
}
通过上面的代码分析,可以非常清晰的看到,spring mvc 主要是依靠 DispatcherServletAutoConfiguration 这个类中的两个内部类进行初始化,
- DispatcherServletConfiguration 负责生成 dispatcherServlet
- DispatcherServletRegistrationConfiguration 负责将 dispatcherServlet 注册到 ServletContext 中,使其生效
两者配合,最终完成初始化工作。
取代 springmvc.xml 配置
我们用一个 @EnableWebMvc 就可以完全取代 xml 配置,其实两者完成的工作是一样的,都是为了创建必要组件的实例
@EnableWebMvc
导入了类 DelegatingWebMvcConfiguration.class
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
DelegatingWebMvcConfiguration
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
// ......
}
/**
* 这里会导入一个核心类 WebMvcConfigurationSupport,在这个类里面会完成很多组件的实例化,比如 HandlerMapping,HandlerAdapter 等 等。
*/
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
}
然后在实例化过程中会涉及到很多钩子方法的调用,而这些钩子方法就是我们需要去实现的,比如获取拦截器的钩子方法,获取静态资源处理的钩子方法等等。
请求之前建立映射关系
在 HandlerMapping 类实例化的时候就会完成 url 和 method 的映射关系,要根据一个请求能够唯一到找到一个类和一个方法。
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
mapping.setOrder(0);
// 钩子方法 getInterceptors
mapping.setInterceptors(getInterceptors(conversionService, resourceUrlProvider));
mapping.setContentNegotiationManager(contentNegotiationManager);
mapping.setCorsConfigurations(getCorsConfigurations());
PathMatchConfigurer pathConfig = getPathMatchConfigurer();
// ......
return mapping;
}
这个是 RequestMappingHandlerMapping 的实例化,通过这个类的实例化后会调用此类的 afterPropertiesSet 方法, 在此方法中会调用父类 AbstractHandlerMethodMapping 中实现的 InitializingBean 接口,然后看一下父类的 afterPropertiesSet 方法。 在这个方法里面完成了映射关系的建立。
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 这里判断类上面是否有@Controller 注解和 @RequestMapping 注解,只有这种类才需要建立映射关系
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
// 如果有 @Controller 注解和 @RequestMapping 注解
if (beanType != null && isHandler(beanType)) {
// 建立 url 和 method 的映射关系
detectHandlerMethods(beanName);
}
}
}
detectHandlerMethods(beanName); 的大体思路是:
- 循环类里面的所有方法
- 收集方法上面的 @RequestMapping 注解,把注解里面的配置信息封装到类里面,该类就是 RequestMappingInfo 类, 并且跟类上面的 @RequestMapping 注解封装类 RequestMappingInfo 合并,比如类上面是/common,方法上面是/queryUser。 这两者合并后就是 /common/queryUser。这样的 url 才是我们需要的。合并完就是这样的 url。
- 然后建立 method 对象和对应的 RequestMappingInfo 的映射关系,把关系存放到 map 中。
- 创建 HandlerMethod 对象,该类型封装了 method、beanName、bean、方法类型等信息。
- 建立 RequestMappingInfo 和 HandlerMethod 的映射关系
- 建立 url 和 RequestMappingInfo 对象的映射关系
- 这样映射关系就已经建立好,这样根据请求 url 我们就可以唯一的找到一个此时我们通过 URL 去找 RequestMappingInfo 对象, 然后通过 RequestMappingInfo 对象再找到对应的 HandlerMethodHandlerMethod 对象了,注意这个对象中还不能进行反射调用,还差参数。
DispatcherServlet 处理请求
一般 servlet 的请求时,应该首先会请求到 servlet 中的 doService 方法,通过 DispatcherServlet 的 doService 方法,我们来看一下具体的请求处理过程
├─DispatcherServlet
│ ├─ extends FrameworkServlet
│ │ ├─ extends HttpServletBean implements ApplicationContextAware
│ │ │ ├─ HttpServletBean extends HttpServlet
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
// ......
// Make framework objects available to handlers and view objects.
request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
// ......
try {
// 调用到核心流程
doDispatch(request, response);
}
finally {
// ......
}
}
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
// 异步管理器
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
// 文件上传
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 这个方法很重要,重点看
// Determine handler for the current request.
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
// 获取跟 MehtodHandler 匹配的 HandlerAdapter 对象
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// ......
// 调用到 Controller 具体方法,核心方法调用,重点看看
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// ......
}
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
// ......
}
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// handlerMappings 实例,是在初始化的 ApplicationListener 实现的时候通过 onRefresh 方法注入进来的。
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
// 获取 HandlerMethod 和过滤器链的包装类
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
}
上述的 handlerMappings 是怎么拥有值的呢?我们来分析一下:
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {
// 此监听类,会在 Spring 完成实例化的时候调用
private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
// 重点看下这里
FrameworkServlet.this.onApplicationEvent(event);
}
}
}
/**
* 然后接着走我们重点看一下 handlerMappings 是怎么实例化的
* FrameworkServlet.this.onApplicationEvent(event); 就会调用下面这个的 onRefresh()
*/
public class DispatcherServlet extends FrameworkServlet {
@Override
protected void onRefresh(ApplicationContext context) {
initStrategies(context);
}
/**
* Initialize the strategy objects that this servlet uses.
* <p>May be overridden in subclasses in order to initialize further strategy objects.
*/
protected void initStrategies(ApplicationContext context) {
// 文件上传
initMultipartResolver(context);
// 语言解析器
initLocaleResolver(context);
initThemeResolver(context);
// handlerMappings
initHandlerMappings(context);
// HandlerAdapters
initHandlerAdapters(context);
// HandlerExceptionResolvers
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
// 视图解析器
initViewResolvers(context);
initFlashMapManager(context);
}
}
下面我们就看一下怎么实例化 handlerMappings 和 handlerAdapters, 如下所示, 通过寻找 HandlerMapping 对应的类来从 context 中去 getBean,由于前面已经通过 @Bean 注册了 RequestMappingHandlerMapping 处理类,此处就可以获取到。
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
if (this.detectAllHandlerMappings) {
// ......
}
else {
try {
// 此处获取到 WebMvcConfigurationSupport 中通过 @Bean 注解的 RequestMappingHandlerMapping 的实例
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
this.handlerMappings = Collections.singletonList(hm);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerMapping later.
}
}
}
寻找 HandlerMethod 的过程,感觉没什么好说的,前面映射关系已经建立好了,现在就是只需要从 request 对象中获取请求 url, 然后从映射关系中获取 HandlerMethod 对象就可以了。
public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
implements HandlerMapping, Ordered, BeanNameAware {
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
// 根据请求中的 url 拿到对应的 HandlerMethod 对象
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
// Ensure presence of cached lookupPath for interceptors and others
if (!ServletRequestPathUtils.hasCachedPath(request)) {
initLookupPath(request);
}
// 获取 HandlerMethod 和过滤器链的包装类
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
if (logger.isTraceEnabled()) {
logger.trace("Mapped to " + handler);
}
else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
logger.debug("Mapped to " + executionChain.getHandler());
}
// 有跨域请求配置或者是跨域请求。就是查看 request 请求头中是否有 Origin 属性
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
// 注解获取跨域配置
CorsConfiguration config = getCorsConfiguration(handler, request);
if (getCorsConfigurationSource() != null) {
// 自定义的钩子方法获取跨域配置
CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
config = (globalConfig != null ? globalConfig.combine(config) : config);
}
if (config != null) {
config.validateAllowCredentials();
}
// 这里设置了跨域的过滤器 CorsInterceptor
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
}
调用到 Controller 具体方法,核心方法调用,重点看看
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// Controller 里面具体方法调用,重点看
// No synchronization on session demanded at all...
mav = invokeHandlerMethod(request, response, handlerMethod);
}
return mav;
}
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
// Controller 里面具体方法调用,重点看
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
}