默认策略
DispatcherServlet#getDefaultStrategies(缩减版)
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) { // 策略接口名称 String key = strategyInterface.getName(); // 默认策略列表 String value = defaultStrategies.getProperty(key); String[] classNames = StringUtils.commaDelimitedListToStringArray(value); List<T> strategies = new ArrayList<>(classNames.length); for (String className : classNames) { // 实例化 Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader()); Object strategy = createDefaultStrategy(context, clazz); strategies.add((T) strategy); } return strategies; } // 默认策略列表 private static final Properties defaultStrategies; static { // 路径名称是:DispatcherServlet.properties try { ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } }
从静态默认策略属性 defaultStrategies
的加载过程中,读取的是 DispatcherServlet.properties
文件内容,看完下面列出来的信息,相信你跟我一样恍然大悟,了解 Spring
配置了哪些默认策略:
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping,\ org.springframework.web.servlet.function.support.RouterFunctionMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\ org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\ org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter,\ org.springframework.web.servlet.function.support.HandlerFunctionAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\ org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\ org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
接下来看看它们各自的初始化过程以及使用场景:
multipartResolver 文件上传相关
private void initMultipartResolver(ApplicationContext context) { try { this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class); catch (NoSuchBeanDefinitionException ex) { // Default is no multipart resolver. this.multipartResolver = null; } }
默认情况下,Spring
是没有 mulitpart
处理,需要自己设定
<!--上传下载--> <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>
注册的 id
为 multipartResolver
LocalResolver 与国际化相关
LocalResolver
接口定义了如何获取客户端的地区
private void initLocaleResolver(ApplicationContext context) { try { this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class); } catch (NoSuchBeanDefinitionException ex) { // We need to use the default. this.localeResolver = getDefaultStrategy(context, LocaleResolver.class); } }
通过寻找 id
为 localeResolver
的 bean
,如果没有的话,将会使用默认的策略进行加载 AcceptHeaderLocaleResolver
,它是基于 URL
参数来控制国际化,例如使用 <a href="?locale=zh_CN">
来设定简体中文,默认参数名为 locale
。
当然还有其他两种,基于 session
和基于 cookie
的配置,想要深入了解的可以去细看~
ThemeResolver 主题更换相关
主题是一组静态资源(例如样式表 css 和图片 image),也可以理解为应用皮肤,使用 Theme
更改主题风格,改善用户体验。
默认注册的 id
是 themeResolver
,类型是 FixedThemeResolver
,表示使用的是一个固定的主题,以下是它的继承体系图:
工作原理是通过拦截器拦截,配置对应的主题解析器,然后返回主题名称,还是使用上面的解析器作为例子:
FixedThemeResolver#resolveThemeName
public String resolveThemeName(HttpServletRequest request) { return getDefaultThemeName(); } public String getDefaultThemeName() { return this.defaultThemeName; }
HandlerMapping 与匹配处理器相关
首先判断 detectAllHandlerMappings
变量是否为 true
,表示是否需要加载容器中所有的 HandlerMapping
,false
将会加载用户配置的。
如注释所说,至少得保证有一个 HandlerMapping
,如果前面两个分支都没寻找到,那么就进行默认策略加载。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // 默认情况下,寻找应用中所有的 HandlerMapping ,包括祖先容器(其实就是 Spring 容器啦) Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerMappings = new ArrayList<>(matchingBeans.values()); // handlerMapping 有优先级,需要排序 AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { // 从上下文中,获取名称为 handlerMapping 的 bean HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } // 需要保证,至少有一个 HandlerMapping // 如果前面两步都没找到 mapping,将会由这里加载默认策略 if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); } }
通过 Debug
得知,之前在加载 Spring
配置时,就已经注入了 RequestMappingHandlerMapping
和 BeanNameUrlHandlerMapping
HandlerAdapter 适配器
套路与前面的一样,使用的默认策略是:HttpRequestHandlerAdapter
、SimpleControllerHandlerAdapter
、 RequestMappingHandlerAdapter
和 HandlerFunctionAdapter
。
说到适配器,可以将它理解为,将一个类的接口适配成用户所期待的,将两个接口不兼容的工作类,通过适配器连接起来。
HandlerExceptionResolver 处理器异常解决器
套路也与前面一样,使用的默认策略是:ExceptionHandlerExceptionResolver
、 ResponseStatusExceptionResolver
和 DefaultHandlerExceptionResolver
。
实现了 HandlerExceptionResolver
接口的 resolveException
方法,在方法内部对异常进行判断,然后尝试生成 ModelAndView
返回。
public ModelAndView resolveException( HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex) { if (shouldApplyTo(request, handler)) { prepareResponse(ex, response); ModelAndView result = doResolveException(request, response, handler, ex); return result; } else { return null; } }
RequestToViewNameTranslator 处理逻辑视图名称
初始化代码逻辑与前面一样,使用的默认策略是:DefaultRequestToViewNameTranslator
使用场景:当 Controller
处理器方法没有返回逻辑视图名称时,Spring
通过该类的约定,提供一个逻辑视图名称。
由于本地测试不出来,所以引用参考资料 7 的例子:
DefaultRequestToViewNameTranslator的转换例子:
http://localhost:8080/gamecast/display.html -> display(视图)
ViewResolver 视图渲染
套路还是跟前面一样,默认策略使用的是:InternalResourceViewResolver
同时,这也是 demo
中,我们手动配置的视图解析器
FlashMapManager 存储属性
默认使用的是:SessionFlashMapManager
,通过与 FlashMap
配合使用,用于在重定向时保存/传递参数。
例如 Post/Redirect/Get
模式,Flash attribute
在重定向之前暂存(根据类名,可以知道范围是 session
级别有效),以便重定向之后还能使用。
RequestMappingHandler
该类作用:配合 @Controller
和 @RequestMapping
注解使用,通过 URL
来找到对应的处理器。
前面在 spring-mvc.xml
文件加载时,初始化了两个重要配置,其中一个就是下面要说的 RequestMappingHandler
,先来看它的继承体系图:
从继承图中看到,它实现了 InitializingBean
接口,所以在初始化时,将会执行 afterPropertiesSet
方法(图片中注释写错方法,请以下面为准),核心调用的初始化方法是父类 AbstractHandlerMethodMapping#initHandlerMethods
方法
AbstractHandlerMethodMapping#initHandlerMethods
protected void initHandlerMethods() { // 获取容器中所有 bean 名字 for (String beanName : this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(obtainApplicationContext(), Object.class) : obtainApplicationContext().getBeanNamesForType(Object.class)) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { // 如果前缀不是 scopedTarget. // 执行 detectHandlerMethods() 方法 Class<?> beanType = obtainApplicationContext().getType(beanName); if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } } } // 打印数量,可以当成空实现 handlerMethodsInitialized(getHandlerMethods()); } protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); // 通过反射,获取类中所有方法 // 筛选出 public 类型,并且带有 @RequestMapping 注解的方法 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { // 通过 RequestMappingHandlerMapping.getMappingForMethod 方法组装成 RequestMappingInfo(映射关系) return getMappingForMethod(method, userType); }); methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); // 通过 mappingRegistry 进行注册上面获取到的映射关系 registerHandlerMethod(handler, invocableMethod, mapping); }); } }
梳理一下代码逻辑,initHandlerMethods
方法将会扫描注册 bean
下所有公共 public
方法,如果带有 @RequestMapping
注解的,将会组装成 RequestMappingInfo
映射关系,然后将它注册到 mappingRegistry
变量中。之后可以通过映射关系,输入 URL
就能够找到对应的处理器 Controller
。
MappingRegistry
该类是 AbstractHandlerMethodMapping
的内部类,是个工具类,用来保存所有 Mapping
和 handler method
,通过暴露加锁的公共方法,避免了多线程对该类的内部变量的覆盖修改。
下面是注册的逻辑:
public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { // 包装 bean 和方法 HandlerMethod handlerMethod = createHandlerMethod(handler, method); // 校验 validateMethodMapping(handlerMethod, mapping); this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } // 跨域参数 CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } // 将映射关系放入 Map<T, MappingRegistration<T>> registry this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
通过前面的包装和校验方法,最后映射关系将会放入这里 Map<T, MappingRegistration<T>> registry
。它是一个泛型的 Map
,key
类型是 RequestMappingInfo
,保存了 @RequestMapping
各种属性的集合,value
类型是 AbstractHandlerMethodMapping
,保存的是我们的映射关系。
从图中可以看出,如果输入的 URL
是 /plain/{name}
,将会找到对应的处理方法 web.controller.BookController#plain{String}
。