使用の原理把持@ModelAttributeレベル(物品のコア原則)における[一緒に]春MVCを学びます

各1

計画と青写真と胸、足の男:私たちはやるべき

序文

Spring MVC注釈ベースのプログラミングモデル大幅に簡素化し提供webするアプリケーションの開発を、我々は受益者です。例えば、我々は、@RestControllerマークされたControllerアセンブリによってコントローラ上で@RequestMapping@ExceptionHandler他の注釈は、マップする要求、例外処理等を示しています。
私は最も重要な利点があると思いコントローラを開発するために、この注釈の方法を使用します:

  1. 柔軟なメソッドシグネチャ(パラメータランダム書き込み)
  2. あなたは、基本クラスを継承する必要はありません
  3. あなたは、インターフェイスを実装する必要はありません

==一言で:柔軟性は、カップリングの非常に強い、非常に低い程度です。==

使用されている多くのノートの中で、Spring MVCそこに非常に強いですが、ほぼ1を見落とし:@ModelAttributeこれについては使用上の注意は、私は非常に少数の人々は本当に私は非常に驚いて作られた、このアノテーションを使用(しても、いくつかのメモを認識していない)になる感じ、/ラインの下にグループ内の一部の人に尋ねました。私は、少なくとも古いプログラマの「よく戦場」のために、これが今起きてはならないと思います。

しかし、それは、この記事はあなたが作るこの盲点を作ることができます見ることが幸運です。
@ModelAttribute注釈(ない開発する必要はありません@RequestMappingので、重要なの)ので、あなたが知らない場合でも、それはまだ通常の書き込みコントローラです。もちろん、ことわざにも、一切のベストがない、唯一のより良い、あなたはそれを習得すれば、それはあなたが助けることができ、より効率的にあなたのコードの再利用性を強くするためにコードを書くために、コードはより保守、より簡潔です。

この知識は、あなたがそれはあなたがビジネスニーズを書き、また働くことができるかわからない場合でも、イントロスペクションのように、反射のようなものです。あなたが巧みに使用することができた場合に期待できる。しかし、あなたの想像力は、将来的に大きくなります。〜それは必要ありませんが、それは良いヘルパーですが

公式の説明@ModelAttribute

まず見てSpring公式JavaDocにそれを言う方法:それは、メソッドのパラメータ/方法はへの結合値を返す内部。のみサポートああ、コントローラのこのタイプを。この方法はまた、上の(戻り値)でマークすることができ、参照方法に注目することができます。web viewModel@RequestMapping

ノートは、しかし、要求処理が例外を起こした場合、基準データとWebビューモデル上の他のすべてのコンテンツは利用できないいつでも例外をトリガすることができるので、Modelコンテンツは信頼できません。このように、でマークされ@Exceptionhandlerていません方法Model〜のパラメータへのアクセスを

// @since 2.5  只能用在入参、方法上
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ModelAttribute {

    @AliasFor("name")
    String value() default "";
    // The name of the model attribute to bind to. 注入如下默认规则
    // 比如person对应的类是:mypackage.Person(类名首字母小写)
    // personList对应的是:List<Person>  这些都是默认规则咯~~~ 数组、Map的省略
    // 具体可以参考方法:Conventions.getVariableNameForParameter(parameter)的处理规则
    @AliasFor("value")
    String name() default "";

    // 若是false表示禁用数据绑定。
    // @since 4.3
    boolean binding() default true;
}

基本的な

私たちは、それが知っている@ModelAttributeもの方法でマークすることができるパラメータにマークすることができます。ここでの原則の深い理解がありますので、その使用を習得し、その後、使用の使用シナリオのさまざまなバック与えますDemo
そして、それは二つのクラスに関連しているModelFactoryModelAttributeMethodProcessor

@ModelAttribute扱うデフォルトRequestの要求のドメインは、Spring MVCまた、提供@SessionAttributes処理とSession見、モデルに関連するデータフィールドを:春のMVCを学ぶために[一緒に]レベルの原則から@SessionAttributesの使用を習得します

