Spring IoC容器

Spring IoC容器简介

IoC也称为依赖注入(DI)。它是一个过程,其中对象仅通过构造函数参数、工厂方法的参数或从工厂方法构造或返回对象实例之后在对象实例上设置的属性来定义它们的依赖性(即,它们与之一起工作的其他对象)。然后,容器在创建bean时注入这些依赖项。这个过程本质上是bean本身的逆过程(因此称为控制反转),它通过直接构造类或诸如Service Locator模式之类的机制来控制依赖项的实例化或位置。

org.springframework.beansorg.springframework.context包是Spring Framework的IoC容器的基础。BeanFactory接口提供了能够管理任何类型的对象的高级配置机制。ApplicationContext是BeanFactory的子接口。它补充了:

  1. 与Spring的AOP特性更容易集成
  2. 消息资源处理(用于国际化)
  3. 事件发布
  4. 应用程序层特定的上下文,例如WebApplicationContext,用于Web应用程序。

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

Spring提供了ApplicationContext接口的几种实现。在独立应用程序中,通常会创建一个ClassPathXmlApplicationContext或FileSystemXmlApplicationContext的实例。

下图显示了Spring如何工作的高级视图
在这里插入图片描述

依赖注入

基于构造函数的依赖注入

基于构造函数的DI由容器调用具有多个参数的构造函数来完成,每个参数表示一个依赖项。调用static具有特定参数的工厂方法来构造bean几乎是等效的,本讨论同样处理构造函数和static工厂方法的参数。以下示例显示了一个只能使用构造函数注入进行依赖注入的类:

public class SimpleMovieLister {

    private MovieFinder movieFinder;
    // 构造函数,以便Spring容器可以注入MovieFinder
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }   
    //...
}

请注意,这个类没有什么特别之处。它是一个POJO,它不依赖于容器特定的接口,基类或注解。

构造函数参数解析

使用参数的类型进行构造函数参数解析匹配。如果bean定义的构造函数参数中不存在潜在的歧义,则在bean定义中定义构造函数参数的顺序是在实例化bean时将这些参数提供给适当的构造函数的顺序。考虑以下类:

public class ThingOne {
    public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
        // ...
    }
}

假设ThingTwo和ThingThree类不是继承相关,则不存在潜在的歧义。因此,下面的配置工作正常,并且不需要在<constructor-arg/>元素中显式指定构造函数参数索引或类型。

<beans>
    <bean id="thingOne" class="ThingOne">
        <constructor-arg ref="thingTwo"/>
        <constructor-arg ref="thingThree"/>
    </bean>

    <bean id="thingTwo" class="ThingTwo"/>

    <bean id="thingThree" class="ThingThree"/>
</beans>

构造函数参数类型匹配
当引用另一个bean时,类型是已知的,并且可以进行匹配(与前面的示例一样)。当使用简单类型时,例如 <value>true</value>,Spring无法确定值的类型,因此无法在没有帮助的情况下按类型进行匹配。考虑以下类:

package examples;
public class ExampleBean {
    private int years;
    private String ultimateAnswer;
    
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

在前面的场景中,如果使用type属性显式指定构造函数参数的类型,则容器可以使用与简单类型匹配的类型。如下例所示:

<bean id="exampleBean" class="examples.ExampleBean">
    <constructor-arg type="int" value="7500000"/>
    <constructor-arg type="java.lang.String" value="42"/>
</bean>

构造函数参数索引
您可以使用该index属性显式指定构造函数参数的索引,如以下示例所示:

<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>

为了使这项工作开箱即用,必须在启用调试标志的情况下编译代码,以便Spring可以从构造函数中查找参数名称。如果您不能或不想使用debug标志编译代码,则可以使用 @ConstructorProperties JDK注解显式命名构造函数参数。然后,示例类必须如下所示:

package examples;
public class ExampleBean {

    //忽略属性
    
