Spring IOC

Spring IOC

Spring Framework 中文文档:https://www.docs4dev.com/docs/zh/spring-framework/5.1.3.RELEASE/reference/core.html

1.1一些定义

Spring 的控制反转(IoC),也称为依赖项注入(DI)

Spring容器指Spring的IoC容器。以下将Spring官方文档中的configuration metadata理解为“配置”一词。

org.springframework.beansorg.springframework.context软件包是 Spring Framework 的 IoC 容器的基础。

BeanFactory接口提供了一种高级配置机制,能够 Management 任何类型的对象。 ApplicationContextBeanFactory的子接口。

Bean: 在 Spring 中,构成应用程序主干并由 Spring IoC 容器 Management 的对象称为 bean

1.2 容器

容器org.springframework.context.ApplicationContext接口代表 Spring IoC 容器,并负责实例化,配置和组装 Bean,容器通过读取配置元数据来获取有关要实例化,配置和组装哪些对象的指令。配置元数据以 XML,Java 注解或 Java 代码表示。它使您能够表达组成应用程序的对象以及这些对象之间的丰富相互依赖关系。

Spring提供了两种类型的IoC容器:

  • BeanFactory接口: IoC容器的基础实现,是Spring框架的基础设施。
  • ApplicationContext接口:提供了更多的高级特性,实际是 BeanFactory 的子接口。ApplicationContext面向使用 Spring 框架的开发者,几乎所有的应用场合都可以直接使用 ApplicationContext 而非底层的 BeanFactory

下图显示了 Spring 的工作原理的高级视图。您的应用程序类与配置元数据结合在一起,以便在创建和初始化ApplicationContext之后,您将具有完全配置且可执行的系统或应用程序。

image-20230220110809121

1.2.1配置元数据
  • 基于 XML 的配置元数据
  • Annotation-based configuration:Spring 2.5 引入了对基于注解的配置元数据的支持。
  • Java-based configuration:从 Spring 3.0 开始,Spring JavaConfig 项目提供的许多功能成为了核心 Spring Framework 的一部分。
1.2.2. 实例化容器
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
1.2.3. 使用容器

通过使用方法T getBean(String name, Class<T> requiredType),您可以检索 bean 的实例。

ApplicationContext允许您读取 bean 定义并访问它们,如以下示例所示

// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");

// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);

// use configured instance
List<String> userList = service.getUsernameList();

1.3. Bean 总览

Spring IoC 容器 Management 一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>定义的形式)。

在容器本身内,这些 bean 定义表示为BeanDefinition对象,其中包含(除其他信息外)以下元数据:

  • 包限定的类名:通常,定义了 Bean 的实际实现类。
  • Bean 行为配置元素,用于声明 Bean 在容器中的行为(作用域,生命周期回调等)。
  • 引用其他 bean 进行其工作所需的 bean。这些引用也称为协作者或依赖项。
  • 要在新创建的对象中设置的其他配置设置,例如,池的大小限制或在 Management 连接池的 bean 中使用的连接数。

该元数据转换为构成每个 bean 定义的一组属性。下表描述了这些属性:

PropertyExplained in…
ClassInstantiating Beans
NameNaming Beans
ScopeBean Scopes
Constructor argumentsDependency Injection
PropertiesDependency Injection
Autowiring modeAutowiring Collaborators
延迟初始化模式(懒加载)Lazy-initialized Beans
Initialization methodInitialization Callbacks
Destruction methodDestruction Callbacks
1.3.1. 命名 bean

bean 名称以小写字母开头,并从那里用驼峰式大小写。示例:accountManageraccountServiceuserDaologinController等。

如果使用 Java 配置,则@Bean注解可用于提供别名。

如果Bean在配置的时候没有给予其名称,Spring默认按照如下规则给其命名:

  1. 以类名为名,并将首字母小写;
  2. 如果类名的前两个字母均为大写,将会保留原始的类名。
1.3.2. 实例化 bean

Bean 定义实质上是创建一个或多个对象的方法。当被询问时,容器将查看命名 bean 的配方,并使用该 bean 定义封装的配置元数据来创建(或获取)实际对象。

三种方式:

1.用构造函数实例化
<bean id="exampleBean" class="examples.ExampleBean"/>