ModelFactory導入ここで説明する@SessionAttributes時間が大部分を導入したが、故意の一部に残っている@ModelAttributeコンテンツは、我々はこの記事で説明していき

モデルファクトリー

ModelFactoryパッケージはどこにorg.springframework.web.method.annotation、あなたはそれを見ることができますし、ウェブを強く関連付けられています。記事の補足として、それだけでかかった気に@ModelAttribute一部を解析します:

// @since 3.1
public final class ModelFactory {

    // 初始化Model 这个时候`@ModelAttribute`有很大作用
    public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod) throws Exception {
        // 拿到sessionAttr的属性
        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        // 合并进容器内
        container.mergeAttributes(sessionAttributes);
        // 这个方法就是调用执行标注有@ModelAttribute的方法们~~~~
        invokeModelAttributeMethods(request, container);
        ... 
    }

    //调用标注有注解的方法来填充Model
    private void invokeModelAttributeMethods(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
        // modelMethods是构造函数进来的  一个个的处理吧
        while (!this.modelMethods.isEmpty()) {
            // getNextModelMethod:通过next其实能看出 执行是有顺序的  拿到一个可执行的InvocableHandlerMethod
            InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod();

            // 拿到方法级别的标注的@ModelAttribute~~
            ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class);
            Assert.state(ann != null, "No ModelAttribute annotation");
            if (container.containsAttribute(ann.name())) {
                if (!ann.binding()) { // 若binding是false  就禁用掉此name的属性  让不支持绑定了  此方法也处理完成
                    container.setBindingDisabled(ann.name());
                }
                continue;
            }

            // 调用目标的handler方法,拿到返回值returnValue 
            Object returnValue = modelMethod.invokeForRequest(request, container);
            // 方法返回值不是void才需要继续处理
            if (!modelMethod.isVoid()){

                // returnValueName的生成规则 上文有解释过  本处略
                String returnValueName = getNameForReturnValue(returnValue, modelMethod.getReturnType());
                if (!ann.binding()) { // 同样的 若禁用了绑定,此处也不会放进容器里
                    container.setBindingDisabled(returnValueName);
                }
        
                //在个判断是个小细节:只有容器内不存在此属性,才会放进去   因此并不会有覆盖的效果哦~~~
                // 所以若出现同名的  请自己控制好顺序吧
                if (!container.containsAttribute(returnValueName)) {
                    container.addAttribute(returnValueName, returnValue);
                }
            }
        }
    }

    // 拿到下一个标注有此注解方法~~~
    private ModelMethod getNextModelMethod(ModelAndViewContainer container) {
        
        // 每次都会遍历所有的构造进来的modelMethods
        for (ModelMethod modelMethod : this.modelMethods) {
            // dependencies:表示该方法的所有入参中 标注有@ModelAttribute的入参们
            // checkDependencies的作用是:所有的dependencies依赖们必须都是container已经存在的属性,才会进到这里来
            if (modelMethod.checkDependencies(container)) {
                // 找到一个 就移除一个
                // 这里使用的是List的remove方法,不用担心并发修改异常??? 哈哈其实不用担心的  小伙伴能知道为什么吗??
                this.modelMethods.remove(modelMethod);
                return modelMethod;
            }
        }

        // 若并不是所有的依赖属性Model里都有,那就拿第一个吧~~~~
        ModelMethod modelMethod = this.modelMethods.get(0);
        this.modelMethods.remove(modelMethod);
        return modelMethod;
    }
    ...
}

ModelFactoryこの部分は実行しますと、すべてのマークを実行@ModelAttributeああ、アノテーション付きメソッド、および順序が実行されます。そこで問題は、となりhandlerMethods、それを「発見」するときでしょうか?この時が来たRequestMappingHandlerAdapterことは、この注釈が付いたものを見つけるためにどのように確認するために、@ModelAttributeプロセッサを~~~

RequestMappingHandlerAdapter

RequestMappingHandlerAdapterそれは私たちがこれだけ、それは場所を気に、非常に大規模なシステムである@ModelAttributeことですModelFactory、次のように作成し、リストの関連するソースコード:

