谈谈springmvc的ModelAttribute和InitBinder

@ModelAttribute和@InitBinder相信大家都不陌生了,但是背后的原理设计到spring的核心知识,源码之下无秘密,让咱们看看:

@InitBinder很简单,就是初始化WebDataBinder,用于数据绑定流程
@ModelAttribute感觉用处不是太多,其实仔细一想:controller方法的参数无非就是来自三个来源:1、request 2、session 3、方法调用前由程序嵌入的值 很明显这个注解就是干第三个活的,调用方法前先调用标有这个注解的方法,把值放入Model里面,这样方法就可以通过@ModelAttribute来获取值了。其实就是一个内置的拦截器

protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception
{
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try
    {	
    	//获取WebDataBinder工厂
    	//@InitBinder
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        
        //获取模型工厂
        //@ModelAttribute
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        
        //控制器包装方法
        //参数解析器,返回值解析器,数据绑定工具类,参数名称获取工具类
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if(this.argumentResolvers != null)
        {
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if(this.returnValueHandlers != null)
        {
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        invocableMethod.setDataBinderFactory(binderFactory);
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        
        //表示本次请求的模型和视图的容器
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        //重定向来的数据
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));		
       	//重点方法,会调用带有@ModelAttribute注解的方法
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);	//调用控制器方法
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally
    {
        webRequest.requestCompleted();
    }
}
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception
{	
	//获取控制器方法所在类的类型
    Class <? > handlerType = handlerMethod.getBeanType();
    //从缓存中获取本类带有@InitBinder的方法
    //Map<Class<?>, Set<Method>> initBinderCache = new ConcurrentHashMap<>(64);
    //用了并发容器,所以不用加锁
    Set < Method > methods = this.initBinderCache.get(handlerType);
    if(methods == null)
    {	
    	//第一次惩罚
    	//缓存没有就要获取了
        methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
        //放到缓存
        this.initBinderCache.put(handlerType, methods);
    }
    //初始化所有@InitBinder注解的方法数组
    //看来是每次请求都会这样做
    List < InvocableHandlerMethod > initBinderMethods = new ArrayList < > ();
    
    //Map<ControllerAdviceBean, Set<Method>> initBinderAdviceCache = new LinkedHashMap<>();
    //表示以ControllerAdvice类为K,@InitBinder方法集合为V的Map
    this.initBinderAdviceCache.forEach((clazz, methodSet) - >
    {
    	//获取支持当前类型的所有@ControllerAdvice类
        if(clazz.isApplicableToBeanType(handlerType))
        {
            Object bean = clazz.resolveBean();
            for(Method method: methodSet)
            {
                initBinderMethods.add(createInitBinderMethod(bean, method));
            }
        }
    });
    for(Method method: methods)
    {	//加入当前controller带有@InitBinder的方法
        Object bean = handlerMethod.getBean();
        initBinderMethods.add(createInitBinderMethod(bean, method));
    }
    //return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
    //WebBindingInitializer对象在父类DefaultDataBinderFactory中
    return createDataBinderFactory(initBinderMethods);
}
//代码流程和上面的基本一样
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory)
{	
	//看javadoc可以知道是处理controller的@SessionAttributes注解的
	//如果当前控制器方法把数据放进Model里面,它会检查数据的K或者类型是否是注解指定的
	//如果是,就把数据放到session里面
	//直到调用SessionStatus.setComplete()
    SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
    Class <? > handlerType = handlerMethod.getBeanType();
    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
    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));
    }
    //ModelFactory需要@ModelAttribute注解的方法集合和数据绑定工厂和session管理器
    return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
//Map<Class<?>, SessionAttributesHandler> sessionAttributesHandlerCache = new ConcurrentHashMap<>(64);
//看看怎样在多线程环境下使用缓存
//注意和上面的区别
private SessionAttributesHandler getSessionAttributesHandler(HandlerMethod handlerMethod)
{
    Class <? > handlerType = handlerMethod.getBeanType();
    SessionAttributesHandler sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
    if(sessionAttrHandler == null)
    {
        synchronized(this.sessionAttributesHandlerCache)
        {
            sessionAttrHandler = this.sessionAttributesHandlerCache.get(handlerType);
            if(sessionAttrHandler == null)
            {
                sessionAttrHandler = new SessionAttributesHandler(handlerType, sessionAttributeStore);
                this.sessionAttributesHandlerCache.put(handlerType, sessionAttrHandler);
            }
        }
    }
    return sessionAttrHandler;
}
public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
throws Exception
{	
	//从http session中获取@SessionAttributes注解以names为K的数据集合
    Map < String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
    //将数据合并到容器
    container.mergeAttributes(sessionAttributes);
    //调用@ModelAttribute注解的方法
    invokeModelAttributeMethods(request, container);
    
    //获取controller方法带有@ModelAttribute注解并且是类上面的
    //@SessionAttributes指定的参数集合
    for(String name: findSessionAttributeArguments(handlerMethod))
    {
    	//如果符合,希望是在Model里面存在的
    	//反之从session里面获取
    	//获取不到直接抛出异常
        if(!container.containsAttribute(name))
        {
            Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);
            if(value == null)
            {
                throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);
            }
            container.addAttribute(name, value);
        }
    }
}
public boolean isHandlerSessionAttribute(String attributeName, Class <? > attributeType)
{
    if(this.attributeNames.contains(attributeName) || this.attributeTypes.contains(attributeType))
    {
        this.knownAttributeNames.add(attributeName);
        return true;
    }
    else
    {
        return false;
    }
}

总结:就是说controller带有@SessionAttributes,在方法执行前会先从session中获取这个注解属性names为K的session数据放到Model里面,然后调用@ModelAttribute注解的方法,最后检查方法参数带有@ModelAttribute注解并且是@SessionAttributes注解指定的数据是否在Model里面,其实仔细想一下,如果是指定的证明在第一步就从session里面获取了,也可能获取不到,这是很正常的,但是@ModelAttribute注解的方法也没有这就不好了,这我想到了一个场景:浏览器第一次调用这个controller的方法,并且这个方法有@SessionAttributes注解指定的@ModelAttribute方法参数,肯定是不可能从session从获取了,只能从@ModelAttribute注解方法里如果没有,那就没有办法完成绑定了,就是不明白为什么会抛出HttpSessionRequiredException异常,我觉得spring官方应该把这个错误的名字改正以下,不然很容易让别人误会,就那第一次浏览器调用来说,如果这样直接抛出异常不是很好把

所以建议使用@ModelAttribute和@SessionAttributes分开避免出现异常,即@SessionAttributes不指定@ModelAttribute注解的参数

猜你喜欢

转载自blog.csdn.net/weixin_42002747/article/details/105853485