Spring Bean

3/25/2022 SpringBean

# Spring Bean

# Spring Bean 基础

# 装配Bean的方法

# 通过xml配置装配Bean

# 通过注解装配Bean

更多的时候已经不再推荐使用XML 的方式去装配Bean ,更多的时候会考虑使用注解C annotation )的方式去装配Bean。使用注解的方式可以减少XML 的配置, 注解功能更为强大, 它既能实现XML 的功能, 也提供了自动装配的功能,采用了自动装配后, 程序员所需要做的决断就少了,更加有利于对程序的开发,这就是“约定优于配置” 的开发原则。

在Spring 中,它提供了两种方式来让Spring IoC 容器发现Bean。

  1. 组件扫描: 通过定义资源的方式, 让Spring IoC 容器扫描对应的包,从而把Bean装配进来。

  2. 自动装配: 通过注解定义,使得一些依赖关系可以通过注解完成。

使用@Component 装配Bean:

@Component(value="role")
public class Role{
	@Value("1")
    private Long id;
    @Value("role_name_1")
    private String roleName;
}

问题:

其一, 对于@ComponentScan 注解, 它只是扫描所在包的Java 类, 但是更多的时候真正需要的是可以扫描所指定的类;

其二, 上面只注入了一些简单的值,而没有注入对象,同样在现实的开发中可以注入对象是十分重要的,也是常见的场景。

自动装配-@Autowired

  • Spring 是先完成Bean 的定义和生成,然后寻找需要注入的资源。也就是当Spring 生成所有的Bean 后,如果发现这个注解,它就会在Bean中查找,然后找到对应的类型,将其注入进来,这样就完成依赖注入了。

  • 所谓自动装配技术是一种由Spring 自己发现对应的Bean, 自动完成装配工作的方式,它会应用到一个十分常用的注解@Autowired 上, 这个时候Spring 会根据类型去寻找定义的Bean 然后将其注入。

使用@Bean 装配Bean

  • 以上都是通过@Component 装配Bean , 但是**@Component 只能注解在类上, 不能注解到方法上**。对于Java 而言, 大部分的开发都需要引入第三方的包( jar 文件) ,而且往往并没有这些包的源码, 这时候将无法为这些包的类加入@Component 注解,让它们变为开发环境的Bean 。

  • 这个时候Spring 给予一个注解@Bean , 它可以注解到方法之上, 并且将方法返回的对象作为Spring 的Bean,存放在IoC 容器中

@Configuration
public class AppConfig {
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
}

# @Component和@Bean的区别?

  1. 作用对象不同: @Component 注解作用于类,而@Bean**注解作用于方法。

  2. @Component通常是通过类路径扫描来自动侦测以及自动装配到Spring容器中(我们可以使用 @ComponentScan 注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean 注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean告诉了Spring这是某个类的实例,当我需要用它的时候还给我。

  3. @Bean 注解比 Component 注解的自定义性更强,而且很多地方我们只能通过 @Bean 注解来注册bean。比如当我们引用第三方库中的类需要装配到 Spring容器时,则只能通过 @Bean来实现。

# Bean的作用域

在默认的情况下, Spring IoC容器只会为配置的Bean 生成一个实例,而不是多个。

有时候我们希望Action 是多个实例,每当我们请求的时候就产生一个独立的对象,而不是默认的一个,这样多个实例就可以在不同的线程运行了,就没有并发问题了。

Spring 提供了4 种作用域,它会根据情况来决定是否生成新的对象。

  1. 单例( singleton ):它是默认的选项,在整个应用中, Spring 只为其生成一个Bean的实例。

  2. 原型( prototype ):当每次注入,或者通过Spring IoC 容器获取Bean 时, Spring 都会为它创建一个新的实例。

  3. 会话( session ):在Web 应用中使用,就是在会话过程中Spring 只创建一个实例。

  4. 请求( request ):在Web 应用中使用的,就是在一次请求中Spring 会创建一个实例,但是不同的请求会创建不同的实例。

  5. 使用方法:使用@Scope注解

# 单例bean的线程安全问题?

大部分时候我们并没有在系统中使用多线程,所以很少有人会关注这个问题。单例 bean 存在线程问题,主要是因为当多个线程操作同一个对象的时候,对这个对象的非静态成员变量的写操作会存在线程安全问题

常见的有两种解决办法:

  1. 在Bean对象中尽量避免定义可变的成员变量(不太现实)。

  2. 在类中定义一个ThreadLocal成员变量,将需要的可变成员变量保存在 ThreadLocal 中(推荐的一种方式)。

# Spring Bean 的生命周期

img

img

# 总览

img

  1. Spring 对 Bean 进行实例化(相当于 new XXX())

  2. Spring 将值和引用注入进 Bean 对应的属性中

  3. 如果 Bean 实现了 BeanNameAware 接口,Spring 将 Bean 的 ID 传递给 setBeanName 方法

    • 作用是通过 Bean 的引用来获得 Bean ID,一般业务中是很少有用到 Bean 的 ID 的
  4. 如果 Bean 实现了 BeanFactoryAware 接口,Spring 将调用 setBeanDactory 方法,并把 BeanFactory 容器实例作为参数传入。

    • 作用是获取 Spring 容器,如 Bean 通过 Spring 容器发布事件
  5. 如果 Bean 实现了 ApplicationContextAware 接口,Spring 容器将调用 setApplicationContext 方法,把应用上下文作为参数传入

    • 作用与 BeanFactory 类似都是为了获取 Spring 容器,不同的是 Spring 容器在调用 setApplicationContext 方法时会把它自己作为 setApplicationContext 的参数传入,而 Spring 容器在调用 setBeanFactory 前需要使用者自己指定(注入)setBeanFactory 里的参数 BeanFactory
  6. 如果 Bean 实现了 BeanPostProcess 接口,Spring 将调用 postProcessBeforeInitialization 方法-前置处理

    • 作用是在 Bean 实例创建成功后对其进行增强处理,如对 Bean 进行修改,增加某个功能
  7. 如果 Bean 实现了 InitializingBean 接口,Spring 将调用 afterPropertiesSet 方法,作用与在配置文件中对 Bean 使用 init-method 声明初始化的作用一样,都是在 Bean 的全部属性设置成功后执行的初始化方法

  8. 如果 Bean 实现了 BeanPostProcess 接口,Spring 将调用 postProcessAfterInitialization 方法-后置处理

    • postProcessBeforeInitialization 是在 Bean 初始化前执行的,而 postProcessAfterInitialization 是在 Bean 初始化后执行的
  9. 经过以上的工作后,Bean 将一直驻留在应用上下文中给应用使用,直到应用上下文被销毁

  10. 如果 Bean 实现了 DispostbleBean 接口,Spring 将调用它的 destory 方法,作用与在配置文件中对 Bean 使用 destory-method 属性的作用一样,都是在 Bean 实例销毁前执行的方法。

# 四个阶段

  1. Bean的实例化阶段-主要是在createBeanInstance()方法中,调用类的构造器方法来创建一个Bean实例。用户可以自定义一个类,继承InstantiationAwareBeanPostProcessorAdapter,重写它的两个方法,对Bean的实例化前后做一些额外的操作,例如打印日志。
public class MyInstantiationAwareBeanPostProcessorAdapter extends InstantiationAwareBeanPostProcessorAdapter {
   @Override
   public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
      if (beanName.equals("car")) {
         System.out.println(beanName + "在实例化之前");
      }
      return super.postProcessBeforeInstantiation(beanClass, beanName);
   }
   @Override
   public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
      if (beanName.equals("car")) {
         System.out.println(beanName + "在实例化之后");
      }
      return super.postProcessAfterInstantiation(bean, beanName);
   }
}
  1. 属性赋值阶段-主要是在populateBean()方法中,对Bean的各项属性进行赋值。

  2. Bean的初始化阶段-主要调用用户自定义的初始化方法init-Method()。用户可以自定义一个类,继承BeanPostProcessor,重写它的两个方法,对Bean的初始化前后做一些额外的操作,例如打印日志。

