Spring官方文档通读-部分三

1.9 基于注释的容器配置

基于注释的配置的引入引发了这种方法是否比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注释提供与自动装配协作者中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,例如 @PostConstruct和@PreDestroy。Spring 3.0增加了对javax.inject包中包含的JSR-330(Java的依赖注入)注释的支持,例如@Inject 和@Named。有关这些注释的详细信息,请参阅 相关章节。

注释注入在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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

</beans>
复制代码

在隐式注册后处理器包括 AutowiredAnnotationBeanPostProcessor, CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor,和前面提到的 RequiredAnnotationBeanPostProcessor。)

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

1.9.1 @Required

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

public class SimpleMovieLister {

private MovieFinder movieFinder;

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

// ...
}
复制代码

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

从@RequiredSpring Framework 5.1开始,注释正式被弃用,支持使用构造函数注入所需的设置(或者InitializingBean.afterPropertiesSet()bean属性setter方法的自定义实现 )。

在本节包含的示例中,JSR 330的@inject注释可以代替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;
    }

    // ...
}

复制代码

您还可以ApplicationContext 通过将注释添加到需要该类型数组的字段或方法来提供特定类型的所有bean ,如以下示例所示:

public class MovieRecommender {

    @Autowired
    private MovieCatalog[] movieCatalogs;

    // ...
}

复制代码

您的目标bean可以实现org.springframework.core.ordered接口,或者如果您希望数组或列表中的项按特定顺序排序,可以使用@order或standard@priority注释。否则,它们的顺序遵循容器中相应目标bean定义的注册顺序。 您可以@Order在目标类级别和@Bean方法上声明注释,可能是通过单个bean定义(在多个定义使用相同bean类的情况下)。@Order值可能会影响注入点的优先级,但要注意它们不会影响单例启动顺序,这是由依赖关系和@DependsOn声明确定的正交关注点。

请注意,标准javax.annotation.Priority注释在该@Bean级别不可用 ,因为它无法在方法上声明。它的语义可以通过@Order值与@Primary每种类型的单个bean 相结合来建模。

Map只要预期的密钥类型是,即使是类型化的实例也可以自动装配String。Map值包含所有期望类型的bean,并且键包含相应的bean名称,如以下示例所示:

public class MovieRecommender {

    private Map<String, MovieCatalog> movieCatalogs;

    @Autowired
    public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
        this.movieCatalogs = movieCatalogs;
    }

    // ...
}
复制代码

默认情况下,当没有匹配的候选bean可用于给定的注入点时,自动布线将失败。对于声明的数组、集合或映射,至少需要一个匹配元素。

默认行为是将带注释的方法和字段视为指示所需的依赖项。您可以更改此行为,如以下示例所示,使框架能够通过将其标记为非必需来跳过不可满足的注入点:

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

    // ...
}
复制代码

如果不可用的依赖项(或多个参数情况下的依赖项之一),则根本不会调用非必需的方法。在这种情况下,根本不会填充非必填字段,保留其默认值。

注入的构造函数和工厂方法参数是一种特殊情况,因为@Autowired由于Spring的构造函数解析算法可能涉及多个构造函数,因此'required'标志的含义有些不同。默认情况下有效地需要构造函数和工厂方法参数,但在单构造函数场景中有一些特殊规则,例如,如果没有匹配的bean可用,则解析为空实例的多元素注入点(数组,集合,映射)。这允许一个通用的实现模式,其中所有依赖项都可以在一个唯一的多参数构造函数中声明,例如声明为没有@Autowired注释的单个公共构造函数。

每个类只能标记一个带注释的构造函数,但可以注释多个非必需的构造函数。在这种情况下,每个都被认为是候选者之一,Spring使用最贪婪的构造函数,其依赖性可以得到满足 - 也就是说,具有最多参数的构造函数。构造函数解析算法与具有重载构造函数的非注释类相同,只是将候选者缩小到带注释的构造函数。

建议使用“必需”属性而@Autowired不是@Requiredsetter方法的注释。“required”属性表示该属性不是自动装配所必需的。如果无法自动装配,则会忽略该属性。@Required另一方面,它更强大,因为它强制通过容器支持的任何方式设置属性。如果未定义任何值,则会引发相应的异常。