<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
2.使用静态工厂方法实例化
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService {
    private static ClientService clientService = new ClientService();
    private ClientService() {}

    public static ClientService createInstance() {
        return clientService;
    }
}
3.使用实例工厂方法实例化
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
    <!-- inject any dependencies required by this locator bean -->
</bean>

<!-- the bean to be created via the factory bean -->
<bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>

public class DefaultServiceLocator {

    private static ClientService clientService = new ClientServiceImpl();

    public ClientService createClientServiceInstance() {
        return clientService;
    }
}

1.4. Dependencies

在编写程序时,通过控制反转,把对象的创建权交给了Spring,但是代码中不可能出现没有依赖的情况,而IoC解耦只是降低他们的依赖关系,但不会消除,例如:业务层仍会调用持久层的方法。

这种业务层和持久层的依赖关系,在使用Spring之后,就让Spring来维护了,简单地说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取,这些便由所谓的依赖注入去实现。

依赖注入(Dependency Injection, DI),是Spring框架核心IoC的具体实现,依赖注入表示组件之间的依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。

eg:

对于两个类UserService和UserDao,它们都在Spring容器中,前者的代码定义中需要使用到后者。以前的做法是在容器外部获得UserService实例和UserDao实例,然后在程序中进行结合;然而,最终程序直接使用的是UserService,所以更好的方式是:在Spring容器中,将UserDao设置到UserSerivice内部。

image-20230220140759475

1.4.1. 依赖注入

DI 存在三个主要变体:

1.基于构造函数的依赖关系注入
package examples;
public class ExampleBean {
    // Number of years to calculate the Ultimate Answer
    private final int years;
    
    // The Answer to Life, the Universe, and Everything
    private final String ultimateAnswer;
    
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}
  • 默认情况下,构造方法的形参匹配是通过参数的类型来进行的。

    • 在无歧义的情况下,形参按照在构造方法中出现的顺序依次进行赋值以初始化Bean。

    • 此时也可以选择显示地指定参数类型来进行匹配,但没有这个必要。

      <bean id="exampleBean" class="examples.ExampleBean">
          <constructor-arg type="int" value="7500000"/>
          <constructor-arg type="java.lang.String" value="42"/>
      </bean>
      
  • 当有歧义时,可通过形参的索引顺序来匹配:

    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg index="0" value="7500000"/>
        <constructor-arg index="1" value="42"/>
    </bean>
    
  • 也可以根据形参的名称进行匹配:

    <bean id="exampleBean" class="examples.ExampleBean">
        <constructor-arg name="years" value="7500000"/>
        <constructor-arg name="ultimateAnswer" value="42"/>
    </bean>
    
2.基于 Setter 的依赖项注入
public class SimpleMovieLister {
    // the SimpleMovieLister has a dependency on the MovieFinder
    private MovieFinder movieFinder;
    
    // a setter method so that the Spring container can inject a MovieFinder
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    
    // business logic that actually uses the injected MovieFinder is omitted...
}

例子:

image-20230221092155432

Spring更推荐使用构造方法注入,同时不建议使用字段注入

3.基于构造函数或基于 setter 的 DI?

尽管Spring既支持构造器注入和setter方法注入,但Spring更推荐使用构造函数注入:

  • The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null.
  • Furthermore, constructor-injected components are always returned to the client (calling) code in a fully initialized state.
  • As a side note, a large number of constructor arguments is a bad code smell, implying that the class likely has too many responsibilities and should be refactored to better address proper separation of concerns.

setter方法注入应该作为一种备选项:

  • Setter injection should primarily only be used for optional dependencies that can be assigned reasonable default values within the class. Otherwise, not-null checks must be performed everywhere the code uses the dependency.

  • One benefit of setter injection is that setter methods make objects of that class amenable to reconfiguration or re-injection later.

    Management through JMX MBeans is therefore a compelling use case for setter injection.

4.循环依赖Circular dependencies

如果主要使用构造函数注入,则可能会创建无法解决的循环依赖方案。

例如:A 类通过构造函数注入需要 B 类的实例,而 B 类通过构造函数注入需要 A 类的实例。如果为将类 A 和 B 相互注入而配置了 bean,则 Spring IoC 容器会在运行时检测到此循环引用,并抛出BeanCurrentlyInCreationException

