When we define an interface in Controller, we usually do something like this:

@GetMapping("/01")
public String hello(Map<String,Object> map) {
    map.put("name"."javaboy");
    return "forward:/index";
}
Copy the code

Few people would define interface methods as private, right? So we have to ask, if you have to define a private method, will it work?

With this question, we begin today’s source code interpretation ~

When we use Spring Boot, we often see HandlerMethod. For example, when we define an interceptor, if the interceptor target is a method, The third parameter of the preHandle is the HandlerMethod:

@Component
public class IdempotentInterceptor implements HandlerInterceptor {
    @Autowired
    TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if(! (handlerinstanceof HandlerMethod)) {
            return true;
        }
        / / to omit...
        return true;
    }
    / /...
}
Copy the code

We’ll see HandlerMethod again and again when we read the SpringMVC source code, so what does it mean? Today, I want to clarify this problem with my friends, to clarify this problem, the previous problem we will understand.

1. An overview

As you can see, there are not many classes in the HandlerMethod system:

HandlerMethod

Encapsulates the Handler and the Method that handles the request.

InvocableHandlerMethod

The HandlerMethod method is added to the call function.

ServletInvocableHandlerMethod

Added support for @responseStatus annotations to the InvocableHandlerMethod and added handling of return values.

ConcurrentResultHandlerMethod

On the basis of ServletInvocableHandlerMethod, added to the result of the asynchronous processing.

These are basically the four components, and Songo is going to talk a little bit more about these four components.

2.HandlerMethod

2.1 bridgedMethod

Before we introduce the HandlerMethod, we would like to talk about the bridgedMethod, because the HandlerMethod will be involved in this thing, and some people may not have heard of bridgedMethod, so Songo will give a brief introduction here.

First of all, will the following code compile an error?

public interface Animal<T> {
    void eat(T t);
}
public class Cat implements Animal<String> {
    @Override
    public void eat(String s) {
        System.out.println("cat eat "+ s); }}public class Demo01 {
    public static void main(String[] args) {
        Animal animal = new Cat();
        animal.eat(newObject()); }}Copy the code

First we define an Animal interface that defines an EAT method and declares a generic. Cat implements the Animal interface and defines generics as strings. When I call it, it’s Animal, it’s Cat, and I call it eat and it passes in Object and guess what? If you call eat and it’s a String that’s fine, but what if it’s not a String?

Songko first said the conclusion: compile no problem, run error.

If you write the above code on your computer, you will find that the development tool prompt parameter type is Object. For example, songo’s IDEA is as follows:

As you can see, when I write code, the development tool will tell me that the parameter type is Object. Some people will be surprised.

We can use reflection to see what methods exist in the Cat class as follows:

public class Demo01 {
    public static void main(String[] args) {
        Method[] methods = Cat.class.getMethods();
        for(Method method : methods) { String name = method.getName(); Class<? >[] parameterTypes = method.getParameterTypes(); System.out.println(name+"("+ Arrays.toString(parameterTypes) +")"); }}}Copy the code

The running results are as follows:

We have two eat methods, one of which takes a String and the other an Object.

The Object method is created by the Java virtual machine at runtime. This method is called the Bridge Method. The subtitle of this section is called bridgedMethod, which is the name of the variable in the source code of HandlerMethod. Bridge ends with a “D”, meaning the method to be bridge, which is the original method with the parameter String. If you see the bridgedMethod in the following source code, you know that this represents the original method with the same parameter type.

2.2 introduce HandlerMethod

Let’s take a quick look at the HandlerMethod.

