Spring中Bean的单例、多例
问题⼀: Spring哪⾥⽤到了单例?
1 springboot 采⽤的是单例模式
2 @Component注解默认实例化的对象是单例,如果想声明成多例对象可以使⽤@Scope(“prototype”)
@Component
@Scope(“prototype”)
3 @Repository默认单例
4 @Service默认单例
5 @Controller默认单例
问题⼆:Spring单例Bean与单例模式的区别?
java单例模式
java单例模式确保⼀个类只有⼀个实例,⾃⾏提供这个实例并向整个系统提供这个实例。
特点:
1,⼀个类只能有⼀个实例;
2,⾃⼰创建这个实例;
3,整个系统都要使⽤这个实例。
Singleton模式主要作⽤是保证在Java应⽤程序中,⼀个类Class只有⼀个实例存在。在很多操作中,⽐如建⽴⽬录 数据库连接都需要这样的单线程操作。⼀些资源管理器常常设计成单例模式。
外部资源:譬如每台计算机可以有若⼲个打印机,但只能有⼀个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若⼲个通信端⼝,系统应当集中管理这些通信端⼝,以避免⼀个通信端⼝被两个请求同时调⽤。
内部资源,譬如,⼤多数的软件都有⼀个(甚⾄多个)属性⽂件存放系统配置。这样的系统应当由⼀个对象来管理这些属性⽂件。
单例模式,能避免实例重复创建;
单例模式,应⽤于避免存在多个实例引起程序逻辑错误的场合;
单例模式,较节约内存。
区别
Spring单例Bean与单例模式的区别在于它们关联的环境不⼀样,单例模式是指在⼀个JVM进程中仅有⼀个实例,⽽Spring单例是指⼀个Spring Bean容器(ApplicationContext)中仅有⼀个实例。
⾸先看单例模式,在⼀个JVM进程中(理论上,⼀个运⾏的JAVA程序就必定有⾃⼰⼀个独⽴的JVM)仅有⼀个实例,于是⽆论在程序中的何处获取实例,始终都返回同⼀个对象,以Java内置的Runtime为例(现在枚举是单例模式的最佳实践),⽆论何时何处获取,下⾯的判断始终为真:
// 基于懒汉模式实现
// 在⼀个JVM实例中始终只有⼀个实例
与此相⽐,Spring的单例Bean是与其容器(ApplicationContext)密切相关的,所以在⼀个JVM进程中,如果有多个Spring容器,即使是单例bean,也⼀定会创建多个实例,代码⽰例如下:
// 第⼀个Spring Bean容器
ApplicationContext context_1 =new FileSystemXmlApplicationContext("classpath:/l");
Person yiifaa_1 = Bean("yiifaa", Person.class);
// 第⼆个Spring Bean容器
ApplicationContext context_2 =new FileSystemXmlApplicationContext("classpath:/l");
Person yiifaa_2 = Bean("yiifaa", Person.class);
// 这⾥绝对不会相等,因为创建了多个实例
System.out.println(yiifaa_1 == yiifaa_2);
以下是Spring的配置⽂件:
<!-- 即使声明了为单例,只要有多个容器,也⼀定会创建多个实例 -->
<bean id="yiifaa" class="com.stixu.anno.Person" scope="singleton">
<constructor-arg name="username">
<value>yiifaa</value>
</constructor-arg>
</bean>
总结
Spring的单例bean与Spring bean管理容器密切相关,每个容器都会创建⾃⼰独有的实例,所以与GOF设计模式中的单例模式相差极⼤,但在实际应⽤中,如果将对象的⽣命周期完全交给Spring管理(不在其他地⽅通过new、反射等⽅式创建),其实也能达到单例模式的效果。
问题三:如何注⼊Bean
⽅法:在WebMvcConfigurerAdapter的⼦类中添加@Bean,返回实例对象即可
package;
/**
* 测试
*
* @author YF-XIACHAOYANG
* @date 2017/12/13 18:04
*/
public class TestBean {
private String name;
/*可以⾃定义构造器*/
public TestBean(String name){
this.name = name;
}
public String getName(){
return name;
}
public TestBean setName(String name){
this.name = name;
return this;
}
public void hello(){
System.out.println(this.name);
}
}
package;
@SpringBootApplication
@ComponentScan(basePackages ="cn.showclear")
@EnableScheduling
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
@Bean//这⾥直接把TestBean类属性⽅法注⼊进来,对外提供调⽤
public TestBean getTestBean(){
return new TestBean("hello bean1!");
}
}
@RestController
@RequestMapping("/data/plan/config/")
public class PlanConfigController {
@Autowired//
private TestBean testBean;
/**
* 加载预案应急事件标签组[含有组内标签信息]
*
* @return
*/
@RequestMapping(value ="loadTagGroupList", method = RequestMethod.POST)
public RespMapJson loadTagGroupList(String groupName){
testBean.hello();
return planConfigService.TagConfigHandle().init(this, groupName));
}
问题四:SpringBoot为何是单例
问题五:Spring单例,为什么service和dao确能保证线程安全
在使⽤Spring时,很多⼈可能对Spring中为什么DAO和Service对象采⽤单实例⽅式很迷惑,这些读者是这么认为的:
DAO对象必须包含⼀个数据库的连接Connection,⽽这个Connection不是线程安全的,所以每个DAO都要包含⼀个不同的Connection 对象实例,这样⼀来DAO对象就不能是单实例的了。
上述观点对了⼀半。对的是“每个DAO都要包含⼀个不同的Connection对象实例”这句话,错的是“DAO
对象就不能是单实例”。
其实Spring在实现Service和DAO对象时,使⽤了ThreadLocal这个类,这个是⼀切的核⼼! 如果你不知道什么事ThreadLocal,请看《深⼊研究java.lang.ThreadLocal类》:。请放⼼,这个类很简单的。
ThreadLocal
1。每个线程中都有⼀个⾃⼰的ThreadLocalMap类对象,可以将线程⾃⼰的对象保持到其中,各管各的,线程可以正确的访问到⾃⼰的对象。
2。将⼀个共⽤的ThreadLocal静态实例作为key,将不同对象的引⽤保存到不同线程的ThreadLocalMap中,然后在线程执⾏的各处通过这个静态ThreadLocal实例的get()⽅法取得⾃⼰线程保存的那个对象,避免了将这个对象作为参数传递的⿇烦。
要弄明⽩这⼀切,⼜得明⽩事务管理在Spring中是怎么⼯作的,所以本⽂就对Spring中多线程、事务的问题进⾏解析。
Spring使⽤ThreadLocal解决线程安全问题:
Spring中DAO和Service都是以单实例的bean形式存在,Spring通过ThreadLocal类将有状态的变量(
例如数据库连接Connection)本地线程化,从⽽做到多线程状况下的安全。在⼀次请求响应的处理线程中, 该线程贯通展⽰、服务、数据持久化三层,通过ThreadLocal 使得所有关联的对象引⽤到的都是同⼀个变量。
参考下⾯代码,这个是《Spring3.x企业应⽤开发实战中的例⼦》,本⽂后⾯也会多次⽤到该书中例⼦(有修改)。
[java] view plaincopy
public class SqlConnection {
//①使⽤ThreadLocal保存Connection变量
privatestatic ThreadLocal <Connection>connThreadLocal = newThreadLocal<Connection>();
publicstatic Connection getConnection(){
// ②如果connThreadLocal没有本线程对应的Connection创建⼀个新的Connection,
// 并将其保存到线程本地变量中。
()==null){
Connection conn =getConnection();
connThreadLocal.set(conn);
return conn;
}else{
();
// ③直接返回线程本地变量
}
}
public voidaddTopic(){
// ④从ThreadLocal中获取线程对应的Connection
try{
Statement stat =getConnection().createStatement();
}catch(SQLException e){
e.printStackTrace();
}
}
}
这个是例⼦展⽰了不同线程使⽤TopicDao时如何使得每个线程都获得不同的Connection实例副本,同时保持TopicDao本⾝是单实例。事务管理器:
事务管理器⽤于管理各个事务⽅法,它产⽣⼀个事务管理上下⽂。下⽂以SpringJDBC的事务管理器DataSourceTransactionManager类为例⼦。
我们知道数据库连接Connection在不同线程中是不能共享的,事务管理器为不同的事务线程利⽤Threa
dLocal类提供独⽴的Connection 副本。事实上,它将Service和Dao中所有线程不安全的变量都提取出来单独放在⼀个地⽅,并⽤ThreadLocal替换。⽽多线程可以共享的部分则以单实例⽅式存在。
事务传播⾏为:
当我们调⽤Service的某个事务⽅法时,如果该⽅法内部⼜调⽤其它Service的事务⽅法,则会出现事务的嵌套。Spring定义了⼀套事务传播⾏为,请参考。这⾥我们假定都⽤的REQUIRED这个类型:如果当前没有事务,就新建⼀个事务,如果已经存在⼀个事务,则加⼊到的当前事务。参考下⾯例⼦(代码不完整):
[java] view plaincopy
@Service("userService")
public class UserService extends BaseService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private ScoreService scoreService;
public void logon(String userName){
updateLastLogonTime(userName);
scoreService.addScore(userName,20);
spring mvc和boot区别}
public void updateLastLogonTime(String userName){
String sql ="UPDATE t_user u SET u.last_logon_time = ? WHERE user_name =?";
jdbcTemplate.update(sql, System.currentTimeMillis(), userName);
}
public static void main(String[] args){
ApplicationContext ctx =new ClassPathXmlApplicationContext("com/baobaotao/l");
UserService service =(UserService) Bean("userService");
service.logon("tom");
}
}
@Service("scoreUserService")
public class ScoreService extends BaseService{
@Autowired
private JdbcTemplate jdbcTemplate;
public void addScore(String userName,int toAdd){
String sql ="UPDATE t_user u SET u.score = u.score + ? WHERE user_name =?";
jdbcTemplate.update(sql, toAdd, userName);
}
}
同时,在配置⽂件中指定UserService、ScoreService中的所有⽅法都开启事务。
上述例⼦中UserService.logon()执⾏开始时Spring创建⼀个新事务,UserService.updateLastLogonTime()和
ScoreService.addScore()会加⼊这个事务中,好像所有的代码都“直接合并”了!
多线程中事务传播的困惑:
还是上⾯那个例⼦,加⼊现在我在UserService.logon()⽅法中⼿动新开⼀个线程,然后在新开的线程中执⾏ScoreService.add()⽅法,此时事务传播⾏为会怎么样?飞线程安全的变量,⽐如Connection会怎样?改动之后的UserService 代码⼤体是:
[java] view plaincopy
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论