//  @since 3.1
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter implements BeanFactoryAware, InitializingBean {

    // 该方法不能标注有@RequestMapping注解,只标注了@ModelAttribute才算哦~
    public static final MethodFilter MODEL_ATTRIBUTE_METHODS = method ->
            (!AnnotatedElementUtils.hasAnnotation(method, RequestMapping.class) && AnnotatedElementUtils.hasAnnotation(method, ModelAttribute.class));
    ...
    // 从Advice里面分析出来的标注有@ModelAttribute的方法(它是全局的)
    private final Map<ControllerAdviceBean, Set<Method>> modelAttributeAdviceCache = new LinkedHashMap<>();

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 每调用一次都会生成一个ModelFactory ~~~
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        ...
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 初始化Model
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
        ...
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }

    // 创建出一个ModelFactory,来管理Model
    // 显然和Model相关的就会有@ModelAttribute @SessionAttributes等注解啦~
    private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        // 从缓存中拿到和此Handler相关的SessionAttributesHandler处理器~~处理SessionAttr
        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
        Class<?> handlerType = handlerMethod.getBeanType();

        // 找到当前类(Controller)所有的标注的@ModelAttribute注解的方法
        Set<Method> methods = this.modelAttributeCache.get(handlerType);
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
            this.modelAttributeCache.put(handlerType, methods);
        }
        
        List<InvocableHandlerMethod> attrMethods = new ArrayList<>();
        // Global methods first
        // 全局的有限,最先放进List最先执行~~~~
        this.modelAttributeAdviceCache.forEach((clazz, methodSet) -> {
            if (clazz.isApplicableToBeanType(handlerType)) {
                Object bean = clazz.resolveBean();
                for (Method method : methodSet) {
                    attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
                }
            }
        });
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
        }
        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
    }

    // 构造InvocableHandlerMethod 
    private InvocableHandlerMethod createModelAttributeMethod(WebDataBinderFactory factory, Object bean, Method method) {
        InvocableHandlerMethod attrMethod = new InvocableHandlerMethod(bean, method);
        if (this.argumentResolvers != null) {
            attrMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        attrMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        attrMethod.setDataBinderFactory(factory);
        return attrMethod;
    }
}

RequestMappingHandlerAdapter処理ロジックのこの部分は:それは要求ごとに来る作成ModelFactory(からグローバル収集するため@ControllerAdvice)+本Controllerコントローラ上のすべてのラベルが持って@ModelAttributeいる方法を注釈付き。
@ModelAttributeマーク(別のメソッドの木@RequestMappingのノート)、それはすべてのコントローラメソッドでき呼び出す前に作成、ModelFactory管理するためのModelデータを-

ModelFactory管理しModel、提供@ModelAttributeし、@SessionAttributesその影響にそれほど

同時に、@ModelAttribute参照処理(戻り値)にマークすることができ、処理の異なる部分の顕著な方法は同じではない、次の更なるメインコースは、ModelAttributeMethodProcessorデビューしなければなりません。

ModelAttributeMethodProcessor

観点から、それは名前ですProcessor:経験に基づいて処理パラメータの両方が、また戻り値処理できるように、HandlerMethodArgumentResolver+をHandlerMethodReturnValueHandler分析@ModelAttributeメソッドのパラメータ注釈の注釈、及び処理@ModelAttribute方法は、戻り値にラベルを付けます。

==その(少し複雑)の処理パラメータの方法を見て:==

