從原理層面掌握HandlerMethod、InvocableHandlerMethod的使用【一塊兒學Spring MVC】

每篇一句

想當火影的人沒有近道可尋,當上火影的人一樣無路可退

前言

HandlerMethod它做爲Spring MVC的非公開API,可能絕大多數小夥伴都對它比較陌生,但我相信你對它又不是那麼的生疏,由於你可能沒用過但確定見過。
好比Spring MVC的攔截器HandlerInterceptor的攔截方法的第三個入參Object handler,雖然它是Object類型,但其實絕大部分狀況下咱們都會看成HandlerMethod來使用;又好比我以前的這篇講RequestMappingHandlerMapping的文章也大量的提到過HandlerMethod這個類。java

經由我這麼「忽悠」,你是否以爲它仍是相對比較重要的一個類了呢?無論你信不信,反正我是這麼認爲的:HandlerMethod它是理解Spring MVC不可或缺的一個類,甚至能夠說是你但願參與到Spring MVC的定製化裏面來不可忽略的一個關鍵API。react

HandlerMethod

HandlerMethod它不是一個接口,也不是個抽象類,且仍是public的。HandlerMethod封裝了不少屬性,在訪問請求方法的時候能夠方便的訪問到方法、方法參數、方法上的註解、所屬類等而且對方法參數封裝處理,也能夠方便的訪問到方法參數的註解等信息。web

// @since 3.1
public class HandlerMethod {

    // Object類型,既能夠是個Bean,也能夠是個BeanName
    private final Object bean;
    // 若是是BeanName,拿就靠它拿出Bean實例了~
    @Nullable
    private final BeanFactory beanFactory;
    private final Class<?> beanType; // 該方法所屬的類
    private final Method method; // 該方法自己
    private final Method bridgedMethod; // 被橋接的方法,若是method是原生的,它的值同method
    // 封裝方法參數的類實例,**一個MethodParameter就是一個入參**
    // MethodParameter也是Spring抽象出來的一個很是重要的概念
    private final MethodParameter[] parameters;
    @Nullable
    private HttpStatus responseStatus; // http狀態碼(畢竟它要負責處理和返回)
    @Nullable
    private String responseStatusReason; // 若是狀態碼裏還要複數緣由,就是這個字段  能夠爲null


    // 經過createWithResolvedBean()解析此handlerMethod實例的handlerMethod。
    @Nullable
    private HandlerMethod resolvedFromHandlerMethod;
    // 標註在**接口入參**上的註解們(此處數據結構複雜,List+二維數組)
    @Nullable
    private volatile List<Annotation[][]> interfaceParameterAnnotations;

    // 它的構造方法衆多  此處我只寫出關鍵的步驟
    public HandlerMethod(Object bean, Method method) {
        ...
        this.beanType = ClassUtils.getUserClass(bean);
        this.bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
        this.parameters = initMethodParameters();
        ...
        evaluateResponseStatus();
    }
    // 這個構造方法拋出了一個異常NoSuchMethodException 
    public HandlerMethod(Object bean, String methodName, Class<?>... parameterTypes) throws NoSuchMethodException {
        ...
        this.method = bean.getClass().getMethod(methodName, parameterTypes);
        this.parameters = initMethodParameters();
        ...
        evaluateResponseStatus();
    }
    // 此處傳的是BeanName
    public HandlerMethod(String beanName, BeanFactory beanFactory, Method method) {
        ...
        // 這部判斷:這個BeanName是必須存在的
        Class<?> beanType = beanFactory.getType(beanName);
        if (beanType == null) {
            throw new IllegalStateException("Cannot resolve bean type for bean with name '" + beanName + "'");
        }
        this.parameters = initMethodParameters();
        ...
        evaluateResponseStatus();
    }

    // 供給子類copy使用的
    protected HandlerMethod(HandlerMethod handlerMethod) { ... }
    
    // 全部構造都執行了兩個方法:initMethodParameters和evaluateResponseStatus

