4.Bean作用域与生命周期

一、案例理解Bean的作用域

假如有个公共Bean,提供A用户与B用户使用,如果A在用的时候修改了公共Bean的数据,导致B用户使用时发生预期之外的逻辑错误,相当于用了脏数据。

原因:因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中Bean 的作⽤域默认也是 singleton 单例模式。

如何使公共Bean在各自的类中被修改,但不影响到其他类呢?

答案是用Bean的作用域。

先看看错误案例:

//公共Bean
@Component

public class Users {
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean(name="u1")
    public User user1() {
        User user = new User();
        user.setId(11);
        user.setName("This is common");
        return user;
    }
}
//A用户修改Bean
@Controller
public class BeanScopesController {
    @Autowired
    private User user1;
    public User getUser1(){
        User user=user1;
        System.out.println("Bean  Name "+user.getName());
        user.setName("A已修改");//进行修改操作
        return user;

    }
}

//B用户访问公共Bean
@Controller
public class BeanScopesController2 {
    @Autowired
    private User user1;
    public User getUser1(){
        User user=user1;
        return user;
    }
}
public class BeanScopesTest {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext(
                "spring-config.xml");
        BeanScopesController beanScopesController=context.getBean(BeanScopesController.class);
        System.out.println("A对象修改之后Name:"+beanScopesController.getUser1().toString());
        BeanScopesController2 beanScopesController2 =context.getBean(
                BeanScopesController2.class );
        System.out.println("B对象得到的Name"+ beanScopesController2.getUser1().toString());

    }
}

结果:

公共Bean被修改了,导致B读取到的数据也是被A修改过的

此时只要在Users加上Bean作用域即可:

@Component

public class Users {
   @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)//作用域
    @Bean(name="u1")
    public User user1() {
        User user = new User();
        user.setId(11);
        user.setName("This is common");
        return user;
    }
}

此时,A可以修改并读取公共Bean的数据,然后B可以读取未修改的公共Bean数据,两者互不干扰。

二、作⽤域定义

限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域

Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值。

2.1 Bean 的 6 种作⽤域

Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的:

1. singleton:单例作⽤域)(全局有且仅有一个实例)

描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象。

场景:通常⽆状态的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新

spring默认选择该作用域

2. prototype:原型作⽤域(多例作⽤域)--每次获取Bean时都有新的实例

描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过applicationContext.getBean等⽅法获取)及装配Bean(即通过@Autowired注入)都是新的对象实例

场景:通常有状态的Bean使用该作用域。

3. request:请求作⽤域

描述:每次http请求会创建新的Bean实例,同时该bean仅在当前HTTP request内有效。

场景:一次http请求和响应的共享Bean

备注:限定SpringMVC中使用

4. session:回话作⽤域

描述:每一次HTTP请求都会产生一个新的bean,同时该bean仅在当前http session内有效

场景:用户回话的共享Bean,比如:记录一个用户的登录信息

备注:限定SpringMVC使用

5. application(了解即可)

描述: 在一个http servlet Context中,定义一个Bean实例

场景:Web应用上下文信息,比如:记录一个应用的共享信息

备注:限定SpringMVC使用

6. websocket(了解即可)

描述:在一个HTTP WebSocket生命周期中,定义了一个Bean实例

场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。

备注:限定Spring WebSocket中使用

2.2设置作用域

使⽤ @Scope 标签就可以⽤来声明 Bean 的作⽤域,⽐如设置 Bean 的作⽤域,如下代码所示

1.在不指定@Scope的情况下,所有的bean都是单实例的bean

@Bean 
public Person person() { 
    return new Person(); 
}

2.指定@Scope为 prototype 表示为多实例

@Bean 
@Scope(value = "prototype") 
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person person() { 
    return new Person(); 
}

3.singleton/prototype模式演示

/**
 * Person类
 */ 
public class Person {
    // Person类无参构造方法
    public Person() {
        System.out.println("执行Person构造器创建Person对象");
    }
}
@Configuration
public class BeanConfig {

    @Bean
    @Scope(value = "singleton")
    public Person person(){
        return new Person();
    }
}

三、spring执行流程和Bean的生命周期

3.1 Spring 执行流程

1.启动容器->加载配置文件(类加载路径下的beans.xml)->根据配置完成Bean初始化->扫描org.example包下边的spring注解->注册Bean对象到容器中->装配Bean的属性

简单一点就是 1.启动容器 2.读取配置进行Bean实例化 3.将Bean加入到容器中 4.装配Bean属性(给当前类的属性DI,进行赋值)

3.2 Bean执行流程

Bean执行流程:启动Spring容器->实例化Bean(分配内存空间,从无到有)->Bean注册到Spring中(存操作)->将Bean装配到需要的类中(取操作)

3.3 Bean生命周期

Bean的生命周期分为以下5大部分:

1.实例化 Bean(为Bean分配内存空间)

2.设置属性(Bean注入与装配)

3.Bean初始化:

        a.执行各种通知:实现了各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware ApplicationContextAware 的接⼝⽅法;

        b.初始化前置方法:执⾏ BeanPostProcessor 初始化前置⽅法;

        c.初始化方法:执⾏ @PostConstruct 初始化⽅法,依赖注⼊操作之后被执⾏;

        d.执⾏⾃⼰指定的 init-method ⽅法(如果有指定的话);

        e.初始化后置方法:执⾏ BeanPostProcessor 初始化后置⽅法

4.使用Bean

5.销毁Bean,执行了@PreDestroy

实例化与初始化的区别:

实例化和属性设置是 Java 级别的系统“事件”,其操作过程不可⼈⼯⼲预和修改;⽽初始化是给开发者提供的,可以在实例化之后,类加载完成之前进⾏⾃定义“事件”处理

举个简单的例子吧:比如买房这件事

1. 先买房(实例化,从⽆到有);

2. 装修(设置属性);

3. 买家电,如洗⾐机、冰箱、电视、空调等([各种]初始化);

4. ⼊住(使⽤ Bean);

5. 卖出去(Bean 销毁

四、@Scope注解的使用场景

@Scope注解的使用场景

  几乎90%以上的业务使用 singleton单例就可以,所以 Spring 默认的类型也是singleton,singleton虽然保证了全局是一个实例,对性能有所提高,但是如果实例中有非静态变量时,会导致线程安全问题,共享资源的竞争。

  当设置为prototype多例时:每次连接请求,都会生成一个bean实例,也会导致一个问题,当请求数越多,性能会降低,因为创建的实例,导致GC频繁,GC时长增加。

五、Bean实例对象的销毁

针对单实例bean的话,容器启动的时候,bean的对象就创建了,而且容器销毁的时候,也会调用Bean的销毁方法;

针对多实例bean的话,容器启动的时候,bean是不会被创建的而是在获取bean的时候被创建,而且bean的销毁不受IOC容器的管理,是由GC来处理的

针对每一个Bean实例,都会有一个initMethod() 和 destroyMethod() 方法,我们可以在Bean 类中自行定义,也可以使用 Spring 默认提供的这两个方法。

public class Person {

    public Person() {
        System.out.println("执行Person构造器创建Person对象");
    }

    public void init() {
        System.out.println("initMethod");
    }

    public void destroy() {
        System.out.println("destroyMethod");
    }
}
@Configuration
public class BeanConfig {

    @Bean(initMethod = "init", destroyMethod = "destroy")   // 可以自己指定方法名,也可以不指定,使用Spring默认提供的方法
    @Scope(value = "singleton")
    public Person person(){
        return new Person();
    }
}

singleton单例,在IOC容器销毁时,就会调用 destroyMethod() 方法来将 Bean对象销毁;prototype多例,

它的Bean实例对象则不受IOC容器的管理,最终由GC来销毁。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值