一、DispatcherServlet 类解析
直接进入 DispatcherServlet 类,核心源码如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
// ...
try {
ModelAndView mv = null;
try {
// 获取映射关系
mappedHandler = getHandler(processedRequest);
// 获取可以处理的方法
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// 调用处理方法生成视图
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
}
// 主要是视图解析、渲染过程
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
}
接下来是一段非常长的分析过程,对上述四个方法:getHandler()、getHandlerAdapter()、handle()、processDispatchResult() 进行分析。
1、getHandler() 方法分析
一个请求过来,第一个进入 getHandler() 方法,通过这个方法找到对应映射关系,核心源码如下:
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
tips:为什么要将 HandlerMethod 或者类本身再次包装 HandlerExecutionChain 呢,因为 SpringMVC 有很多的拦截器,所以就需要将目标方法、拦截器包装到 HandlerExecutionChain 上下文中。
这里的 handlerMappings 集合在哪里初始化的呢?先分析完整个流程再回头看。继续进入 mapping.getHandler(request) 内部逻辑,核心源码如下:
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
}
这里返回的 handler 不一定是都是 HandlerMethod 类型,有可能是类的本身,只有通过 @RequestMapping 注解方式的才会返回 HandlerMethod 类型,返回类本身的比如通过实现 Controller、AbstractController、HttpRequestHandler 接口的类,因为方法就只有一个,找到类就能确定由哪个方法去处理当前请求,所以此时返回的 handler 就是类本身。所以这就是为什么这里是通过 Object 类型来接受返回 handler 对象。
查看 getHandlerInternal() 方法是一个钩子方法,源码如下:
@Nullable
protected abstract Object getHandlerInternal(HttpServletRequest request) throws Exception;
这里有两个比较明显的子类实现分别是:AbstractHandlerMethodMapping、AbstractUrlHandlerMapping 看名字可以知道第一个就是针对 @RequestMapping 注解的,第二个针对实现了 Controller、AbstractController、HttpRequestHandler 接口的。
先分析简单 AbstractUrlHandlerMapping 类,进入核心源码如下:
@Override
@Nullable
protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = initLookupPath(request);
Object handler = lookupHandler(lookupPath, request);
// ...
return handler;
}
initLookupPath() 根据请求 request 传过来的数据生成一个映射路径,相当于 key。根据 key 找到对应 handler。
比如访问下面这个 HelloHttpRequestHandler 类,key = /hello,handler 值就是 HelloHttpRequestHandler 本身的类实例。
@Controller("/hello")
public class HelloHttpRequestHandler implements HttpRequestHandler {
@Override
public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("hello HttpRequestHandler.....");
}
}
最终 lookupPath = /hello,调用 lookupHandler() 方法,核心源码如下:
protected Object lookupHandler(String lookupPath, HttpServletRequest request) throws Exception {
Object handler = getDirectMatch(lookupPath, request);
if (handler != null) {
return handler;
}
}
继续进入 getDirectMatch() 核心源码如下:
@Nullable
private Object getDirectMatch(String urlPath, HttpServletRequest request) throws Exception {
Object handler = this.handlerMap.get(urlPath);
if (handler != null) {
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
validateHandler(handler, request);
return buildPathExposingHandler(handler, urlPath, urlPath, null);
}
return null;
}
从上面这段源码有明显可发现,最终是从 handlerMap 中取查 handler 值,urlPath = /hello,所以这里可以总结出: 实现 Controller、AbstractController、HttpRequestHandler 接口类型的 Handler 都是类本身实例,映射关系在 handlerMap 集合中有保存。对于这个 handlerMap 啥时候初始化赋值的呢?,分析完整个流程在回头看。
然后再分析 AbstractHandlerMethodMapping 类,进入核心源码如下:
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
}
if (!matches.isEmpty()) {
/**
* 一般情况下一个 url 就只会映射到一个 RequestMappingInfo,但是也不能保证只有一个,当一个 url 匹配到多个 RequestMappingInfo
* 时,就要去校验哪个最合适
*/
Match bestMatch = matches.get(0);
return bestMatch.getHandlerMethod();
}
}
上面这段源码主要观察这句 this.mappingRegistry.getRegistrations(),进入核心源码如下:
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
public Map<T, MappingRegistration<T>> getRegistrations() {
return this.registry;
}
}
可以发现 @RequestMapping 注解的映射关系也是保存在一个 Map 类型的 registry 容器中,解析 @RequestMapping 注解被封装成 RequestMappingInfo 对象,作为请求 registry 容器中的 key。
registry 容器的 value 值就是 HandlerMethodMapping#MappingRegistration 类型实例,MappingRegistration 封装着 HandlerMethod 类型从 Handler,也就是 Controller 中对应的具体方法+@RequestMapping 信息被封装成了一个 HandlerMethod 对象。所以可以总结出对于 @RequestMapping 这种形式的映射关系最终也是保存在一个 Map 容器中。
这里 registry 容器中的值是什么初始化进去的呢?分析完整个流程再回头分析。
小总结
分析到这里,可以知道 getHandler() 方法最终会返回两个类型值,一个是 HandlerMethod(使用 @RequestMapping 注解的返回这个类型)、一个是类的本身(实现 Controller、HttpRequestHandler 接口返回类本身实例)。
侧面说明通过 request 请求找到了一个可以处理当前请求的方法,接下来就是看怎么调用该方法,那就要看 getHandlerAdapter() 方法怎么去适配调用,因为每种类型的 Controller 里的方法形式都不一样,所以需要一层适配器封装调用。
2、getHandlerAdapter() 方法分析
接着看第二个大步骤 getHandlerAdapter() 方法,核心源码如下:
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
/**
* 采用策略模式处理不同类型的 handler
*/
if (this.handlerAdapters != null) {
for (HandlerAdapter adapter : this.handlerAdapters) {
if (adapter.supports(handler)) {
return adapter;
}
}
}
}
可以看到提供很多 HandlerAdapter 适配器,对于handlerAdapters 初始化在哪里? 后面分析,用来适配处理不同形式 Controller 的方法调用,因为每一种类型的 Controller 形式有点不一样,所以需要适配器去适配处理,说白了就是中间包装了一层调用而已,这里分析举个常见的:
-
HandlerFunctionAdapter:处理函数式编程类型的 Controller 方法,暂时没用过。
-
HttpRequestHandlerAdapter:处理实现 HttpRequestHandler 接口的 Controller 方法调用,该方法无需要返回值,所以无需返回值时可以定义 HttpRequestHandler 类型的 Controller。
-
SimpleControllerHandlerAdapter:处理实现 Controller、AbstractController 接口的 Controller 方法调用,该方法需要返回值。
-
AbstractHandlerMethodAdapter:处理 @RequestMapping 注解形式 Controller 方法调用,可以有返回值也可以不需要,更像是 Controller 和 HttpRequestHandler 类型组合。
上面那种写法就是很典型的动态策略模式
,非常具有参考意义。闲话少说,接着往下分析。
回到上面那个请求 http://localhost:9292/hello 请求的 Controller 类型为 HttpRequestHandler 类型,所以会被 这个 HttpRequestHandlerAdapter 适配器进行处理。具体看他怎么处理的,核心源码如下:
public class HttpRequestHandlerAdapter implements HandlerAdapter {
@Override
public boolean supports(Object handler) {
/**
* 支持实现了 HttpRequestHandler 接口的 Controller
*/
return (handler instanceof HttpRequestHandler);
}
@Override
@Nullable
public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
((HttpRequestHandler) handler).handleRequest(request, response);
return null;
}
}
从上面源码可得知,handler 此时是 HelloHttpRequestHandler,类型就是 HttpRequestHandler,所以会调用 handle() 处理方法,这个方法做了两件事,回调 HelloHttpRequestHandler 类的 handleRequest() 方法,然后返回一个 null 值。就是做了个简单的调用过程,HttpRequestHandlerAdapter 适配器就走了这两件事情。
在举一个例子,例如下面这个 Controller,代码如下:
@Controller
public class JspController {
@RequestMapping("/toJsp")
public String toJsp() {
System.out.println(">>>>>>toJsp..");
return "abc";
}
}
在请求 http://localhost:9292/toJsp 的时候,就会被 AbstractHandlerMethodAdapter 适配器处理,适配器里面肯定也是封装怎么去调用 JspController 中的 toJsp() 方法。进入核心源码如下:
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {
private int order = Ordered.LOWEST_PRECEDENCE;
@Override
public final boolean supports(Object handler) {
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
@Override
@Nullable
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
return handleInternal(request, response, (HandlerMethod) handler);
}
}
从上述源码中可得知,JspController 是 @RequestMapping 形式的构成的,所以 supports() 方法中 handler 就是 HandlerMethod 类型,刚好 AbstractHandlerMethodAdapter 适配器就可以处理,然后调用到 handle() 处理方法,进入 handleInternal() 核心源码如下:
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
// ...
mav = invokeHandlerMethod(request, response, handlerMethod);
// ...
return mav;
}
继续进入 invokeHandlerMethod() 方法内部,核心源码如下:
@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
/** mvc_tag: 包装成自己方便使用的 DTO */
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 1、请求过来的字段和 Person 里面的字段一一绑定,就是这个绑定器要干的事情
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
// 2、 这几个参数都在 pre_params 标记处准备好了
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
// 3、parameterNameDiscoverer 获取一个方法上参数名称,Spring 封装的一个工具类
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
// 4、 mvc_tag: 在一次请求中共享Model 和 View 数据的临时容器
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
/**
* 5、调用 @ModelAttribute 注解修饰的方法(这是个公共方法),把公共逻辑抽取到这个方法,然后用这个 @ModelAttribute 注解修饰该方法
* 这样再其他方法中就不用手动调用,Spring 会反射调用后把返回结果封装到 ModelAndViewContainer 中的 ModelMap 属性,
* 具体没啥太多可用价值
*/
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
// 6、 开始去调用目标方法,并且把临时容器穿进去了
invocableMethod.invokeAndHandle(webRequest, mavContainer);
// 7、从临时容器中直接抽取出需要的 ModelAndView
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
从上面源码发现做了很多事情:
-
获取参数绑定工厂 binderFactory
-
将 handlerMethod 再次封装成 ServletInvocableHandlerMethod
-
设置参数解析器
-
设置返回值解析器
-
设置数据绑定工厂(@RequestBody 注解有用)
-
new 了个 ModelAndViewContainer 容器,类似于 Context
上面这些获取到的东西在哪里初始化的?全部回过头来再看,继续往后分析,进入 invokeAndHandle() 方法核心源码如下:
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
}
可以分成以下几步:
- 调用 Controller 中具体方法
- 设置视图渲染开关 requestHandled,false 表示需要视图渲染,true 表示不用视图渲染。
- Controller 中方法返回值处理
先看到 invokeForRequest() 方法,核心源码如下:
@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
return doInvoke(args);
}
@Nullable
protected Object doInvoke(Object... args) throws Exception {
Method method = getBridgedMethod();
ReflectionUtils.makeAccessible(method);
try {
if (KotlinDetector.isSuspendingFunction(method)) {
return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);
}
return method.invoke(getBean(), args);
}
}
可以发现这个方法中主要去解析参数值,并且通过反射调用 Controller 中的具体方法。看看他是怎么进行对参数解析的,进入 getMethodArgumentValues() 方法,核心源码如下:
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
if (this.resolvers.supportsParameter(parameter)){
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
}
return args;
}
从上述源码可以得知,对每一个参数进行处理,且每个类型参数都会有一种策略能够进行处理,不能处理直接抛异常,请求流程结束。这也是典型的动态策略模式
。举个例子:
@Controller
public class JspController {
@RequestMapping("/toJsp")
public String toJsp(
@RequestBody Person,
@RequestParam String name,
@PathVariable Integer id,
@RequestHeader String contentType,
String name,Map<String,String> map) {
System.out.println(">>>>>>toJsp..");
return "abc";
}
}
先这种每个参数类型都不一样,必然需要一种策略去处理它,所以对于参数解析器也是有一大堆目前至少>17种。
这里解析完参数值,最终就可以通过反射去调用具体方法,执行自定义逻辑处理。
然后根据返回值设置开关 requestHandled,有返回值设置成 false,表示需要视图渲染,若无则设置成 true,表示没有视图渲染。
然后最后就是返回值的处理,进入 handleReturnValue() 方法核心源码如下:
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
if (handler == null) {
throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
}
handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
boolean isAsyncValue = isAsyncReturnValue(value, returnType);
for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
continue;
}
if (handler.supportsReturnType(returnType)) {
return handler;
}
}
return null;
}
也是一样需要一堆的策略处理不同类型的返回值。如下图示:
返回值处理完成之后,回到最上一层调用,继续进入 return getModelAndView(mavContainer, modelFactory, webRequest); 核心源码如下:
3、创建 ModelAndView 对象
@Nullable
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
modelFactory.updateModel(webRequest, mavContainer);
/**
* 根据 ModelAndViewContainer 容器中的 requestHandled 属性来判断需不需要渲染视图
* 如果是 @ResponseBody 明显是不需要的此时 requestHandled = true,立马就 return 结束了
*/
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (request != null) {
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
}
return mav;
}
从上述这段源码可以得知,requestHandled=true 时,就直接返回 null,表示不需要视图渲染,也就不需要执行下面这段 ModeAndView 的数据准备。如果是 false,就需要视图渲染,那么这里就会执行下面这一段代码,准备好 Mode 数据,指定好逻辑视图,如果有闪存也会准备好闪存数据。最终返回 ModelAndView 出去。那么继续往下执行就是视图解析流程了。
4、视图解析和渲染
进入 processDispatchResult() 核心源码如下:
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
@Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
@Nullable Exception exception) throws Exception {
if (mv != null && !mv.wasCleared()) {
render(mv, request, response);
}
}
从上面这段小小的代码就可以看出,mv 也就是 ModelAndView 对象,如果为 null,就不需要视图渲染。什么时候 mv 为 null?上面已经分析过了,是通过一个 requestHandled 开关控制的,requestHandled=true 时,就直接返回 null,表示不需要视图渲染,也就不需要执行下面这段 ModeAndView 的数据准备。如果是 false,就需要视图渲染。
进入 render() 核心源码如下:
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
// 获取逻辑视图名称
String viewName = mv.getViewName();
// 通过视图解析器生成视图 View
View view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
// 视图再去渲染
view.render(mv.getModelInternal(), request, response);
}
这里举几个常见的视图解析器:
- InternalResourceViewResolver:生成 JSP页面的 View 视图,Spring 不判断是否一定存在 JSP 页面,Tomcat 自行处理
- FreeMarkerViewResolver:生成 .ftl 模版页面的 View 视图,Spring 会判断是否存 .ftl 文件资源
- ThymeleafViewResolver:生成 .html 模版页面的 View 视图,Spring 会判断是否存 .html 文件资源
视图 View 生成成功,就要展示效果是啥样了,那就需要调用 View 视图里面的 render() 渲染方法去展示效果,让你肉眼可见。
最终整个 SpringMVC 调用流程就算结束。剩下就是去分析哪些 get() 到得数据是在哪里初始化的。
一个请求过来,第一个进入 getHandler() 方法,通过这个方法找到对应映射关系,核心源码如下:
二、HandlerMapping 初始化
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
看下面这段代码 handlerMappings 的初始化值在哪里呢?可以看到一段源码如下:
protected void initStrategies(ApplicationContext context) {
initMultipartResolver(context);
initLocaleResolver(context);
initThemeResolver(context);
initHandlerMappings(context);
initHandlerAdapters(context);
initHandlerExceptionResolvers(context);
initRequestToViewNameTranslator(context);
initViewResolvers(context);
initFlashMapManager(context);
}
tips: 在容器启动时,initStrategies() 会被 ContextRefreshListener监听类触发调用到,从而初始化 webmvc 需要的组件。
可以发现在这里初始化 HandlerMapping、HandlerAdapter、ViewResolver,接下来先看一下 initHandlerMappings() 方法,核心源码如下:
private void initHandlerMappings(ApplicationContext context) {
this.handlerMappings = null;
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");
}
}
for (HandlerMapping mapping : this.handlerMappings) {
if (mapping.usesPathPatterns()) {
this.parseRequestPath = true;
break;
}
}
}
从上述源码可以看出从两个地方初始化:@EnableWebMvc
或 DispatcherServlet.properties
:
1、@EnableWebMvc 注解方式
beansOfTypeIncludingAncestors(HandlerMapping.class) 方法可以获取到容器中配置的 HandlerMapping 实现类,一般是在 @EnableWebMvc 注解会引入配置类,在配置类中通过 @Bean 注入 HandlerMapping 实现类。
@EnableWebMvc 注解源码如下:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
是通过 Spring 注解 @Import 引入配置类 DelegatingWebMvcConfiguration,核心源码如下:
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
}
核心源码在父类 WebMvcConfigurationSupport,先看看初始化 HandlerMapping 核心源码如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
@SuppressWarnings("deprecation")
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// ...
RequestMappingHandlerMapping mapping = createRequestMappingHandlerMapping();
// ...
return mapping;
}
@Bean
public BeanNameUrlHandlerMapping beanNameHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// ...
BeanNameUrlHandlerMapping mapping = new BeanNameUrlHandlerMapping();
// ...
return mapping;
}
@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
// ...
return handlerMapping;
}
@Bean
public RouterFunctionMapping routerFunctionMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
RouterFunctionMapping mapping = new RouterFunctionMapping();
// ...
return mapping;
}
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// ...
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
// ...
return handlerMapping
}
@Bean
@Nullable
public HandlerMapping defaultServletHandlerMapping() {
Assert.state(this.servletContext != null, "No ServletContext set");
DefaultServletHandlerConfigurer configurer = new DefaultServletHandlerConfigurer(this.servletContext);
configureDefaultServletHandling(configurer);
return configurer.buildHandlerMapping();
}
}
2、DispatcherServlet.properties 配置方式
如果通过上述配置类都没有找到 HandlerMapping 实现类,则还会有兜底方案,getDefaultStrategies() 方法中会去找 DispatcherServlet.properties
配置文件中配置的 HandlerMapping 实现类。如下图示:
DispatcherServlet.properties
配置文件如下:
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
BeanNameUrlHandlerMapping: 处理实现 Controller、AbstractController、HttpRequestHandler 接口的 Controller 映射关系,其实底层维护的是一个 Map。
RequestMappingHandlerMapping:处理的是 @RequestMapping 形式的 Controller 关系映射,底层都是维护一个 Map 容器。
SimpleUrlHandlerMapping:简单的 url 关系映射,你可以自定义某个 url 然后绑定一个处理器即可,做扩展用的多,比如静态资源访问,Controller 跳转等。
3、SimpleUrlHandlerMapping 简单介绍
这个类的源码如下:
public class SimpleUrlHandlerMapping extends AbstractUrlHandlerMapping {
private final Map<String, Object> urlMap = new LinkedHashMap<>();
public SimpleUrlHandlerMapping() {
}
public SimpleUrlHandlerMapping(Map<String, ?> urlMap) {
setUrlMap(urlMap);
}
public SimpleUrlHandlerMapping(Map<String, ?> urlMap, int order) {
setUrlMap(urlMap);
setOrder(order);
}
public void setMappings(Properties mappings) {
CollectionUtils.mergePropertiesIntoMap(mappings, this.urlMap);
}
}
发现里面维护了一个 urlMap 容器,肯定是用来维护 urlPath 和 handler 的映射关系的。用这个类的时候只需要将 urlPath 和 handler 关系存放到 urlMap 容器中,但是要注意一点,这里只支持 handler 是类本身的 Controller,因为注册关系的时候,只能填一个值,而这个值不能精确到某个方法,所以 @RequestMapping 这种形式的不能通过这个进行绑定。这个只支持实现 Controller、AbstractController、HttpRequestHandler 类型的 Controller。
举个使用案例如下:
@Configuration
public class SimpleUrlConfig {
// @Bean
public SimpleUrlHandlerMapping simpleUrlHandlerMapping() {
/*
Map<String, String> urlMap = new HashMap<>();
urlMap.put("area/index", "helloSimpleController");
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping(urlMap);
或
*/
SimpleUrlHandlerMapping simpleUrlHandlerMapping = new SimpleUrlHandlerMapping();
Properties properties = new Properties();
// key 就是访问路径,你自己随便定义,这里写的是 area/index
// value 值就是 Controller 类本身在 Spring 容器中的 beanName
properties.put("area/index","helloSimpleController");
simpleUrlHandlerMapping.setMappings(properties);
return simpleUrlHandlerMapping;
}
}
// HelloSimpleController 在 Spring 中 beanName = helloSimpleController
@Component
public class HelloSimpleController extends AbstractController {
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
response.getWriter().write("ControllerController execute..");
return null;
}
}
这样就可以将某个 url 访问路径绑定到某个 Controller 上,在浏览器上访问:http://localhost:8887/area/index
就可以访问到 Controller。
然后再看到这个类的另外俩个使用案例如下:
@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/gotoJsp").setViewName("abc2");
}
}
自定义 webmvc 功能大家应该都用过,通过 addResourceHandlers() 指定访问静态资源,通过 addViewControllers() 方法指定跳转到哪个页面。这两个功能的底层都是 SimpleUrlHandlerMapping 类完成的。
先来分析 addResourceHandlers() 添加静态资源访问方法,debug 跟踪最终调用在以下核心源码中:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
@Nullable
public HandlerMapping resourceHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
ResourceHandlerRegistry registry = new ResourceHandlerRegistry(this.applicationContext,
this.servletContext, contentNegotiationManager, pathConfig.getUrlPathHelper());
addResourceHandlers(registry);
AbstractHandlerMapping handlerMapping = registry.getHandlerMapping();
return handlerMapping;
}
@Nullable
protected AbstractHandlerMapping getHandlerMapping() {
if (this.registrations.isEmpty()) {
return null;
}
Map<String, HttpRequestHandler> urlMap = new LinkedHashMap<>();
for (ResourceHandlerRegistration registration : this.registrations) {
ResourceHttpRequestHandler handler = getRequestHandler(registration);
for (String pathPattern : registration.getPathPatterns()) {
urlMap.put(pathPattern, handler);
}
}
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
}
从上述源码可知把传入的静态资源访问路径都封装到了 SimpleUrlHandlerMapping 类中的 urlMap 容器中。这就是你为什么写一行这样的代码 registry.addResourceHandler(“/**”).addResourceLocations(“classpath:/static/”) 就可以在浏览器上访问到静态资源,因为 SimpleUrlHandlerMapping 类帮你建立好了访问路径和 Controller 映射关系。这里的 Controller 就是 ResourceHttpRequestHandler。每一条路径对应一个新的 ResourceHttpRequestHandler 类。
在来分析 addViewControllers() 方法,debug 追踪到调用处,核心源码如下:
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
@Nullable
public HandlerMapping viewControllerHandlerMapping(
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
ViewControllerRegistry registry = new ViewControllerRegistry(this.applicationContext);
addViewControllers(registry);
AbstractHandlerMapping handlerMapping = registry.buildHandlerMapping();
return handlerMapping;
}
@Nullable
protected SimpleUrlHandlerMapping buildHandlerMapping() {
if (this.registrations.isEmpty() && this.redirectRegistrations.isEmpty()) {
return null;
}
Map<String, Object> urlMap = new LinkedHashMap<>();
for (ViewControllerRegistration registration : this.registrations) {
urlMap.put(registration.getUrlPath(), registration.getViewController());
}
for (RedirectViewControllerRegistration registration : this.redirectRegistrations) {
urlMap.put(registration.getUrlPath(), registration.getViewController());
}
return new SimpleUrlHandlerMapping(urlMap, this.order);
}
}
public class ViewControllerRegistration {
private final ParameterizableViewController controller = new ParameterizableViewController();
protected ParameterizableViewController getViewController() {
return this.controller;
}
}
从上述代码中可以知道 SimpleUrlHandlerMapping 类将 urlPath 和 ParameterizableViewController 建立映射关系,这样就知道为什么你在 addViewControllers() 方法中添加一行 registry.addViewController(“/gotoJsp”).setViewName(“abc2”); 这样的代码就可以在浏览器上面进行访问。访问路径是:http://localhost:8887/gotoJsp。
其中 abc.jsp 在 webapp 下面需要存在,不然 404 错误。
最终访问效果如下:
所以 SimpleUrlHandlerMapping 这个类是非常重要的,可以用这个类来做一些动态映射关系绑定等等。
三、HandlerAdapter 初始化
HandlerAdapter 和 HandlerMapping 基本类似,核心源码如下:
private void initHandlerAdapters(ApplicationContext context) {
this.handlerAdapters = null;
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");
}
}
}
一样也是两种方式 @EnableWebMvc
和 DispatcherServletl.properties
。
1、@EnableWebMvc 注解方式
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
@Bean
public RequestMappingHandlerAdapter createRequestMappingHandlerAdapter() {
return new RequestMappingHandlerAdapter();
}
@Bean
public HandlerFunctionAdapter handlerFunctionAdapter() {
HandlerFunctionAdapter adapter = new HandlerFunctionAdapter();
return adapter;
}
@Bean
public HttpRequestHandlerAdapter httpRequestHandlerAdapter() {
return new HttpRequestHandlerAdapter();
}
@Bean
public SimpleControllerHandlerAdapter simpleControllerHandlerAdapter() {
return new SimpleControllerHandlerAdapter();
}
}
2、DispatcherServlet.properties 配置文件
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
四、ViewResolver 初始化
视图解析器初始化,进入核心源码如下:
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);
if (logger.isTraceEnabled()) {
logger.trace("No ViewResolvers declared for servlet '" + getServletName() +
"': using default strategies from DispatcherServlet.properties");
}
}
}
可以发现视图解析的初始化和 HandlerAdapter 和 HandlerMapping 基本一个套路,但是这里需要注意的是 @EnableWebMvc 注解在注入视图解析器时有一些细节。如下所示:
1、@EnableWebMvc 注入视图解析器
进入源码如下:
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
ViewResolverRegistry registry =
new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
configureViewResolvers(registry);
if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, ViewResolver.class, true, false);
// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
// 此时视图解析器就是 InternalResourceViewResolver
if (names.length == 1) {
registry.getViewResolvers().add(new InternalResourceViewResolver());
}
}
ViewResolverComposite composite = new ViewResolverComposite();
composite.setOrder(registry.getOrder());
composite.setViewResolvers(registry.getViewResolvers());
if (this.applicationContext != null) {
composite.setApplicationContext(this.applicationContext);
}
if (this.servletContext != null) {
composite.setServletContext(this.servletContext);
}
return composite;
}
仔细细品上述源码,通过 beanNamesForTypeIncludingAncestors() 方法从 Spring 容器中找到 ViewResolver 接口的实现类,如果你没有配置任何 ViewResolver 的实现类,那么现在容器中只会存在一个那就是当前 @Bean 修饰的 mvcViewResolver() 方法,必然找到只有1个毋庸置疑,此时,SpringMVC 就会帮我们内置一个视图解析器 InternalResourceViewResolver,这个视图解析器时默认解析 JSP 资源文件的。记得配置 suffix、prefix,如果不配,那就在访问路径上把前后缀带上 http://localhost:9292/toJsp?cx=abc2.jsp 即可,如果配置了直接访问 http://localhost:9292/toJsp?cx=abc2 ,反正就是要拼成一条完整路劲。
@Controller
public class ToJspController {
@RequestMapping("/toJsp")
public ModelAndView toJsp(String cx) {
ModelAndView view = new ModelAndView();
System.out.println(">>>>>>toJsp...");
System.out.println("hnhds");
view.addObject("name", "gwm");
view.setViewName(cx);
return view;
}
}
除了默认加载外,还可以定制化,但是你需要实现一个接口 WebMvcConfigurer,然后重写相应的方法,举个例子:
@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/*");
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
/**
* 这里不加 Jackson 也是有默认值存在的,如果重写了的话,默认的 Converts 都不会加载,所以要非常注意这个细节
* 推荐重写 extendMessageConverters() 方法
*/
// converters.add(new MappingJackson2HttpMessageConverter());
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
MappingJackson2HttpMessageConverter jackson2HttpMessageConverter = new MappingJackson2HttpMessageConverter();
ObjectMapper objectMapper = new ObjectMapper();
SimpleDateFormat smt = new SimpleDateFormat("yyyy-MM-dd");
objectMapper.setDateFormat(smt);
jackson2HttpMessageConverter.setObjectMapper(objectMapper);
converters.add(jackson2HttpMessageConverter);
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// InternalResourceViewResolver resolver = new InternalResourceViewResolver();
// resolver.setSuffix(".jsp");
// resolver.setPrefix("/");
// registry.viewResolver(resolver);
// registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp("/",".jsp");
// registry.freeMarker();
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/dist/**").addResourceLocations("classpath:/static/dist/");
registry.addResourceHandler("/theme/**").addResourceLocations("classpath:/static/theme/");
registry.addResourceHandler("/boot/*").addResourceLocations("classpath:/static/");
// registry.addResourceHandler("/boot/**").addResourceLocations("classpath:/static/");
// registry.addResourceHandler("/**").addResourceLocations("classpath:/static/");
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/gotoJsp").setViewName("abc");
}
}
但是要注意在自己定制功能时需要先去看看源码是怎么实现的,因为确实有很多细节稍不留神就容易出错。比如 configureViewResolvers() 方法,当 JSP、FTL 文件同时存在时,你就需要把 FLT 文件放在 JSP 前面才可以正常加解析到资源。
对于这些定制化的功能是怎么被装载到的,这里有个设计模式:委托模式
。就是某个功能实现交给另一个人去做,自己只是调用一下。
这里简单分析下自定义的 configureViewResolvers() 方法是怎么被调用到的。我们知道所有 webmvc 功能基本都在 WebMvcConfigurationSupport 类中写死,但是这样不好,开发者也想到这一点,所以为了能够做扩展或者自定义一些实现,在这个类的每个方法中都埋点,这些埋点就是可以让开发自定义一些功能。就比如在 WebMvcConfigurationSupport 类中注入视图解析器,源码如下:
@Bean
public ViewResolver mvcViewResolver(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager) {
ViewResolverRegistry registry =
new ViewResolverRegistry(contentNegotiationManager, this.applicationContext);
configureViewResolvers(registry);
if (registry.getViewResolvers().isEmpty() && this.applicationContext != null) {
String[] names = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
this.applicationContext, ViewResolver.class, true, false);
// 这里 ==1 指的是上来容器中就有一个,这个是指定此方法 @Bean mvcViewResolver 这个 bean
// 此时视图解析器就是 InternalResourceViewResolver
if (names.length == 1) {
registry.getViewResolvers().add(new InternalResourceViewResolver());
}
}
}
发现这个configureViewResolvers() 方法是一个勾子方法,可以勾到子类实现。这就是一个埋点方便扩展和自定义实现。一般这种模版方法就会在子类中实现,所以看到子类 DelegatingWebMvcConfiguration 中是怎么重写 configureViewResolvers() 这个方法的,源码如下:
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Override
protected void configureViewResolvers(ViewResolverRegistry registry) {
this.configurers.configureViewResolvers(registry);
}
}
可以发现哈,这个子类啥事没干,而是调用 WebMvcConfigurerComposite 类方法,所以这里就是一个委托模式
,这里要特别注意到哈,WebMvcConfigurerComposite 的实例可以直接 new 创建的,并没有交给 Spring 容器管理。接着进入 WebMvcConfigurerComposite 源码如下:
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureViewResolvers(registry);
}
}
}
从上面源码可以发现,并没有做任何事情,而是又交给 WebMvcConfigurer 类,又是委托模式
,所以我们要想定制化功能,就可以去实现 WebMvcConfigurer 接口,然后重写里面的方法。
举个例子这里 MyWebMvcConfigure 去实现 WebMvcConfigurer 接口,定制化 configureViewResolvers() 视图解析器,代码如下:
@Configuration
@EnableWebMvc
public class MyWebMvcConfigure implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.freeMarker();
registry.jsp("/",".jsp");
}
注意这个类需要被 Spring 管理,所以要加上 @Component 或者 @Configuration 注解,在 WebMvcConfigurationSupport 子类 DelegatingWebMvcConfiguration 类中会去把这个类添加到 WebMvcConfigurerComposite 类中的 delegates 集合中,源码如下:
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
}
class WebMvcConfigurerComposite implements WebMvcConfigurer {
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureViewResolvers(registry);
}
}
}
在 WebMvcConfigurerComposite 类中 configureViewResolvers() 方法遍历时就能回调到自定义实现类 MyWebMvcConfigure 中的 configureViewResolvers() 方法完成定制化。
最后还是要提醒一句在定制化时一定要先去看看源码怎么执行,否则容易出现细节错误。
2、DispatcherServlet.properties 配置文件
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
五、registry 或 handlerMap 映射关系建立
最后在回过头看看上面红色字体问题。在哪里初始化 handlerMap 的呢? 通过前面分析 handlerMap 是用来保存实现 Controller、AbstractController、HttpRequestHandler 接口类型的 Controller 映射关系。其中 SimpleHandlerMapping 类中有个 urlMap 容器,可以保存 urlPath 和 Controller 的映射关系,最终其实也是会把 urlMap 容器中的值填充到 handlerMap 容器。接下来看一下一个 Controller 例子,看一下它是在哪个地方将映射关系保存到 handlerMap 中,代码如下:
@org.springframework.stereotype.Controller("/controller")
public class HttpController implements Controller {
@Override
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
return null;
}
}
跟踪源码,最终发现调用处从 Spring 容器启动处触发,源码如下:
public abstract class ApplicationObjectSupport implements ApplicationContextAware {
@Override
public final void setApplicationContext(@Nullable ApplicationContext context) throws BeansException {
if (context == null && !isContextRequired()) {
initApplicationContext(context);
}
}
}
然后继续追踪 initApplicationContext() 方法,最终调用到 registerHandler() 方法,源码如下:
protected void registerHandler(String[] urlPaths, String beanName) throws BeansException, IllegalStateException {
Assert.notNull(urlPaths, "URL path array must not be null");
for (String urlPath : urlPaths) {
registerHandler(urlPath, beanName);
}
}
protected void registerHandler(String urlPath, Object handler) throws BeansException, IllegalStateException {
this.handlerMap.put(urlPath, resolvedHandler);
}
发现最终就是在 Spring 容器启动就把映射关系保存到 handlerMap 容器中。所以后面再 DispatcherServlet 类中就可以直接通过 get() 方法直接取值。
这里 registry 容器中的值是什么初始化进去的呢? 这个通过上面分析就是 @RequestMapping 形式的 Controller 映射关系会保存到这个 Map 容器中。具体看下这个容器中值是什么初始化的呢?
跟踪代码发现也是 Spring 容器时触发调用,核心源码如下:
public abstract class AbstractHandlerMethodMapping<T> extends AbstractHandlerMapping implements InitializingBean {
@Override
public void afterPropertiesSet() {
initHandlerMethods();
}
}
发现借助 Spring 声明周期类 InitializingBean 来实现的,进入 initHandlerMethods() 代码,核心源码如下:
protected void detectHandlerMethods(Object handler) {
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
获取到 @RequestMapping 形式定义的 Controller 中所有的方法,每个方法都要建立映射关系。继续进入 registerHandlerMethod() 方法深处,核心源码如下:
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
继续进入 register() 方法 ,核心源码如下:
class MappingRegistry {
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
public Map<T, HandlerMethod> getMappings() {
return this.mappingLookup;
}
@Nullable
public List<T> getMappingsByUrl(String urlPath) {
return this.urlLookup.get(urlPath);
}
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass())) {
Class<?>[] parameterTypes = method.getParameterTypes();
if ((parameterTypes.length > 0) && "kotlin.coroutines.Continuation".equals(parameterTypes[parameterTypes.length - 1].getName())) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
}
this.readWriteLock.writeLock().lock();
try {
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);
}
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
}
这个 MappingRegistry#register() 主要就是将每个方法映射关系保存到 registry,registry 也就是一个 Map 容器。只是人家的 key 做了一个层包装,解析 @RequestMapping 属性封装成 RequestMappingInfo 对象。然后值就是@RequestMapping 修饰的方法被封装成 HandlerMethod 对象。
六、参数解析器、返回值解析器初始化
最后在看看调用过程中参数解析器、返回值解析器的值是在哪里初始化的?
追踪源码,最终在 RequestMappingHandlerAdapter 类中借助 InitializingBean 声明周期类实现,核心源码如下:
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {
@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);
}
}
}
进入 getDefaultArgumentResolvers() 方法,核心源码如下:
private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
在这上面默认创建了30个参数解析器,最终赋值到全局变量 argumentResolvers 集合中,后面就可以使用。同理返回值也是这样干的,核心源码如下:
private List<HandlerMethodReturnValueHandler> getDefaultReturnValueHandlers() {
List<HandlerMethodReturnValueHandler> handlers = new ArrayList<>(20);
// Single-purpose return value types
handlers.add(new ModelAndViewMethodReturnValueHandler());
handlers.add(new ModelMethodProcessor());
handlers.add(new ViewMethodReturnValueHandler());
handlers.add(new ResponseBodyEmitterReturnValueHandler(getMessageConverters(),
this.reactiveAdapterRegistry, this.taskExecutor, this.contentNegotiationManager));
handlers.add(new StreamingResponseBodyReturnValueHandler());
handlers.add(new HttpEntityMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
handlers.add(new HttpHeadersReturnValueHandler());
handlers.add(new CallableMethodReturnValueHandler());
handlers.add(new DeferredResultMethodReturnValueHandler());
handlers.add(new AsyncTaskMethodReturnValueHandler(this.beanFactory));
// Annotation-based return value types
handlers.add(new ModelAttributeMethodProcessor(false));
handlers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(),
this.contentNegotiationManager, this.requestResponseBodyAdvice));
// Multi-purpose return value types
handlers.add(new ViewNameMethodReturnValueHandler());
handlers.add(new MapMethodProcessor());
// Custom return value types
if (getCustomReturnValueHandlers() != null) {
handlers.addAll(getCustomReturnValueHandlers());
}
// Catch-all
if (!CollectionUtils.isEmpty(getModelAndViewResolvers())) {
handlers.add(new ModelAndViewResolverMethodReturnValueHandler(getModelAndViewResolvers()));
}
else {
handlers.add(new ModelAttributeMethodProcessor(true));
}
return handlers;
}
然后赋值到全局变量 returnValueHandlers 集合中,后续可以使用。这里会发现有两个特殊的参数解析,RequestResponseBodyMethodProcessor、RequestPartMethodArgumentResolver 这里只看第一个,需要借助 MessageConverter 消息转换器才能解析封装( 主要对 @RequestBody、@ResponseBody 参数进行解析转换,而且主要是 JSON 转换成实体、实体转换成 JSON)。
七、MessageConverter 消息转换器初始化
进入 getMessageConverters() 方法,源码如下:
public List<HttpMessageConverter<?>> getMessageConverters() {
return this.messageConverters;
}
那么就需要去看下这个 messageConverters 变量是在哪里赋值的,跟踪源码如下:
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcValidator") Validator validator) {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(contentNegotiationManager);
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer(conversionService, validator));
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(Collections.singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
调用代码 adapter.setMessageConverters(getMessageConverters()); 进入 getMessageConverters() 核心源码:
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
如果我们自己没有配置 messageConverters ,那么就会有默认的 messageConverters,进入 addDefaultHttpMessageConverters() 方法,源码如下:
static {
ClassLoader classLoader = WebMvcConfigurationSupport.class.getClassLoader();
romePresent = ClassUtils.isPresent("com.rometools.rome.feed.WireFeed", classLoader);
jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", classLoader);
jackson2Present = ClassUtils.isPresent("com.fasterxml.jackson.databind.ObjectMapper", classLoader) &&
ClassUtils.isPresent("com.fasterxml.jackson.core.JsonGenerator", classLoader);
jackson2XmlPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.xml.XmlMapper", classLoader);
jackson2SmilePresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.smile.SmileFactory", classLoader);
jackson2CborPresent = ClassUtils.isPresent("com.fasterxml.jackson.dataformat.cbor.CBORFactory", classLoader);
gsonPresent = ClassUtils.isPresent("com.google.gson.Gson", classLoader);
jsonbPresent = ClassUtils.isPresent("javax.json.bind.Jsonb", classLoader);
}
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(new StringHttpMessageConverter());
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new ResourceRegionHttpMessageConverter());
try {
messageConverters.add(new SourceHttpMessageConverter<>());
}
catch (Throwable ex) {
// Ignore when no TransformerFactory implementation is available...
}
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.xml();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(builder.build()));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2HttpMessageConverter(builder.build()));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
else if (jsonbPresent) {
messageConverters.add(new JsonbHttpMessageConverter());
}
if (jackson2SmilePresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.smile();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2SmileHttpMessageConverter(builder.build()));
}
if (jackson2CborPresent) {
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.cbor();
if (this.applicationContext != null) {
builder.applicationContext(this.applicationContext);
}
messageConverters.add(new MappingJackson2CborHttpMessageConverter(builder.build()));
}
}
从上述源码中可以发现,只要你引入相应依赖的第三方 jar 包,那么就会相应自动添加相应的转换器。比如你引入
com.fasterxml.jackson.databind
相关包,就会有 MappingJackson2HttpMessageConverter 消息转换器。所以你在使用 @RequestBody、@ResponseBody 注解时先看看这些包是否存在,若没有可能就转换不了直接报错。
还有一点要注意:如果想定制化 MessageConverter ,推荐重写 extendMessageConverters() 方法,不要重写 configureMessageConverters() 方法,看源码一清二楚,如下:
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}