    // 初始化該方法全部的入參,此處使用的是內部類HandlerMethodParameter
    // 注意:處理了泛型的~~~
    private MethodParameter[] initMethodParameters() {
        int count = this.bridgedMethod.getParameterCount();
        MethodParameter[] result = new MethodParameter[count];
        for (int i = 0; i < count; i++) {
            HandlerMethodParameter parameter = new HandlerMethodParameter(i);
            GenericTypeResolver.resolveParameterType(parameter, this.beanType);
            result[i] = parameter;
        }
        return result;
    }

    // 看看方法上是否有標註了@ResponseStatus註解(接口上或者父類 組合註解上都行)
    // 若方法上沒有,還會去所在的類上去看看有沒有標註此註解
    // 主要只解析這個註解,把它的兩個屬性code和reason拿過來,最後就是返回它倆了~~~
    // code狀態碼默認是HttpStatus.INTERNAL_SERVER_ERROR-->(500, "Internal Server Error")
    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();
        }
    }
    ... // 省略全部屬性的get方法(無set方法)

    // 返回方法返回值的類型  此處也使用的MethodParameter 
    public MethodParameter getReturnType() {
        return new HandlerMethodParameter(-1);
    }
    // 注意和上面的區別。舉個列子:好比方法返回的是Object,但實際return 「fsx」字符串
    // 那麼上面返回永遠是Object.class,下面你實際的值是什麼類型就是什麼類型
    public MethodParameter getReturnValueType(@Nullable Object returnValue) {
        return new ReturnValueMethodParameter(returnValue);
    }

    // 該方法的返回值是不是void
    public boolean isVoid() {
        return Void.TYPE.equals(getReturnType().getParameterType());
    }
    // 返回標註在方法上的指定類型的註解   父方法也成
    // 子類ServletInvocableHandlerMethod對下面兩個方法都有複寫~~~
    @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);
    }


    // resolvedFromHandlerMethod雖然它只能被構造進來,可是它實際是銅鼓調用下面方法賦值
    @Nullable
    public HandlerMethod getResolvedFromHandlerMethod() {
        return this.resolvedFromHandlerMethod;
    }
    // 根據string類型的BeanName把Bean拿出來,再new一個HandlerMethod出來~~~這才靠譜嘛
    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);
    }

    public String getShortLogMessage() {
        return getBeanType().getName() + "#" + this.method.getName() + "[" + this.method.getParameterCount() + " args]";
    }


    // 這個方法是提供給內部類HandlerMethodParameter來使用的~~ 它使用的數據結構仍是蠻複雜的
    private List<Annotation[][]> getInterfaceParameterAnnotations() {
        List<Annotation[][]> parameterAnnotations = this.interfaceParameterAnnotations;
        if (parameterAnnotations == null) {
            parameterAnnotations = new ArrayList<>();

            // 遍歷該方法所在的類全部的實現的接口們(能夠實現N個接口嘛)
            for (Class<?> ifc : this.method.getDeclaringClass().getInterfaces()) {
            
                // getMethods:拿到全部的public的方法,包括父接口的  接口裏的私有方法可不會獲取來
                for (Method candidate : ifc.getMethods()) {
                    // 判斷這個接口方法是否正好是當前method複寫的這個~~~
                    // 恰好是複寫的方法,那就添加進來,標記爲接口上的註解們~~~
                    if (isOverrideFor(candidate)) {
                        // getParameterAnnotations返回的是個二維數組~~~~
                        // 由於參數有多個,且每一個參數前能夠有多個註解
                        parameterAnnotations.add(candidate.getParameterAnnotations());
                    }
                }
            }
            this.interfaceParameterAnnotations = parameterAnnotations;
        }
        return parameterAnnotations;
    }

    
    // 看看內部類的關鍵步驟
    protected class HandlerMethodParameter extends SynthesizingMethodParameter {
        @Nullable
        private volatile Annotation[] combinedAnnotations;
        ...

        // 父類只會在本方法拿,這裏支持到了接口級別~~~
        @Override
        public Annotation[] getParameterAnnotations() {
            Annotation[] anns = this.combinedAnnotations;
            if (anns == null) { // 都只須要解析一次
                anns = super.getParameterAnnotations();
                int index = getParameterIndex();
                if (index >= 0) { // 有入參才須要去分析嘛
                    for (Annotation[][] ifcAnns : getInterfaceParameterAnnotations()) {
                        if (index < ifcAnns.length) {
                            Annotation[] paramAnns = ifcAnns[index];
                            if (paramAnns.length > 0) {
                                List<Annotation> merged = new ArrayList<>(anns.length + paramAnns.length);
                                merged.addAll(Arrays.asList(anns));
                                for (Annotation paramAnn : paramAnns) {
                                    boolean existingType = false;
                                    for (Annotation ann : anns) {
                                        if (ann.annotationType() == paramAnn.annotationType()) {
                                            existingType = true;
                                            break;
                                        }
                                    }
                                    if (!existingType) {
                                        merged.add(adaptAnnotation(paramAnn));
                                    }
                                }
                                anns = merged.toArray(new Annotation[0]);
                            }
                        }
                    }
                }
                this.combinedAnnotations = anns;
            }
            return anns;
        }
    }

    // 返回值的真正類型~~~
    private class ReturnValueMethodParameter extends HandlerMethodParameter {
        @Nullable
        private final Object returnValue;
        public ReturnValueMethodParameter(@Nullable Object returnValue) {
            super(-1); // 此處傳的-1哦~~~~ 比0小是頗有意義的
            this.returnValue = returnValue;
        }
        ...
        // 返回值類型使用returnValue就好了~~~
        @Override
        public Class<?> getParameterType() {
            return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType());
        }
    }
}

能夠看到HandlerMethod它持有的屬性是很是多的,提供的能力也是很強的。
可是不知道小夥伴有沒有發現,雖然它持有了目標的Method,可是它並無提供invoke執行它的能力,若是你要執行它還得本身把Method拿去本身執行。spring

因此總的來講它的職責仍是很單一的:HandlerMethod它只負責準備數據,封裝數據,而而不提供具體使用的方式方法~數組

看看它的繼承樹:
在這裏插入圖片描述
它主要有兩個子類:InvocableHandlerMethodServletInvocableHandlerMethod,從命名就知道他倆都是有invoke調用能力的~數據結構

InvocableHandlerMethod

它是對HandlerMethod的擴展,增長了調用能力。這個能力在Spring MVC但是很是很是重要的,它可以在調用的時候,把方法入參的參數都封裝進來(從HTTP request裏,固然藉助的必然是HandlerMethodArgumentResolver架構

// @since 3.1
public class InvocableHandlerMethod extends HandlerMethod {
    private static final Object[] EMPTY_ARGS = new Object[0];

    // 它額外提供的幾個屬性,能夠看到和數據綁定、數據校驗就扯上關係了~~~

    // 用於產生數據綁定器、校驗器
    @Nullable
    private WebDataBinderFactory dataBinderFactory;
    // HandlerMethodArgumentResolver用於入參的解析
    private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
    // 用於獲取形參名
    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    
    ... // 省略構造函數 所有使用super的
    // 它本身的三大屬性都使用set方法設置進來~~~而且沒有提供get方法
    // 也就是說:它本身內部使用就好了~~~

    // 在給定請求的上下文中解析方法的參數值後調用該方法。 也就是說:方法入參裏就可以自動使用請求域(包括path裏的,requestParam裏的、以及常規對象如HttpSession這種)
    // 解釋下providedArgs做用:調用者能夠傳進來,而後直接doInvoke()的時候原封不動的使用它
    //(彌補了請求域沒有全部對象的不足,畢竟有些對象是用戶自定義的嘛~)
    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        // 雖然它是最重要的方法,可是此處不講,由於核心原來仍是`HandlerMethodArgumentResolver`
        // 它只是把解析好的放到對應位置裏去~~~
        // 說明:這裏傳入了ParameterNameDiscoverer,它是可以獲取到形參名的。
        // 這就是爲什麼註解裏咱們不寫value值,經過形參名字來匹配也是ok的核心緣由~
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) { // trace信息,不然日誌也特多了~
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        return doInvoke(args);
    }

    // doInvoke()方法就不說了,就是個普通的方法調用
    // ReflectionUtils.makeAccessible(getBridgedMethod());
    // return getBridgedMethod().invoke(getBean(), args); 
}

