⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 http://blog.csdn.net/u012410733/article/details/76862212 「carlzhao」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注**微信公众号:【芋道源码】**有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言将得到认真回复。甚至不知道如何读源码也可以请教噢
  4. 新的源码解析文章实时收到通知。每周更新一篇左右
  5. 认真的源码交流微信群。

如果大家看过Spring MVC的源代码都会知道.Spring MVC框架在Spring容器初始化的时候,通过@RequestMapping建立起请求路径与调用方法的映射(没有看过源码的同学应该也能够想明白)。

1、Spring MVC Init

下面我们来看一下Spring MVC创建映射的代码时序图。

这里写图片描述

我们先来看一下RequestMappingInfo里面的属性,然后再来说一下整个时序图干了哪些事。

public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> {

// 请求映射名称,对应@RequestMapping注解中name
private final String name;

// 请求映射资源路径条件(uri),对应@RequestMapping注解中path
private final PatternsRequestCondition patternsCondition;

// 请求映射资源方法(RequestMethod -- GET/POST/PUT/DELETE等),对应@RequestMapping注解中method
private final RequestMethodsRequestCondition methodsCondition;

// 请求映射参数,用于缩小映射,对应@RequestMapping注解中params
private final ParamsRequestCondition paramsCondition;

// 请求映射对应header参数,对应@RequestMapping注解中headers
private final HeadersRequestCondition headersCondition;

// 请求映射对应Content-Type,对应@RequestMapping注解中consumes
private final ConsumesRequestCondition consumesCondition;

// 请求映射响应Content-Type,对应@RequestMapping注解中produces
private final ProducesRequestCondition producesCondition;

// 请求映射自定义条件,对应RequestMappingHandlerMapping#getCustomMethodCondition
private final RequestConditionHolder customConditionHolder;

}

我们可以看到RequestMappingInfo以及它的属性都实现了RequestCondition这个接口。里面有三个方法:

  • combine : 用于合并类上面与方法上面的@RequestMapping注解信息。
  • getMatchingCondition : 获取到对应匹配的Condition;
  • compareTo : 当有多个满足时,选择最佳匹配。

这样就可以RequestMappingInfo就是Spring MVC映射是http请求映射的信息。然后我们再来分析上面的时序图。

  1. 当Spring容器实例化AbstractHandlerMethodMapping对象的继承类的时候,首先调用Spring生命周期实现接口InitializingBean的回调方法afterPropertiesSet。
  2. 然后调用ApplicationContext中的getBeanNamesForType,拿到Spring容器中所有注解的bean对象名称。
  3. 循环遍历beanname拿到bean对象,从这个对象中通过反射拿到类上标注了@Controller或者@RequestMapping注解的类
  4. 然后通过反射从这个类上面拿到所有方法标注了@RequestMapping的方法,并且产生标注@RequestMapping与RequestMappingInfo的映射Map

public class HandlerMethod {

protected final Log logger = LogFactory.getLog(getClass());

private final Object bean;

private final BeanFactory beanFactory;

private final Class<?> beanType;

private final Method method;

private final Method bridgedMethod;

private final MethodParameter[] parameters;

private HttpStatus responseStatus;

private String responseStatusReason;

private HandlerMethod resolvedFromHandlerMethod;

}

其中最重要有三个属性:

  • bean : 当前handler处理类,标注@Controller的实例对象。
  • method : 处理方法,也就是标注了@RequestMapping的方法对象。
  • parameters : 处理方法的方法参数,也就是Spring MVC需要作参数绑定的对象。当Spring MVC接收到HTTP请求的时候,Spring MVC从HttpServletRequest对象里面获取到前端传递过来的请求参数。然后通过HandlerMethodArgumentResolver接口,通过策略模式把HttpServletRequest里面的请求参数绑定到对应方法中的方法中。

有了这三个参数,Spring MVC就可以使用反射.

Object returnValue = method.invoke(bean, parameters);1

2、Spring MVC handler invoke

要明白Spring MVC对于HTTP请求的调用,首先我们要看一下HandlerMethod的类继承关系图了。

这里写图片描述