或者,您可以通过Java 8表达特定依赖关系的非必需特性java.util.Optional,如以下示例所示:

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(Optional<MovieFinder> movieFinder) {
        ...
    }
}
复制代码

从Spring Framework 5.0开始,您还可以使用@Nullable注释(任何包中的任何类型的注释 - 例如,javax.annotation.Nullable来自JSR-305):

public class SimpleMovieLister {

    @Autowired
    public void setMovieFinder(@Nullable MovieFinder movieFinder) {
        ...
    }
}
复制代码

您还可以使用@Autowired对于那些众所周知的解析依赖接口:BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource。这些接口及其扩展接口(如ConfigurableApplicationContext或ResourcePatternResolver)会自动解析,无需特殊设置。以下示例自动装配一个ApplicationContext对象:

public class MovieRecommender {

    @Autowired
    private ApplicationContext context;

    public MovieRecommender() {
    }

    // ...
}

复制代码

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

1.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定义如下:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog" primary="true">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>
复制代码

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

@Primary当可以确定一个主要候选者时,是通过具有多个实例的类型使用自动装配的有效方式。当您需要更多控制选择过程时,可以使用Spring的@Qualifier注释。您可以将限定符值与特定参数相关联,缩小类型匹配集,以便为每个参数选择特定的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;
    }

    // ...
}
复制代码

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

@Primary当可以确定一个主要候选者时,是通过具有多个实例的类型使用自动装配的有效方式。当您需要更多控制选择过程时,可以使用Spring的@Qualifier注释。您可以将限定符值与特定参数相关联,缩小类型匹配集,以便为每个参数选择特定的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定义。

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="main"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier value="action"/> 

        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

复制代码
具有main限定符值的bean与使用相同值限定的构造函数参数连接。
具有action限定符值的bean与使用相同值限定的构造函数参数连接。

对于回退匹配,bean名称被视为默认限定符值。因此,可以用一个定义bean idmain代替嵌套限定符元素,导致相同的匹配结果。但是,尽管您可以使用此约定来按名称引用特定bean,但@Autowired基本上是关于具有可选语义限定符的类型驱动注入。这意味着即使使用bean名称回退,限定符值在类型匹配集中也总是具有缩小的语义。它们在语义上不表示对唯一bean的引用id。良好限定的值是mainEMEApersistent,表达独立于从所述豆的特定部件的特性id,在匿名bean定义的情况下可以自动生成,例如前面例子中的定义。

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

在类型匹配候选项中,根据目标bean名称选择限定符值,不需要@Qualifier注入点处的注释。如果没有其他解析指示符(例如限定符或主要标记),则对于非唯一依赖性情况,Spring会将注入点名称(即字段名称或参数名称)与目标bean名称进行匹配,然后选择同名的候选人,如果有的话。

也就是说,如果您打算按名称表达注释驱动的注入,请不要主要使用@Autowired,即使它能够在类型匹配候选项中通过bean名称进行选择。相反,使用JSR-250 @Resource注释,该注释在语义上定义为通过其唯一名称标识特定目标组件,声明的类型与匹配过程无关。@Autowired具有相当不同的语义:在按类型选择候选bean之后,String 仅在那些类型选择的候选中考虑指定的限定符值(例如,将account限定符与标记有相同限定符标签的bean 匹配)。

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

从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定义的信息。您可以将<qualifier/>标记添加为 标记的子元素,<bean/>然后指定typevalue匹配自定义限定符注释。类型与注释的完全限定类名匹配。或者,为方便起见,如果不存在冲突名称的风险,您可以使用短类名称。以下示例演示了这两种方法:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="Genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="example.Genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean id="movieRecommender" class="example.MovieRecommender"/>

</beans>

复制代码

类路径扫描和托管组件中,您可以看到基于注释的替代方法,即在XML中提供限定符元数据。具体来说,请参阅使用注释提供限定符元数据

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

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

}

复制代码

然后将注释添加到要自动装配的字段或属性中,如以下示例所示:

public class MovieRecommender {

    @Autowired
    @Offline 
    private MovieCatalog offlineCatalog;

    // ...
}

复制代码

现在bean定义只需要一个限定符type,如下例所示:

<bean class="example.SimpleMovieCatalog">
    <qualifier type="Offline"/> 
    <!-- inject any dependencies required by this bean -->
</bean>

复制代码

您还可以定义除简单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
}

复制代码

要自动装配的字段使用自定义限定符进行注释,并包含两个属性的值:genre并且format,如以下示例所示:

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元属性而不是 <qualifier/>元素。如果可用,则<qualifier/>元素及其属性优先,但<meta/>如果不存在此类限定符,则自动装配机制将回退到标记内提供的值 ,如以下示例中的最后两个bean定义:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:annotation-config/>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Action"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <qualifier type="MovieQualifier">
            <attribute key="format" value="VHS"/>
            <attribute key="genre" value="Comedy"/>
        </qualifier>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="DVD"/>
        <meta key="genre" value="Action"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

    <bean class="example.SimpleMovieCatalog">
        <meta key="format" value="BLURAY"/>
        <meta key="genre" value="Comedy"/>
        <!-- inject any dependencies required by this bean -->
    </bean>

</beans>
复制代码

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

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

@Configuration
public class MyConfiguration {

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

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

复制代码

假设前面的bean实现了一个通用接口(即Store<String>和, Store<Integer>),您可以@AutowireStore接口和泛型用作限定符,如下例所示:

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

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

复制代码

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

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

@Configuration
public class MyConfiguration {

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

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

复制代码

假设前面的bean实现了一个通用接口(即Store<String>和, Store<Integer>),您可以@AutowireStore接口和泛型用作限定符,如下例所示:

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

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

复制代码

通用限定符也适用于自动装配列表,Map实例和数组。以下示例自动装配通用List

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

复制代码

1.9.6。运用CustomAutowireConfigurer

CustomAutowireConfigurer 是一个BeanFactoryPostProcessor允许您注册自己的自定义限定符注释类型的,即使它们没有使用Spring的@Qualifier注释进行注释。以下示例显示如何使用CustomAutowireConfigurer

<bean id="customAutowireConfigurer"
        class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
    <property name="customQualifierTypes">
        <set>
            <value>example.CustomQualifier</value>
        </set>
    </property>
</bean>

复制代码

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

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

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

1.9.7。注射用@Resource

Spring还通过在字段或bean属性setter方法上使用JSR-250 @Resourceannotation(javax.annotation.Resource)来支持注入。这是Java EE中的常见模式:例如,在JSF管理的bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。

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

public class SimpleMovieLister {

    private MovieFinder movieFinder;

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

复制代码

这条线注入了一个@Resource

如果未明确指定名称,则默认名称是从字段名称或setter方法派生的。如果是字段,则采用字段名称。在setter方法的情况下,它采用bean属性名称。下面的例子将把bean movieFinder注入其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并解决众所周知的解析依存关系:BeanFactoryApplicationContextResourceLoaderApplicationEventPublisher,和MessageSource 接口。

因此,在以下示例中,customerPreferenceDao字段首先查找名为“customerPreferenceDao”的bean,然后返回到该类型的主要类型匹配 CustomerPreferenceDao

public class MovieRecommender {

    @Resource
    private CustomerPreferenceDao customerPreferenceDao;

    @Resource
    private ApplicationContext context; 

    public MovieRecommender() {
    }

