关于java-web:Spring-Boot-定义接口的方法是否可以声明为-private

6次阅读

共计 8321 个字符,预计需要花费 21 分钟才能阅读完成。

咱们在 Controller 中定义接口的时候,个别都是像上面这样:

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

预计很少有人会把接口办法定义成 private 的吧?那咱们不禁要问,如果非要定义成 private 的办法,那能运行起来吗?

带着这个疑难,咱们开始明天的源码解读~

在咱们应用 Spring Boot 的时候,常常会看到 HandlerMethod 这个类型,例如咱们在定义拦截器的时候,如果拦挡指标是一个办法,则 preHandle 的第三个参数就是 HandlerMethod(以下案例选自松哥之前的视频:手把手教你 Spring Boot 自定义注解):

@Component
public class IdempotentInterceptor implements HandlerInterceptor {
    @Autowired
    TokenService tokenService;
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {if (!(handler instanceof HandlerMethod)) {return true;}
        // 省略...
        return true;
    }
    //...
}

咱们在浏览 SpringMVC 源码的时候,也会重复看到这个 HandlerMethod,那么它到底是什么意思?明天我想和小伙伴们捋一捋这个问题,把这个问题搞清楚了,后面的问题大家也就懂了。

1. 概览

能够看到,HandlerMethod 体系下的类并不多:

HandlerMethod

封装 Handler 和具体解决申请的 Method。

InvocableHandlerMethod

在 HandlerMethod 的根底上减少了调用的性能。

ServletInvocableHandlerMethod

在 InvocableHandlerMethod 的根底上增了对 @ResponseStatus 注解的反对、减少了对返回值的解决。

ConcurrentResultHandlerMethod

在 ServletInvocableHandlerMethod 的根底上,减少了对异步后果的解决。

基本上就是这四个,接下来松哥就来具体说一说这四个组件。

2.HandlerMethod

2.1 bridgedMethod

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

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

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(new Object());
    }
}

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

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

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

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

咱们能够通过反射查看 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) +")");
        }
    }
}

运行后果如下:

能够看到,在理论运行过程中,居然有两个 eat 办法,一个的参数为 String 类型,另一个参数为 Object 类型,这是怎么回事呢?

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

2.2 HandlerMethod 介绍

接下来咱们来简略看下 HandlerMethod。

在咱们后面剖析 HandlerMapping 的时候(参见:SpringMVC 九大组件之 HandlerMapping 深入分析),里边有波及到 HandlerMethod,创立 HandlerMethod 的入口办法是 createWithResolvedBean,因而这里咱们就从该办法开始看起:

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) {// 省略...}
    }
    return args;
}
  1. 首先调用 getMethodParameters 办法获取到办法的所有参数。
  2. 创立 args 数组用来保留参数的值。
  3. 接下来一堆初始化配置。
  4. 如果 providedArgs 中提供了参数值,则间接赋值。
  5. 查看是否有参数解析器反对以后参数类型,如果没有,间接抛出异样。
  6. 调用参数解析器对参数进行解析,解析实现后,赋值。

是不是,很 easy!

4.ServletInvocableHandlerMethod

ServletInvocableHandlerMethod 则是在 InvocableHandlerMethod 的根底上,又减少了两个性能:

  • @ResponseStatus 注解的解决
  • 对返回值的解决

Servlet 容器下 Controller 在查找适配器时发动调用的最终就是 ServletInvocableHandlerMethod。

这里的解决外围办法是 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;}
}
  1. 首先调用父类的 invokeForRequest 办法对申请进行执行,拿到申请后果。
  2. 调用 setResponseStatus 办法解决 @ResponseStatus 注解,具体的解决逻辑是这样:如果没有增加 @ResponseStatus 注解,则什么都不做;如果增加了该注解,并且 reason 属性不为空,则间接输入谬误,否则设置响应状态码。这里须要留神一点,如果响应状态码是 200,就不要设置 reason,否则会依照 error 解决。
  3. 接下来就是对返回值的解决了,returnValueHandlers#handleReturnValue 办法松哥在之前的文章中和大家专门介绍过,这里就不再赘述,传送门:Spring Boot 中如何对立 API 接口响应格局?。

事实上,ServletInvocableHandlerMethod 还有一个子类 ConcurrentResultHandlerMethod,这个反对异步调用后果解决,因为应用场景较少,这里就不做介绍啦。

5. 小结

当初大家能够答复文章题目提出的问题了吧?

正文完
 0