其实它还有一个继承类,只是与我们的分析无关就没有把它画出来。在这个类图中,把关键属性于关键方法都表示了出来。下面我们再来看一下RequestMappingHandlerAdapter这个对象。这个对象是

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
implements BeanFactoryAware, InitializingBean {

// 请求参数处理类,把HttpServletRequest里面的请求参数与HandlerMethod的MethodParameter转化成@RequestMapping的方法参数
private HandlerMethodArgumentResolverComposite argumentResolvers;

// 返回参数处理类
private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

// @Controller类中@InitBinder注解处理方法缓存
private final Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<Class<?>, Set<Method>>(64);

// @ControllerAdvice中@InitBinder注解处理方法缓存
private final Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache =
new LinkedHashMap<ControllerAdviceBean, Set<Method>>();

// @ControllerAdvice中@ModelAttribute注解处理方法缓存
private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache =
new LinkedHashMap<ControllerAdviceBean, Set<Method>>();

// HTTP请求处理方法
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
// 数据绑定工厂类
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

// HTTP调用处理类
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
// 处理@ModelAttribute
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);

WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);

if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}

// 最终会调用到InvocableHandlerMethod#invokeForRequest
// 这里就会处理HttpServletRequest与HandlerMethod的MethodParameter转化成@RequestMapping的方法参数
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}

return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
}

这个类里面的HandlerMethodArgumentResolverComposite里面有Spring MVC对于请求参数解析的默认处理集合。具体可以看RequestMappingHandlerAdapter#afterPropertiesSet方法,当这个对象实例化的时候就会把这些处理类添加进去。HandlerMethodReturnValueHandlerComposite里面是Spring MVC对于返回参数的默认处理集合,同样的也是由上面的方法添加进去的。其它属性是Spring MVC对于注解@ControllerAdvice、@InitBinder、@ModelAttribute、@ExceptionHandler的方法的处理。其实Spring MVC最终都会把这些注解中的方法调用封装成InvocableHandlerMethod这个对象或其子类。最后达到代码复用的目的。

3、Method Argument Resolver

这里我们要讲的是的是Spring MVC调用代码复用,那么我们首先看一下Spring MVC对于请求方法参数的解析。也就是InvocableHandlerMethod#getMethodArgumentValues。下面我们来看一下这个方法的具体代码:

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {

MethodParameter[] parameters = getMethodParameters();
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {

// 通过提供的参数(providedArgs)来获取参数
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
args[i] = resolveProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}

// 通过Spring中的HandlerMethodArgumentResolver来获取参数
if (this.argumentResolvers.supportsParameter(parameter)) {
try {
args[i] = this.argumentResolvers.resolveArgument(
parameter, mavContainer, request, this.dataBinderFactory);
continue;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}

// 如果上面都获取不到,报错
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}

通过上面的代码我们可以看到,这个方法有3个参数。

  • request : 这个对象包含HttpServletRequst与HttpServletResponse对象。
  • mavContainer : 这个对象包括DispatcherServlet#INPUT_FLASH_MAP_ATTRIBUTE与@ModelAttribute里面的值。
  • providedArgs : 传入对象,主要是Spring MVC异常处理传入Exception信息。

通过传入的这3个参数,就可以首先遍历providedArgs看是否有值的对象属性MethodParameter的类型的实例。如果是就获取到这个请求参数对象,否则就通过HandlerMethodArgumentResolver接口会解析请求参数。如果都拿不到这个请求参数对象,那么就会报错。

第一种方式调用主要针对二种情况:

  • @InitBinder注解。进行这种方式调用的时候它会传入一个WebDataBinder对象。这样我们就可以修改这个对象的值了。 具体入口在InitBinderDataBinderFactory#initBinder

public void initBinder(WebDataBinder binder, NativeWebRequest request) throws Exception {
for (InvocableHandlerMethod binderMethod : this.binderMethods) {
if (isBinderMethodApplicable(binderMethod, binder)) {
Object returnValue = binderMethod.invokeForRequest(request, null, binder);
if (returnValue != null) {
throw new IllegalStateException("@InitBinder methods should return void: " + binderMethod);
}
}
}
}

典型用法为:

@InitBinder
public void init(DataBinder binder){
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd mm:HH:ss"));
}

  • @ExceptionHandler注解:它会把Exception,Throwable,HandlerMethod对象传过来。具体的入口在ExceptionHandlerExceptionResolver#doResolveHandlerMethodException:

protected ModelAndView doResolveHandlerMethodException(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod, Exception exception) {

ServletInvocableHandlerMethod exceptionHandlerMethod = getExceptionHandlerMethod(handlerMethod, exception);
if (exceptionHandlerMethod == null) {
return null;
}

exceptionHandlerMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
exceptionHandlerMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);

ServletWebRequest webRequest = new ServletWebRequest(request, response);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();

try {
if (logger.isDebugEnabled()) {
logger.debug("Invoking @ExceptionHandler method: " + exceptionHandlerMethod);
}
Throwable cause = exception.getCause();
if (cause != null) {
// Expose cause as provided argument as well
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, cause, handlerMethod);
}
else {
// Otherwise, just the given exception as-is
exceptionHandlerMethod.invokeAndHandle(webRequest, mavContainer, exception, handlerMethod);
}
}
catch (Throwable invocationEx) {
...
}
}