// 这个处理器用于处理入参、方法返回值~~~~
// @since 3.1
public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

    private static final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
    private final boolean annotationNotRequired;

    public ModelAttributeMethodProcessor(boolean annotationNotRequired) {
        this.annotationNotRequired = annotationNotRequired;
    }


    // 入参里标注了@ModelAttribute 或者(注意这个或者) annotationNotRequired = true并且不是isSimpleProperty()
    // isSimpleProperty():八大基本类型/包装类型、Enum、Number等等 Date Class等等等等
    // 所以划重点:即使你没标注@ModelAttribute  单子还要不是基本类型等类型,都会进入到这里来处理
    // 当然这个行为是是收到annotationNotRequired属性影响的,具体的具体而论  它既有false的时候  也有true的时候
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
                (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
    }

    // 说明:能进入到这里来的  证明入参里肯定是有对应注解的???
    // 显然不是,上面有说  这事和属性值annotationNotRequired有关的~~~
    @Override
    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    
        // 拿到ModelKey名称~~~(注解里有写就以注解的为准)
        String name = ModelFactory.getNameForParameter(parameter);
        // 拿到参数的注解本身
        ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
        if (ann != null) {
            mavContainer.setBinding(name, ann.binding());
        }

        Object attribute = null;
        BindingResult bindingResult = null;

        // 如果model里有这个属性,那就好说,直接拿出来完事~
        if (mavContainer.containsAttribute(name)) {
            attribute = mavContainer.getModel().get(name);
        } else { // 若不存在,也不能让是null呀
            // Create attribute instance
            // 这是一个复杂的创建逻辑:
            // 1、如果是空构造,直接new一个实例出来
            // 2、若不是空构造,支持@ConstructorProperties解析给构造赋值
            //   注意:这里就支持fieldDefaultPrefix前缀、fieldMarkerPrefix分隔符等能力了 最终完成获取一个属性
            // 调用BeanUtils.instantiateClass(ctor, args)来创建实例
            // 注意:但若是非空构造出来,是立马会执行valid校验的,此步骤若是空构造生成的实例,此步不会进行valid的,但是下一步会哦~
            try {
                attribute = createAttribute(name, parameter, binderFactory, webRequest);
            } catch (BindException ex) {
                if (isBindExceptionRequired(parameter)) {
                    // No BindingResult parameter -> fail with BindException
                    throw ex;
                }
                // Otherwise, expose null/empty value and associated BindingResult
                if (parameter.getParameterType() == Optional.class) {
                    attribute = Optional.empty();
                }
                bindingResult = ex.getBindingResult();
            }
        }

        // 若是空构造创建出来的实例,这里会进行数据校验  此处使用到了((WebRequestDataBinder) binder).bind(request);  bind()方法  唯一一处
        if (bindingResult == null) {
            // Bean property binding and validation;
            // skipped in case of binding failure on construction.
            WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
            if (binder.getTarget() != null) {
                // 绑定request请求数据
                if (!mavContainer.isBindingDisabled(name)) {
                    bindRequestParameters(binder, webRequest);
                }
                // 执行valid校验~~~~
                validateIfApplicable(binder, parameter);
                //注意:此处抛出的异常是BindException
                //RequestResponseBodyMethodProcessor抛出的异常是:MethodArgumentNotValidException
                if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
                    throw new BindException(binder.getBindingResult());
                }
            }
            // Value type adaptation, also covering java.util.Optional
            if (!parameter.getParameterType().isInstance(attribute)) {
                attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
            }
            bindingResult = binder.getBindingResult();
        }

        // Add resolved attribute and BindingResult at the end of the model
        // at the end of the model  把解决好的属性放到Model的末尾~~~
        // 可以即使是标注在入参上的@ModelAtrribute的属性值,最终也都是会放进Model里的~~~可怕吧
        Map<String, Object> bindingResultModel = bindingResult.getModel();
        mavContainer.removeAttributes(bindingResultModel);
        mavContainer.addAllAttributes(bindingResultModel);

        return attribute;
    }

    // 此方法`ServletModelAttributeMethodProcessor`子类是有复写的哦~~~~
    // 使用了更强大的:ServletRequestDataBinder.bind(ServletRequest request)方法
    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
        ((WebRequestDataBinder) binder).bind(request);
    }
}

モデルプロパティは、最初のモデルから得られる応答がない場合は使用し、デフォルトコンストラクタを作成するには、次になります(無引数はならないかもしれないが、それは参照を持つことができる)ServletRequest、その後、アップデータバインディングを要求する@Valid追加した場合(チェックノートがあるかどうかを確認)、最後のプロパティが追加されますModel内部

