深入了解Spring之Bean(声明周期等)

GitHub:https://github.com/JDawnF/learning_note

目录

1、Spring 的配置方式

2、Bean Scope作用域

3、Bean的生命周期

实例化 Bean 对象

4、内部Bean

5、Spring 装配

自动装配

6、延迟加载

7、单例 Bean 是否线程安全

8、Spring解决循环依赖


1、Spring 的配置方式

  • Bean 由 Spring IoC 容器实例化,配置,装配和管理。

  • Bean 是基于用户提供给 IoC 容器的配置元数据 Bean Definition 创建。

单纯从 Spring Framework 提供的方式,一共有三种:

1、XML 配置文件。

Bean 所需的依赖项和服务在 XML 格式的配置文件中指定。这些配置文件通常包含许多 bean 定义和特定于应用程序的配置选项。它们通常以 bean 标签开头。例如:

<bean id="studentBean" class="org.edureka.firstSpring.StudentBean">
     <property name="name" value="Edureka"></property>
 </bean>

2、注解配置。

  • @Bean 注解扮演与 <bean /> 元素相同的角色。

  • @Configuration 类允许通过简单地调用同一个类中的其他 @Bean 方法来定义 Bean 间依赖关系。

  • 例如:

     @Configuration
     public class StudentConfig {    
         @Bean
         public StudentBean myStudent() {
             return new StudentBean();
         }
     }

    可以通过在相关的类,方法或字段声明上使用注解,将 Bean 配置为组件类本身,而不是使用 XML 来描述 Bean 装配。默认情况下,Spring 容器中未打开注解装配。因此,需要在使用它之前在 Spring 配置文件中启用它。例如: 

<beans>
 <context:annotation-config/>
 <!-- bean definitions go here
     或者通过下面这种方式,指定一个包:
     <context:component-scan base-package="cn.gacl.java"/>    -->    
 </beans>

3、Java Config 配置。

  • Spring 的 Java 配置是通过使用 @Bean 和 @Configuration 来实现。

目前主要使用 Java Config 配置为主。当然,三种配置方式是可以混合使用的。例如说:

  • Dubbo 服务的配置,一般使用 XML 。

        <dubbo:protocol name="dubbo" port="20884"></dubbo:protocol>
       <dubbo:application name="lehuan-search-service"/>
       <dubbo:registry address="zookeeper://192.168.98.135:2181"/>
     	<!--相当于包扫描-->
       <dubbo:annotation package="com.lehuan.search.service.impl"/>
       <dubbo:provider delay="-1" timeout="10000"/>

     

  • Spring MVC 请求的配置,一般使用 @RequestMapping 注解。

  • Spring MVC 拦截器的配置,一般 Java Config 配置。

另外,现在已经是 Spring Boot 的天下,所以更加是 Java Config 配置为主。

2、Bean Scope作用域

Spring Bean 支持 5 种 Scope ,分别如下:

  • Singleton - 每个 Spring IoC 容器仅有一个单 Bean 实例。默认

    Spring IoC 容器中只会存在一个共享的 Bean 实例,无论有多少个 Bean 引用它,始终指向同一对象。该模式在多线程下是不安全的。Singleton 作用域是 Spring 中的缺省作用域,也可以显示的将 Bean 定义为 singleton 模式,配置为:

    <bean id="userDao" class="com.ioc.UserDaoImpl" scope="singleton"/>

  • Prototype - 每次通过 Spring 容器获取 prototype 定义的 bean 时,容器都将创建一个新的 Bean 实例。

    每个 Bean 实例都有自己的属性和状态,而 singleton 全局只有一个对象。根据经验,对有状态的 bean 使用 prototype 作用域,而对无状态的 bean 使用 singleton作用域。

  • Request - 每一次 HTTP 请求都会产生一个新的 Bean 实例,并且该 Bean 仅在当前 HTTP 请求内有效。

    当前 Http 请求结束,该 bean实例也将会被销毁。

    <bean id="loginAction" class="com.cnblogs.Login" scope="request"/>

  • Session - 每一个的 Session 都会产生一个新的 Bean 实例,同时该 Bean 仅在当前 HTTP Session 内有效。

  • Global session - 在一个全局的 Http Session 中,容器会返回该 Bean 的同一个实例,仅在使用 portlet context 时有效。(在Spring5中)

开发者是可以自定义 Bean Scope ,具体可参见 《Spring(10)—— Bean 作用范围(二)—— 自定义 Scope》

3、Bean的生命周期

  1. 通过构造器或工厂方法创建Bean实例

  2. 为Bean的属性设置值和对其它Bean的引用

  3. 调用Bean的初始化方法

  4. Bean可以使用了

  5. 当容器关闭时,调用Bean的销毁方法

Spring Bean 的初始化流程如下:

实例化 Bean 对象

  1. 实例化一个 Bean,也就是我们常说的 new。Spring 容器根据配置中的 Bean Definition(定义)中实例化 Bean 对象。

    Bean Definition 可以通过 XML,Java 注解或 Java Config 代码提供。

IOC 依赖注入:按照 Spring 上下文对实例化的 Bean 进行配置,也就是 IOC 注入。

setBeanName 实现:如果这个 Bean 已经实现了 BeanNameAware 接口,会调用它实现的 setBeanName(String name) 方法,此处传递的就是 Spring 配置文件中 Bean 的 id 值

BeanFactoryAware 实现:如果这个 Bean 已经实现了 BeanFactoryAware 接口,工厂通过传递自身的实例来调用 setBeanFactory(BeanFactory beanFactory) 方法。setBeanFactory(BeanFactory)传递的是 Spring 工厂自身(可以用这个方式来获取其它 Bean, 只需在 Spring 配置文件中配置一个普通的 Bean 就可以)。

