• 从原理层面掌握@RequestAttribute、@SessionAttribute的使用【一起学Spring MVC】


    每篇一句

    改我们就改得:取其精华,去其糟粕。否则木有意义

    前言

    如果说知道@SessionAttributes这个注解的人已经很少了,那么不需要统计我就可以确定的说:知道@RequestAttribute注解的更是少之又少。我觉得主要有如下两个原因:

    1. @RequestAttribute这个注解很新,Spring4.3后才有
    2. 我们可以使用API调用的方式(ServletRequest.getAttribute())来达到目的,而不用注解。且成本也不太高

    既然Spring推出了这个注解,那必然有它的优点。本文就带大家领略一下它的风骚之处。

    Spring提供的这些注解比如@ModelAttribute@SessionAttributes@RequestAttribute都是为了简化开发,提高复用性。同时另外一个目的是希望完全屏蔽掉源生Servlet API,增加它的扩展性。

    本文我以@RequestAttribute为例进行讲解,因为@SessionAttribute(也是Spring4.3后推出的注解)不管从使用和原理上都是一模一样的。你可以理解成唯一区别是ServletRequest.getAttribute()HttpSession.getAttribute()的区别
    此处再强调一次,这里指的是:org.springframework.web.bind.annotation.SessionAttribute,而非org.springframework.web.bind.annotation.SessionAttributes

    @RequestAttribute

    它比前面介绍的那些@ModelAttribute@SessionAttributes等注解要简单很多,它只能使用在方法入参上。作用:从request中取对应的属性值

    很多小伙伴对getParameter()getAttribute()相关方法傻傻分不清楚。建议你可以先弄清楚paramattribute的区别~

    // @since 4.3
    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface RequestAttribute {
    	@AliasFor("name")
    	String value() default "";
    	@AliasFor("value")
    	String name() default "";
    
    	// 默认情况下 这个属性是必须的(没有就报错了)
    	boolean required() default true;
    }
    

    接下来这句话很重要@RequestAttribute只负责从request里面取属性值,至于你什么时候往里放值,是有多种方式的可以达到的:

    1. @ModelAttribute注解预存
    2. HandlerInterceptor拦截器中预存
    3. 请求转发带过来

    下面分别按照这三种使用场景,给出使用Demo

    @ModelAttribute注解预存

    比较简单,在@ModelAttribute标注的方法上使用源生的HttpServletRequest放值即可

    @RestController
    @RequestMapping
    public class HelloController {
    
    	// 放置attr属性值
        @ModelAttribute
        public Person personModelAttr(HttpServletRequest request) {
            request.setAttribute("myApplicationName", "fsx-application");
            return new Person("非功能方法", 50);
        }
    
        @GetMapping("/testRequestAttr")
        public void testRequestAttr(@RequestAttribute("myApplicationName") String myApplicationName, HttpServletRequest request, ModelMap modelMap) {
            System.out.println(myApplicationName); //fsx-application
    
            // 从request里获取
            System.out.println(request.getAttribute("myApplicationName")); //fsx-application
    
            // 从model里获取
            System.out.println(modelMap.get("myApplicationName")); // null 获取不到attr属性的
            System.out.println(modelMap.get("person")); // Person(name=非功能方法, age=50)
        }
    }
    

    请求/testRequestAttr,结果打印如下:

    fsx-application
    fsx-application
    null
    Person(name=非功能方法, age=50)
    

    这里务必注意:@RequestAttribute("myApplicationName")注解如果省略,是绑定不到attr属性的哦(必须要有注解)~

    但是,这样是可行的:@RequestAttribute String myApplicationName(若注解没有指定,Spring MVC会再去看形参的名字来确认自动绑定)
    但若你写成了这样@RequestAttribute String aaa,那请求就直接400错误了抛出异常:org.springframework.web.bind.ServletRequestBindingException

    HandlerInterceptor拦截器中预存

    简单,直接上代码:

    public class SimpleInterceptor implements HandlerInterceptor {
     
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            request.setAttribute("myApplicationName", "fsx-application");
            return true;
        }
     	...
    }
    

    测试代码:略。

    forward请求转发带过来

    形如这样:

    request.setAttribute("myApplicationName", "fsx-application");
    request.getRequestDispatcher("/index").forward(request, response); 
    

    其实往里放置属性值只需要遵循一个原则:在调用处理器目标方法之前(参数封装之前)任意地方放置即可,属性值是都能被取到的。


    原理剖析

    按照我的习惯,即使它很简单,我也会扒开来看看它的原理部分嘛。
    根据经验很容易想到解析它的是一个HandlerMethodArgumentResolver,它就是RequestAttributeMethodArgumentResolver

    RequestAttributeMethodArgumentResolver

    很明显,它也是@since 4.3才出现的,命名上也很配套嘛。

    public class RequestAttributeMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver {
    
    	// 只处理标注了@RequestAttribute注解的入参
    	@Override
    	public boolean supportsParameter(MethodParameter parameter) {
    		return parameter.hasParameterAnnotation(RequestAttribute.class);
    	}
    
    	// 封装此注解的属性到NamedValueInfo 这里关于参数名的处理有这么一个处理
    	// info.name.isEmpty()也就说如果自己没有指定,就用形参名parameter.getParameterName()
    	@Override
    	protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
    		RequestAttribute ann = parameter.getParameterAnnotation(RequestAttribute.class);
    		Assert.state(ann != null, "No RequestAttribute annotation");
    		return new NamedValueInfo(ann.name(), ann.required(), ValueConstants.DEFAULT_NONE);
    	}
    
    	// 从request请求域去找属性值
    	@Override
    	@Nullable
    	protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
    		return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
    	}
    
    
    	// 若值不存在,抛出异常ServletRequestBindingException
    	@Override
    	protected void handleMissingValue(String name, MethodParameter parameter) throws ServletException {
    		throw new ServletRequestBindingException("Missing request attribute '" + name +
    				"' of type " +  parameter.getNestedParameterType().getSimpleName());
    	}
    
    }
    

    源码短小精悍,非常简单。
    其实它解析入参方面的核心解析流程在其父类AbstractNamedValueMethodArgumentResolver身上,但并不是本文的重点,请详见HandlerMethodArgumentResolver的章节~

    @RequestAttribute属性required默认为true, request.getAttribute获取不到参数就会抛出异常ServletRequestBindingException;required设置为false,即使没有从request中获取到就忽略跳过,赋值为null;

    总结

    这篇文章介绍了@RequestAttribute的使用Demo,以及它很简单的原理分析。相较于之前所有文章,这篇是非常轻松的,希望可以提供给大家一个思路,来使用@RequestAttribute提高你的逼格,哈哈(友情提示:装逼需谨慎哦~)

    说明:因为@SessionAttribute的使用甚至原理几乎一毛一样,所以不用再重复篇幅了

    总结

    这篇文章介绍了@SessionAttributes的核心处理原理,以及也给了一个Demo来介绍它的基本使用,不出意外阅读下来你对它应该是有很好的收获的,希望能帮助到你简化开发~

    相关阅读

    从原理层面掌握HandlerMethod、InvocableHandlerMethod、ServletInvocableHandlerMethod的使用【一起学Spring MVC】
    从原理层面掌握@SessionAttributes的使用【一起学Spring MVC】
    从原理层面掌握@ModelAttribute的使用(核心原理篇)【一起学Spring MVC】
    从原理层面掌握@ModelAttribute的使用(使用篇)【一起学Spring MVC】

    知识交流

    The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~

    若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群
    若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群

  • 相关阅读:
    2020年4月4日训练
    HZNU Training 17 for Zhejiang Provincial Competition 2020
    [kuangbin带你飞]专题四 最短路练习
    三分法
    洛谷多校第一周续
    洛谷春季 ACM 多校训练第五周
    简单数学三月小结
    线段树&树状数组
    图论三月小结
    Java中Double保留后小数位的几种方法
  • 原文地址:https://www.cnblogs.com/yourbatman/p/11324091.html
Copyright © 2020-2023  润新知