RequestScope 注解的妙用

1. Spring Bean 作用域

学过 Spring 的都知道 Spring 有好几种作用域,比如默认的 singleton 单例,再如 prototype 原型,他们都有自己的一个特点,今天要说的是 request 域,很多人可能都没印象,但其实随处可见,比如

image-20210730221615855

我们可以直接在控制器将 HttpServletRequest 注入进来,我们的 controller 是单例的,但是为什么使用 @Autowired 注入的 request 却可以在不同线程下是独立的对象呢,并且贯通这一次线程到结束,相当于一次请求的全局变量。

因为他的作用域就是 request 域,底层采用 ThreadLocal 实现,它是线程本地变量,线程隔离的,所以不会出现线程安全问题,后面有实力了会从源码方面进行讲解哈哈,那么如果我们要定义这么一个”全局变量”该怎么做呢?

今天的重点是 @RequestScope 这个注解,它就能实现我们这一需求。

2. 正文

2.1 @RequestScope

话不多说上代码,首先定义一个类

image-20210730231300568

image-20210730231349539

在我们的 controller 注入这个 bean ,然后 setSum ,默认是单例 bean,我们使用 JMeter 进行压测,100线程并发测试

image-20210730231606918

image-20210730231622577

image-20210730233419972

最后 sum 应该为 101,这里 scopeBean 一直是一个对象,线程共享属性,所以并发了

我们再给 ScopeBean 加上 @RequestScope注解,再测试

image-20210730233841993

image-20210730233942469

可以看到每次 sum 的值都是2,没毛病,而且可以看到此时的 scopeBean 其实是一个代理对象,后面会在源码层面给大家讲

也许有人会说原型模式的效果不也是这样吗

我们继续测试,写一个 Service ,注入 ScopeBean ,返回它

image-20210730234555265

image-20210730234611117

在控制器去获取它,最后调用一下方法看下结果

image-20210730235011358

不出意外的相同,如果此时是原型模式,返回的一定是1,也就验证了开头说的贯穿整个 request ,并且是线程安全的!

那么在我们的线程池中是否还能拿到这个 bean 呢,下次继续

2.2 @Scope

其实 @RequestScope 就是的 @Scope 的封装

image-20210801134008814

可以看到这个注解上面指定了一个元注解 @Scope ,它就是用来定义我们 bean 的作用域的,然后还可以看到有一个参数 proxyMode,这个是指定我们的代理模式,看下这个枚举

image-20210801134402778

DEFAULTNO 的作用是一样的(源码),INTERFACES 是使用 JDK 的 InvocationHandler 来进行代理创建实例,TARGET_CLASS 是使用 CGLIB 进行代理创建实例,一般来说目标对象是实现了接口我们使用 InvocationHandler ,如果没有则使用 CGLIB。

然后可以看到参数上面还有个 @AliasFor注解,这个在 Spring之观察者模式 讲过的,但是当时没有用到 annotation 这个属性,它的作用其实是给元注解属性取别名,以及给属性赋值

image-20210801142956105

当没有指定 value 和 attribute 时缺省为 属性名,然后值的类型要相同。

最后效果就是指定 bean 的作用域为 request 并且 代理模式为 CGLIB,那肯定就有人好奇为什么要使用代理了,不使用有什么影响

1
Error creating bean with name 'scopeBean': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.

这是没有指定时报的错误,意思就是作用域 request 没有激活,要知道的是我们使用 @Autowired 注入的 ScopeBean 会在项目启动时进行实例化,那么此时怎么会有 request 呢,所以它告诉我们可以考虑给 bean 定义一个范围代理,这样初始化时只需要去实例化代理就行了,目标对象在我们请求时再进行实例化,这样不就能绑定上线程了吗(本意想从源码进行解读,暂时理不太清哈哈,所以说个大概意思)

有 reqeust 那也少不了 session,它也是同理,现在基本不会用了,了解下即可。

至于场景的话大概就是封装一些用户信息,比如请求用户,ip 等等用作审计等地方,其他的可以自行发挥

3. 线程池测试


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!