HandlerMethod、InvocableHandlerMethodの使用を原理的に把握【Spring MVCを共に学ぶ】


ひとつひとつ
火影になりたい人は近道がなく、火影になった人も退く道がない.
前言HandlerMethodそれはSpring MVCの非公開APIとして、ほとんどのパートナーはそれに慣れていないかもしれませんが、私はあなたがそれに対してそんなに疎かではないと信じています.あなたは使ったことがないかもしれませんが、見たことがあるかもしれません.例えばSpring MVCのブロックHandlerInterceptorのブロック方法の3番目のパラメータObject handlerは、Objectタイプであるが、ほとんどの場合、HandlerMethodとして使用される.例えば、私の前のこのRequestMappingHandlerMappingの記事はHandlerMethodというクラスに大量に言及しました.
私がこのように「ぶらぶら」していることを通じて、あなたはそれが相対的に重要なクラスだと思いますか?あなたが信じても信じなくても、どうせ私はそう思っています.HandlerMethodそれはSpring MVCを理解するのに欠かせないクラスであり、Spring MVCのカスタマイズに参加して無視できない重要なAPIと言えるでしょう.
HandlerMethod HandlerMethodインタフェースでも抽象クラスでもpublicでもありません.HandlerMethodには多くの属性がカプセル化されており、リクエストメソッドにアクセスする際にメソッド、メソッドパラメータ、メソッド上の注釈、所属クラスなどに容易にアクセスでき、メソッドパラメータの注釈などの情報にも容易にアクセスできる.
// @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 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 getMethodAnnotation(ClassannotationType) {
return AnnotatedElementUtils.findMergedAnnotation(this.method, annotationType);
}
publicboolean hasMethodAnnotation(ClassannotationType) {
return AnnotatedElementUtils.hasAnnotation(this.method, annotationType);
}
//resolvedFromHandlerMethodは  するしかありませんが、  には ドラム び しの の  で を けています。
@Nullable
public HandlerMethod getResolvedFromHandlerMethod() {
return this.resolvedFromHandlerMethod;
}
//stringタイプのBeanNameによってBeanを して、もう1つの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 getInterfaceParameterAnnotations() {
List 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は2    を します~~~~
//パラメータが  あるため、 パラメータの に  の  を けることができます
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 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を自分で実行しなければなりません.HandlerMethodデータの準備、データのカプセル化だけを担当し、具体的な使用方法を提供しません.
その継承木を見てみましょう.主に2つのサブクラスがあります.InvocableHandlerMethodServletInvocableHandlerMethodです.名前から彼ら2人は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()ですよ~~~
このサブクラスが主に提供する能力は、invokeがターゲットBeanを呼び出すターゲットメソッドを提供する能力であり、この呼び出しの過程で多くの文章を書くことができ、もちろん最も核心的な論理は様々なHandlerMethodArgumentResolverが完成し、詳細は以下を参照してください.InvocableHandlerMethodというサブクラスは呼び出し能力を提供しているが、ServletのAPIとバインドされていない.結局、Spring自身が通用するNativeWebRequestを使用しているので、soはもう一つのサブクラスがこのことをしていると考えやすい.
ServletInvocableHandlerMethod
これはInvocableHandlerMethodへの拡張であり、戻り値と応答ステータスコードを増加させる処理であり、またServletInvocableHandlerMethodに内部クラスConcurrentResultHandlerMethodが継承され、異常呼び出し結果処理をサポートし、Servletコンテナの下でControllerがアダプタを検索する際に呼び出しを開始する最終的にServletInvocableHandlerMethodである.
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            
            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>,     @ResponseBody-style   reactive       
        //   reactive      
        @Override
        public  boolean hasMethodAnnotation(Class annotationType) {
            // Ensure @ResponseBody-style handling for values collected from a reactive type
            // even if actual return type is ResponseEntity>
            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) () -> {
                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をカプセル化し、要求を処理するためのMethodである.InvocableHandlerMethodは、メソッドパラメータの解析およびメソッドの呼び出し能力を増加させる.ServletInvocableHandlerMethodこれに基づいて、次の3つの能力が追加されました.
  • @ResponseStatus注釈のサポート
        1.        `@ResponseStatus` ,**            **。 **  ,    returnValue=null  reason   **(  null   “”),         (      )
  • の戻り値returnValueに対する処理
        1.           `HandlerMethodReturnValueHandlerComposite`    
  • 非同期処理結果に対する処理
  • 使用例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 intList, List personList,
                                 Set intSet, Set personSet,
                                 Map 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のカスタマイズ処理を習得する際には、同様に使用することを学ぶ必要がある.
    最後に、あなたが関心を持っていないかもしれない小さな詳細をヒントにします.
  • HandlerMethodorg.springframework.web.method包下に位置する、3.1後にしかない
  • である.
  • MethodParameterは、org.springframework.coreコアパッケージ内に配置される.2.0
  • に存在します
    関連読書
    【小家Spring】Spring MVCコンテナのweb 9大コンポーネントの---HandlerAdapterソースコードの詳細---戻り値プロセッサHandlerMethodReturnValueHandlerを読む記事
    知識交流
    =The last:本文があなたに役に立つと思ったら、いいねを押してもいいですよ.もちろんあなたの友达の輪を分かち合ってもっと多くの友达に見せても ~==
    **技術内容に興味がある場合は、wx群交流:Java 、 3 に参加してください.グループQRコードが無効になった場合は、wx番号:fsx641385712(または下のwx QRコードをスキャン)を追加します.さらに備考:"java "の文字は、手動でグループ**に招待されます.
    記事 または の場合は`:
    原文リンク-原文リンク-原文リンク