Skip to content

Spring MVC 之自动配置

Springmvc 是基于 servlet 规范来完成的一个请求响应模块,也是 spring 中比较大的一个模块,现在基本上都是零 xml 配置了, 采用的是约定 大于配置的方式,所以我们的 springmvc 也是采用这种零 xml 配置的方式。要完成这种过程,要解决两个问题

  1. 取代 web.xml 配置,
  2. 取代 springmvc.xml 配置。

取代 web.xml 配置

DispatcherServletAutoConfiguration

spring boot 主要是通过 org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration 来完成初始化工作。

这里配置了 DispatcherServlet.java

首先,DispatcherServlet 被作为一个普通 Bean 被定义和注册到容器;

然后,又定义了另外一个 DispatcherServletRegistrationBean bean 用来添加该 DispatcherServlet bean 到 ServletContext;

java
@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

java
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

DelegatingWebMvcConfiguration

java
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
    // ......
}

/**
 * 这里会导入一个核心类 WebMvcConfigurationSupport,在这个类里面会完成很多组件的实例化,比如 HandlerMapping,HandlerAdapter 等 等。
 */
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {

}

然后在实例化过程中会涉及到很多钩子方法的调用,而这些钩子方法就是我们需要去实现的,比如获取拦截器的钩子方法,获取静态资源处理的钩子方法等等。

请求之前建立映射关系

在 HandlerMapping 类实例化的时候就会完成 url 和 method 的映射关系,要根据一个请求能够唯一到找到一个类和一个方法。

java
@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 方法。 在这个方法里面完成了映射关系的建立。

java
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); 的大体思路是:

  1. 循环类里面的所有方法
  2. 收集方法上面的 @RequestMapping 注解,把注解里面的配置信息封装到类里面,该类就是 RequestMappingInfo 类, 并且跟类上面的 @RequestMapping 注解封装类 RequestMappingInfo 合并,比如类上面是/common,方法上面是/queryUser。 这两者合并后就是 /common/queryUser。这样的 url 才是我们需要的。合并完就是这样的 url。
  3. 然后建立 method 对象和对应的 RequestMappingInfo 的映射关系,把关系存放到 map 中。
  4. 创建 HandlerMethod 对象,该类型封装了 method、beanName、bean、方法类型等信息。
  5. 建立 RequestMappingInfo 和 HandlerMethod 的映射关系
  6. 建立 url 和 RequestMappingInfo 对象的映射关系
  7. 这样映射关系就已经建立好,这样根据请求 url 我们就可以唯一的找到一个此时我们通过 URL 去找 RequestMappingInfo 对象, 然后通过 RequestMappingInfo 对象再找到对应的 HandlerMethodHandlerMethod 对象了,注意这个对象中还不能进行反射调用,还差参数。

DispatcherServlet 处理请求

一般 servlet 的请求时,应该首先会请求到 servlet 中的 doService 方法,通过 DispatcherServlet 的 doService 方法,我们来看一下具体的请求处理过程

├─DispatcherServlet
│ ├─ extends FrameworkServlet
│ │ ├─ extends HttpServletBean implements ApplicationContextAware
│ │ │ ├─ HttpServletBean extends HttpServlet
java
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 是怎么拥有值的呢?我们来分析一下:

java
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 处理类,此处就可以获取到。

java
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 对象就可以了。

java
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());

java
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();
        }
    }
}