解决方案——构造器注入不支持循环依赖,setter注入支持循环依赖:

4.自动织入

在XML中,可以通过<bean/>标签的autowire属性来配置自动织入,其有4个模式:

ModeExplanation
no(默认)无自动装配。 Bean 引用必须由ref元素定义。对于大型部署,建议不要更改默认设置,因为明确指定协作者可以提供更好的控制和清晰度。在某种程度上,它记录了系统的结构。
byName按属性名称自动布线。 Spring 寻找与需要自动装配的属性同名的 bean。例如,如果一个 bean 定义被设置为按名称自动装配,并且包含一个master属性(即,它具有setMaster(..)方法),那么 Spring 将查找一个名为master的 bean 定义并使用它来设置属性。
byType如果容器中恰好存在一个该属性类型的 bean,则使该属性自动装配。如果存在多个错误,则会引发致命异常,这表明您可能不对该 bean 使用byType自动装配。如果没有匹配的 bean,则什么也不会发生(未设置该属性)。
constructor类似于byType,但适用于构造函数参数。如果容器中不存在构造函数参数类型的一个 bean,则将引发致命错误。

注意:

  • byNamebyType自动织入时,相应的字段必须有setter方法,否则无法注入。
  • Explicit dependencies in property and constructor-arg settings always override autowiring.
  • You cannot autowire simple properties such as primitives, Strings, and Classes (and arrays of such simple properties). This limitation is by-design.

如果不希望某些bean参与到自动织入的体系,即不希望它们可以被自动织入到其他类:

  • 可以通过设置标签的autowire-candidate属性为false来实现。不过,这个属性只对byType类型织入的模式有效。
  • You can also limit autowire candidates based on pattern-matching against bean names.
5.方法注入(用来解决单例对多例的问题)

在大多数应用场景中,容器中的大多数 bean 是singletons。当单例 Bean 需要与另一个单例 Bean 协作或非单例 Bean 需要与另一个非单例 Bean 协作时,通常可以通过将一个 Bean 定义为另一个 Bean 的属性来处理依赖性。

但是在 bean A是singleton和bean B是非singleton时就可能出现问题。因为bean B为非singleton , 那么bean B是希望他的使用者在一些情况下创建一个新实例,而bean A使用字段把bean B的一个实例缓存了下来,每次都使用的是同一个实例。

解决方案:

一个解决方案是放弃某些控制反转。您可以通过实现ApplicationContextAware接口来使 bean A 知道容器,并在每次 bean A 需要它时对容器进行 getBean(“ B”)调用询问(通常是新的)bean B 实例。以下示例显示了此方法:

// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;

// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

public class CommandManager implements ApplicationContextAware {

    private ApplicationContext applicationContext;

    public Object process(Map commandState) {
        // grab a new instance of the appropriate Command
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    protected Command createCommand() {
        // notice the Spring API dependency!
        return this.applicationContext.getBean("command", Command.class);
    }

    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
}

然而这种方式并不推荐,因为这样的代码就和Spring API强绑定了。相反,Method Injection, a somewhat advanced feature of the Spring IoC container, lets you handle this use case cleanly.

Spring提供两种机制来注入方法,分别是 Lookup Method Injection 和Arbitrary method replacement。

  • Lookup Method Injection 只提供返回值注入
  • Arbitrary method replacement 可以替换任意方法来达到注入
1.Lookup Method Injection

查找方法注入是容器重写容器 Management 的 Bean 上的方法并返回容器中另一个命名 Bean 的查找结果的能力。查找通常涉及prototype bean.。Spring 框架通过使用从 CGLIB 库生成字节码来动态生成覆盖该方法的子类来实现此方法注入。

基于此原理,Lookup Method Injection有如下限制:

  • 为了使此动态子类起作用,Spring bean 容器子类的类也不能为final,并且要覆盖的方法也不能为final
  • 对具有abstract方法的类进行单元测试需要您自己对该类进行子类化,并提供abstract方法的存根实现。
  • 组件扫描也需要具体方法,这需要具体的类别。
  • 另一个关键限制是,查找方法不适用于工厂方法,尤其不适用于配置类中的@Bean方法,因为在这种情况下,容器不负责创建实例,因此无法创建运行时生成的子类在飞行中。

对于上述场景,Lookup Method Injection的解决方案是:

package fiona.apple;

// no more Spring imports!

public abstract class CommandManager {