    @ConstructorProperties({"years", "ultimateAnswer"})
    public ExampleBean(int years, String ultimateAnswer) {
        this.years = years;
        this.ultimateAnswer = ultimateAnswer;
    }
}

基于Setter的依赖注入

在调用无参数构造函数或无参数static工厂方法来实例化bean之后,基于setter的DI由bean上的容器调用setter方法完成。

以下示例显示了一个只能通过使用纯setter注入进行依赖注入的类。它不依赖于容器特定的接口、基类或注解。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

ApplicationContext支持它所管理的bean基于构造器或基于setter的DI。在已经通过构造函数方法注入了一些依赖项之后,它还支持基于setter的DI。然而,大多数Spring用户不直接使用这些类(即编程),而是使用XML bean定义、注解的组件(即,用@组件、@Controller等来注解的类),或基于Java的@配置类中的“bean”方法。然后将这些类转换为实例,并用于加载整个Spring IoC容器中。

基于构造函数 or 基于setter的DI?

由于您可以混合基于构造函数和基于setter的DI,因此将构造函数用于强制依赖项,将setter方法用于可选依赖项的配置方法是一个很好的经验法则。请注意,在setter方法上使用@Required注解可用于使属性成为必需的依赖项。

Spring团队通常提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保不需要所需的依赖项null。此外,构造函数注入的组件总是以完全初始化的状态返回到客户端(调用)代码。

Setter注入应主要仅用于可在类中指定合理默认值的可选依赖项。否则,必须在代码使用依赖项的任何位置执行非空检查。setter注入的一个好处是setter方法使该类的对象可以在以后重新配置或重新注入。因此,通过JMX MBean进行管理是二次注入的一个引人注目的用例。

使用depends-on依赖注入

如果bean是另一个bean的依赖项,那通常意味着将一个bean设置为另一个bean的属性。通常,您可以使用基于XML的配置元数据中的<ref/> 元素来完成此操作。但是,有时bean之间的依赖关系不那么直接。例如,需要触发类中的静态初始化程序,例如数据库驱动程序注册。depends-on在初始化使用此元素的bean之前,该属性可以显式强制初始化一个或多个bean(用逗号隔开)。以下示例使用该depends-on属性表示对单个bean的依赖关系:

<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
    <property name="manager" ref="manager" />
</bean>

<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />

使用lazy-init懒加载Bean

默认情况下,ApplicationContext实现会急切地创建和配置所有单例bean作为初始化过程的一部分。通常,这种预先实例化是可取的,因为配置或周围环境中的错误会立即被发现。如果不希望出现这种情况,可以通过将bean定义标记为延迟初始化来阻止单例bean的预实例化。延迟初始化的bean告诉IoC容器在第一次请求时创建bean实例,而不是在启动时。

在XML中,此行为由 素lazy-init上的属性控制<bean/>,如以下示例所示:

<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>

当前面的配置被ApplicationContext使用时,在ApplicationContext启动时并不立即预实例化lazy bean,而not.lazy bean将立即预实例化。

但是,当一个懒惰的初始化bean是一个非懒惰初始化的单个bean的依赖项时,Apple上下文会在启动时创建懒惰的初始化bean,因为它必须满足SuntLon的依赖关系。懒惰的初始化bean被注入到一个不懒惰初始化的别名bean中。

但是,当延迟初始化的bean是未进行延迟初始化的单例bean的依赖项时,ApplicationContext会在启动时创建延迟初始化的bean,因为它必须满足单例的依赖关系。惰性初始化的bean被注入到其他地方的单例bean中,而不是懒惰初始化的。

您还可以通过在<beans/>元素上使用default-lazy-init属性来控制容器级别的延迟初始化,以下示例显示:

<beans default-lazy-init="true">
    <!-- 不会预先实例化任何bean... -->
</beans>

自动装配

Spring容器可以自动连接协作bean之间的关系。通过检查ApplicationContext的内容,您可以让Spring自动为您的bean解析协作者(其他bean)。自动装配具有以下优点:

  1. 自动装配可以显着减少指定属性或构造函数参数的需要。

