About backend processor - HandlerMethod

Keywords: Java Spring Spring MVC

catalogue

preface

HandlerMethod

ServletInvocableHandlerMethod

InvocableHandlerMethod

summary

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.

Posted by rathersurf on Fri, 15 Oct 2021 17:42:03 -0700