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