  2. 自动装配可以随着对象的发展更新配置。例如,如果需要向类添加依赖项,则可以自动满足该依赖项,而无需修改配置。因此,自动装配在开发期间尤其有用,而不会在代码库变得更稳定时否定切换到显式布线的选项。

使用基于XML的配置元数据时,可以使用元素的autowire属性为bean定义指定autowire模式的<bean/>。自动装配功能有四种模式:

模式说明
no(默认)无自动装配。Bean引用必须由ref元素定义。
byName按属性名称自动装配。Spring查找与需要自动装配的属性同名的bean。
byType如果容器中恰好存在属性类型的一个bean,则允许自动装配属性。如果存在多个则抛出一个异常,如果没有匹配的bean,则不会发生任何事情(没有设置属性)。
constructor类似byType但适用于构造函数参数。如果容器中没有构造函数参数类型的一个bean,则会抛出异常。

自动装配的局限和缺点

自动装配在项目中一致使用时效果最佳。如果一般不使用自动装配,那么开发人员使用它来连接一个或两个bean定义可能会让人感到困惑。

  1. property和constructor-arg设置中的显式依赖项总是重写自动连接。不能自动装配简单的属性,如Strings和Classes(以及这些简单属性的数组)。这种限制是间接设计的。
  2. 自动装配不如显式装配精确。
  3. Spring容器生成文档的工具可能无法获得连接信息。
  4. 容器中的多个bean定义可能与要自动装配的setter方法或构造函数参数指定的类型匹配。对于数组,集合或 Map实例,这不一定是个问题。但是,对于期望单个值的依赖关系,这种模糊性不是任意解决的。如果没有可用的唯一bean定义,则抛出异常。

在后一种情况下,您有几种选择:

  1. 放弃自动装配以支持显式装配。
  2. 通过将autowire-candidate属性设置为false,避免自动连接定义的bean。
  3. 通过将其<bean/>元素的primary属性设置为true,指定单个bean定义作为主候选。
  4. 如基于注解的容器配置中所述,使用基于注解的配置实现更细粒度的控制。

Bean范围(scope属性)

您不仅可以控制要插入到从特定bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean定义创建的对象的范围。这种方法是强大和灵活的,因为您可以通过配置来选择您创建的对象的范围。Bean可以被定义为部署在多个范围中的一个。Spring框架支持六个作用域,其中四个作用域只有在使用Web感知的应用上下文时才可用。还可以创建自定义范围。

范围描述
singleton(默认)将单个bean定义范围限定为每个Spring IoC容器的单个对象实例。
prototype将单个bean定义范围限定为任意数量的对象实例。
request将单个bean定义范围限定为单个HTTP请求的生命周期。也就是说,每个HTTP请求都有自己的bean实例,它是在单个bean定义的后面创建的。仅在Web感知Spring ApplicationContext上下文中有效。
session将单个bean定义限定到HTTP会话的生命周期。仅在感知Web的Spring ApplicationContext上下文中有效。
application将单个bean定义限定到ServletContext的生命周期。仅在感知Web的Spring ApplicationContext上下文中有效。
websocket将单个bean定义限定到WebSocket的生命周期。仅在感知Web的Spring ApplicationContext上下文中有效。

只有在使用Web感知的Spring ApplicationContext实现(例如XmlWebApplicationContext)时,request、session、application和websocket范围才可用。如果将这些范围与常规Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则将抛出未知bean范围的IllegalStateException异常。

singleton

只管理单个bean的一个共享实例,所有对具有与该bean定义匹配的ID或ID的bean的请求都会导致Spring容器返回一个特定的bean实例。

换句话说,当您将bean定义为单例时,Spring IoC容器将准确地创建由该bean定义的对象的一个实例。此单个实例存储在此类单例bean的缓存中,并且该命名bean的所有后续请求和引用都返回缓存的对象。下图显示了单例作用域的工作原理:
在这里插入图片描述
单例范围是Spring中的默认范围。要将bean定义为XML中的单例,您可以定义bean,如以下示例所示:

<bean id="accountService" class="DefaultAccountService" scope="singleton"/>

prototype

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

下图说明了Spring prototype属性范围:
在这里插入图片描述
与其他范围相比,Spring不管理 prototype属性bean的完整生命周期。容器实例化、配置或以其他方式组装原型对象并将其交给客户端,而不再记录该原型实例。因此,尽管初始化生命周期回调方法被调用在所有对象上,而不管作用域如何,但是在原型的情况下,配置的销毁生命周期回调不会被调用。客户端代码必须清理原型范围的对象,并释放原型bean所占据的资源。

初始Web配置

为了支持bean的范围界定在request、session、application和websocket(即具有web作用域bean),在定义你的bean之前需要做少量的初始配置。(标准范围不需要此初始设置:singleton和prototype。)如何完成此初始设置取决于您的特定Servlet环境。

实际上,如果您在Spring Web MVC中访问由Spring DispatcherServlet处理的请求中的作用域bean,则不需要特殊的设置。DispatcherServlet已经公开了所有相关状态。

如果您使用Servlet 2.5 Web容器,并且在Spring之外处理请求DispatcherServlet(例如,使用JSF或Struts时),则需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener。对于Servlet 3.0+,可以使用该WebApplicationInitializer接口以编程方式完成。或者对于旧容器,将以下声明添加到Web应用程序的web.xml文件中:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

或者,如果您的监听器设置存在问题,请考虑使用Spring RequestContextFilter。过滤器映射取决于周围的Web应用程序配置,因此您必须根据需要进行更改。以下清单显示了Web应用程序的过滤器部分:

<web-app>
    ...
    <filter>
        <filter-name>requestContextFilter</filter-name>
        <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
    </filter>
    <filter-mapping>
        <filter-name>requestContextFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    ...
</web-app>

DispatcherServlet、RequestContextListener和RequestContextFilter都是做的完全一样的事情。即,将HTTP请求对象绑定到服务于该请求的线程。这使得请求和会话范围的bean在调用链中进一步可用。

request

考虑以下用于bean定义的XML配置:

<bean id="loginAction" class="com.something.LoginAction" scope="request"/>

Spring容器通过为每个HTTP请求使用LoginAction bean定义来创建新实例。也就是说,loginAction bean的范围在HTTP请求级别。当请求完成处理时,将丢弃作用域为请求的bean。

使用注解驱动的组件或Java配置时,@RequestScope注解可用于将组件分配给request范围。以下示例显示了如何执行此操作:

@RequestScope
@Component
public class LoginAction {
    // ...
}

session

考虑以下用于bean定义的XML配置:

<bean id="loginAction" class="com.something.UserPreferences" scope="session"/>

Spring容器通过在单个HTTP会话的生命期中使用UserPreferences bean定义来创建UserPreferences bean的新实例。换言之,UserPreferences bean在HTTP会话级别上有效的作用域,与request范围的bean一样。当HTTP会话最终被丢弃时,作用域为该特定HTTP会话的bean也被丢弃。

在使用注解驱动的组件或Java配置时,可以使用@SessionScope注解将组件分配session范围。

application

考虑bean定义的以下XML配置:

<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>

Spring容器通过为整个web应用程序使用一次appPreferences bean定义来创建AppPreferences bean的新实例。也就是说,appPreferences bean的范围在ServletContext级别,并作为一个常规的ServletContext属性存储。这与Spring单例bean有点相似,但在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring’ApplicationContext’(在任何给定的web应用程序中可能有多个)的单例。

使用注解驱动的组件或Java配置时,可以使用@ApplicationScope注解将组件分配application范围。

(更多内容请查看Spring官网)

生命周期回调

为了与容器对bean生命周期的管理进行交互,您可以实现Spring InitializingBean和DisposableBean接口。容器为前者调用afterPropertiesSet(),为后者调用destroy(),以便bean在初始化和销毁bean时执行某些操作。除了初始化和销毁回调,Spring托管对象还可以实现Lifecycle接口,以便这些对象可以参与启动和关闭过程,由容器自己的生命周期驱动。

初始化回调

org.springframework.beans.factory.InitializingBean接口允许bean在容器设置了bean上的所有必要属性之后执行初始化工作。InitializingBean接口指定一个方法:

void afterPropertiesSet() throws Exception;

我们不建议使用InitializingBean接口,因为它不必要地将代码耦合到Spring。我们建议使用@PostConstruct注解或指定POJO初始化方法。对于基于XML的配置元数据,可以使用init-method属性指定具有无效无参数签名的方法的名称。使用Java配置可以使用@bean的initMethod属性。考虑以下示例:

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

前面的示例与以下示例(由两个列表组成)具有几乎完全相同的效果:

<bean id="exampleInitBean" class="AnotherExampleBean"/>
public class AnotherExampleBean implements InitializingBean {
    public void afterPropertiesSet() {
        // do some initialization work
    }
}

但是,前面两个示例中的第一个没有将代码耦合到Spring。

毁灭回调

实现org.springframework.beans.factory.DisposableBean接口允许bean在包含它的容器被销毁时获得回调。DisposableBean接口指定一个方法:

void destroy() throws Exception;

我们不建议使用DisposableBean回调接口,因为它会不必要地将代码耦合到Spring。我们建议使用@PreDestroy注解或指定bean定义支持的泛型方法。使用基于XML的配置元数据,可以使用<bean/>上的destroy-method属性。使用Java配置,可以使用@bean的destroyMethod属性。考虑以下定义:

<bean id="exampleInitBean" class="ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
    public void cleanup() {
        // do some destruction work (like releasing pooled connections)
    }
}

前面的定义与以下定义几乎完全相同:

<bean id="exampleInitBean" class="AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
    public void destroy() {
        // do some destruction work (like releasing pooled connections)
    }
}

但是,前面两个定义中的第一个没有将代码耦合到Spring。

默认初始化和销毁​​方法

在编写不使用特定于Spring的InitializingBean和DisposableBean回调接口的初始化和销毁方法回调时,你通常写一些方法名字如init(),initialize(),dispose(),等等。理想情况下,此类生命周期回调方法的名称在项目中是标准化的,以便所有开发人员使用相同的方法名称并确保一致性。

可以将Spring容器配置为“look”,用于命名初始化和销毁回调方法名称。这意味着,您可以编写应用程序类,并使用名为init()的初始化回调,而不必为每个bean定义配置init-method="init"属性。Spring IoC容器在创建bean时调用该方法。该特性还强制执行用于初始化和销毁方法回调的一致命名约定。

假设您的初始化回调方法名为init(),而您的销毁回调方法名为destroy()。然后,您的类类似于以下示例中的类:

public class DefaultBlogService {
	private BlogDao blogDao;
	
    public void setBlogDao(BlogDao blogDao) {
        this.blogDao = blogDao;
    }
    // 这是初始化回调方法
    public void init() {
        if (this.blogDao == null) {
            throw new IllegalStateException("The [blogDao] property must be set.");
        }
    }
}

然后,可以在类似于以下内容的bean中使用该类:

<beans default-init-method="init">

    <bean id="blogService" class="com.something.DefaultBlogService">
        <property name="blogDao" ref="blogDao" />
    </bean>
</beans>

在顶层<beans/>元素属性上存在default-init-method属性导致Spring IoC容器将bean类上的名为init的方法识别为初始化方法回调。当创建和组装bean时,如果bean类有这样的方法,则在适当的时间调用它。也可以通过在顶层<beans/>元素上使用default-destroy-method属性配置destroy方法回调。

在现有的bean类已经具有与约定不同的回调方法的情况下,可以通过使用<bean/>本身的init-method和destroy-method属性(在XML中)指定方法名称来覆盖默认方法。

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

结合生命周期机制

从Spring 2.5开始,有三个控制bean生命周期行为的选项:

  1. InitializingBean 和 DisposableBean 回调接口
  2. 自定义 init() 和 destroy() 方法
  3. @PostConstruct和@PreDestroy 注解。

您可以组合这些机制来控制给定的bean。

如果为bean配置了多个生命周期机制,并且每个机制都配置了不同的方法名称,则每个配置的方法都按照如下列出的顺序执行。但是,如果init()为多个生命周期机制配置了相同的方法名称,则该方法将只执行一次。

为同一个bean配置的多个生命周期机制,具有不同的初始化方法,如下所示:

  1. 用@PostConstruct注解的方法
  2. 由InitializingBean回调接口定义的afterPropertiesSet()方法
  3. 自定义配置的init()方法

Destroy方法以相同的顺序调用:

  1. 用@PreDestroy注解的方法
  2. 由DisposableBean回调接口定义的destroy()方法
  3. 自定义配置的destroy()方法

启动和停止回调

Lifecycle 接口为具有自己的生命周期需求(例如启动和停止某个后台进程)的任何对象定义基本方法:

public interface Lifecycle {
    void start();
    void stop();
    boolean isRunning();
}

任何Spring管理的对象都可以实现生命周期接口。然后,当ApplicationContext本身接收到开始和停止信号(例如,对于运行时的停止/重新启动场景)时,它将这些调用级联到在该上下文中定义的所有生命周期实现。它通过委托给LifecycleProcessor来完成此操作,如下面的清单所示:

public interface LifecycleProcessor extends Lifecycle {
    void onRefresh();
    void onClose();
}

LifecycleProcessor它本身是Lifecycle 接口的扩展。它还添加了另外两种方法来响应刷新和关闭的上下文。

注意,常规的org.springframework.context.Lifecycle接口是显式启动和停止时调用的,并不意味着在上下文刷新时自动启动。对于特定bean(包括启动阶段)的自动启动的细粒度控制,可以考虑实现org.springframework.context.SmartLife。另外,请注意,停止通知不保证在销毁之前到来。在常规关闭时,在传播一般销毁回调之前,所有Lifecycle bean首先接收停止通知。但是,在上下文生命周期中的热刷新或中止的刷新尝试中,只调用destroy方法。

启动和关闭调用的顺序可能很重要。如果任意两个对象之间存在“依赖”关系,则依赖方在其依赖项之后开始,并在依赖项之前停止。然而,有时,直接依赖关系未知。您可能只知道特定类型的对象应该在其他类型的对象之前启动。在这些情况下,SmartLifecycle接口定义另一个选项,即在其超级接口Phased上定义的getPhase()方法。下面的清单显示了Phased接口的定义:

public interface Phased {
    int getPhase();
}

以下清单显示了SmartLifecycle接口的定义:

public interface SmartLifecycle extends Lifecycle, Phased {
    boolean isAutoStartup();
    void stop(Runnable callback);
}

启动时,相位最低的对象首先启动。当停止时,遵循相反的顺序。因此,实现SmartLifecycle并且其getPhase()方法返回Integer.MIN_VALUE的对象将是第一个开始和最后一个停止的对象。另一方面Integer.MAX_VALUE的相位值将指示对象应该在最后开始并首先停止(可能是因为它取决于要运行的其他进程)。在考虑阶段值时,还必须知道没有实现SmartLifecycle的“正常”生命周期对象的默认阶段为0。因此,任何负相位值都表明对象应该在这些标准组件之前开始(并在它们之后停止)。对于任何正相位值,反之亦然。

SmartLifecycle定义的stop方法接受回调。任何实现都必须在该实现的关闭过程完成之后调用该回调的run()方法。这在必要时启用异步关机,因为LifecycleProcessor接口的默认实现DefaultLifecycleProcessor将等待到每个阶段中对象组的超时值以调用该回调。默认的每阶段超时时间为30秒。通过在上下文中定义名为lifecycleProcessor 的bean,可以覆盖默认的生命周期处理器实例。如果只想修改超时,定义以下内容就足够了:

<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
    <!-- 以毫秒为单位的超时值 -->
    <property name="timeoutPerShutdownPhase" value="10000"/>
</bean>

如前所述,LifecycleProcessor接口还定义了用于刷新和关闭上下文的回调方法。后者驱动关闭过程,就像已经显式调用stop()一样,当上下文关闭时发生。另一方面,“刷新”回调启用了SmartLifecycle bean的另一个特性。当刷新上下文时(在所有对象被实例化和初始化之后),调用该回调。此时,默认的生命周期处理器检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。如果为真,则该对象在该点启动,而不是等待上下文或其自己的start()方法的显式调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。阶段值和任何“依赖”关系决定了如前所述的启动顺序。

(启动和停止回调这节我任然没搞明白)

基于注解的容器配置

基于注解的配置提供了XML设置的替代方案,它依赖字节码元数据来连接组件,而不是角括号声明。开发人员通过使用相关类、方法或字段声明上的注解将配置移动到组件类本身,而不是使用XML来描述bean连接。注解注入在XML注入之前执行。因此,XML配置覆盖了通过两种方法连接的属性的注解。

与往常一样,您可以将它们注册为单独的bean定义,但是也可以通过在基于XML的Spring配置中包括以下标记来隐式注册它们(请注意包含context命名空间):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>

<context:annotation-config/>只在定义它的相同应用程序上下文中查找bean上的注解。这意味着,如果将<context:annotation-config/>放入DispatcherServlet的WebApplicationContext中,则它只检查控制器中的@Autowiredbean,而不检查服务。

@Autowired

