Bean的作用域
我们先来看下面这段代码
首先是一个Dog类 (此处使用lombok来完成setter、getter、toString方法)
@Setter
@Getter
public class Dog {
private String name;
}
然后在DogBeanConfig类里面写一个返回Dog的方法,并将这个方法的返回对象存入Spring容器
@Component
public class DogBeanConfig {
@Bean
public Dog singleDog() {
Dog dog = new Dog();
return dog;
}
}
然后在类里分别注入这个Dog对象
RestController
public class DogController {
@Autowired
private Dog singleDog;
@RequestMapping("/single")
public String single(){
Dog contextDog = (Dog)applicationContext.getBean("singleDog");
return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
}
}
结果
多次访问, 得到的都是同⼀个对象, 并且 @Autowired 和 applicationContext.getBean() 也是同⼀个对象.(在打印对象时,Java的Object.toString()
方法(如果类没有重写这个方法)会返回对象的类名加上这个哈希码的十六进制表示) 快速查看对象的类型或唯一性时,这种输出非常有用。
这就是Bean的不同作⽤域了
Bean的六种作用域
(在Spring中⽀持6种作⽤域,后4种在Spring MVC环境才⽣效)
1. 单例模式:singleton(默认)-- 每次注入都是同一份对象。默认为这种模式也是Spring为了性能的考虑。
2. 原型模式:prototype -- 每次注入都是一个新的对象。
3. 请求作用域(适用于Spring MVC / Spring Web):request -- 每次http请求中共享一份Bean对象。
4. 回话作用域(适用于Spring MVC / Spring Web):session -- 每次会话(session)共享一份Bean对象。
5. 全局作用域(适用于Spring MVC / Spring Web):applicaton -- 每个http servlet context中共享一份Bean对象。
6. websocket:适用于Spring WebSocket项目。
Bean的作用域设置
Bean设置作用域使用 @Scope 注解
使用方法:
在@Scope 注解里加上作用域名,比如:@Scope("prototype") 此时被该注解修饰的类为原型模式。
定义⼏个不同作⽤域的Bean
package demo.beanscope;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.ApplicationScope;
import org.springframework.web.context.annotation.RequestScope;
import org.springframework.web.context.annotation.SessionScope;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 22652
* Date: 2024-08-03
* Time: 13:37
*/
@Component
public class DogBeanConfig {
@Bean
public Dog dog() {
Dog dog = new Dog();
dog.setName("旺旺");
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Dog singleDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Dog prototypeDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@RequestScope
public Dog requestDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@SessionScope
public Dog sessionDog() {
Dog dog = new Dog();
return dog;
}
@Bean
@ApplicationScope
public Dog applicationDog() {
Dog dog = new Dog();
return dog;
}
}
@RequestScope 等同于 @Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)其他同理。
测试不同作⽤域的Bean取到的对象是否⼀样
package demo.beanscope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* Created with IntelliJ IDEA.
* Description:
* User: 22652
* Date: 2024-08-03
* Time: 13:41
*/
@RestController
public class DogController {
@Autowired
private Dog singleDog;
@Autowired
private Dog prototypeDog;
@Autowired
private Dog requestDog;
@Autowired
private Dog sessionDog;
@Autowired
private Dog applicationDog;
@Autowired
private ApplicationContext applicationContext;
@RequestMapping("/single")
public String single(){
Dog contextDog = (Dog)applicationContext.getBean("singleDog");
return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/prototype")
public String prototype(){
Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");
return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;
}
@RequestMapping("/request")
public String request(){
Dog contextDog = (Dog)applicationContext.getBean("requestDog");
return
"dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/session")
public String session(){
Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
return
"dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();
}
@RequestMapping("/application")
public String application(){
Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
return
"dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();
}
}
观察Bean的作⽤域
单例作⽤域: http://127.0.0.1:8080/single 多次访问, 得到的都是同⼀个对象, 并且 @Autowired 和 applicationContext.getBean() 也是同⼀个对象。
多例作⽤域: http://127.0.0.1:8080/prototype 观察ContextDog, 每次获取的对象都不⼀样(注⼊的对象在Spring容器启动时, 就已经注⼊了, 所以多次 请求也不会发⽣变化)
请求作⽤域: http://127.0.0.1:8080/request 在⼀次请求中, @Autowired 和 applicationContext.getBean() 也是同⼀个对象. 但是每次请求, 都会重新创建对象
会话作⽤域: http://127.0.0.1:8080/session 在⼀个session中, 多次请求, 获取到的对象都是同⼀个.
换⼀个浏览器访问, 发现会重新创建对象.(另⼀个Session)
Application作⽤域: http://127.0.0.1:8080/application在⼀个应⽤中, 多次访问都是同⼀个对象
Application scope就是对于整个web容器来说, bean的作⽤域是ServletContext级别的. 这个和 singleton有点类似,区别在于: Application scope是ServletContext的单例, singleton是⼀个 ApplicationContext的单例. 在⼀个web容器中ApplicationContext可以有多个. (了解即可)
Bean的生命周期
⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程, 我们把这个过程就叫做⼀个对象的⽣命周期. Bean 的⽣命周期分为以下5个部分:
1. 实例化(为Bean分配内存空间)
2. 属性赋值(Bean注⼊和装配, ⽐如 @AutoWired )
3. 初始化
a. 执⾏各种通知, 如 BeanNameAware , BeanFactoryAware , ApplicationContextAware 的接⼝⽅法.
b. 执⾏初始化⽅法:
- xml定义 init-method
- 使⽤注解的⽅式 @PostConstruct
- 执⾏初始化后置⽅法( BeanPostProcessor )
4. 使⽤Bean
5. 销毁Bean
a. 销毁容器的各种⽅法, 如 @PreDestroy , DisposableBean 接⼝⽅法, destroymethod.
如图
//通知需要BeanNameAware接口
public class BeanComponent implements BeanNameAware {
@Override
public void setBeanName(String s) {
System.out.println("执行了通知: " + s);
}
public void myInit() {
System.out.println("XML方式的初始化方法");
}
@PostConstruct
public void doPostConstruct() {
System.out.println("注解方法的初始化方法");
}
public void myDestroy() {
System.out.println("XML方式的销毁方法");
}
@PreDestroy
public void doPreDestroy() {
System.out.println("注解方式的销毁方法");
}
public void sayHi() {
System.out.println("执行 sayHi方法");
}
}
下面我们来执行这个类。
public static void main(String[] args) {
//启动容器
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
//从容器获取Bean对象
BeanComponent beanComponent = context.getBean("beanComponent", BeanComponent.class);
//执行Bean对象的内容
beanComponent.sayHi();
//销毁Bean对象的方法
context.destroy();
}
和上述的Bean的生命周期相匹配。
注意:属性注入一定要在初始化之前。
因为在初始化方法中可能会使用到注入的对象,如果初始化在属性注入前面,此时就会报错。
为什么要先设置属性在进⾏初始化呢?
@Service
public class UserService {
public UserService(){
System.out.println("调⽤ User Service 构造⽅法");
}
public void sayHi(){
System.out.println("User Service SayHi.");
}
}
@Controller
public class UserController {
@Resource
private UserService userService;
@PostConstruct
public void postConstruct() {
userService.sayHi();
System.out.println("执⾏ User Controller 构造⽅法");
}
}
我们可以发现很可能是为了避免空指针异常。