    public Object process(Object commandState) {
        // grab a new instance of the appropriate Command interface
        Command command = createCommand();
        // set the state on the (hopefully brand new) Command instance
        command.setState(commandState);
        return command.execute();
    }

    // okay... but where is the implementation of this method?
    protected abstract Command createCommand();
}
  • XML配置方式
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" class="fiona.apple.AsyncCommand" scope="prototype">
    <!-- inject dependencies here as required -->
</bean>

<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
    <lookup-method name="createCommand" bean="myCommand"/>
</bean>
  • 注解配置方式:@Lookup
public abstract class CommandManager {

    public Object process(Object commandState) {
        Command command = createCommand();
        command.setState(commandState);
        return command.execute();
    }

    @Lookup("myCommand")
    // 直接使用 @Lookup 也可以
    protected abstract Command createCommand();
}

eg:

@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
@Component
public class BeanA {
    private final long seed;
    public BeanA(){
        this.seed = new Random().nextLong();
    }

    public String buildSomeThingRandomly() {
        Random random = new Random(seed);
        int randomInt = random.nextInt();
        return String.format("Random Number %s with seed %s",randomInt,seed);
    }
}
@Component
public class BeanB {
    @Autowired
    private BeanA beanA;

    public BeanA getBeanA() {
        return beanA;
    }

    @Scheduled(cron = "* * * * * *")
    public void printSomeThingRandomly() {
        String someThingRandomly = this.getBeanA().buildSomeThingRandomly();
        System.out.println(someThingRandomly);
    }
}
// 输出
Random Number 1586210808 with seed 2179622416075008138
Random Number 1586210808 with seed 2179622416075008138
Random Number 1586210808 with seed 2179622416075008138
Random Number 1586210808 with seed 2179622416075008138
Random Number 1586210808 with seed 2179622416075008138
@Component
public abstract class BeanC {

    @Lookup
    public abstract BeanA getBeanA();


    @Scheduled(cron = "* * * * * *")
    public void printSomeThingRandomly() {
        String someThingRandomly = this.getBeanA().buildSomeThingRandomly();
        System.out.println(someThingRandomly);
    }
}
// 输出
Random Number -1261524243 with seed -4945937026954008486
Random Number -1554757235 with seed -8156080203187105189
Random Number 2054829974 with seed 5764439875306040691
Random Number 495717651 with seed -1359344661393749186
Random Number -1032269692 with seed -2877575698638354616
2.Arbitrary method replacement

在XML配置中,可以通过replaced-method标签来替换一个已经存在的方法实现。

例如,现在想替换一个类中的computeValue方法:

public class MyValueCalculator {

    public String computeValue(String input) {
        // some real code...
    }

    // some other methods...
}

A class that implements the org.springframework.beans.factory.support.MethodReplacer interface provides the new method definition, as the following example shows:

/**
 * meant to be used to override the existing computeValue(String)
 * implementation in MyValueCalculator
 */
public class ReplacementComputeValue implements MethodReplacer {

    /**
     * 重新实现某个Bean的某个方法。
     * @param o 某Bean对象
     * @param m 被替换的方法
     * @param args 方法的实参列表
     */
    public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
        // get the input value, work with it, and return a computed result
        String input = (String) args[0];
        ...
        return ...;
    }
}

用于部署原始类并指定方法覆盖的 Bean 定义类似于以下示例:

<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
    <!-- arbitrary method replacement -->
    <replaced-method name="computeValue" replacer="replacementComputeValue">
        <arg-type>String</arg-type>
    </replaced-method>
</bean>

<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>

1.5 Bean Scope

The Spring Framework supports six scopes, four of which are available only if you use a web-aware ApplicationContext. You can also create a custom scope.