對於最後的invoke(),說明一點:這裏但是執行的目標方法getBean()哦~~~app

這個子類主要提供的能力就是提供了invoke調用目標Bean目標方法的能力,在這個調用過程當中可大有文章可爲,固然最爲核心的邏輯但是各類各樣的HandlerMethodArgumentResolver來完成的,詳見下文有分曉。
InvocableHandlerMethod這個子類雖然它提供了調用了能力,可是它卻依舊尚未和Servlet的API綁定起來,畢竟使用的是Spring本身通用的的NativeWebRequest,so很容易想到它還有一個子類就是幹這事的~異步

ServletInvocableHandlerMethod

它是對InvocableHandlerMethod的擴展,它增長了返回值和響應狀態碼的處理,另外在ServletInvocableHandlerMethod有個內部類ConcurrentResultHandlerMethod繼承於它,支持異常調用結果處理,Servlet容器下Controller在查找適配器時發起調用的最終就是ServletInvocableHandlerMethodide

public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {
    private static final Method CALLABLE_METHOD = ClassUtils.getMethod(Callable.class, "call");

    // 處理方法返回值
    @Nullable
    private HandlerMethodReturnValueHandlerComposite returnValueHandlers;

    // 構造函數略
    
    // 設置處理返回值的HandlerMethodReturnValueHandler
    public void setHandlerMethodReturnValueHandlers(HandlerMethodReturnValueHandlerComposite returnValueHandlers) {
        this.returnValueHandlers = returnValueHandlers;
    }


    // 它不是複寫,可是是對invokeForRequest方法的進一步加強  由於調用目標方法仍是靠invokeForRequest
    // 本處是把方法的返回值拿來進一步處理~~~好比狀態碼之類的
    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
        // 設置HttpServletResponse返回狀態碼 這裏面仍是有點意思的  由於@ResponseStatus#code()在父類已經解析了  可是子類才用
        setResponseStatus(webRequest);


        // 重點是這一句話:mavContainer.setRequestHandled(true); 表示該請求已經被處理過了
        if (returnValue == null) {

            // Request的NotModified爲true 有@ResponseStatus註解標註 RequestHandled=true 三個條件有一個成立,則設置請求處理完成並返回
            if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
                mavContainer.setRequestHandled(true);
                return;
            }
        // 返回值不爲null,@ResponseStatus存在reason 一樣設置請求處理完成並返回
        } else if (StringUtils.hasText(getResponseStatusReason())) {
            mavContainer.setRequestHandled(true);
            return;
        }

        // 前邊都不成立,則設置RequestHandled=false即請求未完成
        // 繼續交給HandlerMethodReturnValueHandlerComposite處理
        // 可見@ResponseStatus的優先級仍是蠻高的~~~~~
        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
        
            // 關於對方法返回值的處理,參見:https://blog.csdn.net/f641385712/article/details/90370542
            this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        } catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }

    // 設置返回的狀態碼到HttpServletResponse 裏面去
    private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
        HttpStatus status = getResponseStatus();
        if (status == null) { // 若是調用者沒有標註ResponseStatus.code()此註解  此處就忽略它
            return;
        }

        HttpServletResponse response = webRequest.getResponse();
        if (response != null) {
            String reason = getResponseStatusReason();

            // 此處務必注意:如有reason,那就是sendError  哪怕你是200哦~
            if (StringUtils.hasText(reason)) {
                response.sendError(status.value(), reason);
            } else {
                response.setStatus(status.value());
            }
        }

        // 設置到request的屬性,把響應碼給過去。爲了在redirect中使用
        // To be picked up by RedirectView
        webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
    }

    private boolean isRequestNotModified(ServletWebRequest webRequest) {
        return webRequest.isNotModified();
    }


    // 這個方法RequestMappingHandlerAdapter裏有調用
    ServletInvocableHandlerMethod wrapConcurrentResult(Object result) {
        return new ConcurrentResultHandlerMethod(result, new ConcurrentResultMethodParameter(result));
    }

    // 內部類們
    private class ConcurrentResultMethodParameter extends HandlerMethodParameter {
        @Nullable
        private final Object returnValue;
        private final ResolvableType returnType;
        public ConcurrentResultMethodParameter(Object returnValue) {
            super(-1);
            this.returnValue = returnValue;
            // 主要是這個解析 兼容到了泛型類型 好比你的返回值是List<Person> 它也能把你的類型拿出來
            this.returnType = (returnValue instanceof ReactiveTypeHandler.CollectedValuesList ?
                    ((ReactiveTypeHandler.CollectedValuesList) returnValue).getReturnType() :
                    ResolvableType.forType(super.getGenericParameterType()).getGeneric());
        }

        // 若返回的是List  這裏就是List的類型哦  下面纔是返回泛型類型
        @Override
        public Class<?> getParameterType() {
            if (this.returnValue != null) {
                return this.returnValue.getClass();
            }
            if (!ResolvableType.NONE.equals(this.returnType)) {
                return this.returnType.toClass();
            }
            return super.getParameterType();
        }

        // 返回泛型類型
        @Override
        public Type getGenericParameterType() {
            return this.returnType.getType();
        }


        // 即便實際返回類型爲ResponseEntity<Flux<T>>,也要確保對@ResponseBody-style處理從reactive 類型中收集值
        // 是對reactive 的一種兼容
        @Override
        public <T extends Annotation> boolean hasMethodAnnotation(Class<T> annotationType) {
            // Ensure @ResponseBody-style handling for values collected from a reactive type
            // even if actual return type is ResponseEntity<Flux<T>>
            return (super.hasMethodAnnotation(annotationType) ||
                    (annotationType == ResponseBody.class && this.returnValue instanceof ReactiveTypeHandler.CollectedValuesList));
        }
    }


    // 這個很是有意思   內部類繼承了本身(外部類) 進行加強
    private class ConcurrentResultHandlerMethod extends ServletInvocableHandlerMethod {
        // 返回值
        private final MethodParameter returnType;

        // 此構造最終傳入的handler是個Callable
        // result方法返回值 它支持支持異常調用結果處理
        public ConcurrentResultHandlerMethod(final Object result, ConcurrentResultMethodParameter returnType) {
            super((Callable<Object>) () -> {
                if (result instanceof Exception) {
                    throw (Exception) result;
                } else if (result instanceof Throwable) {
                    throw new NestedServletException("Async processing failed", (Throwable) result);
                }
                return result;
            }, CALLABLE_METHOD);


            // 給外部類把值設置上  由於wrapConcurrentResult通常都先調用,是對本類的一個加強
            if (ServletInvocableHandlerMethod.this.returnValueHandlers != null) {
                setHandlerMethodReturnValueHandlers(ServletInvocableHandlerMethod.this.returnValueHandlers);
            }
            this.returnType = returnType;
        }
        ...
    }
}

