二月再见,三月你好!
继续上一篇《Spring源码解读(第五弹)-梦开始的地方,关于webmvc启动的那点东西》未完成的流程,以及遗留的问题,闲话不说,直接上猛料吧!直接给你整倒沫子~
initStrategies
先来看看我们今天的猪脚DispatcherServlet#initStrategies()方法。这里方法里面初始化的组件都是web相关的,说的简单点,就是写Controller才会用到的东西。
protected void initStrategies(ApplicationContext context) {
//初始化文件上传使用的MultiResolver
initMultipartResolver(context);
//初始化LocaleResolver,用于国际化
initLocaleResolver(context);
//初始化主题解析器ThemeResolver
initThemeResolver(context);
//初始化请求映射的HandlerMaping
initHandlerMappings(context);
//初始化请求处理的适配器HandlerAdapter
initHandlerAdapters(context);
//初始化异常处理解析器HandlerExceptionResolver
initHandlerExceptionResolvers(context);
//初始化用于从请求中获取viewname的RequtstToviewNameResolver
initRequestToViewNameTranslator(context);
//初始化视图解析器ViewResolver
initViewResolvers(context);
//初始化FlashMapperManager
initFlashMapManager(context);
}
这里面的大部分方法都很简单,就是用来初始化一些web能用到的一些bean。这个时候,发挥我们做题的精神(先做简单的),所以接下来我们先把简单的快速来过一遍,核心的留在最后。
由于这些都是跟请求有关的东西,也就是说这些东西在初始化之后,需要有请求调用的时候才会用到,而且有些东西并不是特别重要和核心,所以在当前介绍初始化的流程里面,不会过多的去讲解里面的细节,只会简单的跟大家提到有这个东西,也更有精力去理解我们想要去了解的部分。没错,就是我们上一篇留下的那些问题。
initMultipartResolver
public static final String MULTIPART_RESOLVER_BEAN_NAME = "multipartResolver";
private void initMultipartResolver(ApplicationContext context) {
try {
this.multipartResolver = context.getBean(MULTIPART_RESOLVER_BEAN_NAME, MultipartResolver.class);
...
} catch(NoSuchBeanDefinitionException) {
this.multipartResolver = null;
}
}
这个东西大佬们一眼就能看出来了,对的,就是用来处理文件上传的。方法第一行就是从当前容器中获取一个名字叫multipartResolver
的MultipartResolver
类型的对象,可以看到如果没有的话,就是null处理。
initLocaleResolver
public static final String LOCALE_RESOLVER_BEAN_NAME = "localeResolver";
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);
}
}
方法第一行就是从当前容器中获取一个名字叫localeResolver
的LocaleResolver
类型的对象,这个对象大家可能有点陌生,因为平时几乎用不到。这个类就是用来获取当前时区Locale
的,配合后面ThemeResolver
来实现国际化,在有国际化需求的时候用。这么说可能还是有点懵,给你举个例子,你进入某些网站的时候,你可以选择一个语言,然后你的页面上的文本就会变成你选择的语言,国际化就是用来干这个的,当然,这些数据会配置在后端的特定配置文件中,这篇暂时不会讲解这个,感兴趣的朋友可以自行去了解。
在catch代码块,可以看到这个时候如果没有的话就会调用getDefaultStrategy方法取一个默认的。这个方法在后面还会用到,就放到后面统一看了。
initThemeResolver
public static final String THEME_RESOLVER_BEAN_NAME = "themeResolver";
private void initThemeResolver(ApplicationContext context) {
try {
this.themeResolver = context.getBean(THEME_RESOLVER_BEAN_NAME, ThemeResolver.class);
} catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.themeResolver = getDefaultStrategy(context, ThemeResolver.class);
}
}
第一行代码,也是一如既往地从容器中获取一个指定名称,指定类型的bean。ThemeResolver
就是用来处理我们是上面说的国际化配置的,根据不同的Locale
返回指定的配置和主题资源,用来改变页面显示。catch块中依然是会默认初始化一个,如果没有的话。
initHandlerMappings(重点画个圈)
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
// 是否需要检查有没有存在多个HanderMapping
if (this.detectAllHandlerMappings) {
// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerMappings = new ArrayList<>(matchingBeans.values());
// We keep HandlerMappings in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerMappings);
}
}
else {
try {
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.
}
}
// Ensure we have at least one HandlerMapping, by registering
// a default HandlerMapping if no other mappings are found.
if (this.handlerMappings == null) {
this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerMappings declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
代码开始长起来了,但是别怕,有用的只有几行。大致逻辑不过就是找到一堆HandlerMapping
或者默认初始化一个而已。这个东西就是用来存储我们Controller中接口路径与目标方法的映射关系的。等下我们会专门讲这个核心的玩意儿。
initHandlerAdapters(重点画个圈)
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
// 是否需要检查有没有存在多个HanderMapping
if (this.detectAllHandlerAdapters) {
// Find all HandlerAdapters in the ApplicationContext, including ancestor contexts.
Map<String, HandlerAdapter> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerAdapters = new ArrayList<>(matchingBeans.values());
// We keep HandlerAdapters in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerAdapters);
}
}
else {
try {
HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class);
this.handlerAdapters = Collections.singletonList(ha);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default HandlerAdapter later.
}
}
// Ensure we have at least some HandlerAdapters, by registering
// default HandlerAdapters if no other adapters are found.
if (this.handlerAdapters == null) {
this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerAdapters declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
代码依然有点长,但是你已经不害怕了,因为和上一个都差不多,只不过这里要取的bean变成了HandlerAdapter
了而已。关于它的具体介绍请看后面。
initHandlerExceptionResolvers
private void initHandlerExceptionResolvers(ApplicationContext context) {
this.handlerExceptionResolvers = null;
// 是否需要检查有没有存在多个HandlerExceptionResolver
if (this.detectAllHandlerExceptionResolvers) {
// Find all HandlerExceptionResolvers in the ApplicationContext, including ancestor contexts.
Map<String, HandlerExceptionResolver> matchingBeans = BeanFactoryUtils
.beansOfTypeIncludingAncestors(context, HandlerExceptionResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.handlerExceptionResolvers = new ArrayList<>(matchingBeans.values());
// We keep HandlerExceptionResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.handlerExceptionResolvers);
}
}
else {
try {
HandlerExceptionResolver her =
context.getBean(HANDLER_EXCEPTION_RESOLVER_BEAN_NAME, HandlerExceptionResolver.class);
this.handlerExceptionResolvers = Collections.singletonList(her);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, no HandlerExceptionResolver is fine too.
}
}
// Ensure we have at least some HandlerExceptionResolvers, by registering
// default HandlerExceptionResolvers if no other resolvers are found.
if (this.handlerExceptionResolvers == null) {
this.handlerExceptionResolvers = getDefaultStrategies(context, HandlerExceptionResolver.class);
if (logger.isTraceEnabled()) {
logger.trace("No HandlerExceptionResolvers declared in servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
这个的代码依然有点长,但是有用的也就那么两句儿。大佬们应该都用过 @ControllerAdvice+@ExceptionHandler这俩玩意儿了吧。这俩玩意儿加上现在这个HandlerExceptionResolver
,就是实现我们全局异常处理能力的。
initRequestToViewNameTranslator
public static final String REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME = "viewNameTranslator";
private void initRequestToViewNameTranslator(ApplicationContext context) {
try {
this.viewNameTranslator =
context.getBean(REQUEST_TO_VIEW_NAME_TRANSLATOR_BEAN_NAME, RequestToViewNameTranslator.class);
}
catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.viewNameTranslator = getDefaultStrategy(context, RequestToViewNameTranslator.class);
}
}
从方法名也可看出来,这个bean的作用就是将通过将返回值将解析转换成一个视图文件的名字。在以前后端没有分离的时候,如果你的接口返回一个页面(视图)名称,这个页面(视图)名称就是我们的viewName
,这货就是通过这个名称去寻找对应的页面(视图),不过现在前后端分离后,这货就派不上用场了。
initViewResolvers
private void initViewResolvers(ApplicationContext context) {
this.viewResolvers = null;
// 是否要检查多容器中有没有多个视图视图解析器
if (this.detectAllViewResolvers) {
// Find all ViewResolvers in the ApplicationContext, including ancestor contexts.
Map<String, ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(context, ViewResolver.class, true, false);
if (!matchingBeans.isEmpty()) {
this.viewResolvers = new ArrayList<>(matchingBeans.values());
// We keep ViewResolvers in sorted order.
AnnotationAwareOrderComparator.sort(this.viewResolvers);
}
}
else {
try {
ViewResolver vr = context.getBean(VIEW_RESOLVER_BEAN_NAME, ViewResolver.class);
this.viewResolvers = Collections.singletonList(vr);
}
catch (NoSuchBeanDefinitionException ex) {
// Ignore, we'll add a default ViewResolver later.
}
}
// Ensure we have at least one ViewResolver, by registering
// a default ViewResolver if no other resolvers are found.
if (this.viewResolvers == null) {
this.viewResolvers = getDefaultStrategies(context, ViewResolver.class);
}
}
视图解析器(ViewResolver)是 Spring MVC 的重要组成部分,负责将逻辑视图名解析为具体的视图对象。上一个方法的RequestToViewNameTranslator
视图转换器将返回值解析成视图的名称,但是渲染当然是由当前ViewResolver
来处理视图,渲染到页面。
initFlashMapManage
private void initFlashMapManager(ApplicationContext context) {
try {
this.flashMapManager = context.getBean(FLASH_MAP_MANAGER_BEAN_NAME, FlashMapManager.class);
} catch (NoSuchBeanDefinitionException ex) {
// We need to use the default.
this.flashMapManager = getDefaultStrategy(context, FlashMapManager.class);
}
}
有过Web开发经验的人都知道,在之前的时候后端有请求转发
和请求重定向
两种方式,现在几乎不会用到了,前后端分离后都是前端直接跳转,请求转发的时候Request是同一个,所以可以在转发后拿到转发前的所有信息;但是重定向后Request
是新的,如果需要在重定向前设置一些信息以便于重定向后获取使用。这就是 FlashMap
存在的意义,FlashMap
借助 session ,重定向前通过FlashMapManager
将信息放入FlashMap
,重定向后 再借助 FlashMapManager
从 session中找到重定向后需要的 FalshMap
。
小总结
上面方法很多,但是大部分逻辑是差不多的,都是先检查容器中是否已经有对应的bean了,如果没有,就可能会初始化一个。对于部分的bean,会有一个detect
开头的属性,用来判断是否需要检查一下容器中有没有多个同类型的bean,如果没有开启这个属性,那就直接初始化默认的。
对于那些通过bean名字获取的bean,我们如果有需求,都可以用一个新的同名的bean去覆盖掉它原有的。
getDefaultStrategy
在上面讨论到的流程中,多次用到getDefaultStrategy()这个方法。我们来看一下这个方法到底干了啥。
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
// 请看这里(重点)
String value = defaultStrategies.getProperty(key);
if (value != null) {
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;
}
else {
return new LinkedList<>();
}
}
protected Object createDefaultStrategy(ApplicationContext context, Class<?> clazz) {
// 用容器,给我们创建一个指定类型的对象
return context.getAutowireCapableBeanFactory().createBean(clazz);
}
上面代码中标记了两个方法,一个是defaultStrategies#getProperty(),另一个是createDefaultStrategy(),对于createDefaultStrategy()这方法,实际上就是利用当前的容器,给我们创建一个指定类型的对象,看过之前《Spring源码解读(第二弹)-带你深入理解bean完整的生命周期全过程》的道友对这个createBean方法应该是很熟悉了。
那么现在我们就只剩下defaultStrategies#getProperty()方法了。
defaultStrategies属性
来,我们日常先看代码。从代码中可以看到,这个属性是通过静态代码段来进行初始化的,逻辑很简单,就是读取了一个类路径下面的DispatcherServlet.properties
文件。
// DispatcherServlet.java
private static final String DEFAULT_STRATEGIES_PATH = "DispatcherServlet.properties";
private static final Properties defaultStrategies;
static {
// Load default strategy implementations from properties file.
// This is currently strictly internal and not meant to be customized
// by application developers.
try {
ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
}
}
我们直接通过Idea定位到当前类所在的位置(这个各位大佬肯定会的吧)。可以看到,和DispatcherServlet
一起的就是这个配置文件了,来看看它到底是个啥。
DispatcherServlet.properties
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
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
是不是恍然大悟了,这货就定义了一些默认的实现类的全限定类名,这个时候DispatcherServlet#initStrategies()方法中的流程就更清晰了对不对。
RequestMappingHandlerAdapter
由于之后会介绍自定义的HandlerAdapter
和HandlerMapping
,因此会简单介绍一下这两个东西。
好了,进入我们喜欢的HandlerAdapter
。从上面DispatcherServlet.properties
默认的配置中我们可以看到三个Adapter,由于我们现在都是使用的@Controller来实现的接口,而RequestMappingHandlerAdapter
这个适配器就是用来处理路径到方法映射的适配器,当然这个也是其中最有理解意义的一个,我们先来看一下它的继承关系。
很愉快地就发现了两个我们比较关心的接口。一个叫InitializingBean
,另一个就是猪脚的亲生父类HandlerAdapter
。
InitializingBean
有经验的道友一看到InitializingBean
,就已经找到了它的实现方法afterPropertiesSet(),我们一起来看一下。
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
在这个方法里面,初始化了很多东西,这些当然也都是请求的时候才会用到。主要是初始化了argumentResolvers
、initBinderArgumentResolvers
、returnValueHandlers
以及@ControllerAdvice
注释的类相关的modelAttributeAdviceCache
、initBinderAdviceCache
和requestBodyAdvice
这些属性。注意:这些属性都是存放到一个列表里面的,使用过程不会对列表进行排序,因此先添加的就在最前面,按顺序调用,这个在后面将请求处理流程的时候会详细解释
1. argumentResolvers:用于给处理器方法(接口方法)和注释了@ModelAttribute的方法设置参数。
2. initBinderArgumentResolvers:用于给注释了@InitBinder的方法设置参数。
3. returnValueHandlers:返回值处理器,处理我们接口方法返回的数据。
4. modelAttributeAdviceCache和initBinderAdviceCache:分别用于缓存@ControllerAdvice注释的类里面注释了@ModelAttribute和@InitBinder的方法,也就是全局的@ModelAttribute和@initBinder方法。每个处理器自己的@ModelAttribute和@initBinder方法是在第一次使用处理器处理请求时缓存起来的,这种做法既不需要启动时就花时间遍历每个Controller查找@ModelAttribute和@InitBinder方法,又能在调用过一次后再调用相同处理器处理请求时不需要再次查找而从缓存中获取。这两种缓存的思路类似于单例模式中的饿汉式和懒汉式。
5. requestBodyAdvice:用于保存前面介绍过的实现了RequestBodyAdvice接口,可以修改返回的RequestBody的类。
好了,这个方法大体流程就介绍完了,我们继续往下。
HandlerAdapter
了解设计模式的大佬应该猜到了,这个实际上就是一个适配器模式。这个Adapter就是用来适配不同种类的Controller接口,然后在对目标方法进行调用的时候,就直接用对应的Adapter去进行调用。把统一的调用接口由Adapter封装,然后由不同种类的Adapter去处理不同类型的Contoller的,屏蔽掉Controller彼此的差异性。
你可能有疑问了,为什么还有不同的Controller?对,你没有听错,确实有。这里简单举个例子,Spring的版本中有一个接口名字就叫Controller
,实现这个接口的Controller就会由SimpleHandlerAdapter
去调用执行。
在后续的自定义当中,也会去实现我们自定义的Adapter逻辑。
我们来看一下接口,以及对应的实现方法。
HandlerAdapter
public interface HandlerAdapter {
boolean supports(Object handler);
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
long getLastModified(HttpServletRequest request, Object handler);
}
解释
supports: 判断是否能够处理当前的handler,我们之前说了,每种类型的Controller都有其对应的Adapter,就是这个意思
handler: 核心方法,利用 Handler 处理请求,然后返回一个ModelAndView,DispatcherServlet最终就是调用此方法,来返回一个ModelAndView对象
getLastModified: 获取当前请求的最后更改时间,主要用于供给浏览器判断当前请求是否修改过,从而判断是否可以直接使用之前缓存的结果,不使用缓存的情况下,直接返回-1就好
我们继续简单来看RequestMappingHandlerAdapter
对其的实现。实现是在它的超类AbstractHandlerMethodAdapter
当中。
由于之后介绍的自定义的HandlerAdapter
和HandlerMapping
的内容需要,所以稍微解释一下这里面的部分逻辑,这里我们关注supports()方法就好。至于核心handler()方法在后面讲解请求流程的篇章中会详细介绍。
AbstractHandlerMethodAdapter
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
RequestMappingHandlerAdapter
@Override
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
可以看出来,这个RequestMappingHandlerAdapter
子类实现的supportsInternal()方法直接返回的true,因此实际上它只处理handler为HandlerMethod
的类型,我们先记住这个类型,因为这个在后续介绍HandlerMapping
的时候也有它。
不知不觉篇幅已经拉的很长了,今天就到这儿吧,剩下的对于RequestMapping
的介绍,以及我们的自定义处理,就放到下一篇去吧!
Good Luck~
原文始发于微信公众号(心猿易码):Spring源码解读(第六弹)-梦开始的地方,webmvc相关组件的初始化-initStrategies
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/133316.html