Spring ——配置IOC容器详解

Spring IOC容器 及配置详解

译自Spring官方文档关于IOC容器章节 

出处:https://docs.spring.io/spring/docs/4.3.9.RELEASE/spring-framework-reference/html/beans.html#beans-introduction

 关于Spring IOC容器了解更多:

Spring IoC容器和bean简介

本章介绍了Spring Framework实现的控制反转(IoC)[1]原理。IoC也称为依赖注入(DI)。这是一个过程,通过这个过程,对象定义它们的依赖关系,即它们使用的其他对象,只能通过构造函数参数,工厂方法的参数,或者在构造或从工厂方法返回后在对象实例上设置的属性。 。然后容器 在创建bean时注入这些依赖项。这个过程基本上是反向的,因此名称Inversion of Control(IoC),bean本身通过使用类的直接构造来控制其依赖关系的实例化或位置,或者诸如服务定位器模式。

org.springframework.beansorg.springframework.context包是Spring框架的IoC容器的基础。该 BeanFactory 接口提供了一种能够管理任何类型对象的高级配置机制。 ApplicationContext 是一个子界面BeanFactory。它增加了与Spring的AOP功能的更容易的集成; 消息资源处理(用于国际化),事件发布; 和特定于应用程序层的上下文,例如WebApplicationContext 在Web应用程序中使用的上下文。

简而言之,它BeanFactory提供了配置框架和基本功能,并ApplicationContext添加了更多企业特定的功能。它ApplicationContext是完整的超集,BeanFactory在本章中专门用于Spring的IoC容器的描述。有关使用BeanFactory而不是ApplicationContext,参考 第7.16节“BeanFactory”的更多信息。

在Spring中,构成应用程序主干并由Spring IoC 容器管理的对象称为bean。bean是一个由Spring IoC容器实例化,组装和管理的对象。否则,bean只是应用程序中众多对象之一。Bean及其之间的依赖 关系反映在容器使用的配置元数据中。

7.2容器概述

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

ApplicationContextSpring的开箱即用的几个接口实现。在独立应用程序中,通常创建ClassPathXmlApplicationContext 或的实例FileSystemXmlApplicationContext。虽然XML是定义配置元数据的传统格式,但您可以通过提供少量XML配置来声明性地支持这些其他元数据格式,从而指示容器使用Java注释或代码作为元数据格式。

在大多数应用程序方案中,不需要显式用户代码来实例化Spring IoC容器的一个或多个实例。例如,在Web应用程序场景中,应用程序文件中的简单八行(左右)样板Web描述符XML web.xml通常就足够了(请参见第7.15.4节“Web应用程序的便捷ApplicationContext实例化”)。如果您使用的是 Spring工具套件 Eclipse驱动的开发环境,只需点击几下鼠标或按键即可轻松创建此样板文件配置。

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

图7.1。Spring IoC容器

7.9基于注释的容器配置

注释是否比配置Spring的XML更好?

基于注释的配置的引入引发了这种方法是否比XML更“好”的问题。简短的答案取决于它。答案很长,每种方法都有其优点和缺点,通常由开发人员决定哪种策略更适合他们。由于它们的定义方式,注释在其声明中提供了大量上下文,从而导致更短更简洁的配置。但是,XML擅长在不触及源代码或重新编译它们的情况下连接组件。一些开发人员更喜欢将布线靠近源,而另一些开发人员则认为注释类不再是POJO,而且配置变得分散且难以控制。

无论选择如何,Spring都可以兼顾两种风格,甚至可以将它们混合在一起。值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注释,而无需触及目标组件源代码,并且在工具方面,Spring Tool Suite支持所有配置样式 。

基于注释的配置提供了XML设置的替代方案,该配置依赖于字节码元数据来连接组件而不是角括号声明。开发人员不是使用XML来描述bean连接,而是通过在相关的类,方法或字段声明上使用注释将配置移动到组件类本身。正如在“示例:RequiredAnnotationBeanPostProcessor”一节中所提到的BeanPostProcessor,结合注释是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required强制执行所需属性的可能性注解。Spring 2.5使得有可能采用相同的通用方法来驱动Spring的依赖注入。本质上,@Autowired注释提供的功能与第7.4.5节“自动装配协作者”中所述的功能相同,但具有更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,例如 @PostConstruct@PreDestroy。Spring 3.0增加了对javax.inject包中包含的JSR-330(Java的依赖注入)注释的支持,例如@Inject 和@Named。有关这些注释的详细信息,请参阅 相关章节。

注释注入 XML注入之前执行,因此后一种配置将覆盖通过两种方法连接的属性的前者。

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




    

(该隐式注册的后处理器包括 AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessorPersistenceAnnotationBeanPostProcessor,以及前述 RequiredAnnotationBeanPostProcessor)。

仅查找在定义它的同一应用程序上下文中的bean上的注释。这意味着,如果你 输入一个WebApplicationContextfor DispatcherServlet,它只会检查@Autowired你的控制器中的bean,而不是你的服务。有关更多信息,请参见 第22.2节“DispatcherServlet”。

7.9.1 @Required

@Required注释适用于bean属性setter方法,如下面的例子:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...

}

此注释仅指示必须在配置时通过bean定义中的显式属性值或通过自动装配填充受影响的bean属性。如果尚未填充受影响的bean属性,容器将引发异常; 这允许急切和明确的失败,以后避免NullPointerExceptions等。仍然建议您将断言放入bean类本身,例如,放入init方法。即使您在容器外使用类,这样做也会强制执行那些必需的引用和值。

7.9.2 @Autowired

在下面的示例中,@Inject可以使用JSR 330的注释代替Spring的@Autowired注释。有关详细信息,请参见此处

您可以将@Autowired注释应用于构造函数:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}

从Spring Framework 4.3开始,@Autowired如果目标bean只定义了一个构造函数,则不再需要构造函数。如果有几个构造函数可用,则必须注释至少一个构造函数,以便教导容器必须使用哪个构造函数。

正如所料,您还可以将@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;
    }

    // ...

}

您也可以应用于@Autowired字段,甚至可以将它与构造函数混合使用:

public class MovieRecommender {

    private final CustomerPreferenceDao customerPreferenceDao;

    @Autowired
    private MovieCatalog movieCatalog;

    @Autowired
    public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
        this.customerPreferenceDao = customerPreferenceDao;
    }

    // ...

}

通过将注释添加到需要该类型数组的字段或方法,也可以提供特定类型的所有 bean ApplicationContext

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...

}

这同样适用于类型集合:

public class MovieRecommender {

    private Set movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Set movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...

}

如果希望将数组或列表中的项目排序为特定顺序,则Bean可以实现org.springframework.core.Ordered接口或使用@Order或标准@Priority注释。

即使键入的地图也可以自动装配,只要预期的密钥类型是String。Map值将包含所需类型的所有bean,并且键将包含相应的bean名称:

public class MovieRecommender {

    private Map movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...

}

默认情况下,只要候选bean可用,自动装配就会失败; 默认行为是将带注释的方法,构造函数和字段视为指示所需的依赖项。可以更改此行为,如下所示。

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...

}

只有一个每类注释构造函数可以作为标记要求,但多个非必需的构造函数可以被注解。在这种情况下,每个都被认为是候选者之一,Spring使用最贪婪的构造函数,其依赖性可以得到满足,即具有最多参数的构造函数。

@Autowired’s required attribute is recommended over the `@Required注解。将所需的属性表示该属性不需要自动装配的目的,如果它不能自动装配的属性被忽略。@Required另一方面,它更强大,因为它强制执行由容器支持的任何方式设置的属性。如果未注入任何值,则会引发相应的异常。

您还可以使用@Autowired对于那些众所周知的解析依赖接口:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisher,和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContextResourcePatternResolver)会自动解析,无需特殊设置。

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...

}

@Autowired@Inject@Resource,和@Value注释由Spring处理 BeanPostProcessor实现这反过来又意味着你不能在您自己的应用这些注释BeanPostProcessorBeanFactoryPostProcessor类型(如果有的话)。这些类型必须通过XML或使用Spring @Bean方法显式“连接” 。

7.9.3使用@Primary微调基于注释的自动装配

由于按类型自动装配可能会导致多个候选人,因此通常需要对选择过程有更多控制权。实现这一目标的一种方法是使用Spring的 @Primary注释。@Primary表示当多个bean可以自动装配到单值依赖项时,应该优先选择特定的bean。如果候选者中只存在一个“主”bean,则它将是自动装配的值。

假设我们有以下配置定义firstMovieCatalog为 主要 配置MovieCatalog

@Configuration
public class MovieConfiguration {

    @Bean
    @Primary
    public MovieCatalog firstMovieCatalog() { ... }

    @Bean
    public MovieCatalog secondMovieCatalog() { ... }

    // ...

}

通过这样的配置,以下MovieRecommender将自动装配 firstMovieCatalog

public class MovieRecommender {

    @Autowired
    private MovieCatalog movieCatalog;

    // ...

}

相应的bean定义如下所示。




    

    
        
    

    
        
    

    

7.9.4使用限定符微调基于注释的自动装配

@Primary当可以确定一个主要候选者时,是通过具有多个实例的类型使用自动装配的有效方式。当需要更多地控制选择过程时,@Qualifier可以使用Spring的注释。您可以将限定符值与特定参数相关联,缩小类型匹配集,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值:

public class MovieRecommender {

    @Autowired
    @Qualifier("main")
    private MovieCatalog movieCatalog;

    // ...

}

@Qualifier注释也可以在单独的构造器参数或方法参数指定:

public class MovieRecommender {

    private MovieCatalog movieCatalog;

    private CustomerPreferenceDao customerPreferenceDao;

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

    // ...

}

相应的bean定义如下所示。具有限定符值“main”的bean与使用相同值限定的构造函数参数连接。




    

    
        

        
    

    
        

        
    

    

对于回退匹配,bean名称被视为默认限定符值。因此,您可以使用id“main”而不是嵌套的限定符元素来定义bean,从而得到相同的匹配结果。但是,虽然您可以使用此约定来按名称引用特定bean,但@Autowired基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回退,限定符值在类型匹配集中也总是具有缩小的语义; 它们在语义上不表示对唯一bean id的引用。好的限定符值是“主要”或“EMEA”或“持久性”,表示独立于bean的特定组件的特征id

限定符也适用于类型集合,如上所述,例如, Set。在这种情况下,根据声明的限定符的所有匹配bean都作为集合注入。这意味着限定符不必是唯一的; 它们只是简单地构成过滤标准。例如,您可以MovieCatalog使用相同的限定符值“action” 定义多个bean,所有这些bean都将注入带Set注释的注释中@Qualifier("action")

如果您打算按名称表达注释驱动的注入,请不要主要使用 @Autowired,即使技术上能够通过@Qualifier值引用bean名称 。相反,使用JSR-250 @Resource注释,该注释在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。@Autowired具有相当不同的语义:按类型选择候选bean后,指定的字符串限定符值将仅在这些类型选择的候选项中被考虑,例如,将“帐户”限定符与标记有相同限定符标签的bean匹配。

对于本身定义为集合/映射或数组类型的@Resource bean ,是一个很好的解决方案,通过唯一名称引用特定集合或数组bean。也就是说,从4.3开始,@Autowired只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中,集合/映射和数组类型也可以通过Spring的类型匹配算法进行匹配 。在这种情况下,限定符值可用于在相同类型的集合中进行选择,如上一段所述。

从4.3开始,@Autowired还考虑自我引用注入,即引用回到当前注入的bean。请注意,自我注射是一种后备; 对其他组件的常规依赖性始终具有优先权。从这个意义上说,自我引用并不参与常规的候选人选择,因此特别是不是主要的; 相反,它们总是最低优先级。在实践中,仅使用自引用作为最后的手段,例如,通过bean的事务代理调用同一实例上的其他方法:考虑在这种情况下将受影响的方法分解为单独的委托bean。或者,使用@Resource它可以通过其唯一名称获得代理回到当前bean。

@Autowired适用于字段,构造函数和多参数方法,允许在参数级别缩小限定符注释。相比之下,@Resource 仅支持具有单个参数的字段和bean属性setter方法。因此,如果您的注入目标是构造函数或多参数方法,请坚持使用限定符。

您可以创建自己的自定义限定符注释。只需定义注释并@Qualifier在定义中提供注释:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {

    String value();
}

然后,您可以在自动装配的字段和参数上提供自定义限定符:

public class MovieRecommender {

    @Autowired
    @Genre("Action")
    private MovieCatalog actionCatalog;
    private MovieCatalog comedyCatalog;

    @Autowired
    public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
        this.comedyCatalog = comedyCatalog;
    }

    // ...

}

接下来,提供候选bean定义的信息。您可以将标记添加为 标记的子元素,然后指定type和 value匹配自定义限定符注释。该类型与注释的完全限定类名匹配。或者,为方便起见,如果不存在冲突名称的风险,您可以使用短类名。以下示例演示了这两种方法。




    

    
        
        
    

    
        
        
    

    

在第7.10节“类路径扫描和托管组件”中,您将看到基于注释的替代方法,即以XML格式提供限定符元数据。具体来说,请参见第7.10.8节“使用注释提供限定符元数据”。

在某些情况下,使用没有值的注释可能就足够了。当注释用于更通用的目的并且可以跨多种不同类型的依赖项应用时,这可能很有用。例如,您可以提供 在没有Internet连接时将搜索的脱机目录。首先定义简单注释:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {

}

然后将注释添加到要自动装配的字段或属性中:

public class MovieRecommender {

    @Autowired
    @Offline
    private MovieCatalog offlineCatalog;

    // ...

}

现在bean定义只需要一个限定符type


    
    

您还可以定义除简单value属性之外或代替简单属性接受命名属性的自定义限定符注释。如果随后在要自动装配的字段或参数上指定了多个属性值,则bean定义必须匹配所有此类属性值才能被视为自动装配候选。例如,请考虑以下注释定义:

@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {

    String genre();

    Format format();

}

在这种情况下Format是一个枚举:

public enum Format {
    VHS, DVD, BLURAY
}

要自动装配的字段使用自定义限定符进行注释,并包含两个属性的值:genreformat

public class MovieRecommender {

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Action")
    private MovieCatalog actionVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.VHS, genre="Comedy")
    private MovieCatalog comedyVhsCatalog;

    @Autowired
    @MovieQualifier(format=Format.DVD, genre="Action")
    private MovieCatalog actionDvdCatalog;

    @Autowired
    @MovieQualifier(format=Format.BLURAY, genre="Comedy")
    private MovieCatalog comedyBluRayCatalog;

    // ...

}

最后,bean定义应包含匹配的限定符值。此示例还演示了可以使用bean 属性而不是 子元素。如果可用,则其属性优先,但如果不存在此类限定符,则自动装配机制将回退到标记中提供的值 ,如以下示例中的最后两个bean定义。




    

    
        
            
            
        
        
    

    
        
            
            
        
        
    

    
        
        
        
    

    
        
        
        
    

7.9.5使用泛型作为自动装配限定符

除了@Qualifier注释之外,还可以使用Java泛型类型作为隐式的限定形式。例如,假设您具有以下配置:

@Configuration
public class MyConfiguration {

    @Bean
    public StringStore stringStore() {
        return new StringStore();
    }

    @Bean
    public IntegerStore integerStore() {
        return new IntegerStore();
    }

}

假设上述bean实现一个通用接口,即Store和 Store,你可以@AutowireStore界面和通用将作为一个限定:

@Autowired
private Store s1; //  qualifier, injects the stringStore bean

@Autowired
private Store s2; //  qualifier, injects the integerStore bean

通用限定符也适用于自动装配列表,地图和数组:

// Inject all Store beans as long as they have an  generic
// Store beans will not appear in this list
@Autowired
private List> s;

7.9.6 CustomAutowireConfigurer

这 CustomAutowireConfigurer 是一个BeanFactoryPostProcessor使您能够注册自己的自定义限定符注释类型,即使它们没有使用Spring的@Qualifier注释注释。


    
        
            example.CustomQualifier
        
    

通过以下方式AutowireCandidateResolver确定autowire候选人:

  • autowire-candidate每个bean定义 的值
  • 元素上 default-autowire-candidates可用的 任何模式
  • @Qualifier注释 的存在以及注册的任何自定义注释CustomAutowireConfigurer

当多个bean有资格作为autowire候选者时,“primary”的确定如下:如果候选者中只有一个bean定义具有primary 设置为的属性true,则将选择它。

7.9.7 @Resource

Spring还支持@Resource在字段或bean属性setter方法上使用JSR-250 注释进行注入。这是Java EE 5和6中的常见模式,例如在JSF 1.2托管bean或JAX-WS 2.0端点中。Spring也支持Spring管理对象的这种模式。

@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;
    }

}

提供注解的名称解析由一个bean的名称 ApplicationContext,其中的CommonAnnotationBeanPostProcessor知道。如果您SimpleJndiBeanFactory明确配置Spring,则可以通过JNDI解析名称 。但是,建议您依赖于默认行为,只需使用Spring的JNDI查找功能来保持间接级别。

在专属情况下,@Resource不指定明确的名称,以及类似的使用@Autowired@Resource发现的主要类型的比赛,而不是一个具体的bean并解决众所周知的解析依存关系:BeanFactory, ApplicationContextResourceLoaderApplicationEventPublisher,和MessageSource 接口。

因此,在以下示例中,customerPreferenceDao字段首先查找名为customerPreferenceDao的bean,然后返回到该类型的主类型匹配 CustomerPreferenceDao。基于已知的可解析依赖性类型注入“上下文”字段ApplicationContext

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...

}

7.9.8 @PostConstruct和@PreDestroy

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

public class CachingMovieLister {

    @PostConstruct
    public void populateMovieCache() {
        // populates the movie cache upon initialization...
    }

    @PreDestroy
    public void clearMovieCache() {
        // clears the movie cache upon destruction...
    }

}

7.12.5编写基于Java的配置

使用@Import注释

就像在Spring XML文件中使用元素来帮助模块化配置一样,@Import注释允许@Bean从另一个配置类加载定义:

@Configuration
public class ConfigA {

     @Bean
    public A a() {
        return new
    }

}

@Configuration
@Import(ConfigA.class)
public class ConfigB {

    @Bean
    public B b() {
        return new
    }

}

现在,不需要同时指定ConfigA.classConfigB.class实例化上下文,只ConfigB需要显式提供:

public static void
    ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);

    // now both beans A and B will be available...
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

这种方法简化了容器实例化,因为只需要处理一个类,而不是要求开发人员@Configuration在构造期间记住可能大量的 类。

从Spring Framework 4.2开始,@Import还支持对常规组件类的引用,类似于AnnotationConfigApplicationContext.register方法。如果您想避免组件扫描,使用一些配置类作为显式定义所有组件的入口点,这将特别有用。

注入对导入的@Bean定义的依赖关系

上面的例子有效,但很简单。在大多数实际情况中,bean将跨配置类相互依赖。使用XML时,这本身并不是问题,因为不涉及编译器,可以简单地声明ref="someBean"并相信Spring会在容器初始化期间解决它。当然,在使用@Configuration类时,Java编译器会对配置模型施加约束,因为对其他bean的引用必须是有效的Java语法。

幸运的是,解决这个问题很简单。正如我们已经讨论过的, @Bean方法可以有任意数量的参数来描述bean的依赖关系。让我们考虑一个更真实的场景,其中有几个@Configuration 类,每个类都依赖于在其他类中声明的bean:

@Configuration
public class ServiceConfig {

    @Bean
    public TransferService transferService(AccountRepository accountRepository) {
        return new TransferServiceImpl(accountRepository);
    }

}

@Configuration
public class RepositoryConfig {

    @Bean
    public AccountRepository accountRepository(DataSource dataSource) {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

还有另一种方法可以达到相同的效果。请记住,@Configuration类最终只是容器中的另一个bean:这意味着它们可以像任何其他bean一样利用 @Autowired@Value注入等等!

确保以这种方式注入的依赖项只是最简单的。@Configuration 在上下文初始化期间很早就处理了类,并强制以这种方式注入依赖项可能会导致意外的早期初始化。尽可能采用基于参数的注入,如上例所示。

另外,要特别注意BeanPostProcessorBeanFactoryPostProcessor定义@Bean。这些通常应该声明为static @Bean方法,而不是触发其包含配置类的实例化。否则,@Autowired并且@Value不会对配置类本身,因为它是被作为一个bean实例创建太早上班。

@Configuration
public class ServiceConfig {

    @Autowired
    private AccountRepository accountRepository;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(accountRepository);
    }

}

@Configuration
public class RepositoryConfig {

    private final DataSource dataSource;

    @Autowired
    public RepositoryConfig(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

}

@Configuration
@Import({ServiceConfig.class, RepositoryConfig.class})
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return new DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // everything wires up across configuration classes...
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

@Configuration仅在Spring Framework 4.3中支持类中的构造函数注入。还要注意,不需要指定@Autowired目标bean是否只定义了一个构造函数; 在上面的例子中,@AutowiredRepositoryConfig构造函数上没有必要。

在上面的场景中,使用@Autowired效果很好并提供了所需的模块性,但确定声明自动装配的bean定义的确切位置仍然有些模棱两可。例如,作为开发人员ServiceConfig,您如何确切地知道@Autowired AccountRepositorybean的声明位置?它在代码中并不明确,这可能就好了。请记住, Spring Tool Suite提供的工具可以呈现图形,显示所有内容的连接方式 - 这可能就是您所需要的。此外,您的Java IDE可以轻松找到该AccountRepository类型的所有声明和用法,并将快速显示@Bean返回该类型的方法的位置。

如果这种歧义是不可接受的,并且您希望从IDE中直接从一个@Configuration类导航到另一个类,请考虑自行装配配置类:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        // navigate 'through' the config class to the @Bean method!
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }

}

在上面的情况中,它是完全明确AccountRepository的定义。但是,ServiceConfig现在紧紧地联系在一起RepositoryConfig; 这是权衡。通过使用基于接口的类或基于@Configuration类的抽象类,可以在某种程度上减轻这种紧密耦合。考虑以下:

@Configuration
public class ServiceConfig {

    @Autowired
    private RepositoryConfig repositoryConfig;

    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl(repositoryConfig.accountRepository());
    }
}

@Configuration
public interface RepositoryConfig {

    @Bean
    AccountRepository accountRepository();

}

@Configuration
public class DefaultRepositoryConfig implements RepositoryConfig {

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(...);
    }

}

@Configuration
@Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config!
public class SystemTestConfig {

    @Bean
    public DataSource dataSource() {
        // return DataSource
    }

}

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123", "C456");
}

现在ServiceConfig与具体的松散耦合 DefaultRepositoryConfig,内置的IDE工具仍然很有用:开发人员很容易获得实现的类型层次结构RepositoryConfig。通过这种方式,导航@Configuration类及其依赖关系与导航基于接口的代码的常规过程没有什么不同。

有条件地包括@Configuration类或@Bean方法

基于某些任意系统状态,有条件地启用或禁用完整@Configuration类或甚至单个@Bean方法通常很有用。一个常见的例子是@Profile只有在Spring中启用了特定的配置文件时才使用注释来激活bean Environment( 有关详细信息,请参见第7.13.1节“Bean定义配置文件”)。

@Profile注释是使用所谓的更灵活的注释实际执行@Conditional。该@Conditional注释指示特定 org.springframework.context.annotation.Condition前应谘询的实施@Bean是注册。

Condition接口的实现只是提供一个matches(…​) 返回true或的方法false。例如,以下是Condition用于的实际 实现@Profile

@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
    if (context.getEnvironment() != null) {
        // Read the @Profile annotation attributes
        MultiValueMap attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
        if (attrs != null) {
            for (Object value : attrs.get("value")) {
                if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
                    return true;
                }
            }
            return false;
        }
    }
    return true;
}

有关更多详细信息,请参阅javadocs。 @Conditional

结合Java和XML配置

Spring的@Configuration类支持并非旨在成为Spring XML的100%完全替代品。诸如Spring XML命名空间之类的一些工具仍然是配置容器的理想方式。在XML方便或必要的情况下,您可以选择:例如,以“以XML为中心”的方式实例化容器 ClassPathXmlApplicationContext,或者以“以Java为中心”的方式使用AnnotationConfigApplicationContext@ImportResource根据需要导入XML 的注释。 。

以XML为中心的@Configuration类的使用

最好从XML引导Spring容器并@Configuration以ad-hoc方式包含 类。例如,在使用Spring XML的大型现有代码库中,根据需要创建@Configuration类并将其包含在现有XML文件中会更容易。下面你将找到@Configuration在这种“以XML为中心”的情况下使用类的选项。

请记住,@Configuration类最终只是容器中的bean定义。在这个例子中,我们创建了一个@Configuration名为的类,AppConfig并将其system-test-config.xml作为定义包含在内。因为 已打开,容器将识别 @Configuration注释并 正确处理@Bean声明的方法AppConfig

@Configuration
public class AppConfig {

    @Autowired
    private DataSource dataSource;

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public TransferService transferService() {
        return new TransferService(accountRepository());
    }

}

system-test-config.xml


    
    
    

    

    
        
        
        
    

jdbc.properties

jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml");
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

system-test-config.xml上面,AppConfig 没有声明一个id 元素。虽然这样做是可以接受的,但是没有其他bean可以引用它,并且不太可能通过名称从容器中显式获取它。与DataSourcebean 类似- 它只是由类型自动装配,因此id不严格要求显式bean 。

因为@Configuration带有元注释@Component,注释@Configuration类自动成为组件扫描的候选者。使用与上面相同的方案,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明 ,因为启用相同的功能。

system-test-config.xml


    
    
    

    
        
        
        
    

@Configuration以类为中心使用带@ImportResource的XML

@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,只需使用@ImportResource和定义所需的XML。这样做可以实现“以Java为中心”的方法来配置容器并将XML保持在最低限度。

@Configuration
@ImportResource("classpath:/com/acme/properties-config.xml")
public class AppConfig {

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    @Bean
    public DataSource dataSource() {
        return new DriverManagerDataSource(url, username, password);
    }

}
properties-config.xml

    
jdbc.properties
jdbc.url=jdbc:hsqldb:hsql://localhost/xdb
jdbc.username=sa
jdbc.password=
public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}

 

你可能感兴趣的:(Spring,IOC,容器)