您可以将@Autowired注解应用于构造函数,如以下示例所示:

public class MovieRecommender {

    private final TempDao tempDao;

    @Autowired
    public MovieRecommender(TempDao tempDao) {
        this.tempDao = tempDao;
    }
    // ...
}

从Spring Framework 4.3开始,如果目标bean仅定义一个构造函数,则不再需要对这样的构造函数使用@Autowired注解。然而,如果几个构造函数可用,则必须对至少一个构造函数进行注解,以教导容器使用哪个构造函数。

您还可以将@Autowired注解应用于“传统”setter方法,如以下示例所示:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
    // ...
}

您还可以将注解应用于具有任意名称和多个参数的方法,如以下示例所示:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public void prepare(MovieCatalog movieCatalog,
            CustomerPreferenceDao customerPreferenceDao) {
        this.movieCatalog = movieCatalog;
        this.customerPreferenceDao = customerPreferenceDao;
    }
    // ...
}

确保目标组件(例如,MovieCatalog或CustomerPreferenceDao)始终由您用于@Autowired注解注入点的类型声明。否则,注入可能会失败,因为在运行时没有找到类型匹配。

您也可以应用于@Autowired字段,如以下示例所示:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;
    // ...
}

@Resource

Spring还通过@Resource在字段或bean属性setter方法上使用JSR-250注解来支持注入。这是Java EE 5和6中的常见模式。Spring也支持Spring管理对象的这种模式。

在字段上使用

public class SimpleMovieLister {

    @Resource(name="myMovieFinder") 
    private MovieFinder movieFinder;

    public void dosomething() {
    	movieFinder.dosomthing();
    }
}

@Resource具有name属性。默认情况下,Spring将该值解释为要注入的bean名称。换句话说,它遵循如下示例中所演示的名称语义:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource(name="myMovieFinder") 
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

如果没有显式指定名称,则默认名称从字段名或setter方法派生。对于字段,它接受字段名。对于setter方法,它使用bean属性名。下面的示例将让名为movieFinder的bean注入其setter方法:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Resource
    public void setMovieFinder(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}

@PostConstruct 和 @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注解,还识别JSR-250生命周期注解。在Spring 2.5中引入的,对这些注解的支持提供了另一种替代方法,可以替代在初始化回调和销毁回调中描述的方法。假设CommonAnnotationBeanPostProcessor注册在Spring ApplicationContext中,则携带这些注解之一的方法在生命周期的同一点被调用,作为相应的Spring生命周期接口方法或显式声明的回调方法。在下面的示例中,缓存在初始化时预先填充,在销毁时清除:

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // 对象完全初始化后执行该方法...
    }
    @PreDestroy
    public void clearMovieCache() {
        // 对象销毁时执行该方法...
    }
}

类路径扫描和管理组件

@Component 与 @Controller、@Service、@Repository

@Repository注解是满足存储库角色或原型(也称为数据访问对象或DAO)的任何类的标记。这个标记的用途之一是自动翻译异常。

Spring提供了进一步的原型化注解:@Component、@Service和@Controller。@Component是任何Spring管理组件的通用原型。@Repository、@Service和@Controller是@Component对于更具体的用例的专门化(分别在持久层、服务层和表示层中)。因此,您可以使用@Component来注解你的组件类,但是通过使用@Repository,@Service或者@Controller ,你的类能更好地被工具处理,或与切面进行关联。例如:

@Component 
public class demoUtil {
    // ....
}

自动检测类并注册Bean定义

Spring可以自动检测原型化类,并将相应的BeanDefinition实例注册到ApplicationContext。例如,以下两个类符合这种自动检测的条件:

@Service
public class SimpleMovieLister {

    private MovieFinder movieFinder;

    @Autowired
    public SimpleMovieLister(MovieFinder movieFinder) {
        this.movieFinder = movieFinder;
    }
}
@Repository
public class JpaMovieFinder implements MovieFinder {
    //为了清楚起见,省略了实现...
}

要自动检测这些类并注册相应的bean,您需要添加 @ComponentScan到您的@Configuration类,其中该basePackages属性是两个类的公共父包。(或者,您可以指定以逗号或分号或空格分隔的列表,其中包含每个类的父包。)

