catalogue
preface
Through the previous article, we know the general process of Spring MVC executing the Controller method. The important step is to find the appropriate back-end processor (handler) to execute the target method through the handler, and the method marked with @ RequestMapping will correspond The handler type is HandlerMethod. This article will explore what HandlerMethod is.
HandlerMethod
public class HandlerMethod { /** Logger that is available to subclasses. */ protected final Log logger = LogFactory.getLog(getClass()); // The corresponding bean marked with @ Controller can be an object instance or a bean name private final Object bean; // bean container @Nullable private final BeanFactory beanFactory; // Type of bean private final Class<?> beanType; // Target method private final Method method; // The bridged method is generally the same as the value of the method field, that is, to find the target method to be executed private final Method bridgedMethod; // Encapsulated parameter information private final MethodParameter[] parameters; // Response code status @Nullable private HttpStatus responseStatus; // Reason corresponding to response code @Nullable private String responseStatusReason; // When the HandlerMethod is obtained through the following createWithResolvedBean, this object will be passed @Nullable private HandlerMethod resolvedFromHandlerMethod; // Constructor to assign values to the above fields public HandlerMethod(Object bean, Method method) { Assert.notNull(bean, "Bean is required"); Assert.notNull(method, "Method is required"); this.bean = bean; this.beanFactory = null; this.beanType = ClassUtils.getUserClass(bean); this.method = method; this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method); this.parameters = initMethodParameters(); evaluateResponseStatus(); this.description = initDescription(this.beanType, this.method); } // Construction method public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { ... ellipsis } // Construction method public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) { ... ellipsis } // A new handlerMethod is copied through handlerMethod. In the previous article, when a new ServletInvocableHandlerMethod type is created through the Handler of handlerMethod type, this constructor is called protected HandlerMethod(HandlerMethod handlerMethod) { Assert.notNull(handlerMethod, "HandlerMethod is required"); this.bean = handlerMethod.bean; this.beanFactory = handlerMethod.beanFactory; this.beanType = handlerMethod.beanType; this.method = handlerMethod.method; this.bridgedMethod = handlerMethod.bridgedMethod; this.parameters = handlerMethod.parameters; this.responseStatus = handlerMethod.responseStatus; this.responseStatusReason = handlerMethod.responseStatusReason; this.description = handlerMethod.description; this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; } // Construction method private HandlerMethod(HandlerMethod handlerMethod, Object handler) { ... ellipsis } // Generate a wrapper class for the parameters of the method private MethodParameter[] initMethodParameters() { int count = this.bridgedMethod.getParameterCount(); MethodParameter[] result = new MethodParameter[count]; for (int i = 0; i < count; i++) { result[i] = new HandlerMethodParameter(i); } return result; } // If @ ResponseStatus is marked on the method, the response code and response reason in the annotation will be returned private void evaluateResponseStatus() { ResponseStatus annotation = getMethodAnnotation(ResponseStatus.class); if (annotation == null) { annotation = AnnotatedElementUtils.findMergedAnnotation(getBeanType(), ResponseStatus.class); } if (annotation != null) { this.responseStatus = annotation.code(); this.responseStatusReason = annotation.reason(); } } //... omit some get methods // Judge whether the return value of the method is void public boolean isVoid() { return Void.TYPE.equals(getReturnType().getParameterType()); } // Get annotation on method @Nullable public <A extends Annotation> A getMethodAnnotation(Class<A> annotationType) { return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType); } public <A extends Annotation> boolean hasMethodAnnotation(Class<A> annotationType) { return AnnotatedElementUtils.hasAnnotation(this.method, annotationType); } // Create a HandlerMethod through a bean. If the bean has not been created at this time, the getBean method will be called to create the bean instance first. In addition, this object will be assigned to resolvedFromHandlerMethod public HandlerMethod createWithResolvedBean() { Object handler = this.bean; if (this.bean instanceof String) { Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory"); String beanName = (String) this.bean; handler = this.beanFactory.getBean(beanName); } return new HandlerMethod(this, handler); } // Similar to toString method public String getShortLogMessage() { return getBeanType().getName() + "#" + this.method.getName() + "[" + this.method.getParameterCount() + " args]"; } // Type of return value private class ReturnValueMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; public ReturnValueMethodParameter(@Nullable Object returnValue) { super(-1); this.returnValue = returnValue; } protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { super(original); this.returnValue = original.returnValue; } @Override public Class<?> getParameterType() { return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); } @Override public ReturnValueMethodParameter clone() { return new ReturnValueMethodParameter(this); } } }
You can see that the HandlerMethod saves the bean information, that is, the Controller information, so you can know which object it is, and then the Method saves the corresponding Method information to know which Method to call. Therefore, there will be as many handlermethods as there are @ RequestMapping marked methods in a @ Controller. However, it feels like an entity class. There is no execution Method. Although we know the bean information and Method information, there is no way to trigger the execution of the Method through this class. At this time, we can look at the previous article. How did we execute the Controller Method in the previous article? We should note that one step is to convert an instance of the HandlerMethod type to An instance of type ServletInvocableHandlerMethod. So let's continue to analyze what this type does.
ServletInvocableHandlerMethod
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod { // Callback method when exception occurs private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call"); // Return value for processing method @Nullable private HandlerMethodReturnValueHandlerComposite returnValueHandlers; // Constructor public ServletInvocableHandlerMethod(Object handler, Method method) { super(handler, method); } public ServletInvocableHandlerMethod(HandlerMethod handlerMethod) { super(handlerMethod); } // Sets the HandlerMethodReturnValueHandler that handles the return value public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) { this.returnValueHandlers = returnValueHandlers; } // This method triggers the execution of the target method public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // Execution target method Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // Set response status setResponseStatus(webRequest); 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; } // Setting this ID means that the request has not been processed mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null, "No return value handlers"); try { // Process the return value of the target method this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue), ex); } throw ex; } } // Set response status private void setResponseStatus(ServletWebRequest webRequest) throws IOException { HttpStatus status = getResponseStatus(); if (status == null) { return; } HttpServletResponse response = webRequest.getResponse(); if (response != null) { String reason = getResponseStatusReason(); if (StringUtils.hasText(reason)) { response.sendError(status.value(), reason); } else { response.setStatus(status.value()); } } // To be picked up by RedirectView webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status); } // Is the request modified private boolean isRequestNotModified(ServletWebRequest webRequest) { return webRequest.isNotModified(); } private void disableContentCachingIfNecessary(ServletWebRequest webRequest) { if (isRequestNotModified(webRequest)) { HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class); Assert.notNull(response, "Expected HttpServletResponse"); if (StringUtils.hasText(response.getHeader(HttpHeaders.ETAG))) { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); Assert.notNull(request, "Expected HttpServletRequest"); } } } private String formatErrorForReturnValue(@Nullable Object returnValue) { return "Error handling return value=[" + returnValue + "]" + (returnValue != null ? ", type=" + returnValue.getClass().getName() : "") + " in " + toString(); } // The ServletInvocableHandlerMethod that needs to be obtained in the asynchronous case is an internal class ServletInvocableHandlerMethod wrapConcurrentResult(Object result) { return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result)); } // Inherit the class itself to handle asynchronous situations private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod { //... omit code } }
As can be seen from the above, this class is InvocableHandlerMethod and invokeAndHandle method are provided. Blind guessing is to supplement HandlerMethod does not have the function of calling the target method, so the focus can be on this method, which can be passed in this method invokeForRequest calls the target method, and invokeForRequest is its parent class InvocableHandlerMethod only has methods, so we need to study InvocableHandlerMethod.
InvocableHandlerMethod
public class InvocableHandlerMethod extends HandlerMethod { private static final Object[] EMPTY_ARGS = new Object[0]; @Nullable private WebDataBinderFactory dataBinderFactory; // Used to parse request parameters private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite(); // Used to get the parameter name private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); // Constructor public InvocableHandlerMethod(HandlerMethod handlerMethod) { super(handlerMethod); } public InvocableHandlerMethod(Object bean, Method method) { super(bean, method); } public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException { super(bean, methodName, parameterTypes); } //.. Omit the set method // Get the parameter value and execute the target method @Nullable public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // Get parameter value Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } // Call the target method with the passed in parameter return doInvoke(args); } // Get parameter value protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { MethodParameter[] parameters = getMethodParameters(); if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); args[i] = findProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver")); } try { args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory); } catch (Exception ex) { // Leave stack trace for later, exception may actually be resolved and handled... if (logger.isDebugEnabled()) { String exMsg = ex.getMessage(); if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) { logger.debug(formatArgumentError(parameter, exMsg)); } } throw ex; } } return args; } // Execution target method @Nullable protected Object doInvoke(Object... args) throws Exception { ReflectionUtils.makeAccessible(getBridgedMethod()); try { // getBridgedMethod of the parent class obtains the target method and executes it through reflection return getBridgedMethod().invoke(getBean(), args); } catch (IllegalArgumentException ex) { assertTargetBean(getBridgedMethod(), getBean(), args); String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument"); throw new IllegalStateException(formatInvokeError(text, args), ex); } catch (InvocationTargetException ex) { // Unwrap for HandlerExceptionResolvers ... Throwable targetException = ex.getTargetException(); if (targetException instanceof RuntimeException) { throw (RuntimeException) targetException; } else if (targetException instanceof Error) { throw (Error) targetException; } else if (targetException instanceof Exception) { throw (Exception) targetException; } else { throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException); } } } }
This class is The parent class of ServletInvocableHandlerMethod, which is HandlerMethod. This class can the request parameter value of the method, or get the bean and target method of the parent class, and then pass in the parameters to execute the target method through reflection.
summary
From the above analysis, it can be seen that the HandlerMethod saves the Controller object and the target method, but does not give the interface to execute the target method. InvocableHandlerMethod As The HandlerMethod class has enhanced it by adding the functions of parsing request parameter values and executing target methods. However, it seems that it has nothing to do with servlets and cannot be associated with HTTP, so it is available again ServletInvocableHandlerMethod, which is right Further enhancements to invocablehandlermethod. ServletInvocableHandlerMethod has the function of executing the target method with the help of the parent class. In addition, it adds support for @ ResponseStatus HandlerMethodReturnValueHandlerComposite is used to continue the operation of the return value and the processing of asynchronous results, which is used to connect with the Servlet API.