ScopeDescription
singleton(默认)将每个 Spring IoC 容器的单个 bean 定义范围限定为单个对象实例。
prototype将单个 bean 定义的作用域限定为任意数量的对象实例。
request将单个 bean 定义的范围限定为单个 HTTP 请求的生命周期。也就是说,每个 HTTP 请求都有一个在单个 bean 定义后面创建的 bean 实例。仅在可感知网络的 Spring ApplicationContext中有效。
session将单个 bean 定义的范围限定为 HTTP Session的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
application将单个 bean 定义的范围限定为ServletContext的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
websocket将单个 bean 定义的范围限定为WebSocket的生命周期。仅在可感知网络的 Spring ApplicationContext上下文中有效。
1.5.1. singleton

仅 Management 一个 singleton bean 的一个共享实例,并且所有对具有 ID 或与该 bean 定义相匹配的 ID 的 bean 的请求都会导致该特定的 bean 实例由 Spring 容器返回。

换句话说,当您定义一个 bean 定义并且其作用域为单例时,Spring IoC 容器将为该 bean 定义所定义的对象创建一个实例。**该单个实例存储在此类单例 bean 的高速缓存中,并且对该命名 bean 的所有后续请求和引用都返回该高速缓存的对象。**下图显示了单例作用域如何工作:

image-20230220155851692

Singleton 范围是 Spring 中的默认范围。要将 bean 定义为 XML 中的单例,可以定义 bean,如以下示例所示:

<bean id="accountService" class="com.something.DefaultAccountService"/>

<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2. prototype

每次对特定 bean 提出请求时,bean 部署的非单一原型范围都会导致创建一个新 bean 实例。也就是说,将 Bean 注入到另一个 Bean 中,或者您可以通过容器上的getBean()方法调用来请求它。通常,应将原型作用域用于所有有状态 Bean,将单例作用域用于 StatelessBean。

下图说明了 Spring 原型范围:

image-20230220160158048

以下示例将 bean 定义为 XML 原型:

<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/> 
1.5.3. 具有原型 Bean 依赖关系的 Singleton Bean

单例的Bean中注入的prototype-bean实例始终是同一个对象,不会随着调用的重复而重新获取新的prototype-bean实例。如果你需要每次获取一个全新的prototype-bean实例,参见Method Injection即可。

1.5.4.Request, Session, Application, and WebSocket Scopes

暂略。

1.5.5.使用自定义范围

To integrate your custom scopes into the Spring container, you need to implement the org.springframework.beans.factory.config.Scope interface. The Scope interface has four methods to get objects from the scope, remove them from the scope, and let them be destroyed.

public interface Scope {
    Object get(String var1, ObjectFactory<?> var2);

    @Nullable
    Object remove(String var1);

    void registerDestructionCallback(String var1, Runnable var2);

    @Nullable
    Object resolveContextualObject(String var1);

    @Nullable
    String getConversationId();
}

在编写并测试一个或多个自定义Scope实现之后,您需要使 Spring 容器意识到您的新作用域。以下方法是在 Spring 容器中注册新的Scope的中心方法:

void registerScope(String scopeName, Scope scope);

此方法在ConfigurableBeanFactory接口上声明,该接口可通过 Spring 附带的大多数具体ApplicationContext实现上的BeanFactory属性使用。

eg: 如平台的TenantScope

image-20230221091209521

1.6 自定义bean的性质

Spring 框架提供了许多接口,可用于自定义 Bean 的性质。本节将它们分组如下:

1.6.1. bean的生命周期回调

要与容器对 bean 生命周期的 Management 进行交互,可以实现 Spring InitializingBeanDisposableBean接口。容器对前者调用afterPropertiesSet(),对后者调用destroy(),以使 Bean 在初始化和销毁 Bean 时执行某些操作。

Tip

通常,在现代 Spring 应用程序中,JSR-250 @PostConstruct@PreDestroy注解 被认为是接收生命周期回调的最佳实践。使用这些注解意味着您的 bean 没有耦合到特定于 Spring 的接口。有关详细信息,请参见使用@PostConstruct 和@PreDestroy

如果您不想使用 JSR-250 注解,但仍然想删除耦合,请考虑使用init-methoddestroy-method对象定义元数据。

1.单个Bean的控制

实现方式

