Spring Boot 定义接口的方法是否可以声明为 private?


可以看到,HandlerMethod 体系下的类并不多:

HandlerMethod

封装 Handler 和具体处理请求的 Method。

InvocableHandlerMethod

在 HandlerMethod 的基础上增加了调用的功能。

ServletInvocableHandlerMethod

在 InvocableHandlerMethod 的基础上增了对 @ResponseStatus 注解的支持、增加了对返回值的处理。

ConcurrentResultHandlerMethod

在 ServletInvocableHandlerMethod 的基础上,增加了对异步结果的处理。

基本上就是这四个,接下来猿哥就来详细说一说这四个组件。

2.HandlerMethod


2.1 bridgedMethod

在正式开始介绍 HandlerMethod 之前,想先和大家聊聊 bridgedMethod,因为在 HandlerMethod 中将会涉及到这个东西,而有的小伙伴可能还没听说过 bridgedMethod,因此松哥在这里做一个简单介绍。

首先考考大家,下面这段代码编译会报错吗?

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());

}

}

首先我们定义了一个 Animal 接口,里边定义了一个 eat 方法,同时声明了一个泛型。Cat 实现了 Animal 接口,将泛型也定义为了 String。当我调用的时候,声明类型是 Animal,实际类型是 Cat,这个时候调 eat 方法传入了 Object 对象大家猜猜会怎么样?如果调用 eat 方法时传入的是 String 类型那就肯定没问题,但如果不是 String 呢?

松哥先说结论:编译没问题,运行报错。

如果小伙伴们在自己电脑上写出上面这段代码,你会发现这样一个问题,开发工具中提示的参数类型竟然是 Object,以松哥的 IDEA 为例,如下:

大家看到,在我写代码的时候,开发工具会给我提示,这个参数类型是 Object,有的小伙伴会觉得奇怪,明明是泛型,怎么变成 Object 了?

我们可以通过反射查看 Cat 类中到底有哪些方法,代码如下:

public class Demo01 {

public
作者私人号:vip1024c
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) +“)”);

}

}

}

运行结果如下:

可以看到,在实际运行过程中,竟然有两个 eat 方法,一个的参数为 String 类型,另一个参数为 Object 类型,这是怎么回事呢?

这个参数类型为 Object 的方法其实是 Java 虚拟机在运行时创建出来的,这个方法就是我们所说的 bridge method。本节的小标题叫做 bridgedMethod,这是 HandlerMethod 源码中的变量名,bridge 结尾多了一个 d,含义变成了被 bridge 的方法,也就是参数为 String 的原方法,大家在接下来的源码中看到了 bridgedMethod 就知道这表示参数类型不变的原方法。

2.2 HandlerMethod 介绍

接下来我们来简单看下 HandlerMethod。

public HandlerMethod createWithResolvedBean() {

Object handler = this.bean;

if (this.bean instanceof String) {

String beanName = (String) this.bean;

handler = this.beanFactory.getBean(beanName);

}

return new HandlerMethod(this, handler);

}

这个方法主要是确认了一下 handler 的类型,如果 handler 是 String 类型,则根据 beanName 从 Spring 容器中重新查找到 handler 对象,然后构建 HandlerMethod:

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;

}

这里的参数都比较简单,没啥好说的,唯一值得介绍的地方有两个:parameters 和 responseStatus。

parameters

parameters 实际上就是方法参数,对应的类型是 MethodParameter,这个类的源码我这里就不贴出来了,主要和大家说一下封装的内容包括:参数的序号(parameterIndex),参数嵌套级别(nestingLevel),参数类型(parameterType),参数的注解(parameterAnnotations),参数名称查找器(parameterNameDiscoverer),参数名称(parameterName)等。

HandlerMethod 中还提供了两个内部类来封装 MethodParameter,分别是:

  • HandlerMethodParameter:这个封装方法调用的参数。

  • ReturnValueMethodParameter:这个继承自 HandlerMethodParameter,它封装了方法的返回值,返回值里边的 parameterIndex 是 -1。

注意,这两者中的 method 都是 bridgedMethod。

responseStatus

这个主要是处理方法的 @ResponseStatus 注解,这个注解用来描述方法的响应状态码,使用方式像下面这样:

@GetMapping(“/04”)

@ResponseBody

@ResponseStatus(code = HttpStatus.OK)

public void hello4(@SessionAttribute(“name”) String name) {

System.out.println("name = " + name);

}

从这段代码中大家可以看到,其实 @ResponseStatus 注解灵活性很差,不实用,当我们定义一个接口的时候,很难预知到该接口的响应状态码是 200。

在 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();

}

}

可以看到,这段代码也比较简单,找到注解,把里边的值解析出来,赋值给相应的变量。

这下小伙伴们应该明白了 HandlerMethod 大概是个怎么回事。

3.InvocableHandlerMethod


看名字就知道,InvocableHandlerMethod 可以调用 HandlerMethod 中的具体方法,也就是 bridgedMethod。我们先来看下 InvocableHandlerMethod 中声明的属性:

private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();

private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();

@Nullable

private WebDataBinderFactory dataBinderFactory;

主要就是这三个属性:

  • resolvers:这个不用说,参数解析器,前面的文章中松哥已经和大家聊过这个问题了。

  • parameterNameDiscoverer:这个用来获取参数名称,在 MethodParameter 中会用到。

  • dataBinderFactory:这个用来创建 WebDataBinder,在参数解析器中会用到。

具体的请求调用方法是 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) {

// 省略 …

}

}

首先调用 getMethodArgumentValues 方法按顺序获取到所有参数的值,这些参数值组成一个数组,然后调用 doInvoke 方法执行,在 doInvoke 方法中,首先获取到 bridgedMethod,并设置其可见(意味着我们在 Controller 中定义的接口方法也可以是 private 的),然后直接通过反射调用即可。当我们没看 SpringMVC 源码的时候,我们就知道接口方法最终肯定是通过反射调用的,现在,经过层层分析之后,终于在这里找到了反射调用代码。

最后猿哥再来说一下负责参数解析的 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) {

// 省略…

最后

小编精心为大家准备了一手资料

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

BATJ面试要点及Java架构师进阶资料

lveArgument(parameter, mavContainer, request, this.dataBinderFactory);

}

catch (Exception ex) {

// 省略…

最后

小编精心为大家准备了一手资料

[外链图片转存中…(img-EwXg6eSN-1721063705229)]

[外链图片转存中…(img-8ion4YFi-1721063705229)]

以上Java高级架构资料、源码、笔记、视频。Dubbo、Redis、设计模式、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术

【附】架构书籍

  1. BAT面试的20道高频数据库问题解析
  2. Java面试宝典
  3. Netty实战
  4. 算法

[外链图片转存中…(img-nSZvVpps-1721063705230)]

BATJ面试要点及Java架构师进阶资料

[外链图片转存中…(img-A0k9vvvj-1721063705231)]

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值