从原理层面掌握@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入群" 字样,会手动邀请入群

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容