上一篇:SpringMVC源码分析-DispatcherServlet实例化干了些什么html
先吐槽一下。。。写了两小时的博客忽然被俺家小屁孩按了刷新,东西不见了,建议OSCHINA可以自动定时保存啊。让我先安静一下。。。。java
由于前面在阅读Tomcat源码的时候时序图中没有画调用Servlet init方法的步骤,在这里补充一下文字说明。 Tomcat调用顺序:web
StandardWrapperValve.invoke() StandardWrapper.allocate() StandardWrapper.loadServlet() DispatcherServet(实际是父类的).init()
经过上面的调用过程最终到达了DispatcherServlet的init(),由Spring的DefaultListableBeanFactory去完成DispatcherServlet.properties中配置类的初始化工做spring
图中最核心的方法就是DispatcherServlet.initStrategies(),见源代码json
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完成他们的实例化。因为时间、篇幅等缘由就不分析里面的每个类的实例化和功能,这三种分别拿一个比较典型的类进行分析。app
使用RequestMappingHandlerMapping做为典型进行讲解它的实例化,不包含它的处理请求的部分源码分析
此类干的重要事情就是收集Controller当中的符合规则的HandlerMethod,在处理请求的时候,使用请求路径和这些HandlerMethod进行匹配,找最优匹配进行处理ui
虽然我选择了这个类作讲解,可是从哪里入手看喃?既然这个类是被Spring实例化的,那么它确定实现什么接口口、继承了什么类或者使用了什么Spring的特殊注解,拿出它的类结构图看看this
能够看到实现了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; }
这里有必要把RequestMappingInfo单独解释一下,RequestMappingInfo对应@RequestMapping,里面的属性在init的时候也会根据@RequestMapping的参数值对应设置
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { @Nullable private final String name; //Patterns对应url,就是RequestMapping注解value中的配置 private final PatternsRequestCondition patternsCondition; //Methods对应 http method,如GET,POST,PUT,DELETE等 private final RequestMethodsRequestCondition methodsCondition; //params对应http request parameter private final ParamsRequestCondition paramsCondition; //headers 对应http request 的请求头 private final HeadersRequestCondition headersCondition; //consumes对应request的提交内容类型content type,如application/json, text/html private final ConsumesRequestCondition consumesCondition; //produces指定返回的内容类型的content type,仅当request请求头中的(Accept)类型中包含该指定类型才返回 private final ProducesRequestCondition producesCondition; //若是以上都还不能达到你过滤请求的目的,还能够自定义 private final RequestConditionHolder customConditionHolder; //-------------省略不少代码------------------ /** * 请求匹配到多个RequestMapping,则须要排序,选择最优的HandlerMethod进行处理 */ public int compareTo(RequestMappingInfo other, HttpServletRequest request) { int result; // Automatic vs explicit HTTP HEAD mapping if (HttpMethod.HEAD.matches(request.getMethod())) { result = this.methodsCondition.compareTo(other.getMethodsCondition(), request); if (result != 0) { return result; } } result = this.patternsCondition.compareTo(other.getPatternsCondition(), request); if (result != 0) { return result; } result = this.paramsCondition.compareTo(other.getParamsCondition(), request); if (result != 0) { return result; } result = this.headersCondition.compareTo(other.getHeadersCondition(), request); if (result != 0) { return result; } result = this.consumesCondition.compareTo(other.getConsumesCondition(), request); if (result != 0) { return result; } result = this.producesCondition.compareTo(other.getProducesCondition(), request); if (result != 0) { return result; } // Implicit (no method) vs explicit HTTP method mappings result = this.methodsCondition.compareTo(other.getMethodsCondition(), request); if (result != 0) { return result; } result = this.customConditionHolder.compareTo(other.customConditionHolder, request); if (result != 0) { return result; } return 0; } }
到此Spring已经使用RequestMappingHandlerMapping为使用@Controller和@RequestMapping注解的Controller类收集好了全部的请求映射,等待处理请求。
使用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命名确实也很规范,经过看名称几乎就能知道它是干什么的,因此开发当中命名很重要,不要嫌弃名称太长,写起来太麻烦。