HandlerMethod用於封裝Handler和處理請求的MethodInvocableHandlerMethod增長了方法參數解析和調用方法的能力;ServletInvocableHandlerMethod在此基礎上在增長了以下三個能力:

  1. @ResponseStatus註解的支持

    1.當一個方法註釋了`@ResponseStatus`後,**響應碼就是註解上的響應碼**。 **而且,而且若是returnValue=null或者reason不爲空**(不爲null且不爲「」),將中斷處理直接返回(再也不渲染頁面)
  2. 對返回值returnValue的處理

    1. 對返回值的處理是使用`HandlerMethodReturnValueHandlerComposite`完成的
  3. 異步處理結果的處理

使用示例

文首說了,HandlerMethod做爲一個非公開API,若是你要直接使用起來,仍是稍微要費點勁的。
但本文仍是給出一個Demo,給出小夥伴們最爲關心也是對大家最有用的一個需求:ModelFactory.getNameForParameter(parameter)這個靜態方法是給入參生成默認名稱的,固然默認處理方案最底層依賴的是它Conventions.getVariableNameForParameter(parameter),爲了驗證這塊對象、Object、List等等經常使用數據結構的默認處理,此處我藉助HandlerMethod一次性所有打印出這個結論:

@Getter
@Setter
@ToString
public class Person {