@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig  {
	@Bean
    public SimpleMovieLister getSimpleMovieLister() { ... }
    @Bean
    public JpaMovieFinder getJpaMovieFinder() { ... }
    ...
}

为了简明起见,前面的示例可以写成@ComponentScan(“org.example”)。

以下替代方法使用XML:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example"/>

</beans>

使用过滤器自定义扫描

默认情况下,用@Component、@Repository、@Service、@Controller注解的类,或者本身都标注有一个自定义的注解@Component是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan注解的includeFilters或excludeFilters 参数(或者作为component-scan元素的子元素include-filter或exclude-filter)。每个筛选器元素都需要type和expression属性。下表描述了过滤选项:

过滤器类型表达式例子描述
annotation (default)org.example.SomeAnnotation要在目标组件中的类型级别出现的注解
assignableorg.example.SomeClass目标组件可分配给(扩展或实现)的类(或接口)
aspectjorg.example…*Service+要由目标组件匹配的AspectJ类型表达式
regexorg.example.Default.*要由目标组件类名匹配的正则表达式
customorg.example.MyTypeFilterorg.springframework.core.type.TypeFilter接口的自定义实现。

以下示例显示忽略所有@Repository注解并使用“stub”存储库的配置:

@Configuration
@ComponentScan(basePackages = "org.example",
        includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
        excludeFilters = @Filter(Repository.class))
public class AppConfig {
    ...
}

以下清单显示了等效的XML:

<beans>
    <context:component-scan base-package="org.example">
        <context:include-filter type="regex" expression=".*Stub.*Repository"/>
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
    </context:component-scan>
</beans>

命名自动检测组件

当一个组件被自动检测为扫描过程的一部分时,它的bean名称是由扫描器已知的BeanNameGenerator策略生成的。默认情况下,包含名称值的任何Spring原型注解(@Component、@Repository、@Service和@Controller)都向相应的bean定义提供该名称。

如果这些注解不包含任何名称值或任何其他检测到的组件(例如由自定义过滤器发现的那些组件),则默认bean名称生成器返回非大写的非限定类名称。例如,如果检测到以下组件类,名称将是myMovieLister和movieFinderImpl:

@Service("myMovieLister")
public class SimpleMovieLister {
    // ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

如果不想依赖默认的bean命名策略,可以提供自定义的bean命名策略。首先,实现BeanNameGenerator接口,并确保包括默认的无参数构造函数。然后,在配置扫描器时提供完全限定的类名,如下面的示例注解和bean定义所示:

@Configuration
@ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class)
public class AppConfig {
    ...
}
<beans>
    <context:component-scan base-package="org.example"
        name-generator="org.example.MyNameGenerator" />
</beans>

作为一般规则,当其他组件可能显式引用该名称时,考虑使用注解指定该名称。另一方面,只要容器负责装配,自动生成的名称就足够了。

为自动检测组件提供范围

与Spring管理的组件一样,自动检测组件的默认和最常见的范围是singleton。但是,有时您需要一个可以由@Scope注解指定的不同范围。您可以在注解中提供范围的名称,如以下示例所示:

@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
    // ...
}

基于java的容器配置

@Bean 和 @Configuration

@Bean注解用于指示方法实例化、配置和初始化要由Spring IoC容器管理的新对象。对于那些熟悉Spring的<beans/>XML配置的人来说,@Bean注解与<bean/>元素扮演相同的角色。您可以在任何Spring@Component中使用@Bean注解的方法。然而,它们最常与@Configurationbean一起使用。

用@Configuration注解一个类表明它的主要目的是作为bean定义的源。此外,@Configuration类允许通过调用同一类中的其他@Bean方法来定义bean间的依赖关系。最简单的可能的@Configuration类如下:

@Configuration
public class AppConfig {

    @Bean
    public MyService myService() {
        return new MyServiceImpl();
    }
}

上面的AppConfig类等效于以下Spring <beans/>XML:

<beans>
    <bean id="myService" class="com.acme.services.MyServiceImpl"/>
</beans>

(自己的理解:@Bean等价于XML配置文件中的<bean></bean>,@Configuration则等价于<beans></beans>)

注:更多内容请查看Spring官网


  1. 本文来源 https://docs.spring.io/spring/docs/5.1.3.RELEASE/spring-framework-reference/core.html
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值