2006-12-22
Spring Singleton的陷阱
关键字: Spring
这是一个真实的案例,我们在项目中使用Spring和ACEGI,我之所以选择ACEGI,除了它对权限的良好控制外,
我还看好它的SecurityContextHolder,通过代码
我可以很容易在系统任意一层得到用户的信息,而不用把用户信息在参数里传来传去,(这也是struts的缺点之一)
但是我在每一次要得到用户信息的时候都写上面的一段代码,未免有些麻烦,所以我在BaseService, BaseDao里都提供了如下方法:
这样在其他的Service和Dao类里可以通过
来得到用户的信息,这也为问题的产生提供了温床。请看如下代码:
这段代码在单元测试的时候不会用任何问题,但是在多用户测试的情况下,你会发现任何调用SomeService里someMethod()方法
的userID和userName都是同一个人,也就是第一个登陆的人的信息。Why?
其根本原因是Spring的Bean在默认情况下是Singleton的,Bean SomeServece的实例只会生成一份,也就是所SomeServece实例的user
对象只会被初始化一次,就是第一次登陆人的信息,以后不会变了。所以BaseService想为开发提供方便,却给开发带来了风险
正确的用法应该是这样的
我还看好它的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
}
}
- 19:01
- 浏览 (15332)
- 论坛浏览 (17457)
- 评论 (42)
- 分类: 技术天地
- 相关推荐
评论
JavaFlasher
2007-01-30
回复
小心的问一下 :
像本贴作者 最后说的
正确的用法应该是这样的
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
}
}
这样不是 重新获取么?
引用
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
}
}
这样不是 重新获取么?
jianfeng008cn
2006-12-28
回复
很有意思的一个帖子嘛 尤其是“老哥改行”这一说 !
再次无语。。。。。
Service既然是单例了,为什么还要写属性在里面?单例的东西不能写任何表示状态的东西。。。。
直接这样就可以了
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
}
}
ahuaxuan 写道
不好意思啊,让写代码的人来看看这篇文章吧,其实我们都是对事不对人的,我个人的想法是让中国软件的水平成为世界一流,需要大家努力,大家不应该程序员相轻,楼主见量啊,努力努力,呵呵
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
}
不放入成员变量就行了啊,别人都说了无数次了.
不知行否.
这个是正解,一定不能这么用
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
}
不放入成员变量就行了啊,别人都说了无数次了.
不知行否.
不好意思啊,让写代码的人来看看这篇文章吧,其实我们都是对事不对人的,我个人的想法是让中国软件的水平成为世界一流,需要大家努力,大家不应该程序员相轻,楼主见量啊,努力努力,呵呵
ahuaxuan 写道
feiing正确,楼主没有明白我的话,呵呵,可能我说得不够明白,全局变量会在实例初始化的时候赋值,private UserInfo user = super.getUserInfo(); 这样写,这个user变量只会被一次赋值(产生这个service对象的时候),不会再变了,并且,user是代表状态的,不能够作为service(singleton)的全局变量的,这在哪里都有说明的呀,所以不是我不明白你在说什么,是你不明白我在说什么,我说
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧
引用
你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo(); ,大错特错,
,这还不够清楚吗。 一是只会被赋值一次,二是如果多线程修改user会造成数据紊乱(虽然你这里没有修改,但是你这样用会误导组里其他不知道得成员,使他们认为这样用是没有问题得),单例的service里不能放有状态的,这是原则,难道我说得真得不够明白吗???
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧
这个我从一开始就知道! :shock:
那段错误的程序不是我写出来的, :shock: :shock:
feiing正确,楼主没有明白我的话,呵呵,可能我说得不够明白,全局变量会在实例初始化的时候赋值,private UserInfo user = super.getUserInfo(); 这样写,这个user变量只会被一次赋值(产生这个service对象的时候),不会再变了,并且,user是代表状态的,不能够作为service(singleton)的全局变量的,这在哪里都有说明的呀,所以不是我不明白你在说什么,是你不明白我在说什么,我说
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧
引用
你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo(); ,大错特错,
,这还不够清楚吗。 一是只会被赋值一次,二是如果多线程修改user会造成数据紊乱(虽然你这里没有修改,但是你这样用会误导组里其他不知道得成员,使他们认为这样用是没有问题得),单例的service里不能放有状态的,这是原则,难道我说得真得不够明白吗???
我明白你的意思,你就是找出了个原因,真正的有一个原则性的东西你违反了,现在应该明白点了吧
ahuaxuan 写道
象楼主这样写肯定出错呀,大家都用一个service对象,你又把user设置为它的全局变量,而且还这样写:private UserInfo user = super.getUserInfo(); ,大错特错,第一,spring管理的单列模式的bean中的全局变量应该是无状态的(这一点无论哪个文档上都有写吧,看来楼主对spring不是很熟,user明显是有状态的怎么能用作全局变量呢,肯定要把super.getUserInfo()放在service具体的方法里),第二看到楼上很多人不知道在说什么,不知所芸,有人说“可以修改配置文件,修改参数吧!!”,还有人说“改改配置,写个全局变量,什么问题也就解决了,哪有这么麻烦”,还有人说用ThreadLocal,acegi这种得到用户的方式本来就是ThreadLocal,还需要再用一个ThreadLocal吗??其实关键不在ThreadLocal这个地方。上面有个兄弟说得其实很清楚明白,只是我看大家都没有理解,flyspider说道:
这才是正确的,只不过大家都没有明天他在说什么
引用
Spring建议以singleton方式注入的依赖尽量是stateless的
这才是正确的,只不过大家都没有明天他在说什么
这位仁兄貌似没有明白我在说什么?
Feiing 写道
这种老哥, 还是去做别的行业吧
人都需要一个成长的过程的,不能把人一棒子打死,不能期待项目组里的都是老鸟,怎么都会有些小鸟的,小鸟只要勤奋认学,总会成为老鸟的!
这篇帖子的主要目的也是在说明四楼的哥们说的事情
只不过这个在项目中真实的出现了,所以发贴记下来
flyspider 写道
Spring建议以singleton方式注入的依赖尽量是stateless的
只不过这个在项目中真实的出现了,所以发贴记下来
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
}
}
问题不是还是存在?
发表评论
该博客是同时发布到论坛的,无法评论在论坛已被锁定的帖子
最新评论
-
使用ubuntu作为工作桌面详 ...
Oracle的Oracle SQLDeveloper可以用也可以把windowz ...
-- by zhoun -
使用ubuntu作为工作桌面详 ...
Ivan Li 写道moonjava 写道一些数据库的客户端呢? Oracle可 ...
-- by moonjava -
使用ubuntu作为工作桌面详 ...
moonjava 写道一些数据库的客户端呢? Oracle可以用Oracle S ...
-- by Ivan Li -
使用ubuntu作为工作桌面详 ...
写得不错!只是觉得linux下顺手的软件太少
-- by vdgame -
使用ubuntu作为工作桌面详 ...
一些数据库的客户端呢?
-- by moonjava







评论排行榜