最後に、コードはリストに追加されます。mavContainer.addAllAttributes(bindingResultModel);ここで私は参照値は掲載:
ここに画像を挿入説明
次の例を(モデル〜内に人が存在するため)、それは普通の人ではなく、ヌルの値を出力します
リクエストのリンクを、次のとおりです。/testModelAttr?name=wo&age=10

    @GetMapping("/testModelAttr")
    public void testModelAttr(@Valid Person person, ModelMap modelMap) {
        Object personAttr = modelMap.get("person");
        System.out.println(personAttr); //Person(name=wo, age=10)
    }

注意:がpersonマークされていない@ModelAtrributeが、modelMap.get("person")それでもああの値を取得することができ、理由として、私は自分で考えることができ、上記の理由を分析したしました。


下例中:

    @GetMapping("/testModelAttr")
    public void testModelAttr(Integer age, Person person, ModelMap modelMap) {
        System.out.println(age); // 直接封装的值
        System.out.println("-------------------------------");
        System.out.println(modelMap.get("age"));
        System.out.println(modelMap.get("person"));
    }

要求:/testModelAttr?name=wo&age=10入力:

10
-------------------------------
null
Person(name=wo, age=10)

あなたが見ることができる一般的なタイプをマークされていない場合(一般的なタイプを理解するために注意を払う)@ModelAtrribute、それが自動的に認識されていない、Modelとヨーヨーに入れて~~~あなたが書く場合:

    @GetMapping("/testModelAttr")
    public void testModelAttr(@ModelAttribute("age") Integer age, Person person, ModelMap modelMap) {
        System.out.println(age); // 直接封装的值
        System.out.println("-------------------------------");
        System.out.println(modelMap.get("age"));
        System.out.println(modelMap.get("person"));
    }

次のように印刷は次のとおりです。

10
-------------------------------
10
Person(name=wo, age=10)

上記の場合との違いに注意して、メモリを向上させるようにしてください。ピットを踏むために時間を使用しないでください〜


==その処理方法(非常に簡単な)の(戻り値)を見て:==

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

    // 方法返回值上标注有@ModelAttribute注解(或者非简单类型)  默认都会放进Model内哦~~
    @Override
    public boolean supportsReturnType(MethodParameter returnType) {
        return (returnType.hasMethodAnnotation(ModelAttribute.class) ||
                (this.annotationNotRequired && !BeanUtils.isSimpleProperty(returnType.getParameterType())));
    }

    // 这个处理就非常非常的简单了,注意:null值是不放的哦~~~~
    // 注意:void的话  returnValue也是null
    @Override
    public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
            ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {

        if (returnValue != null) {
            String name = ModelFactory.getNameForReturnValue(returnValue, returnType);
            mavContainer.addAttribute(name, returnValue);
        }
    }
}

それの方法は、の値を返します(もちろん、することはできませんnullではない場合、プロセスは、非常に単純であるvoid)の上に置かれるModel内部の使用のために、

概要

この記事では、についてです@ModelAttribute基本原則、彼は実際に私たちの重要な理論的なサポートするために使用します。主原則に基づいて記事次の一連の、様々なシナリオの下で使用を示すDemoので、お楽しみに〜

関連読書

スプリングMVCを学習する[一緒に] @SessionAttributesの使用を習得する原理レベルで
スプリングMVCと原則[レベル]から@ RequestAttribute、@ SessionAttributeの使用を習得学習
スプリングMVCを学習する[一緒に】原理のレベルから(紙を使用)@ModelAttributeの使用を習得します

知識交換

==最後に:あなたはあなたにこの記事が参考に思われる場合は、賞賛の聖歌を指すように望むことができます。もちろん、友人のサークルは、より小さなパートナーも見ているので、共有する作者本人许可的~==を

技術内容に興味を持っている場合、グループWX交換に参加することができますJava高工、架构师3群
グループは、2次元コードを失敗した場合、WX番号を追加してください:fsx641385712(または2次元コードがWXの下でスキャンされます)。そして注:"java入群"単語は、手動でグループに招待されます

記事の場合格式混乱または图片裂开「ここをクリック:説明リンク-テキストリンク-テキストリンクを

おすすめ

転載: www.cnblogs.com/fangshixiang/p/11361070.html