SpringMVC源码分析-DispatcherServlet-init方法分析
上一篇:SpringMVC源码分析-DispatcherServlet实例化干了些什么
先吐槽一下。。。写了两小时的博客突然被俺家小屁孩按了刷新,东西不见了,建议OSCHINA能够自动定时保存啊。让我先安静一下。。。。
因为前面在阅读Tomcat源码的时候时序图中没有画调用Servlet init方法的步骤,在这里补充一下文字说明。 Tomcat调用顺序:
StandardWrapperValve.invoke()
StandardWrapper.allocate()
StandardWrapper.loadServlet()
DispatcherServet(实际是父类的).init()
通过上面的调用过程最终到达了DispatcherServlet的init(),由Spring的DefaultListableBeanFactory去完成DispatcherServlet.properties中配置类的初始化工作
时序图
说明
图中最核心的方法就是DispatcherServlet.initStrategies(),见源代码
protected void initStrategies(ApplicationContext context) {
/**
* 这里的每一个方法都非常的重要,他们都遵循一个逻辑:如果在容器中没有拿到对应类型的对象,则使用DispatcherServlet.properties当中配置
* 的值,使用Spring容器创建一个新的Bean(注意是prototype,所以在singletonObjebs中无法找到它们。。。为什么是prototype喃,说实话我也不
* 是很确定,可能因为Servlet可能会有多个吧。。。但想想又觉得很牵强因为DispatcherServlet只有一个对象,而且又是在init方法初始化的,
* init方法只会被调用一次,无论处理多少请求,它们都会是线程共享的啊。。。也许Spring并不想在singletonObjects当中存放它才设置为prototype
* 的吧
*
* 因为是用Spring进行实例化的,所以可以使用Spring提供的扩展点干预这些默认的类的行为,比如可以将protyotype设置为singleton,可以修改它们
* 一些属性的默认值等等
*/
//文件上传相关
initMultipartResolver(context);
//国际化 先从容器中拿名为localResolve的LocaleResolver.class对象,如果没有则使用默认的AcceptHeaderLocaleResolver(Spring-createBean)
initLocaleResolver(context);
//主题样式 先从容器中拿名为themeResolver的ThemeResolver.class对象,如果没有则使用默认的FixedThemeResolver(Spring-createBean)
initThemeResolver(context);
/**--------------非常重要-------------
* 初始化请求映射规则
* 先从容器中拿名为HandlerMapping.class对象列表,如果没有则使用默认的以下三个配置项
* RequestMappingHandlerMapping:这是最重要的,是通常我们在Controller当中配置的RequestMapping的映射处理类
* BeanNameUrlHandlerMapping:是我们的Controller实现了Controller接口,然后用@Component("beanName"),访问时url:localhost:port/contextpath/beanName
* RouterFunctionMapping:通过HttpRequestHandler实现Controller,具体细节不清楚,从来没见过更没用过
*/
initHandlerMappings(context);
/**--------------非常重要-------------
* Request\Response的重要处理,比如入参与出参的同样格式化
* 先从容器中拿名为HandlerAdapter.class对象列表,如果没有则使用默认的以下四个配置项:
* HttpRequestHandlerAdapter:
* SimpleControllerHandlerAdapter:
* RequestMappingHandlerAdapter:
* HandlerFunctionAdapter:
*/
initHandlerAdapters(context);
/**
* 异常 先从容器中拿HandlerExceptionResolver.class对象列表,如果没有则使用默认的以下三个配置项
* ExceptionHandlerExceptionResolver:
* ResponseStatusExceptionResolver:
* DefaultHandlerExceptionResolver:
*/
initHandlerExceptionResolvers(context);
//请求到视图的转换 从容器中拿名为viewNameTranslator的RequestToViewNameTranslator,如果没有则使用默认的DefaultRequestToViewNameTranslator
initRequestToViewNameTranslator(context);
//视图转换器 从容器中拿ViewResolver对象列表,如果没有则使用默认的InternalResourceViewResolver
initViewResolvers(context);
//重定向视图管理器 从容器中拿名为flashMapManager的FlashMapManager,如果没有则使用默认的SessionFlashMapManager
initFlashMapManager(context);
}
源码中已经比较详细的写了每个方法的注释,就不再赘述了。
概要说明
这里面的每个方法当然都很重要,但是结合日常开发来分析,initHandlerMappings,initHandlerAdapters,initViewResolvers它们三个是解决请求映射、参数解析-绑定-格式化、视图渲染等功能,所以重点拿initHandlerMappings,initHandlerAdapters方法来分析。这两个方法底层逻辑都是先从Spring中找有么有对应的Bean,如果没有则使用DispatcherServlet.properties中配置的Bean,由Spring完成他们的实例化。由于时间、篇幅等原因就不分析里面的每一个类的实例化和功能,这三种分别拿一个比较典型的类进行分析。
initHandlerMappings
使用RequestMappingHandlerMapping作为典型进行讲解它的实例化,不包含它的处理请求的部分
此类干的重要事情就是收集Controller当中的符合规则的HandlerMethod,在处理请求的时候,使用请求路径和这些HandlerMethod进行匹配,找最优匹配进行处理
虽然我选择了这个类做讲解,但是从哪里入手看喃?既然这个类是被Spring实例化的,那么它肯定实现什么接口口、继承了什么类或者使用了什么Spring的特殊注解,拿出它的类结构图看看
可以看到实现了InitializingBean接口,Spring在实例化对象快结束的时候会调用实现类的afterPropertiesSet()方法。
那就看看这个方法
public void afterPropertiesSet() {
this.config = new RequestMappingInfo.BuilderConfiguration();
this.config.setUrlPathHelper(getUrlPathHelper());
this.config.setPathMatcher(getPathMatcher());
this.config.setSuffixPatternMatch(this.useSuffixPatternMatch);
this.config.setTrailingSlashMatch(this.useTrailingSlashMatch);
this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch);
this.config.setContentNegotiationManager(getContentNegotiationManager());
//这里面就是收集Controller的映射关系
super.afterPropertiesSet();
}
把断点打到这里一步步的往下走,最终可以找到关键代码,这个过程就省略了,我直接把调用栈贴出来,已经对比较重要方法做注释
RequestMappingHandlerMapping.afterPropertiesSet()
AbstractHandlerMethodMapping.afterPropertiesSet()
AbstractHandlerMethodMapping.initHandlerMethods()
AbstractHandlerMethodMapping.processCandidateBean()
AbstractHandlerMethodMapping.detectHandlerMethods()
RequestMappingHandlerMapping.getMappingForMethod()
AbstractHandlerMethodMapping.initHandlerMethods()
protected void initHandlerMethods() {
//循环Spring Context当中所有的Bean,看是否满足被@Controller或者@RequestMapping修饰的条件,如果满足则进行映射关系收集
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
processCandidateBean(beanName);
}
}
/**
* 开启锁,返回一个只可读的请求路径与HandlerMethod的映射Map
*/
handlerMethodsInitialized(getHandlerMethods());
}
AbstractHandlerMethodMapping.processCandidateBean()
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);
}
}
//isHandler:看BeanType是否@Controller或者@RequestMapping修饰,如果满足条件,才进行下面的映射关系收集逻辑
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName);
}
}
AbstractHandlerMethodMapping.detectHandlerMethods()
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
/**
* 从这里开始有两层调用都用到了lambda表达式,看源码时需要注意,在DEBUG时,会跳回到上一层的lambda表达式对应的代码段执行
*
* selectMethods里面是在从缓存(创建Bean的时候因为其他功能已经解析了一遍类的Class)中拿类的所有Method,然后
* 调用getMappingForMethod()方法将Controller符合条件的method与期望请求路建立关系以备请求时做匹配使用,并将匹配到结果放入methods集合
* 当中
*/
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
/**
* 注册HandlerMethod,只有注册之后,在后面才能从registry当中拿到一个只读的Mapping
*/
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
RequestMappingHandlerMapping.getMappingForMethod()
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
/**
* 从Method上面拿到请求路径
*/
RequestMappingInfo info = createRequestMappingInfo(method);
if (info != null) {
/**
* 从Type上面拿到请求路径
*/
RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
if (typeInfo != null) {
/**
* 进行组合:比如method上面是"/user/{age}",type上面是"/my",则组合后的结果就是"/my/user/{age}"
*/
info = typeInfo.combine(info);
}
String prefix = getPathPrefix(handlerType);
if (prefix != null) {
info = RequestMappingInfo.paths(prefix).build().combine(info);
}
}
return info;
}
到此Spring已经使用RequestMappingHandlerMapping为使用@Controller和@RequestMapping注解的Controller类收集好了所有的请求映射,等待处理请求。
initHandlerAdapters
使用RequestMappingHandlerAdapter作为典型进行讲解它的实例化,不包含它的处理请求的部分
分析它的套路和RequestMappingHandlerMapping一样,上来就看afterPropertiesSet()方法
public void afterPropertiesSet() {
/**
* 找到Spring容器中被@ControllerAdvice和@RestControllerAdvice修饰的Bean,并添加到一个List当中
* @ControllerAdvice是一个Controller增强器,在项目中曾经被用来做异常统一处理
* 这两个注解可以结合@ExceptionHandler, @InitBinder, @ModelAttribute三个注解使用将方法作用到全局上面
* 详细使用参考官网:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html
*/
initControllerAdviceCache();
/**
* ----------------非常重要----------------------
* argumentResolvers:参数解析器
* initBinderArgumentResolvers:初始化参数绑定解析器
* returnValueHandlers:Controller.method invoke之后返回值解析器
*
* SpringMVC都默认提供了一大批各种各样的解析器,它们共同组成了SpringMVC的强大功能
*
*/
if (this.argumentResolvers == null) {
/**
* 获取默认的参数解析器,这些解析器是写死在代码中的
*/
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
/**
* 取默认的InitBinder参数解析器,这些解析器是写死在代码中的
*/
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
/**
* 取默认的返回值解析器,这些解析器是写死在代码中的
*/
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
此方法很简单,就是实例化默认的各种解析器,在此之前将@ControllerAdvice修饰的Bean找出来放在一个集合当中
关于@ControllerAdvice可以在官网文档中去出看或者百度
随便找一个Revolver看看它的功能
RequestParamMethodArgumentResolver负责识别@RequestParam的参数,将其中的name和真正请求当中的Key进行匹配
PathVariableMethodArgumentResolver负责识别rest风格中具体位置中的参数值
Spring命名确实也很规范,通过看名称几乎就能知道它是干什么的,所以开发当中命名很重要,不要嫌弃名称太长,写起来太麻烦。