    // ...
}
复制代码

context根据已知的可解析依赖类型注入该字段: ApplicationContext

1.9.8。使用@PostConstruct@PreDestroy

CommonAnnotationBeanPostProcessor不仅承认了@Resource注解也是JSR-250的生命周期注解:javax.annotation.PostConstructjavax.annotation.PreDestroy。在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...
    }
}

复制代码

有关组合各种生命周期机制的效果的详细信息,请参阅 组合生命周期机制

例如@Resource@PostConstruct@PreDestroy注释类型是JDK 6到8的标准Java库的一部分。但是,整个javax.annotation 包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api工件需要是现在通过Maven Central获得,只需像任何其他库一样添加到应用程序的类路径中。


1.10 1.11再补


1.12 基于Java的容器配置

本节介绍如何在Java代码中使用注释来配置Spring容器。它包括以下主题:

1.12.1。基本概念:@Bean@Configuration

Spring的新Java配置支持中的中心工件是 @Configuration注释类和@Bean注释方法。

@Bean注释被用于指示一个方法实例,配置和初始化为通过Spring IoC容器进行管理的新对象。对于那些熟悉Spring的<beans/>XML配置的人来说,@Bean注释与<bean/>元素扮演的角色相同。你可以@Bean在任何Spring中使用-annotated方法 @Component。但是,它们最常用于@Configuration bean类。

对类进行注释@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>

复制代码

完整@Configuration vs“lite”@Bean模式?

@Bean在未注释的类中声明方法时 @Configuration,它们被称为以“精简”模式处理。在一个@Component或甚至在一个普通的旧类中声明的Bean方法被认为是“精简”,包含类的主要目的不同,并且@Bean方法在那里是一种奖励。例如,服务组件可以通过@Bean每个适用组件类的附加方法将管理视图公开给容器。在这种情况下,@Bean方法是通用的工厂方法机制。

与full不同@Configuration,lite @Bean方法不能声明bean间依赖关系。相反,它们对其包含组件的内部状态进行操作,并且可选地,对它们可以声明的参数进行操作。@Bean因此,这种方法不应该引用其他 @Bean方法。每个这样的方法实际上只是特定bean引用的工厂方法,没有任何特殊的运行时语义。这里的积极副作用是不必在运行时应用CGLIB子类,因此在类设计方面没有限制(也就是说,包含类可能是final等等)。

在常见的场景中,@Bean方法将在@Configuration类中声明,确保始终使用“完整”模式,并因此将交叉方法引用重定向到容器的生命周期管理。这可以防止@Bean通过常规Java调用意外地调用相同的 方法,这有助于减少在“精简”模式下操作时难以跟踪的细微错误。

@Bean@Configuration注解的深度在以下章节中讨论。首先,我们将介绍使用基于Java的配置创建弹簧容器的各种方法。

@Bean@Configuration注解的深度在以下章节中讨论。首先,我们将介绍使用基于Java的配置创建弹簧容器的各种方法。

1.12.2 使用实例化Spring容器AnnotationConfigApplicationContext

以下部分AnnotationConfigApplicationContext介绍了在Spring 3.0中引入的Spring。这种通用ApplicationContext实现不仅能够接受@Configuration类作为输入,还能接受 @Component使用JSR-330元数据注释的普通类和类。

@Configuration提供类作为输入时,@Configuration类本身被注册为bean定义,并且@Bean类中的所有声明的方法也被注册为bean定义。

@Component提供JSR-330类时,它们被注册为bean定义,并且假定DI元数据例如@Autowired@Inject在必要时在这些类中使用。

简单的施工

与实例化a时Spring XML文件用作输入的方式大致相同 ClassPathXmlApplicationContext,可以@Configuration在实例化时使用类作为输入AnnotationConfigApplicationContext。这允许完全无XML使用Spring容器,如以下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

复制代码

如前所述,AnnotationConfigApplicationContext并不仅限于使用@Configuration类。@Component可以将任何或JSR-330带注释的类作为输入提供给构造函数,如以下示例所示:

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

复制代码

前面的例子中假定MyServiceImplDependency1以及Dependency2使用Spring依赖注入注解,例如@Autowired

用编程方式构建容器 register(Class<?>…​)

您可以AnnotationConfigApplicationContext使用no-arg构造函数实例化一个,然后使用该register()方法对其进行配置。这种方法在以编程方式构建时特别有用AnnotationConfigApplicationContext。以下示例显示了如何执行此操作:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.register(AppConfig.class, OtherConfig.class);
    ctx.register(AdditionalConfig.class);
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}
复制代码
使用启用组件扫描 scan(String…​)

要启用组件扫描,您可以@Configuration按如下方式注释您的类:

@Configuration
@ComponentScan(basePackages = "com.acme") 
public class AppConfig  {
    ...
}
复制代码

此注释可启用组件扫描。

有经验的Spring用户可能熟悉与Spring context:命名空间等效的XML声明,如下例所示:

<beans>
    <context:component-scan base-package="com.acme"/>
</beans>
复制代码

在前面的示例中,com.acme扫描包以查找任何已 @Component注释的类,并将这些类注册为容器中的Spring bean定义。AnnotationConfigApplicationContext公开 scan(String…​)方法以允许相同的组件扫描功能,如以下示例所示:

public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
    ctx.scan("com.acme");
    ctx.refresh();
    MyService myService = ctx.getBean(MyService.class);
}

复制代码

请记住,@Configuration类是元注释@Component,因此它们是组件扫描的候选者。在前面的示例中,假设AppConfigcom.acme包(或下面的任何包)中声明了它,它在调用期间被拾取scan()。之后refresh(),它的所有@Bean 方法都被处理并在容器中注册为bean定义。

支持Web应用程序 AnnotationConfigWebApplicationContext

可用的WebApplicationContext变体。在配置Spring servlet侦听器,Spring MVC等时 ,可以使用此实现。以下代码段配置典型的Spring MVC Web应用程序(请注意context-param和init-param的使用):AnnotationConfigApplicationContext``AnnotationConfigWebApplicationContext``ContextLoaderListener``DispatcherServlet``web.xml``contextClass

    <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext
        instead of the default XmlWebApplicationContext -->
    <context-param>
        <param-name>contextClass</param-name>
        <param-value>
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext
        </param-value>
    </context-param>

    <!-- Configuration locations must consist of one or more comma- or space-delimited
        fully-qualified @Configuration classes. Fully-qualified packages may also be
        specified for component-scanning -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>com.acme.AppConfig</param-value>
    </context-param>

    <!-- Bootstrap the root application context as usual using ContextLoaderListener -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- Declare a Spring MVC DispatcherServlet as usual -->
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext
            instead of the default XmlWebApplicationContext -->
        <init-param>
            <param-name>contextClass</param-name>
            <param-value>
                org.springframework.web.context.support.AnnotationConfigWebApplicationContext
            </param-value>
        </init-param>
        <!-- Again, config locations must consist of one or more comma- or space-delimited
            and fully-qualified @Configuration classes -->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>com.acme.web.MvcConfig</param-value>
        </init-param>
    </servlet>

    <!-- map all requests for /app/* to the dispatcher servlet -->
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>
</web-app>

复制代码

1.12.3。使用@Bean注释

@Bean是方法级注释和XML <bean/>元素的直接模拟。注释支持一些提供的属性<bean/>,例如:* init-method * destroy-method * autowiring * name

您可以在带@Bean注释的类@Configuration或带 注释的类中使用注释@Component

声明一个Bean

要声明bean,可以使用注释注释方法@Bean。您可以使用此方法在ApplicationContext指定为方法的返回值的类型中注册bean定义。默认情况下,bean名称与方法名称相同。以下示例显示了@Bean方法声明:

@Configuration
public class AppConfig {

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

复制代码

上述配置与以下Spring XML完全等效:

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

复制代码

这两个声明都将一个名为transferServiceavailable 的bean命名为ApplicationContext绑定到类型的对象实例,TransferServiceImpl如下图所示:

transferService - > com.acme.TransferServiceImpl

您还可以@Bean使用接口(或基类)返回类型声明您的方法,如以下示例所示:

@Configuration
public class AppConfig {

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

复制代码

但是,这会将高级类型预测的可见性限制为指定的接口类型(TransferService)。然后,TransferServiceImpl只有容器已知的完整类型()一次,已经实例化了受影响的单例bean。非延迟单例bean根据其声明顺序进行实例化,因此您可能会看到不同的类型匹配结果,具体取决于另一个组件何时尝试通过非声明类型进行匹配(例如@Autowired TransferServiceImpl,只有transferService在实例化bean之后才会解析)。

如果您始终通过声明的服务接口引用您的类型,则您的 @Bean返回类型可以安全地加入该设计决策。但是,对于实现多个接口的组件或可能由其实现类型引用的组件,更可能声明可能的最具体的返回类型(至少与引用您的bean的注入点所需的具体相同)。

Bean依赖项

@Bean注释的方法可以有任意数量的参数来描述构建该bean所需的依赖关系。例如,如果我们TransferService 需要a AccountRepository,我们可以使用方法参数来实现该依赖关系,如下例所示:

@Configuration
public class AppConfig {

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

复制代码

解析机制与基于构造函数的依赖注入非常相似。有关详细信息,请参阅相关部分

接收生命周期回调

使用@Bean注释定义的任何类都支持常规生命周期回调,并且可以使用JSR-250中的注释@PostConstruct@PreDestroy注释。有关更多详细信息,请参阅 JSR-250注释

完全支持常规的Spring 生命周期回调。如果bean实现InitializingBeanDisposableBean或者Lifecycle它们各自的方法由容器调用。

还完全支持标准*Aware接口集(例如BeanFactoryAwareBeanNameAwareMessageSourceAwareApplicationContextAware等)。

@Bean注释支持指定任意初始化和销毁回调方法,就像春天XML的init-method,并destroy-method在属性上的bean元素,如下例所示:

public class BeanOne {

    public void init() {
        // initialization logic
    }
}

public class BeanTwo {

    public void cleanup() {
        // destruction logic
    }
}

@Configuration
public class AppConfig {

    @Bean(initMethod = "init")
    public BeanOne beanOne() {
        return new BeanOne();
    }

    @Bean(destroyMethod = "cleanup")
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}

复制代码

默认情况下,使用Java配置定义的具有public closeshutdown method的bean 会自动使用销毁回调登记。如果您有公共 closeshutdown方法,并且您不希望在容器关闭时调用它,则可以添加@Bean(destroyMethod="")到bean定义以禁用默认(inferred)模式。

对于使用JNDI获取的资源,您可能希望默认执行此操作,因为其生命周期在应用程序之外进行管理。特别是,确保始终为a执行此操作DataSource,因为已知它在Java EE应用程序服务器上存在问题。

以下示例显示如何防止自动销毁回调 DataSource

@Bean(destroyMethod="")
public DataSource dataSource() throws NamingException {
 return (DataSource) jndiTemplate.lookup("MyDS");
}

复制代码

此外,使用@Bean方法,您通常使用编程JNDI查找,通过使用Spring JndiTemplateJndiLocatorDelegate帮助程序或直接JNDI InitialContext用法但不使用JndiObjectFactoryBean变量(这将强制您将返回类型声明为FactoryBean类型而不是实际目标类型,使得更难以用于其他@Bean打算在此处引用所提供资源的方法中的交叉引用调用。

BeanOne前面注释中的示例的情况下,init() 在构造期间直接调用该方法同样有效,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        BeanOne beanOne = new BeanOne();
        beanOne.init();
        return beanOne;
    }

    // ...
}

复制代码

当您直接使用Java工作时,您可以使用对象执行任何您喜欢的操作,并且不必总是依赖于容器生命周期。

指定Bean范围

Spring包含@Scope注释,以便您可以指定bean的范围。

使用@Scope注释

您可以指定使用@Bean注释定义的bean 应具有特定范围。您可以使用Bean Scopes部分中指定的任何标准作用域 。

默认范围是singleton,但您可以使用@Scope注释覆盖它,如以下示例所示:

@Configuration
public class MyConfiguration {

    @Bean
    @Scope("prototype")
    public Encryptor encryptor() {
        // ...
    }
}

复制代码
@Scopescoped-proxy

Spring提供了一种通过作用域代理处理作用域依赖项的便捷方法 。使用XML配置时创建此类代理的最简单方法是<aop:scoped-proxy/>元素。使用@Scope注释在Java中配置bean 提供了对该proxyMode属性的等效支持。默认值为no proxy(ScopedProxyMode.NO),但您可以指定ScopedProxyMode.TARGET_CLASSScopedProxyMode.INTERFACES

如果将scoped代理示例从XML参考文档(请参阅范围代理)移植 到@Bean使用Java,它类似于以下内容:

// an HTTP Session-scoped bean exposed as a proxy
@Bean
@SessionScope
public UserPreferences userPreferences() {
    return new UserPreferences();
}

@Bean
public Service userService() {
    UserService service = new SimpleUserService();
    // a reference to the proxied userPreferences bean
    service.setUserPreferences(userPreferences());
    return service;
}

复制代码
自定义Bean命名

默认情况下,配置类使用@Bean方法的名称作为结果bean的名称。但是,可以使用name属性覆盖此功能,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean(name = "myThing")
    public Thing thing() {
        return new Thing();
    }
}

复制代码
Bean Aliasing 别名

正如Naming Beans中所讨论的,有时需要为单个bean提供多个名称,也称为bean别名。 为此目的name@Bean注释的属性接受String数组。以下示例显示如何为bean设置多个别名:

@Configuration
public class AppConfig {

    @Bean({"dataSource", "subsystemA-dataSource", "subsystemB-dataSource"})
    public DataSource dataSource() {
        // instantiate, configure and return DataSource bean...
    }
}

复制代码
Bean描述

有时,提供更详细的bean文本描述会很有帮助。当bean(可能通过JMX)进行监视时,这可能特别有用。

要向a添加描述@Bean,可以使用 @Description 注释,如以下示例所示:

@Configuration
public class AppConfig {

    @Bean
    @Description("Provides a basic example of a bean")
    public Thing thing() {
        return new Thing();
    }
}

复制代码

1.12.4。使用@Configuration注释

@Configuration是一个类级别的注释,指示对象是bean定义的来源。@Configurationclasses通过公共@Bean注释方法声明bean 。@Bean@Configuration类上的方法的调用也可用于定义bean间依赖项。请参阅基本概念:@Bean@Configuration一般性介绍。

注入bean间依赖关系

当bean彼此依赖时,表达该依赖关系就像让一个bean方法调用另一个bean一样简单,如下例所示:

@Configuration
public class AppConfig {

    @Bean
    public BeanOne beanOne() {
        return new BeanOne(beanTwo());
    }

    @Bean
    public BeanTwo beanTwo() {
        return new BeanTwo();
    }
}
复制代码

在前面的示例中,beanOne接收对beanTwo构造函数注入的引用。

这种声明bean间依赖关系的@Bean方法只有在@Configuration类中声明方法时才有效。您不能使用普通@Component类声明bean间依赖项。

这种声明bean间依赖关系的@Bean方法只有在@Configuration类中声明方法时才有效。您不能使用普通@Component类声明bean间依赖项。

查找方法注入

如前所述,查找方法注入是一项很少使用的高级功能。在单例范围的bean依赖于原型范围的bean的情况下,它很有用。将Java用于此类配置提供了实现此模式的自然方法。以下示例显示如何使用查找方法注入:

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();
}

复制代码

通过使用Java配置,您可以创建一个子类,CommandManager其中抽象createCommand()方法被覆盖,以便查找新的(原型)命令对象。以下示例显示了如何执行此操作:

@Bean
@Scope("prototype")
public AsyncCommand asyncCommand() {
    AsyncCommand command = new AsyncCommand();
    // inject dependencies here as required
    return command;
}

@Bean
public CommandManager commandManager() {
    // return new anonymous implementation of CommandManager with createCommand()
    // overridden to return a new prototype Command object
    return new CommandManager() {
        protected Command createCommand() {
            return asyncCommand();
        }
    }
}

复制代码
有关基于Java的配置如何在内部工作的更多信息

请考虑以下示例,该示例显示了@Bean两次调用的带注释的方法:

@Configuration
public class AppConfig {

    @Bean
    public ClientService clientService1() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientService clientService2() {
        ClientServiceImpl clientService = new ClientServiceImpl();
        clientService.setClientDao(clientDao());
        return clientService;
    }

    @Bean
    public ClientDao clientDao() {
        return new ClientDaoImpl();
    }
}

复制代码

clientDao()被称为一次进入clientService1()和进入一次clientService2()。由于此方法创建了一个新实例ClientDaoImpl并将其返回,因此通常需要两个实例(每个服务一个)。这肯定会有问题:在Spring中,实例化的bean singleton默认具有范围。这就是魔术的用武之地:所有@Configuration类都在启动时被子类化CGLIB。在子类中,子方法在调用父方法并创建新实例之前,首先检查容器是否有任何缓存(作用域)bean。

从Spring 3.2开始,不再需要将CGLIB添加到类路径中,因为CGLIB类已经重新打包org.springframework.cglib并直接包含在spring-core JAR中。

由于CGLIB在启动时动态添加功能,因此存在一些限制。特别是,配置类不能是最终的。但是,从4.3开始,配置类允许使用任何构造函数,包括使用 @Autowired默认注入的单个非默认构造函数声明。

如果您希望避免任何CGLIB强加的限制,请考虑@Bean 在非@Configuration类上声明您的方法(例如,在普通@Component类上)。@Bean然后拦截方法之间的跨方法调用,因此您必须完全依赖于构造函数或方法级别的依赖注入。

1.12.5。编写基于Java的配置

@Configuration
public class ConfigA {

    @Bean
    public A a() {
        return new A();
    }
}

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

    @Bean
    public B b() {
        return new B();
    }
}

复制代码

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

public static void main(String[] args) {
    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:这意味着它们可以利用 @Autowired@Value注入以及与任何其他bean相同的其他功能。

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

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

以下示例显示了如何将一个bean自动连接到另一个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是否仅定义了一个构造函数。在前面的示例中,构造函数@Autowired上没有必要RepositoryConfig

完全符合条件的进口豆类,便于导航

在前面的场景中,使用@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类及其依赖关系与导航基于接口的代码的常规过程没有什么不同。

如果要影响某些bean的启动创建顺序,可以考虑将它们中的一些声明为@Lazy(用于在第一次访问时创建而不是在启动时)或@DependsOn某些其他bean(确保在当前bean之前创建特定的其他bean,超出后者的直接依赖意味着什么)。

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

基于某些任意系统状态,有条件地启用或禁用完整@Configuration类或甚至单个@Bean方法通常很有用。一个常见的例子是@Profile只有在Spring中启用了特定的配置文件时才使用注释激活bean Environment( 有关详细信息,请参阅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<String, Object> 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;
}
复制代码

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

结合Java和XML配置

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

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

最好从XML引导Spring容器并@Configuration以ad-hoc方式包含 类。例如,在使用Spring XML的大型现有代码库中,可以@Configuration根据需要更轻松地创建类,并将其包含在现有XML文件中。在本节的后面部分,我们将介绍@Configuration在这种“以XML为中心”的情况下使用类的选项。

@Configuration类声明为普通的Spring <bean/>元素

请记住,@Configuration类最终是容器中的bean定义。在本系列示例中,我们创建了一个@Configuration名为的类,AppConfig并将其system-test-config.xml作为<bean/>定义包含在其中。因为 <context:annotation-config/>已打开,容器会识别@Configuration注释并 正确处理@Bean声明的方法AppConfig

以下示例显示了Java中的普通配置类:

@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文件的一部分:

<beans>
    <!-- enable processing of annotations such as @Autowired and @Configuration -->
    <context:annotation-config/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="com.acme.AppConfig"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>
复制代码

以下示例显示了一个可能的jdbc.properties文件:

jdbc.url = JDBC:HSQLDB:HSQL://本地主机/ 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);
    // ...
}

复制代码

使用<context:component-scan />来获取@Configuration

因为@Configuration带有元注释@Component,注释@Configuration类自动成为组件扫描的候选者。使用与前一个示例中描述的相同的方案,我们可以重新定义system-test-config.xml以利用组件扫描。请注意,在这种情况下,我们不需要显式声明<context:annotation-config/>,因为<context:component-scan/>启用相同的功能。

以下示例显示了已修改的system-test-config.xml文件:

<beans>
    <!-- picks up and registers AppConfig as a bean definition -->
    <context:component-scan base-package="com.acme"/>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>

    <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
</beans>

复制代码
@Configuration 以类为中心的XML使用 @ImportResource

@Configuration类是配置容器的主要机制的应用程序中,仍然可能需要使用至少一些XML。在这些场景中,您可以@ImportResource根据需要使用和定义尽可能多的XML。这样做可以实现“以Java为中心”的方法来配置容器并将XML保持在最低限度。以下示例(包括配置类,定义bean的XML文件,属性文件和main类)显示了如何使用@ImportResource注释来实现根据需要使用XML的“以Java为中心”的配置:

@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
<beans>
    <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/>
</beans>
复制代码

jdbc.properties jdbc.url = JDBC:HSQLDB:HSQL://本地主机/ XDB jdbc.username = SA jdbc.password =

public static void main(String[] args) {
    ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    TransferService transferService = ctx.getBean(TransferService.class);
    // ...
}
复制代码

转载于:https://juejin.im/post/5d03b0c2f265da1b695d5b9b

猜你喜欢

转载自blog.csdn.net/weixin_34248258/article/details/93169532