典型用法为:

@ExceptionHandler(value = {Exception.class})
@ResponseBody
public CommonResponse handlerException(HttpServletRequest request, HttpServletResponse response, Exception e){
if(e instanceof MethodArgumentNotValidException) {
MethodArgumentNotValidException exception = (MethodArgumentNotValidException) e;
List<ObjectError> allErrors = exception.getBindingResult().getAllErrors();
if(CollectionUtils.isEmpty(allErrors)){
return new CommonResponse(-1, "系统异常");
}
StringBuilder sb = new StringBuilder();
for (ObjectError allError : allErrors) {
sb.append(allError.getDefaultMessage()).append("\n");
}
return new CommonResponse(-1, sb.toString());
}
if(e instanceof BindException) {
BindException exception = (BindException) e;
List<ObjectError> allErrors = exception.getBindingResult().getAllErrors();
if(CollectionUtils.isEmpty(allErrors)){
return new CommonResponse(-1, "系统异常");
}
StringBuilder sb = new StringBuilder();
for (ObjectError allError : allErrors) {
sb.append(allError.getDefaultMessage()).append("\n");
}
return new CommonResponse(-1, sb.toString());
}
return new CommonResponse(-1, "系统异常");
}

当然它们都可以放在@Controller里面,也可以放在@ControllerAdvice里面。这样可以方便的控制它们的作用范围。

4、Spring MVC Invoke

上面我们解析了Spring MVC中的HTTP请求的具体调用过程。下面我们就来分析一下Spring MVC使用这一种方式的其它调用。来领略一下Spring代码的复用艺术。

4.1 @InitBinder

注解@InitBinder可以应用到@Controller类中的方法上也可以应用@ControllerAdvice的方法上。只不是一个局部的,是作用于当前类的HTTP请求;而另一个是全局的,作用为所有的HTTP请求。在调用HTTP请求的时候都会@Controller类与@ControllerAdvice类中的@InitBinder方法都检索出来,只不过在具体HTTP调用的时候是否应用就要看是不是匹配了。下面分析一下作用于@Controller里面的@InitBinder.

这里写图片描述

我们可以看到在容器初始化的时候并不会初始@InitBinder这个注解标注了的方法。而是在首次HTTP调用的时候会初始化当前处理方法类中的@InitBinder方法。以及@ControllerAdvice中的@InitBinder方法。这样的好处就是每个Controller中即可以使用全局的InitBinder方法也可以和其它Controller相互隔绝。它的调用时候是先进行HTTP的处理调用,在这个处理调用的参数解析的时候会进行InitBinder方法处理。这里是和HTTP处理方法都是调用的InvocableHandlerMethod#invokeForRequest。

4.2 @ModelAttribute

注解@ModelAttribute可以应用到@Controller类中的方法上也可以应用@ControllerAdvice的方法上。只不是一个局部的,是作用于当前类的HTTP请求;而另一个是全局的,作用为所有的HTTP请求。

这里写图片描述

这里只分析了一下@ModelAttribute注解在当前类的时候,并没有分析在@ControllerAdvice。大家如果感兴趣可以自行分析。可以看到这个过程发生在HTTP正式处理之前。把获取到的值塞到ModelAndViewContainer中,用于后面使用。这里是和HTTP处理方法都是调用的InvocableHandlerMethod#invokeForRequest。

4.3 @ExceptionHandler

这里写图片描述

注解@ExceptionHandler可以应用到@Controller类中的方法上也可以应用@ControllerAdvice的方法上。只不是一个局部的,是作用于当前类的HTTP请求;而另一个是全局的,作用为所有的HTTP请求。

它的效果与@InitBinder类似,即可以有全局的配置又可以有局部的配置。当HTTP请求处理发生异常的时候,它会首先在当前类找是否有对应异常的@ExceptionHandler处理方法如果有就用它进行处理。如果没有,就找一下@ControllerAdvice注解类里面是否有对应异常的处理,有就处理。如果没有就用Spring MVC的默认异常处理方式。

666. 彩蛋

如果你对 Java 感兴趣,欢迎加入我的知识星球一起交流。

知识星球

文章目录
  1. 1. 1、Spring MVC Init
  2. 2. 3、Method Argument Resolver
  3. 3. 4、Spring MVC Invoke
    1. 3.1. 4.1 @InitBinder
    2. 3.2. 4.2 @ModelAttribute
    3. 3.3. 4.3 @ExceptionHandler
  4. 4. 666. 彩蛋