When we looked at HandlerMapping earlier (see: HandlerMethod (createWithResolvedBean) is the entry method to create HandlerMethod. So let’s start with this method:

public HandlerMethod createWithResolvedBean(a) {
	Object handler = this.bean;
	if (this.bean instanceof String) {
		String beanName = (String) this.bean;
		handler = this.beanFactory.getBean(beanName);
	}
	return new HandlerMethod(this, handler);
}
Copy the code

If handler is of type String, the beanName handler object is retrieved from the Spring container, and the HandlerMethod is constructed.

private HandlerMethod(HandlerMethod handlerMethod, Object handler) {
	this.bean = handler;
	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.resolvedFromHandlerMethod = handlerMethod;
	this.description = handlerMethod.description;
}
Copy the code

Parameters and responseStatus are the only parameters worth introducing.

parameters

Parameters are actually method parameters, the corresponding type is MethodParameter, the source of this class I will not post here, mainly to tell you the contents of the package includes: ParameterIndex parameter, nestingLevel parameter, parameterType parameter, parameterAnnotations parameter, ParameterName Discoverer, parameterName, etc.

HandlerMethod also provides two inner classes that encapsulate MethodParameter:

  • HandlerMethodParameter: This encapsulates the method call parameter.
  • ReturnValueMethodParameter: this inherited from HandlerMethodParameter, it encapsulates the method return value, the return value inside parameterIndex is 1.

Note that both methods are bridgedMethods.

responseStatus

This is the @responsestatus annotation that handles the method. This annotation describes the method’s ResponseStatus code as follows:

@GetMapping("/04")
@ResponseBody
@ResponseStatus(code = HttpStatus.OK)
public void hello4(@SessionAttribute("name") String name) {
    System.out.println("name = " + name);
}
Copy the code

As you can see from this code, the @responsestatus annotation is not very flexible and useful, and when we define an interface, it’s very difficult to predict that the ResponseStatus code of the interface is 200.

EvaluateResponseStatus in the handlerMethod, the evaluateResponseStatus method is called when its constructor is called to handle the @responseStatus annotation as follows:

private void evaluateResponseStatus(a) {
	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(); }}Copy the code

As you can see, this code is also relatively simple: find the annotation, parse out the value inside, and assign the value to the corresponding variable.

So you can see how HandlerMethod works.

3.InvocableHandlerMethod

As the name suggests, the InvocableHandlerMethod can call a specific method in the HandlerMethod, namely the bridgedMethod. Let’s first look at the properties declared in the InvocableHandlerMethod:

private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Nullable
private WebDataBinderFactory dataBinderFactory;
Copy the code

These are the three main attributes:

  • Resolvers: Needless to say, parameter parsers, which Songo already talked about in the previous article.
  • ParameterNameDiscoverer: This is used to get the parameter name, as used in MethodParameter.
  • DataBinderFactory: This is used to create the WebDataBinder and is used in the parameter parser.

The specific request invocation method is invokeForRequest. Let’s look at it:

@Nullable
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
		Object... providedArgs) throws Exception {
	Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
	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);
	}
	catch (InvocationTargetException ex) {
		/ / to omit...}}Copy the code

The getMethodArgumentValues method is called to get the values of all parameters in sequence, which form an array, and then the doInvoke method is called to execute. In the doInvoke method, the bridgedMethod is first retrieved. Make it visible (meaning that the interface methods we define in Controller can also be private) and call it directly through reflection. When we didn’t look at the SpringMVC source code, we knew that interface methods must eventually be called by reflection, and now, after several layers of analysis, we finally found the reflection call code here.

GetMethodArgumentValues = getMethodArgumentValues = getMethodArgumentValues

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) {
			/ / to omit...}}return args;
}
Copy the code
  1. You first call the getMethodParameters method to get all of the method’s parameters.
  2. Create an ARGS array to hold the values of the parameters.
  3. Next up is a bunch of initial configurations.
  4. If a parameter value is provided in providedArgs, it is assigned directly.
  5. Check to see if a parameter parser supports the current parameter type. If not, throw an exception.
  6. The parameter parser is called to parse the parameters, and when the parsing is complete, the value is assigned.

Easy, isn’t it?

4.ServletInvocableHandlerMethod

ServletInvocableHandlerMethod is on the basis of InvocableHandlerMethod, added two functions:

  • right@ResponseStatusAnnotation handling
  • Processing of returned values

Controller under the Servlet container to find the adapter would eventually ServletInvocableHandlerMethod launched a call.

The core processing method here is invokeAndHandle, as follows:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
	Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
	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;
	}
	mavContainer.setRequestHandled(false);
	try {
		this.returnValueHandlers.handleReturnValue(
				returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
	}
	catch (Exception ex) {
		throwex; }}Copy the code
  1. The invokeForRequest method of the parent class is invoked to execute the request and get the result.
  2. Call the setResponseStatus method@ResponseStatusAnnotation, the specific processing logic is as follows: if not added@ResponseStatusAnnotation, do nothing; If the annotation is added and the Reason attribute is not empty, an error is printed, otherwise the response status code is set. One thing to note here is that if the response status code is 200, do not set Reason, otherwise it will be treated as error.
  3. The next step is to handle the return value. ReturnValueHandlers# handleReturnValue is a method that songo described in a previous article. .

In fact, there’s a subclass ServletInvocableHandlerMethod ConcurrentResultHandlerMethod, this support asynchronous invocation result processing, because the usage scenario is less, don’t do is introduced here.

5. Summary

Now can you answer the question posed by the title of the article?