接下来我们来简单看下HandlerMethod。
主要就是这三个属性:
事实上,ServletInvocableHandlerMethod还有一个子类ConcurrentResultHandlerMethod,这个支持异步调用结果处理,因为使用场景较少,这里就不做介绍啦。
这个方法主要是确认了一下股票交易股票交易接口 C,接口 C,handler的类型,如果股票交易股票交易接口 C,接口 C,handler是String类型,则根据beanName从Spring容器中重新查找到股票交易股票交易接口 C,接口 C,handler对象,然后构建HandlerMethod:
private HandlerMethod(HandlerMethod 股票交易股票交易接口 C,接口 C,handlerMethod, Object 股票交易股票交易接口 C,接口 C,handler) {
this.bean = 股票交易股票交易接口 C,接口 C,handler;
this.beanFactory = 股票交易股票交易接口 C,接口 C,handlerMethod.beanFactory;
this.beanType = 股票交易股票交易接口 C,接口 C,handlerMethod.beanType;
this.method = 股票交易股票交易接口 C,接口 C,handlerMethod.method;
this.bridgedMethod = 股票交易股票交易接口 C,接口 C,handlerMethod.bridgedMethod;
this.parameters = 股票交易股票交易接口 C,接口 C,handlerMethod.parameters;
this.responseStatus = 股票交易股票交易接口 C,接口 C,handlerMethod.responseStatus;
this.responseStatusReason = 股票交易股票交易接口 C,接口 C,handlerMethod.responseStatusReason;
this.resolvedFromHandlerMethod = 股票交易股票交易接口 C,接口 C,handlerMethod;
this.description = 股票交易股票交易接口 C,接口 C,handlerMethod.description;
}
HandlerMethod中还提供了两个内部类来封装MethodParameter,分别是:
resolvers:这个不用说,参数解析器,前面的文章中松哥已经和大家聊过这个问题了。parameterNameDiscoverer:这个用来获取参数名称,在MethodParameter中会用到。dataBinderFactory:这个用来创建WebDataBinder,在参数解析器中会用到。
在InvocableHandlerMethod的基础上增了对@ResponseStatus注解的支持、增加了对返回值的处理。
我们在阅读SpringMVC源码的时候,也会反复看到这个HandlerMethod,那么它到底是什么意思?今天我想和小伙伴们捋一捋这个问题,把这个问题搞清楚了,前面的问题大家也就懂了。
我们可以通过反射查看Cat类中到底有哪些方法,代码如下:
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) +')');
}
}
}
对@ResponseStatus注解的处理对返回值的处理
首先我们定义了一个Animal股票交易接口 C,接口,里边定义了一个eat方法,同时声明了一个泛型。Cat实现了Animal股票交易接口 C,接口,将泛型也定义为了String。当我调用的时候,声明类型是Animal,实际类型是Cat,这个时候调eat方法传入了Object对象大家猜猜会怎么样?如果调用eat方法时传入的是String类型那就肯定没问题,但如果不是String呢?
在ServletInvocableHandlerMethod的基础上,增加了对异步结果的处理。
在股票交易股票交易接口 C,接口 C,handlerMethod中,在调用其构造方法的时候,都会调用evaluateResponseStatus方法处理@ResponseStatus注解,如下:
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();
}
}
松哥先说结论:编译没问题,运行报错。
运行结果如下:
是不是,很easy!
Servlet容器下Controller在查找适配器时发起调用的最终就是ServletInvocableHandlerMethod。
可以看到,HandlerMethod体系下的类并不多:
看名字就知道,InvocableHandlerMethod可以调用HandlerMethod中的具体方法,也就是bridgedMethod。我们先来看下InvocableHandlerMethod中声明的属性:
private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
@Nullable
private WebDataBinderFactory dataBinderFactory;
首先调用父类的invokeForRequest方法对请求进行执行,拿到请求结果。调用setResponseStatus方法处理@ResponseStatus注解,具体的处理逻辑是这样:如果没有添加@ResponseStatus注解,则什么都不做;如果添加了该注解,并且reason属性不为空,则直接输出错误,否则设置响应状态码。这里需要注意一点,如果响应状态码是200,就不要设置reason,否则会按照error处理。接下来就是对返回值的处理了,returnValueHandlers#handleReturnValue方法松哥在之前的文章中和大家专门介绍过,这里就不再赘述,传送门:SpringBoot中如何统一API股票交易接口 C,接口响应格式?。
如果小伙伴们在自己电脑上写出上面这段代码,你会发现这样一个问题,开发工具中提示的参数类型竟然是Object,以松哥的IDEA为例,如下:
封装Handler和具体处理请求的Method。
大家看到,在我写代码的时候,开发工具会给我提示,这个参数类型是Object,有的小伙伴会觉得奇怪,明明是泛型,怎么变成Object了?
在正式开始介绍HandlerMethod之前,想先和大家聊聊bridgedMethod,因为在HandlerMethod中将会涉及到这个东西,而有的小伙伴可能还没听说过bridgedMethod,因此松哥在这里做一个简单介绍。
从这段代码中大家可以看到,其实@ResponseStatus注解灵活性很差,不实用,当我们定义一个股票交易接口 C,接口的时候,很难预知到该股票交易接口 C,接口的响应状态码是200。
首先调用getMethodParameters方法获取到方法的所有参数。创建args数组用来保存参数的值。接下来一堆初始化配置。如果providedArgs中提供了参数值,则直接赋值。查看是否有参数解析器支持当前参数类型,如果没有,直接抛出异常。调用参数解析器对参数进行解析,解析完成后,赋值。
这里的参数都比较简单,没啥好说的,唯一值得介绍的地方有两个:parameters和responseStatus。
这个参数类型为Object的方法其实是Java虚拟机在运行时创建出来的,这个方法就是我们所说的bridgemethod。本节的小标题叫做bridgedMethod,这是HandlerMethod源码中的变量名,bridge结尾多了一个d,含义变成了被bridge的方法,也就是参数为String的原方法,大家在接下来的源码中看到了bridgedMethod就知道这表示参数类型不变的原方法。
可以看到,在实际运行过程中,竟然有两个eat方法,一个的参数为String类型,另一个参数为Object类型,这是怎么回事呢?
最后松哥再来说一下负责参数解析的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) {
// 省略...
}
}
return args;
}
parameters实际上就是方法参数,对应的类型是MethodParameter,这个类的源码我这里就不贴出来了,主要和大家说一下封装的内容包括:参数的序号,参数嵌套级别,参数类型,参数的注解,参数名称查找器,参数名称等。
概览
估计很少有人会把股票交易接口 C,接口方法定义成private的吧?那我们不禁要问,如果非要定义成private的方法,那能运行起来吗?
首先考考大家,下面这段代码编译会报错吗?
public interface Animal {
void eat(T t);
}
public class Cat implements Animal {
@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(new Object());
}
}
ServletInvocableHandlerMethod则是在InvocableHandlerMethod的基础上,又增加了两个功能:
这下小伙伴们应该明白了HandlerMethod大概是个怎么回事。
现在大家可以回答文章标题提出的问题了吧?
在HandlerMethod的基础上增加了调用的功能。
这里的处理核心方法是invokeAndHandle,如下:
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) {
throw ex;
}
}
具体的请求调用方法是invokeForRequest,我们一起来看下:
@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) {
// 省略 ...
}
}
基本上就是这四个,接下来松哥就来详细说一说这四个组件。
在我们前面分析HandlerMapping的时候,里边有涉及到HandlerMethod,创建HandlerMethod的入口方法是createWithResolvedBean,因此这里我们就从该方法开始看起:
public HandlerMethod createWithResolvedBean() {
Object 股票交易股票交易接口 C,接口 C,handler = this.bean;
if (this.bean instanceof String) {
String beanName = (String) this.bean;
股票交易股票交易接口 C,接口 C,handler = this.beanFactory.getBean(beanName);
}
return new HandlerMethod(this, 股票交易股票交易接口 C,接口 C,handler);
}
在我们使用SpringBoot的时候,经常会看到HandlerMethod这个类型,例如我们在定义拦截器的时候,如果拦截目标是一个方法,则preHandle的第三个参数就是HandlerMethod:
@Component
public class IdempotentInterceptor implements HandlerInterceptor {
@Autowired
TokenService tokenService;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object 股票交易股票交易接口 C,接口 C,handler) throws Exception {
if (!(股票交易股票交易接口 C,接口 C,handler instanceof HandlerMethod)) {
return true;
}
//省略...
return true;
}
//...
}
首先调用getMethodArgumentValues方法按顺序获取到所有参数的值,这些参数值组成一个数组,然后调用doInvoke方法执行,在doInvoke方法中,首先获取到bridgedMethod,并设置其可见,然后直接通过反射调用即可。当我们没看SpringMVC源码的时候,我们就知道股票交易接口 C,接口方法最终肯定是通过反射调用的,现在,经过层层分析之后,终于在这里找到了反射调用代码。
可以看到,这段代码也比较简单,找到注解,把里边的值解析出来,赋值给相应的变量。
HandlerMethodParameter:这个封装方法调用的参数。ReturnValueMethodParameter:这个继承自HandlerMethodParameter,它封装了方法的返回值,返回值里边的parameterIndex是-
这个主要是处理方法的@ResponseStatus注解,这个注解用来描述方法的响应状态码,使用方式像下面这样:
@GetMapping('/04')
@ResponseBody
@ResponseStatus(code = HttpStatus.OK)
public void hello4(@SessionAttribute('name') String name) {
System.out.println('name = ' + name);
}
注意,这两者中的method都是bridgedMethod。
带着这个疑问,我们开始今天的源码解读~
文章为作者独立观点,不代表股票交易接口观点