使用“回调”参与到Bean的生命周期的方式有3种:

  • 1.使用JSR-250的@PostConstruct@PreDestroy 注解(强烈推荐)

  • 2.Spring配置中配置Bean的init-methoddestroy-method(推荐)

    <bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/>
    
    public class ExampleBean {
    
        public void init() {
            // do some initialization work
        }
    }
    

    使用XML配置时,顶级的<beans/>标签中可以通过default-init-methoddefault-destroy-method属性配置默认的Bean初始化和销毁方法,详情不在此阐述。

  • 3.实现Spring的InitializingBeanDisposableBean接口(不推荐,与SpringAPI强耦合了):

    package org.springframework.beans.factory;
    
    public interface InitializingBean {
        void afterPropertiesSet() throws Exception;
    }
    
    package org.springframework.beans.factory;
    
    public interface DisposableBean {
        void destroy() throws Exception;
    }
    

如上所述,配置生命周期回调的方式有三种,如果对同一个Bean同时配置了这三种方式,其执行顺序如下:

(方便记忆:如果对同一个bean同时实现了这三种方式,其顺序是:注解 → 接口 → xml

  • 初始化
    • Methods annotated with @PostConstruct
    • afterPropertiesSet() as defined by the InitializingBean callback interface
    • A custom configured init() method
  • 销毁(同初始化)
    • Methods annotated with @PreDestroy
    • destroy() as defined by the DisposableBean callback interface
    • A custom configured destroy() method

Spring 容器保证在为 bean 提供所有依赖项后立即调用配置的初始化回调。因此,在原始 bean 引用上调用了初始化回调,这意味着 AOP 拦截器等尚未应用于 bean。首先完全创建目标 bean,然后应用带有其拦截器链的 AOP 代理(例如)。如果目标 Bean 和代理分别定义,则您的代码甚至可以绕过代理与原始目标 Bean 进行交互。因此,将拦截器应用于init方法将是不一致的,因为这样做会将目标 Bean 的生命周期耦合到其代理或拦截器,并在代码直接与原始目标 Bean 交互时留下奇怪的语义。

2 容器级别的控制:Lifecycle接口
1.Lifecycle接口

Lifecycle 接口属于面向生命周期的一种通用的基本接口,其中定义的方法是当任何一个Java对象想要表达生命周期这一概念时都会需要的方法,如表示启动的start()、表示停止的stop()

package org.springframework.context;

/**
 * `start/stop` 方法在执行前,会调用 `isRunning` 方法返回的状态来决定是否执行 `start/stop` 方法。
 */
public interface Lifecycle {
    void start();

    void stop();

    boolean isRunning();
}

凡是被 Spring 管理的对象都可以选择实现 Lifecycle 接口。当 Spring 容器 ApplicationContext 接收到 start/stop 信号时,Spring 会将该信号传递给容器内所有实现了 Lifecycle 接口的对象,从而触发相应的生命周期方法。Spring 的这一机制是委托给 LifecycleProcessor 来实现的,其源码如下:

package org.springframework.context;

public interface LifecycleProcessor extends Lifecycle {
    void onRefresh();

    void onClose();
}

注意:

•Spring 使用接口Lifecycle 来让 bean 参与到 Spring 容器的启动/停止生命周期中。

•当 Spring 容器 ApplicationContext 接收到 start/stop 信号时,Spring会将该信号传递给容器内所有实现了 Lifecycle 接口的对象,从而触发相应的生命周期方法。

•Spring 容器在启动与销毁时,不会向容器中的 Lifeccyle bean 自动传递 start/stop 信号,需要手动调用容器的 start/stop 方法来传递;如果需要自动触发,可以让 bean 实现 SmartLifecycle 接口。

2.SmartLifecycle接口
  • The isAutoStartup() return value indicates whether this object should be started at the time of a context refresh.
  • The callback-accepting stop(Runnable) method is useful for objects that have an asynchronous shutdown process. Any implementation of this interface must invoke the callback’s run() method upon shutdown completion to avoid unnecessary delays in the overall ApplicationContext shutdown.
package org.springframework.context;

public interface SmartLifecycle extends Lifecycle, Phased {
    int DEFAULT_PHASE = 2147483647;

    default boolean isAutoStartup() {
        return true;
    }

    default void stop(Runnable callback) {
        this.stop();
        callback.run();
    }

    default int getPhase() {
        return 2147483647;
    }
}
SmartLifecycle实现类的执行顺序

当Bean之间存在依赖关系时,它们触发startupshutdown方法的时机需要明确:如果A depends-on B,则是B先于A触发startup,同时B滞后于A触发shutdown

然而,有时候Bean之间的依赖关系不是那么明确,此时可以通过SmartLifecycle接口的getPhase()方法(继承自Phased接口)来定义一个绝对的顺序:

package org.springframework.context;

public interface Phased {
    int getPhase();
}

对于startup方法,phase最小的对象最早执行;对于shutdown方法则正好相反,phase最小的对象最晚执行。

实际上,对于未实现SmartLifecycle接口的Lifecycle对象,其对应的phase值默认被设定为0。

3.两种生命周期回调的顺序
SmartLifecycle与InitializingBean、DisposableBean的相对执行顺序

对于一个同时实现了SmartLifecycleInitializingBeanDisposableBean的bean而言,我们知道,在bean完成初始化后会执行InitializingBean的回调,而容器初始化完成后会执行SmartLifecyclestart回调,那么它们的顺序是如何的呢?

这实际上是bean初始化完成与容器初始化完成的标准定义问题。对于spring容器,其会在其中所有的bean初始化完成之后才认为自己步入了初始化完成的阶段,因此,一个 bean 的 InitializingBean 回调会早于其SmartLifecyclestart回调;而对于销毁方法,过程正好相反。顺序如下所示:

smart postConstruct
smart start
smart stop
smart preDestroy

eg:

@Component
public class SmartLifecycleTest implements SmartLifecycle {
    private boolean running = false;
    @Override
    public void start() {
        System.out.println("smart start");
        this.running =true;
    }

    @Override
    public void stop() {
        System.out.println("smart stop");
        this.running = false;
    }

    @Override
    public boolean isRunning() {
        return this.running;
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("postConstruct");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("preDestroy");
    }
}

image-20230221095459364

1.7 xxxAware接口

  • Spring 框架的一个设计理念是“使用时对用户无感”,即用户只通过配置与Spring交互,业务系统API与Spring框架不做绑定,从业务系统的视角而言根本不知道有Spring框架的存在。

  • 但有时候我们可能不得不主动利用Spring的一些特性,于是Spring提供了一系列Aware的存在。

NameInjected DependencyExplained in…
ApplicationContextAware声明ApplicationContextApplicationContextAware 和 BeanNameAware
ApplicationEventPublisherAware附件ApplicationContext的事件发布者。ApplicationContext 的其他功能
BeanClassLoaderAware类加载器,用于加载 Bean 类。Instantiating Beans
BeanFactoryAware声明BeanFactoryApplicationContextAware 和 BeanNameAware
BeanNameAware声明 bean 的名称。ApplicationContextAware 和 BeanNameAware
BootstrapContextAware运行容器的资源适配器BootstrapContext。通常仅在支持 JCA 的ApplicationContext实例中可用。JCA CCI
LoadTimeWeaverAware定义的编织器,用于在加载时处理类定义。在 Spring Framework 中使用 AspectJ 进行加载时编织
MessageSourceAware解决消息的已配置策略(支持参数化和国际化)。ApplicationContext 的其他功能
NotificationPublisherAwareSpring JMX 通知发布者。Notifications
ResourceLoaderAware配置的加载程序,用于对资源的低级别访问。Resources
ServletConfigAware当前容器运行的ServletConfig。仅在可感知网络的 Spring ApplicationContext中有效。Spring MVC
ServletContextAware当前容器运行的ServletContext。仅在可感知网络的 Spring ApplicationContext中有效。Spring MVC
1. ApplicationContextAware

如果一个Bean(注意前提它得是一个Spring的Bean)实现了org.springframework.context.ApplicationContextAware接口,该Bean的实例会被提供一个指向ApplicationContext的引用。因此,一个Bean可以借此操作创造它们的(母体)ApplicationContext

ApplicationContextAware接口的定义如下:

public interface ApplicationContextAware extends Aware {
    void setApplicationContext(ApplicationContext var1) throws BeansException;
}
2. BeanNameAware

获取Bean在IoC容器中的名字。

public interface BeanNameAware {
    void setBeanName(String name) throws BeansException;
}

1.8 IoC容器的扩展接口

通常情况下,程序开发者不需要去继承ApplicationContext接口的实现类,而可以通过Spring提供的特殊集成接口来对 IoC 容器进行扩展。

  • 1.BeanFactoryPostProcessor: 操作配置
  • 2.BeanPostProcessor:操作bean
  • 3.FactoryBean: 定制bean的实例化逻辑
1.BeanFactoryPostProcessor: 操作配置

BeanFactoryPostProcessor 的语义与 BeanPostProcessor 类似,区别在于 BeanFactoryPostProcessor 操作的是 Bean 的配置数据。也就是说,在Spring的 IoC 容器实例化所有 Bean 对象(不包含BeanFactoryPostProcessor 对象,因为显然它们需要事先被实例化)之前,会让 BeanFactoryPostProcessor 读取配置数据,并允许对配置进行改变,从而改变 Bean 的一些特性。

BeanFactoryPostProcessor 接口的定义可以看出,尽管在技术上一个 BeanFactoryPostProcessor 也是可以直接操作 Bean 对象本身的——使用 BeanFactory.getBean() 即可获取相应的 bean ——然而并不推荐这样做!因为这实际上造成了 Bean 被提前创建,与标准的 IoC 容器赋予的生命周期相脱离,很可能导致一些负面效果。

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;

@FunctionalInterface
public interface BeanFactoryPostProcessor {
    void postProcessBeanFactory(ConfigurableListableBeanFactory var1) throws BeansException;
}
2.BeanPostProcessor: Customizing Beans(自定义beans)

BeanPostProcessor 对象操作的是被实例化了 Bean 对象,也就是说,首先由 Spring IoC 容器创建 Bean 实例,然后BeanPostProcessor 开始干它们的活儿。

BeanPostProcessor 接口包含两个回调方法,当在 Spring 容器中注册了一个 BeanPostProcessor 后,每当Spring 容器创建一个 Bean 对象,注册的 BeanPostProcessor 对象在 Bean 对象的初始化方法 (container initialization methods, such as InitializingBean.afterPropertiesSet() or any declared init method) 执行前后都会收到一个回调:

package org.springframework.beans.factory.config;

import org.springframework.beans.BeansException;
import org.springframework.lang.Nullable;

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

一些 Spring AOP机制的底层类就是基于** BeanPostProcessor 的;@Autowired 注解的生效原理也基于此

3. FactoryBean: Customizing Instantiation Logic(自定义实例化逻辑)

注意这可不是BeanFactory

package org.springframework.beans.factory;

import org.springframework.lang.Nullable;

public interface FactoryBean<T> {
    String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";

    /**
     * Returns an instance of the object this factory creates. The instance can possibly be shared, depending on whether this factory returns singletons or prototypes.
     */
    @Nullable
    T getObject() throws Exception;

    /**
     * Returns the object type returned by the getObject() method or null if the type is not known in advance.
     */
    @Nullable
    Class<?> getObjectType();

    /**
     * Returns true if this FactoryBean returns singletons or false otherwise.
     */
    default boolean isSingleton() {
        return true;
    }
}

FactoryBean 接口表示一个工厂 Bean,即它可以生成某一个类型的 Bean 实例,FactoryBean 最大的作用即是可以让我们自定义 Bean 的创建过程。

  • 如果一些情况下使用XML/注解等配置的手段来创建 Bean 显得太复杂了,那么可以选择使用 FactoryBean 来通过代码定义创建 Bean 的过程,只要最后将 FactoryBean 纳入 Spring 容器即可(即FactoryBean本身需要是一个容器中的 Bean)。
  • 如平台的 com.glodon.coral.service.provider.swagger.DocketFactoryBean
  • 再如 mybatis的一个mapper 接口能生成一个spring的bean,也利用了一个MapperFactoryBean
  • Spring框架本身也定义和使用了大量的 FactoryBean,差不多有50多个。
  • 当你需要从容器中获取一个FactoryBean本身,而非该FactoryBean创建的 Bean 时,只需要在调用ApplicationContextgetBean() 方法时,传入(由该FactoryBean创建的)Bean的id然后加上一个&前缀。

1.9 Bean的生命周期过程图解

https://www.bilibili.com/video/BV1584y1r7n6/?spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=10e44ae36004503f76479031cb3e0ca1

image-20230221105216394

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值