    @NotNull
    private String name;
    @NotNull
    @Positive
    private Integer age;

    public Object demoMethod(Person person, Object object,
                             List<Integer> intList, List<Person> personList,
                             Set<Integer> intSet, Set<Person> personSet,
                             Map<String, Object> myMap,
                             String name, Integer age,
                             int number, double money) {
        return "hello parameter";
    }
}

藉助HandlerMethod完成此測試用例

public static void main(String[] args) {
        // 準備一個HandlerMethod
        HandlerMethod handlerMethod = new HandlerMethod(new Person(), getPersonSpecfyMethod());
        // 拿到該方法全部的參數
        MethodParameter[] methodParameters = handlerMethod.getMethodParameters();
        for (MethodParameter parameter : methodParameters) {
            Class<?> parameterType = parameter.getParameterType();
            String nameForParameter = ModelFactory.getNameForParameter(parameter);
            System.out.println("類型" + parameterType.getName() + "--->缺省的modelKey是:" + nameForParameter);
        }
    }

    private static Method getPersonSpecfyMethod() {
        for (Method method : Person.class.getMethods())
            if (method.getName().equals("demoMethod"))
                return method;
        return null;
    }

運行,打印結果以下:

類型com.fsx.bean.Person--->缺省的modelKey是:person
類型java.lang.Object--->缺省的modelKey是:object
類型java.util.List--->缺省的modelKey是:integerList
類型java.util.List--->缺省的modelKey是:personList
類型java.util.Set--->缺省的modelKey是:integerList // 能夠看到即便是set 名稱也是同List的
類型java.util.Set--->缺省的modelKey是:personList
類型java.util.Map--->缺省的modelKey是:map
類型java.lang.String--->缺省的modelKey是:string
類型java.lang.Integer--->缺省的modelKey是:integer
類型int--->缺省的modelKey是:int
類型double--->缺省的modelKey是:double

這個結果是不一樣類型對應的缺省的ModelKey,但願小夥伴們可以記下來,這對理解和正確使用`
@SessionAttribute、@ModelAttribute`都是很重要的~

總結

HandlerMethod雖然接觸少,但並不影響它的重要性。在理解Spring MVC的處理流程上它很重要,在與使用者關係較大的攔截器HandlerInterceptor定製化處理的時候,學會使用它同樣是很是有必要的。

在最後還提示你們一個你可能沒有關心到的小細節:

  1. HandlerMethod位於org.springframework.web.method包下,且是3.1後纔有的
  2. MethodParameter位於org.springframework.core核心包中。2.0就存在了

相關閱讀

【小家Spring】Spring MVC容器的web九大組件之---HandlerAdapter源碼詳解---一篇文章帶你讀懂返回值處理器HandlerMethodReturnValueHandler

知識交流

==The last:若是以爲本文對你有幫助,不妨點個讚唄。固然分享到你的朋友圈讓更多小夥伴看到也是被做者本人許可的~==

**若對技術內容感興趣能夠加入wx羣交流:Java高工、架構師3羣
若羣二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。而且備註:"java入羣" 字樣,會手動邀請入羣**

若文章格式混亂或者圖片裂開,請點擊`: 原文連接-原文連接-原文連接

==若對Spring、SpringBoot、MyBatis等源碼分析感興趣,可加我wx:fsx641385712,手動邀請你入羣一塊兒飛==

相關文章
相關標籤/搜索