ApplicationContextAware 实现:如果这个 Bean 已经实现了 ApplicationContextAware 接口,会调用setApplicationContext(ApplicationContext)方法,传入 Spring 上下文(同样这个方式也 可以实现步骤 4 的内容,但比 4 更好,因为 ApplicationContext 是 BeanFactory 的子接 口,有更多的实现方法)

postProcessBeforeInitialization 接口实现-初始化预处理:如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessBeforeInitialization(Object obj, String s)方法,BeanPostProcessor 经常被用 作是 Bean 内容的更改,并且由于这个是在 Bean 初始化结束时调用那个的方法,也可以被应 用于内存或缓存技术。

init-method:如果 Bean 在 Spring 配置文件中配置了<bean />的 init-method 属性,会自动调用其配置的初始化方法。

postProcessAfterInitialization:如果这个 Bean 关联了 BeanPostProcessor 接口,将会调用 postProcessAfterInitialization(Object obj, String s)方法。 注:以上工作完成以后就可以应用这个 Bean 了,那这个 Bean 是一个 Singleton 的,所以一 般情况下我们调用同一个 id 的 Bean 会是在内容地址相同的实例,当然在 Spring 配置文件中 也可以配置非 Singleton。

Destroy 过期自动清理阶段:当 Bean 不再需要时,会经过清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy()方法;

destroy-method 自配置清理:

  1. 最后,如果这个 Bean 的 Spring 配置中配置了 <bean />的destroy-method 属性,会自动调用其配置的 销毁方法。

  2. bean 标签有两个重要的属性(init-method 和 destroy-method)。用它们你可以自己定制 初始化和注销方法。它们也有相应的注解(@PostConstruct 和@PreDestroy)。

 <bean id="" class="" init-method="初始化方法" destroy-method="销毁方法"> 

4、内部Bean

只有将 Bean 仅用作另一个 Bean 的属性时,才能将 Bean 声明为内部 Bean。

  • 为了定义 Bean,Spring 提供基于 XML 的配置元数据在 <property><constructor-arg> 中提供了 <bean>元素的使用。

  • 内部 Bean 总是匿名的,并且它们总是作为原型 Prototype 。

例如,假设我们有一个 Student 类,其中引用了 Person 类。这里我们将只创建一个 Person 类实例并在 Student 中使用它。示例代码如下:

// Student.java
 public class Student {
     private Person person;
     // ... Setters and Getters
 }
 // Person.java
 ​
 public class Person {
     private String name;
     private String address;    
     // ... Setters and Getters
 }
 <!-- bean.xml -->
 ​
 <bean id=“StudentBean" class="com.edureka.Student">
     <property name="person">
         <!--This is inner bean -->
         <bean class="com.edureka.Person">
             <property name="name" value=“Scott"></property>
             <property name="address" value=“Bangalore"></property>
         </bean>
     </property>
 </bean>

可以理解为在一个bean标签中引用了另一个bean,并且这个内部bean是作为外部bean的属性存在的。

5、Spring 装配

当 Bean 在 Spring 容器中组合在一起时,它被称为装配Bean 装配。Spring 容器需要知道需要什么 Bean 以及容器应该如何使用依赖注入来将 Bean 绑定在一起,同时装配 Bean 。

装配,和上文提到的 DI 依赖注入,实际是一个东西。

自动装配

Spring 容器能够自动装配 Bean 。也就是说,可以通过检查 BeanFactory 的内容让 Spring 自动解析 Bean 的协作者。

自动装配的不同模式:

  • no - 这是默认设置,表示没有自动装配。应使用显式 Bean 引用进行装配。

  • byName - 它根据 Bean 的名称注入对象依赖项。它匹配并装配其属性与 XML 文件中由相同名称定义的 Bean 。

  • 【最常用】byType - 它根据类型注入对象依赖项。如果属性的类型与 XML 文件中的一个 Bean 类型匹配,则匹配并装配属性。是@Autowired这个Spring注解默认的注入方式。

  • 构造函数(constructor) - 它通过调用类的构造函数来注入依赖项。它有大量的参数。

  • autodetect - 首先容器尝试通过构造函数使用 autowire 装配,如果不能,则尝试通过 byType 自动装配。

6、延迟加载

默认情况下,容器启动之后会将所有作用域为单例的 Bean 都创建好,但是有的业务场景我们并不需要它提前都创建好。此时,我们可以在Bean 中设置 lzay-init = "true"

  • 这样,当容器启动之后,作用域为单例的 Bean ,就不在创建。

  • 而是在获得该 Bean 时,才真正在创建加载。

7、单例 Bean 是否线程安全

Spring 框架并没有对单例 Bean 进行任何多线程的封装处理。

  • 关于单例 Bean 的线程安全和并发问题,需要开发者自行去搞定。

  • 并且,单例的线程安全问题,也不是 Spring 应该去关心的。Spring 应该做的是,提供根据配置,创建单例 Bean 或多例 Bean 的功能。

当然,但实际上,大部分的 Spring Bean 并没有可变的状态(比如Serview 类和 DAO 类),所以在某种程度上说 Spring 的单例 Bean 是线程安全的。

如果你的 Bean 有多种状态的话,就需要自行保证线程安全。最浅显的解决办法,就是将多态 Bean 的作用域( Scope )由 Singleton 变更为 Prototype 。

8、Spring解决循环依赖

参照:Spring之循环依赖与解决方案

 

参照:芋道源码

posted @ 2019-05-26 09:53  白晨冬阳  阅读(231)  评论(0编辑  收藏  举报