作用域
对于Spring来说作用域只有两个作用域:单例作用域和多例作用域
下面举一个例子来说明他们之间的区别
@Setter
@Getter
@ToString
public class User {
private int id;
private String name;
}
@Component
public class UserBeans {
@Bean
public User user(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
}
@Controller
public class UserController {
@Autowired
private User user;
public void useBeans(){
System.out.println(user);
User myUser = user;
myUser.setName("李四");
System.out.println("myUser ->" + myUser);
System.out.println("user ->" + user);
}
}
@Controller
public class UserController2 {
@Resource
private User user;
public void printBeans(){
System.out.println("user ->" + user);
}
}
这里我创建了两个类用于获取user里面的内容,我们预想的是一个获取到的是“张三”,一个获取到的是“李四”
但是实际上获取到的是这样的效果
User(id=1, name=张三)
myUser ->User(id=1, name=李四)
user ->User(id=1, name=李四)
user ->User(id=1, name=李四)
也就是说当我们修改了一次User以后,所有的User都会随之改变
换句话说,所有的类使用的都是同一个User对象!
但是当我们给User添加作用域的注解以后就会打印出来的结果就会不同了
@Bean
@Scope("prototype")//设置为多例作用域
public User user(){
User user = new User();
user.setId(1);
user.setName("张三");
return user;
}
打印结果如下
User(id=1, name=张三)
myUser ->User(id=1, name=李四)
user ->User(id=1, name=李四)
user ->User(id=1, name=张三)
我们可以发现不同的类里面使用的User是不同的User了
用更官方的语言来解释
使用单例作用域(singleton):
该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象
使用多例作用域(prototype):每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的对象实例
当我们不给Bean设置作用域的时候,此时Spring默认该Bean使用的是单例作用域,这也就解释了为什么一开始打印出来的结果并不是我们想要的结果
对于Spring来说作用域只有这么两个,当然对于Spring家族的其他成员来说就不再是只有这两个了,例如说 Request作用域(每当HTTPS发送一个请求就建立一个新的bean)和Session作用域(每当HTTPS发送一个Session对话就建立一个新的bean)
Bean的生命周期
对于Spring来说他的大体的执行流程:首先创建Spring容器,再根据xml配置文件来初始化相应的bean对象,再将bean对象存储到Spring容器中,当需要的时候将bean注入到相应的对象中
那么对于Bean来说他的生命周期分为五步:
1. 实例化:当Spring容器启动时,它会创建Bean的实例。实例化Bean就是在内存中给Bean开辟一块内存空间,这可以通过在配置文件中定义<bean>元素或使用@Component注解来实现。
2. 属性赋值:在创建Bean的实例之后,Spring容器将会注入Bean的属性。这可以通过setter方法、构造函数注入、自动装配等方式来实现。
3. 初始化:在属性赋值完成之后,Spring容器将会调用Bean的初始化方法。这可以通过实现InitializingBean接口或在配置文件中定义init-method属性来实现。
4. 使用:在初始化之后,Bean就可以被应用程序使用了。
5. 销毁:当Spring容器关闭时,它会调用Bean的销毁方法。这可以通过实现DisposableBean接口或在配置文件中定义destroy-method属性来实现。
总之,在Spring框架中,Bean的生命周期由容器来管理,开发者可以通过实现一些特定的接口或在配置文件中定义一些特定的属性来控制Bean的生命周期。这样做可以使开发者更加方便地管理Bean的状态,从而提高应用程序的可维护性和可扩展性。
关于Bean的初始化又分为:
Bean 初始化 实现了各种 Aware 通知的⽅法
执⾏ BeanPostProcessor 初始化前置⽅法
执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏
执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话)
执⾏ BeanPostProcessor 初始化后置⽅法
public class BeansLife implements BeanNameAware, BeanPostProcessor {
//init方法(使用xml文件的初始化方法)
public void beansLifeInit(){
System.out.println("do the bean's life init method");//执行xml里面声明的init方法
}
//使用注解的初始化方法
@PostConstruct
public void postConstruct(){
System.out.println("do the postConstruct");//执行注解初始化方法
}
//容器销毁前的执行的方法
@PreDestroy
public void preDestroy(){
System.out.println("do the pre-destory method");//执行销毁前的方法
}
//使用bean里面的方法
public void sayHi(){
System.out.println("Hi");
}
//设置各种属性的优先级是最高的,因为有可能会调用各种各样的其他类里面的方法所以如果不先声明设 置的话后面执行各种初始化就很有可能会报错
@Override
public void setBeanName(String s) {
System.out.println("设置各种属性");
}
}
我们通过setBeanName这个方法可以将各种属性在初始化前设置完毕
例如设置一些名称
public class MyBean implements BeanNameAware {
private String beanName;
@Override
public void setBeanName(String name) {
this.beanName = name;
System.out.println("BeanNameAware接口方法被调用,当前Bean名称为:" + name);
}
public void doSomething() {
System.out.println("执行某个操作,当前Bean名称为:" + beanName);
}
}
public static void main(String[] args) {
ApplicationContext context =
new ClassPathXmlApplicationContext("spring-config.xml");
MyBean myBean = (MyBean) context.getBean("myBean");
myBean.doSomething();
}
此时的name与我们指定bean的名称是一样的,所以打印出来的结果就是bean的名称
关于执⾏⾃⼰指定的 init-method ⽅法,我们在xml文件中声明bean对象时可以同时指定init-method方法,这个方法也是通过xml的方式进行初始化的方法
<bean id="life" class="com.java.demo.component.BeansLife" init-method="beansLifeInit"/>
指定init方法我们要注意的是,init方法的名称要和指定方法的名称一样
当我们运行这段代码的时候可以发现
public static void main(String[] args) {
ClassPathXmlApplicationContext context = new
ClassPathXmlApplicationContext("spring-config.xml");
BeansLife beansLife = context.getBean("life",BeansLife.class);
beansLife.sayHi();
context.destroy();//容器销毁
}
运行结果如下
设置各种属性/当前bean的名称->life
do the postConstruct -> 注解的方式来进行初始化
do the bean's life init method -> XML文件的方式来进行初始化
Hi
do the pre-destory method
那么通过这段代码我们就可以发现各个方法的优先级是怎样的了
我们要注意的是通过执行代码时:
注解来进行初始化的方法优先级要大于xml文件进行初始化的方法
这是因为我们更多的是使用注解的方式进行存储Bean对象而不是XML文件声明的方式,所以相应的,注解的优先级会更高