1. Spring Bean 作用域
学过 Spring 的都知道 Spring 有好几种作用域,比如默认的 singleton
单例,再如 prototype
原型,他们都有自己的一个特点,今天要说的是 request
域,很多人可能都没印象,但其实随处可见,比如
我们可以直接在控制器将 HttpServletRequest 注入进来,我们的 controller 是单例的,但是为什么使用 @Autowired 注入的 request 却可以在不同线程下是独立的对象呢,并且贯通这一次线程到结束,相当于一次请求的全局变量。
因为他的作用域就是 request
域,底层采用 ThreadLocal
实现,它是线程本地变量,线程隔离的,所以不会出现线程安全问题,后面有实力了会从源码方面进行讲解哈哈,那么如果我们要定义这么一个”全局变量”该怎么做呢?
今天的重点是 @RequestScope
这个注解,它就能实现我们这一需求。
2. 正文
2.1 @RequestScope
话不多说上代码,首先定义一个类
在我们的 controller 注入这个 bean ,然后 setSum ,默认是单例 bean,我们使用 JMeter 进行压测,100线程并发测试
最后 sum 应该为 101,这里 scopeBean 一直是一个对象,线程共享属性,所以并发了
我们再给 ScopeBean 加上 @RequestScope
注解,再测试
可以看到每次 sum 的值都是2,没毛病,而且可以看到此时的 scopeBean 其实是一个代理对象,后面会在源码层面给大家讲
也许有人会说原型模式的效果不也是这样吗
我们继续测试,写一个 Service ,注入 ScopeBean ,返回它
在控制器去获取它,最后调用一下方法看下结果
不出意外的相同,如果此时是原型模式,返回的一定是1,也就验证了开头说的贯穿整个 request ,并且是线程安全的!
那么在我们的线程池中是否还能拿到这个 bean 呢,下次继续
2.2 @Scope
其实 @RequestScope 就是的 @Scope 的封装
可以看到这个注解上面指定了一个元注解 @Scope ,它就是用来定义我们 bean 的作用域的,然后还可以看到有一个参数 proxyMode
,这个是指定我们的代理模式,看下这个枚举
DEFAULT
和 NO
的作用是一样的(源码),INTERFACES
是使用 JDK 的 InvocationHandler 来进行代理创建实例,TARGET_CLASS
是使用 CGLIB 进行代理创建实例,一般来说目标对象是实现了接口我们使用 InvocationHandler ,如果没有则使用 CGLIB。
然后可以看到参数上面还有个 @AliasFor注解,这个在 Spring之观察者模式 讲过的,但是当时没有用到 annotation 这个属性,它的作用其实是给元注解属性取别名,以及给属性赋值
当没有指定 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 等等用作审计等地方,其他的可以自行发挥