public class NdBeanPostProcessor implements BeanPostProcessor {
   public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("NdBeanPostProcessor 在" + beanName + "对象初始化之前调用......");
      if (beanName.equals("car")) {
         return new CglibInterceptor().getIntance(bean.getClass());
      }
      return bean;
   }
   public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      System.out.println("NdBeanPostProcessor 在" + beanName + "对象初始化之后调用......");
      return bean;
   }
}
  1. Bean销毁阶段,用户可以自定义destroyMethod()方法,在Bean被销毁时被调用。

# Spring Bean 注册

注册 Spring Bean 实际上是将 BeanDefinition 注册到 IoC 容器中。

# XML 配置元信息

Spring 的传统配置方式。在 <bean> 标签中配置元数据内容。

缺点是当 JavaBean 过多时,产生的配置文件足以让你眼花缭乱。

# 注解配置元信息

使用 @Bean、@Component、@Import 注解注册 Spring Bean。

# Java API 配置元信息

  • 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
  • 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,Be anDefinitionRegistry)
  • 配置类方式:AnnotatedBeanDefinitionReader#register(Class...)

# Spring Bean 实例化

Spring Bean 实例化方式:

  • 常规方式

    • 通过构造器(配置元信息:XML、Java 注解和 Java API)
    • 通过静态工厂方法(配置元信息:XML、Java 注解和 Java API)
    • 通过 Bean 工厂方法(配置元信息:XML、Java 注解和 Java API)
    • 通过 FactoryBean(配置元信息:XML、Java 注解和 Java API)
  • 特殊方式

    • 通过 ServiceLoaderFactoryBean(配置元信息:XML、Java 注解和 Java API )
    • 通过 AutowireCapableBeanFactory#createBean(java.lang.Class, int, boolean)
    • 通过 BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)

# Spring Bean 初始化和销毁

Spring Bean 初始化和销毁的方式有以下几种:

  1. 使用 @PostConstruct 和 @PreDestroy 注解分别指定相应的初始化方法和销毁方法

  2. 实现 InitializingBean 接口的 afterPropertiesSet() 方法来编写初始化方法;实现 DisposableBean 接口的 destroy() 方法来编写销毁方法。

  3. 自定义初始化方法

    • XML 配置:<bean init-method="init" destroy="destroy" ... />
    • Java 注解:@Bean(initMethod = "init", destroyMethod = "destroy")
    • Java API:AbstractBeanDefinition#setInitMethodName(String) 和 AbstractBeanDefinition#setDestroyMethodName(String) 分别定义初始化和销毁方法

注意:如果同时存在,执行顺序会按照序列执行。

# Spring Bean 垃圾回收

Spring Bean 垃圾回收步骤:

  1. 关闭 Spring 容器(应用上下文):context.close()
  2. 执行 GC
  3. Spring Bean 覆盖的 finalize() 方法被回调
Last Updated: 3/28/2022, 9:29:49 PM