1.通过案例引入Bean作用域问题
如果有一个公共的Bean,提供给A用户和B用户使用,然而在使用途中A用户修改了公共Bean的数据,导致B用户在使用时发生了预期之外的问题。
我们预期的结果是,公共Bean可以在各自的类中被修改,但不能影响到其他类。
1.1 被修改的Bean案例
公共Bean:
@Component public class UserBeans { @Bean(name="u1") public User getUser1() { User user=new User(); user.setId(1); user.setName("张三"); return user; } }
A用户使用时,进行了修改操作:
@Component public class BeanScope1 { @Autowired private User user1; public User getUser() { User user=user1; user.setName("李四"); return user; } }
B用户再去使用公共Bean:
@Component public class BeanScope2 { @Autowired private User user1; public User getUser() { User user=user1; return user; } }
打印A用户和B用户公共Bean的值:
发现两个用户获取到的name都是修改过后的值。
1.2 原因分析
产生上述问题的原因是因为Bean在默认情况下是单例状态(singleton),也就是所有人使用的都是这一个对象,要想不出现上边这样的问题,还需再对Bean的作用域进行深一步了解,下边我们一起来看:
2.Bean的作用域
2.1 作用域定义
限定程序中变量的可用范围叫做作用域,或者说在源代码中定义变量的某个区域就叫做作用域。
Bean的作用域是指Bean在Spring整个框架中的某种行为模式,比如singleton单例作用域,就表示Bean在整个Spring中只有一份,它是全局共享的,那么当其他人修改了这个值之后,另一个人读到的就是被修改的值。
2.2 Bean的6种作用域
Spring容器在初始化一个Bean实例的时候,同时会指定该实例的作用域。有以下六种:
1、singleton:单例作用域
该作用域为Spring默认选择的作用域
2、prototype:原型作用域(多例作用域)
每次对该作用域下的Bean的请求,都会创建新的实例
3、request:请求作用域
每次http请求会创建新的Bean实例,类似于prototype(限定SpringMVC中使⽤)
4、session:会话作用域
在一个http session中,定义一个Bean实例(限定SpringMVC中使⽤)
5、application:全局作用域
在一个http servlet Context中,定义一个Bean实例(限定SpringMVC中使⽤)
6、websocket:HTTP WebSocket 作用域
在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例(限定Spring WebSocket中使⽤)
单例作用域(singleton)VS全局作用域(application)
singleton是Spring Core的作用域;application 是Spring Web中的作用域
singleton作用于IoC容器,而application作用于Servlet容器。
2.3 设置作用域
使用@Scope标签就可以声明Bean的作用域,该标签既可以修饰方法,也可以修饰类,有以下两种设置方式:
1、直接设置值:@Scope("prototype")
2、使用枚举设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
比如设置Bean的作用域,如下代码所示:
@Component public class UserBeans { @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) @Bean(name="user1") public User getUser1() { User user=new User(); user.setId(1); user.setName("张三"); return user; } }
这样用户A修改就并不会修改公共类了
3.Bean的生命周期
所谓生命周期就是指一个对象从诞生到销毁的整个生命过程,我们把这个过程就叫做一个对象的生命周期。
3.1 阐述
Bean的生命周期分为以下5大部分:
1、实例化(给bean分配内存空间)
2、设置属性(对象注入)
3、初始化
a)执行各种通知(执行各种Aware)
b)执行初始化的前置方法
c)执行构造方法,两种执行方式,一种是执行@PostConstruct,另一种是执行init-method
d)执行初始化的后置方法
4、使用Bean
5、销毁Bean
a)@PreDestory
b)重写DisposableBean接口方法
c)destory-method
其整个生命周期就像我们买房住房的流程一样,1实例化对应的就是我们买房子,先占据了空间;2设置属性对应的就是进行装修,完成基础的配置;3初始化对应的就是买家电(各种什么电视、冰箱、空调、洗衣机什么的),这步最复杂;4使用Bean对应的就是入住,在前边所有工作完成以后就可以使用了;5销毁Bean对应的就是卖房,在卖房前也要完成旧房的收拾工作。
执行流程图如下:
思考:为什么要先设置属性再进行初始化呢?
下边代码进行解释:
@Service public class UserService { public void sayHi() { System.out.println("你好,Service"); } } @Controller public class UserController { private UserService userService; //构造方法注入 @Autowired public UserController(UserService userService) { this.userService = userService; } }
在UserController对象的构造方法中,调用到了UserService对象,如果先进性初始化,因为UserService对象并没有注入,将会导致空指针异常,所以Bean必须先设置属性,再进行初始化。
3.2 演示
下边我们来通过代码演示Bean的生命周期:
public class BeanLifeComponent implements BeanNameAware { @PostConstruct public void postConstruct() { System.out.println("执行了@PostConstruct"); } public void init() { System.out.println("执行了init-method"); } public void use() { System.out.println("使用bean"); } @PreDestroy public void preDestory() { System.out.println("执行了@PreDestory"); } @Override public void setBeanName(String s) { System.out.println("执行了Aware通知"); } }
xml配置如下:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:content="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <content:component-scan base-package="com.beans"></content:component-scan> <bean id="beanLifeComponent" class="com.beans.BeanLifeComponent" init-method="init"></bean> </beans>
调用类: