文章目录

  • 【探索Spring底层】SpringBean初始化销毁与Scope
  • 1. Spring Bean初始化与销毁
  • 2. Scope的类型
  • 3. Scope失效问题及其解决
  • 3.1 @Lazy
  • 3.2 proxyMode
  • 3.3 ObjectFactory
  • 3.4 ApplicationContext


【探索Spring底层】SpringBean初始化销毁与Scope

1. Spring Bean初始化与销毁

Spring Bean的初始化方法有三种

  • 通过@PostConstruct注解标注在这个Bean内方法上
  • 实现InitializingBean接口,重写afterPropertiesSet方法
  • 利用@Bean注解指定该Bean中的某个方法为初始化方法
@Bean(initMethod = "init3")
public class Bean1 implements InitializingBean {
    private static final Logger log = LoggerFactory.getLogger(Bean1.class);

    @PostConstruct
    public void init1() {
        log.debug("初始化1");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        log.debug("初始化2");
    }

    public void init3() {
        log.debug("初始化3");
    }
}

Spring Bean销毁的方法对应也是有三种

  • 通过@PreDestroy标注Bean中某个方法作为销毁方法
  • 实现DisposableBean接口,并重写destroy方法作为销毁方法
  • 通过注入Bean时的@Bean注解的destroyMethod属性指明销毁方法
@Bean(destroyMethod = "destroy3")
public Bean2 bean2() {
    return new Bean2();
}
public class Bean2 implements DisposableBean {
    private static final Logger log = LoggerFactory.getLogger(Bean2.class);

    @PreDestroy
    public void destroy1() {
        log.debug("销毁1");
    }

    @Override
    public void destroy() throws Exception {
        log.debug("销毁2");
    }

    public void destroy3() {
        log.debug("销毁3");
    }
}

既然有三种方法初始化和销毁,那么它们肯定有顺序

移除被spring容器管理的bean_移除被spring容器管理的bean

移除被spring容器管理的bean_spring_02

对于初始化来说,三种方法执行顺序如下:

  1. @PostConstruct
  2. 实现InitializingBean接口
  3. @Bean(initMethod = “init3”)

而对于销毁来说,三种方法的执行顺序如下:

  1. @PreDestroy
  2. 实现DisposableBean接口
  3. @Bean(destroyMethod = “destroy3”)

2. Scope的类型

Scope是Spring中的一个关键属性,其指定了Spring中的Bean的生命周期,也可以理解为Spring容器的创建方法。

一般来说我们使用得最多得是singleton,也就是单例模式,这个也是Spring中Bean的默认Scope

Scope有五种:

  1. singleton:容器启动时创建(未设置延迟),容器关闭时销毁
  2. prototype,每次使用时创建,不会自动销毁,需要调用 DefaultListableBeanFactory.destroyBean(bean) 销毁
  3. request,每次请求用到此 bean 时创建,请求结束时销毁
  4. session,每个会话用到此 bean 时创建,会话结束时销毁
  5. application,web 容器用到此 bean 时创建,容器停止时销毁

3. Scope失效问题及其解决

模拟场景出现

@ComponentScan("com.itheima.a08.sub")
public class A08_1 {

    private static final Logger log = LoggerFactory.getLogger(A08_1.class);

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context =
                new AnnotationConfigApplicationContext(A08_1.class);
                
        E e = context.getBean(E.class);
        log.debug("{}", e.getF1().getClass());
        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());
        log.debug("{}", e.getF1());

        context.close();
    }
@Component
public class E {

    @Autowired
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}
@Scope("prototype")
@Component
public class F1 {
}

运行上面的程序,会发现打印的bean均为同一个,但是这个设置F1的Scope为prototype

移除被spring容器管理的bean_移除被spring容器管理的bean_03

这个F1希望的是多例,但是输出的确实同一个,也就是单例

这是因为它们是同一个对象,而不是期望的多例对象

对于单例对象来讲,依赖注入仅发生了一次,后续再没有用到多例的 F,因此 E 用的始终是第一次依赖注入的 F




e 创建

e set 注入 f

f 创建


想要解决这个也很简单,一共有四种方法解决


3.1 @Lazy

第一种则是使用需要使用一个@Lazy注解来生成代理

使用该注解代理之后,代理对象虽然还是同一个,但是使用代理对象的任意方法时候,由代理创建新的f对象



使用f方法

使用f方法

使用f方法

e 创建

e set 注入 f代理

f 创建

f 创建

f 创建


@Component
public class E {

    @Autowired
    @Lazy
    private F1 f1;

    public F1 getF1() {
        return f1;
    }
}

注意

  • @Lazy 也可以加在成员变量上,但加在 set 方法上的目的是可以观察输出,加在成员变量上就不行了
  • @Autowired 加在 set 方法的目的类似
@Component
public class E {

    @Autowired
    @Lazy
    public void setF(F f) {
        this.f = f;
        log.info("setF(F f) {}", f.getClass());
    }

    // ...
}

3.2 proxyMode

在使用@Scope定义F1时候,直接在 @Scope 注解加上proxyMode = ScopedProxyMode.TARGET_CLASS(其实底层本质也是自定义 TargetSource)

@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
@Component
public class F2 {
}

3.3 ObjectFactory

ObjectFactory是一个普通的对象工厂接口,其是一个函数式接口

@FunctionalInterface
public interface ObjectFactory<T> {

   /**
    * Return an instance (possibly shared or independent)
    * of the object managed by this factory.
    * @return the resulting instance
    * @throws BeansException in case of creation errors
    */
   T getObject() throws BeansException;

}

里面的getObject是可以从对应域中获取指定的对象

@Component
public class E {

    @Autowired
    private ObjectFactory<F1> f1;

    public F1 getF1() {
        return f1.getObject();
    }

}

3.4 ApplicationContext

最后一种就是最常见的ApplicationContext,因为F1已经注册在Spring容器中了,只需要调用ApplicationContextg的getBean方法就可以获取到多例子

@Component
public class E {

    @Autowired
    private ApplicationContext context;

    public F1 getF1() {
        return context.getBean(F1.class);
    }
}