Bean的作用域
我们先来看下面这段代码
首先是一个User类(此处使用lombok来完成setter、getter、toString方法)
@Setter
@Getter
@ToString
public class User {
private int id;
private String name;
}
然后在GetBean类里面写一个返回User的方法,并将这个方法的返回对象存入Spring容器
@Component
public class GetBean {
@Bean
public User user() {
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
然后在两个类里分别注入这个User对象,并在其中一个类里面修改User对象里面的内容
//类1
@Controller
public class UserController {
@Autowired
private User user;
public void printUser() {
System.out.println(user);
user.setName("悟空");
System.out.println("user -> " + user);
}
}
//类2
@Controller
public class UserController2 {
@Autowired
private User user;
public void printUser2() {
System.out.println("user -> " + user);
}
}
最后我们来输出内容(先输出修改的类,后输出没修改的类)
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
context.getBean("userController", UserController.class).printUser();
context.getBean("userController2", UserController2.class).printUser2();
}
输出结果如下:
可以看到,最开始这个User对象的内容是 1,张三
修改后在类1里面变成了 1,悟空
而在没修改的类里面也变成了 1,悟空
这是因为,Bean的作用域默认是全局只有一份(singleton),类似于单例模式。
如果我们想要不同作用域的Bean就需要进行设置。
Bean的六种作用域
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 注解
使用方法:
1. 在@Scope 注解里加上作用域名,比如:@Scope("prototype") 此时被该注解修饰的类为原型模式。
2. 如下:
设置了prototype作用域之后我们再回到最开始的例子。
此时它的输出为:
Spring的执行流程
1. 加载容器(加载配置文件)
2. 根据配置完成Bean的初始化(扫描配置范围内的五大类注解)
3. 将被五大类注解修饰的类注册到Spring容器中
4.注入Bean对象(@Autowired、@Resource)
Bean的生命周期
1. 开辟内存空间(实例化)
2. 设置属性(将属性注入:@Autowried、@Resource)
3. 初始化
3.1 各种通知
3.2 初始化前置方法
3.3 初始化方法(xml方式、注解方式)
3.4 初始化后置方法
4. 使用Bean
5. 销毁Bean(xml方式、注解方式)
举个例子:
首先是一个类,里面实现了通知方法、xml方式和注解方式的初始化方法、xml方式和注解方式的销毁方法。
//通知需要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的生命周期相匹配。
注意:属性注入一定要在初始化之前。
因为在初始化方法中可能会使用到注入的对象,如果初始化在属性注入前面,此时就会报错。
如下:
//通知需要BeanNameAware接口
public class BeanComponent implements BeanNameAware {
/**
* 将对象注入
*/
@Autowired
private User user;
@Override
public void setBeanName(String s) {
System.out.println("执行了通知: " + s);
}
/**
* 在初始化方法里面使用这个注入的对象
*/
public void myInit() {
System.out.println("XML方式的初始化方法");
System.out.println(user.toString());
}
@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方法");
}
}
此时继续之前的输出:
如果是先初始化再属性注入,此时在myInit方法中就会报错。
比如,我将User对象取消注解,此时如下: