`
IvanLi
  • 浏览: 604009 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Spring Singleton的陷阱

阅读更多
这是一个真实的案例,我们在项目中使用Spring和ACEGI,我之所以选择ACEGI,除了它对权限的良好控制外,
我还看好它的SecurityContextHolder,通过代码
Authentication auth = SecurityContextHolder.getContext().getAuthentication();

我可以很容易在系统任意一层得到用户的信息,而不用把用户信息在参数里传来传去,(这也是struts的缺点之一)
但是我在每一次要得到用户信息的时候都写上面的一段代码,未免有些麻烦,所以我在BaseService, BaseDao里都提供了如下方法:
  /**
	 * get current login user info
	 * @return UserInfo
	 */
	protected UserInfo getUserInfo()
	{
		return getUserContext().getUserInfo();
	}
	
	/**
	 * get current login user context
	 * @return UserContext
	 */
	protected UserContext getUserContext()
	{
		Authentication auth = SecurityContextHolder.getContext().getAuthentication();
		return (UserContext) auth.getPrincipal();
	}

这样在其他的Service和Dao类里可以通过
super.getUserContext(), super.getUserInfo()

来得到用户的信息,这也为问题的产生提供了温床。请看如下代码:
  public class SomeServece extends BaseService implements SomeInterFace  
  {
      private UserInfo user = super.getUserInfo();
      
      public someMethod()
      {
         int userID = this.user.getUserID();
         String userName = this.user.getUserName();
         //bla bla do something user userID and userNaem
      }
  }    


这段代码在单元测试的时候不会用任何问题,但是在多用户测试的情况下,你会发现任何调用SomeService里someMethod()方法
的userID和userName都是同一个人,也就是第一个登陆的人的信息。Why?

其根本原因是Spring的Bean在默认情况下是Singleton的,Bean SomeServece的实例只会生成一份,也就是所SomeServece实例的user
对象只会被初始化一次,就是第一次登陆人的信息,以后不会变了。所以BaseService想为开发提供方便,却给开发带来了风险

正确的用法应该是这样的
  public class SomeServece extends BaseService implements SomeInterFace  
  {
      
      
      public someMethod()
      {
         int userID = super.getUserInfo().getUserID();
         String userName = super.getUserInfo().getUserName();
         //bla bla do something user userID and userNaem
      }
  }    
分享到:
评论
41 楼 IvanLi 2007-02-01  
这是正确的用法
40 楼 JavaFlasher 2007-01-30  

小心的问一下 :

引用
Acegi SecurityContextHolder 默认采用 ThreadLocal 存储 authentication 对象, 每次使用当然都要重新获取,


像本贴作者 最后说的

引用

正确的用法应该是这样的
public class SomeServece extends BaseService implements SomeInterFace    
{  
      
      
    public someMethod()  
    {  
       int userID = super.getUserInfo().getUserID();  
       String userName = super.getUserInfo().getUserName();  
       //bla bla do something user userID and userNaem  
    }  



这样不是  重新获取么? 
39 楼 jianfeng008cn 2006-12-28  
很有意思的一个帖子嘛  尤其是“老哥改行”这一说 !
38 楼 差沙 2006-12-27  
不好意思,不知道怎么删除自己的帖子。。刚才的回复是废话
37 楼 差沙 2006-12-27  
再次无语。。。。。

Service既然是单例了,为什么还要写属性在里面?单例的东西不能写任何表示状态的东西。。。。

直接这样就可以了

public class SomeServece extends BaseService implements SomeInterFace     
{   

    public someMethod()   
    {   
       int userID = getUserInfo().getUserID();   
       String userName = getUserInfo().getUserName();   
       //bla bla do something user userID and userNaem   
    }   
}  
36 楼 shaucle 2006-12-25  
友谊第一,呵呵
35 楼 IvanLi 2006-12-25  
ahuaxuan 写道
不好意思啊,让写代码的人来看看这篇文章吧,其实我们都是对事不对人的,我个人的想法是让中国软件的水平成为世界一流,需要大家努力,大家不应该程序员相轻,楼主见量啊,努力努力,呵呵

34 楼 IvanLi 2006-12-25  
shaucle 写道
Ivan Li 写道
Feiing 写道
Ivan Li 写道

满足上面的三个条件,你有什么高招?你无法要求组内所有的人都很清楚Spring


不知道 java 里面有一种叫 Util 的方法?

写成Util方法,跟放在Base里有什么两样?如果提供了Util方法,写继承类的老哥一样回怎么写
 public class SomeServece extends BaseService implements SomeInterFace    
 {  
     private   
       
     public someMethod()  
     {  
        int userID = this.user.getUserID();  
        String userName = this.user.getUserName();  
        //bla bla do something user userID and userNaem  
 }  
 }      

问题不是还是存在?

     public someMethod()  
     {  UserInfo user = someUtil.getUserInfo();
        int userID = this.user.getUserID();  
        String userName = this.user.getUserName();  
        //bla bla do something user userID and userNaem  
 } 

不放入成员变量就行了啊,别人都说了无数次了.

不知行否.

这个是正解,一定不能这么用
33 楼 shaucle 2006-12-25  
晕死,按了个ctrl+x
32 楼 shaucle 2006-12-25  
Ivan Li 写道
Feiing 写道
Ivan Li 写道

满足上面的三个条件,你有什么高招?你无法要求组内所有的人都很清楚Spring


不知道 java 里面有一种叫 Util 的方法?

写成Util方法,跟放在Base里有什么两样?如果提供了Util方法,写继承类的老哥一样回怎么写
 public class SomeServece extends BaseService implements SomeInterFace    
 {  
     private   
       
     public someMethod()  
     {  
        int userID = this.user.getUserID();  
        String userName = this.user.getUserName();  
        //bla bla do something user userID and userNaem  
 }  
 }      

问题不是还是存在?

     public someMethod()  
     {  UserInfo user = someUtil.getUserInfo();
        int userID = this.user.getUserID();  
        String userName = this.user.getUserName();  
        //bla bla do something user userID and userNaem  
 } 

不放入成员变量就行了啊,别人都说了无数次了.

不知行否.
31 楼 ahuaxuan 2006-12-25  
不好意思啊,让写代码的人来看看这篇文章吧,其实我们都是对事不对人的,我个人的想法是让中国软件的水平成为世界一流,需要大家努力,大家不应该程序员相轻,楼主见量啊,努力努力,呵呵
30 楼 IvanLi 2006-12-25  
ahuaxuan 写道
feiing正确,楼主没有明白我的话,呵呵,可能我说得不够明白,全局变量会在实例初始化的时候赋值,private UserInfo user = super.getUserInfo(); 这样写,这个user变量只会被一次赋值(产生这个service对象的时候),不会再变了,并且,user是代表状态的,不能够作为service(singleton)的全局变量的,这在哪里都有说明的呀,所以不是我不明白你在说什么,是你不明白我在说什么,我说
引用
你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo(); ,大错特错,
,这还不够清楚吗。 一是只会被赋值一次,二是如果多线程修改user会造成数据紊乱(虽然你这里没有修改,但是你这样用会误导组里其他不知道得成员,使他们认为这样用是没有问题得),单例的service里不能放有状态的,这是原则,难道我说得真得不够明白吗???
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧

这个我从一开始就知道! 
那段错误的程序不是我写出来的,   是我贴出来讨论的   气死我了!
29 楼 ahuaxuan 2006-12-25  
feiing正确,楼主没有明白我的话,呵呵,可能我说得不够明白,全局变量会在实例初始化的时候赋值,private UserInfo user = super.getUserInfo(); 这样写,这个user变量只会被一次赋值(产生这个service对象的时候),不会再变了,并且,user是代表状态的,不能够作为service(singleton)的全局变量的,这在哪里都有说明的呀,所以不是我不明白你在说什么,是你不明白我在说什么,我说
引用
你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo(); ,大错特错,
,这还不够清楚吗。 一是只会被赋值一次,二是如果多线程修改user会造成数据紊乱(虽然你这里没有修改,但是你这样用会误导组里其他不知道得成员,使他们认为这样用是没有问题得),单例的service里不能放有状态的,这是原则,难道我说得真得不够明白吗???
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧
28 楼 IvanLi 2006-12-25  
ahuaxuan 写道
象楼主这样写肯定出错呀,大家都用一个service对象,你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo();   ,大错特错,第一,spring管理的单列模式的bean中的全局变量应该是无状态的(这一点无论哪个文档上都有写吧,看来楼主对spring不是很熟,user明显是有状态的怎么能用作全局变量呢,肯定要把super.getUserInfo()放在service具体的方法里),第二看到楼上很多人不知道在说什么,不知所芸,有人说“可以修改配置文件,修改参数吧!!”,还有人说“改改配置,写个全局变量,什么问题也就解决了,哪有这么麻烦”,还有人说用ThreadLocal,acegi这种得到用户的方式本来就是ThreadLocal,还需要再用一个ThreadLocal吗??其实关键不在ThreadLocal这个地方。上面有个兄弟说得其实很清楚明白,只是我看大家都没有理解,flyspider说道:
引用
Spring建议以singleton方式注入的依赖尽量是stateless的

这才是正确的,只不过大家都没有明天他在说什么

这位仁兄貌似没有明白我在说什么?
27 楼 IvanLi 2006-12-25  
Feiing 写道
这种老哥, 还是去做别的行业吧

人都需要一个成长的过程的,不能把人一棒子打死,不能期待项目组里的都是老鸟,怎么都会有些小鸟的,小鸟只要勤奋认学,总会成为老鸟的!
26 楼 IvanLi 2006-12-25  
这篇帖子的主要目的也是在说明四楼的哥们说的事情
flyspider 写道
Spring建议以singleton方式注入的依赖尽量是stateless的

只不过这个在项目中真实的出现了,所以发贴记下来
25 楼 Feiing 2006-12-25  
这种老哥, 还是去做别的行业吧
24 楼 IvanLi 2006-12-25  
Feiing 写道
Ivan Li 写道

满足上面的三个条件,你有什么高招?你无法要求组内所有的人都很清楚Spring


不知道 java 里面有一种叫 Util 的方法?

写成Util方法,跟放在Base里有什么两样?如果提供了Util方法,写继承类的老哥一样回怎么写
 public class SomeServece extends BaseService implements SomeInterFace    
 {  
     private UserInfo user = someUtil.getUserInfo();  
       
     public someMethod()  
     {  
        int userID = this.user.getUserID();  
        String userName = this.user.getUserName();  
        //bla bla do something user userID and userNaem  
 }  
 }      

问题不是还是存在?
23 楼 Feiing 2006-12-25  
Ivan Li 写道

满足上面的三个条件,你有什么高招?你无法要求组内所有的人都很清楚Spring


不知道 java 里面有一种叫 Util 的方法?
22 楼 shaucle 2006-12-25  
不清楚自然会用得不好

就像俺们组里的成员用exception一样,到处乱用,乱catch

后来的办法就是专门开个会,统一讨论一下,再定制标准

相关推荐

    通俗易懂spring之singleton和prototype.docx

    Singleton模式意味着每个Bean在Spring容器中只会存在一个实例。当你通过`ApplicationContext`获取一个配置为`scope="singleton"`的Bean时,无论你调用`getBean`多少次,都会返回同一个对象实例。例如,在`UserDao`的...

    Spring实战之Bean的作用域singleton和prototype用法分析

    首先,`singleton`是Spring默认的作用域,意味着当Spring容器初始化时,对于每个具有`singleton`作用域的Bean,它只会创建一个唯一的实例。这个实例在整个应用程序生命周期中被共享,无论何时请求该Bean,Spring都会...

    Spring中的singleton和prototype的实现

    Spring中的singleton和prototype的实现 Spring框架中,bean的作用域是指在容器中bean的实例化和生命周期管理。其中,singleton和prototype是两个最基本的bean作用域。本文将详细介绍Spring中的singleton和...

    spring6pdf详细讲解

    Spring 还提供了多种范围的 Bean,包括 singleton、prototype 等。 Spring 的 IoC 容器是框架的核心组件,它负责管理和实例化 Bean,并提供了依赖注入和控制反转的功能。 Spring 的 IoC 容器可以自动装配 Bean,...

    C++完美实现Singleton模式

    ### C++中实现Singleton模式的关键知识点 #### 一、Singleton模式简介 Singleton模式是一种常用的软件设计模式,旨在确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中经常被用于控制对共享资源...

    简单了解spring bean作用域属性singleton和prototype的区别

    Spring Bean作用域属性singleton和prototype的区别详解 Spring Framework中,Bean的作用域属性是指Bean实例的生命周期和作用域。Spring提供了五种作用域:singleton、prototype、request、session和global session...

    Singleton

    Singleton模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这个模式在许多场景下非常有用,例如当系统只需要一个共享的配置对象或者管理某种资源时。Singleton模式的核心特点是单例...

    C++ 实现的singleton 模式

    **C++实现的Singleton模式详解** Singleton模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,例如管理共享资源,如数据库连接池,或者确保某个...

    Qt qml Singleton 单例模式

    在Qml中,我们可以通过Qt的Singleton组件来实现这一模式。 首先,让我们理解单例模式的基本概念。在软件工程中,单例模式保证一个类只有一个实例,并提供一个全局访问点。这个设计模式在许多场景下都很实用,比如...

    传智播客2016spring资料4

    2. **Spring Bean**:介绍Spring容器如何创建、初始化、配置和管理Bean,以及Bean的作用域(如Singleton、Prototype等)。 3. **AOP**:讲解面向切面编程的概念,如何在不修改源代码的情况下,实现对程序行为的统一...

    singleton设计模式java实现及对比

    **Singleton设计模式** Singleton设计模式是软件工程中最常用的设计模式之一,它的主要目的是确保一个类只有一个实例,并提供全局访问点。在Java中,Singleton模式的实现有多种方式,每种方式都有其优缺点,我们将...

    25个经典的Spring面试问题包含答案

    默认情况下,Spring容器会为每个Bean创建一个实例,若需实现单例,只需在XML配置中设置`scope="singleton"`,或在注解中使用`@Scope("singleton")`。 16. **Spring中如何处理异常?** Spring支持异常翻译,将底层...

    详尽的Spring2.0学习提纲

    4. Bean的作用域:掌握singleton、prototype、request、session等不同作用域的概念及其应用场景。 三、AOP(面向切面编程) 1. AOP基础:理解AOP的基本概念,包括切面、通知、连接点、切入点表达式等。 2. Spring ...

    Singleton Pattern 源码

    单例模式(Singleton Pattern)是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制资源的唯一性、全局配置对象或者缓存服务等。本篇文章将深入探讨...

    spring bean的生命周期

    - **Singleton Beans的懒加载**:如果Bean的scope为singleton,并且在XML配置中没有设置`lazy-init="true"`,那么Spring容器在启动时就会实例化这些Bean。 - **Prototype Beans的每次请求创建**:scope为prototype...

    Spring设计思想.ppt

    Spring框架是Java开发中广泛应用的轻量级框架,它的设计思想体现了软件工程的诸多原则,如依赖注入、面向切面编程等。其中,Spring的核心设计理念体现在对两种基本设计模式的实现上:工厂模式和单态模式。 首先,...

    spring架构详解 spring架构详解

    Spring框架广泛运用了多种设计模式,如工厂模式(BeanFactory)、单例模式(Singleton)、代理模式(AOP代理)、装饰器模式(BeanPostProcessor)等,这些模式的运用使得Spring具有高度的灵活性和可扩展性。...

    Singleton 单例模式

    Singleton 单例模式是软件设计模式中的一种,它限制了类的实例化过程,确保一个类在整个系统中只有一个实例存在。这种模式常用于系统资源管理,比如数据库连接、线程池或者缓存服务等,因为这些资源往往需要全局共享...

Global site tag (gtag.js) - Google Analytics