- 2.9.1. @Required
- 2.9.2. 使用@Autowired
- 2.9.3. 使用@Primary微调基于注释的自动装配
- 2.9.4. 微调带有限定符的基于注释的自动装配
- 2.9.5.使用泛型作为自动装配限定符
- 2.9.6. 使用 CustomAutowireConfigurer
- 2.9.7. 注射用@Resource
- 2.9.8. 使用 @Value
- 2.9.9. 使用 @PostConstruct and @PreDestroy
在配置 Spring 方面,注解是否比 XML 更好?
引入基于注释的配置提出了这样一个问题:这种方法是否比XML“更好”。简短的回答是“视情况而定”。
长期的答案是,每种方法都有其优点和缺点,通常,由开发人员决定哪种策略更适合他们。由于注释的定义方式,注释在其声明中提供了大量上下文,从而导致配置更简短、更简洁。然而,XML擅长连接组件,而无需修改它们的源代码或重新编译它们。一些开发人员更喜欢接近源代码进行连接,而另一些开发人员则认为带注释的类不再是pojo,而且配置变得分散且更难控制。
无论哪种选择,Spring都可以容纳这两种风格,甚至将它们混合在一起。值得指出的是,通过它的JavaConfig选项,Spring允许以一种非侵入性的方式使用注释,而不涉及目标组件的源代码,而且,就工具而言,所有配置样式都由用于Eclipse的Spring Tools支持。
基于注释的配置提供了XML设置的另一种选择,它依赖于连接组件的字节码元数据,而不是尖括号声明。开发人员不使用XML来描述bean连接,而是通过在相关类、方法或字段声明上使用注释将配置转移到组件类本身。正如在示例:AutowiredAnnotationBeanPostProcessor中所提到的,将BeanPostProcessor与注释结合使用是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注释强制执行必需属性的可能性。Spring 2.5使遵循同样的通用方法来驱动Spring的依赖注入成为可能。本质上,@Autowired注释提供了与Autowiring collaborator中描述的相同的功能,但具有更细粒度的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,比如@PostConstruct和@PreDestroy。Spring 3.0增加了对javax中包含的JSR-330 (Java依赖注入)注释的支持。例如@Inject和@Named。有关这些注释的详细信息可以在相关部分找到。
注意:
注释注入在XML注入之前执行。因此,XML配置覆盖了通过两种方法连接的属性的注释。
和往常一样,您可以将post-processor注册为单个bean定义,但也可以通过在基于xml的Spring配置中包含以下标记来隐式注册它们(注意包含了上下文名称空间):
<?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>
context:annotation-config/元素隐式注册以下后处理器:
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EventListenerMethodProcessor
注意:
context:annotation-config/只在定义它的应用程序上下文中查找bean上的注释。这意味着,如果您将context:annotation-config/放在DispatcherServlet的WebApplicationContext中,它只检查您的控制器中的@Autowired bean,而不是您的服务。更多信息请参见DispatcherServlet。
2.9.1. @Required
@Required注释应用于bean属性设置方法,如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
此注释指出,必须在配置时通过bean定义中的显式属性值或通过自动装配来填充受影响的bean属性。如果未填充受影响的bean属性,容器将抛出异常。这允许紧急和显式的失败,避免以后出现NullPointerException或类似的实例。我们仍然建议将断言放入bean类本身(例如,放入init方法)。
这样做将强制执行那些必需的引用和值,即使在容器外部使用该类时也是如此。
必须将RequiredAnnotationBeanPostProcessor注册为一个bean,以支持@Required注释。
注意:
@ required注释和RequiredAnnotationBeanPostProcessor正式弃用Spring框架5.1,使用构造函数注入所需设置的(或一个自定义实现InitializingBean.afterPropertiesSet()或一个自定义@PostConstruct method)和bean属性setter方法)。
2.9.2. 使用@Autowired
注意
在本节包含的示例中,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开始,如果目标bean只定义一个构造函数开始,就不再需要在这样的构造函数上使用@Autowired注释。但是,如果有几个构造函数可用,并且没有主/默认构造函数,那么至少有一个构造函数必须使用@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;
}
// ...
}
你也可以将@Autowired应用到字段中,甚至将其与构造函数混合使用,如下面的示例所示:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
确保您的目标组件(例如MovieCatalog或CustomerPreferenceDao)始终是由您用于@ autowiredannotated注入点的类型声明的。否则,注入可能会在运行时由于“没有找到类型匹配”错误而失败。
对于通过类路径扫描找到的xml定义的bean或组件类,容器通常事先知道具体的类型。然而,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达性。对于实现多个接口的组件,或者对于可能由其实现类型引用的组件,考虑在工厂方法上声明最特定的返回类型(至少按照引用bean的注入点所要求的特定类型)。
您还可以通过向期望该类型数组的字段或方法添加@Autowired注释,指示Spring从ApplicationContext提供特定类型的所有bean,如下例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这同样适用于类型化集合,如下例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果希望数组或列表中的项按特定顺序排序,您的目标bean可以实现org.springframework.core.Ordered接口,或者使用@Order或标准@Priority注释。否则,它们的顺序遵循容器中相应目标bean定义的注册顺序。
您可以在目标类级别和@Bean方法上声明@Order注释,可能是针对单个bean定义(在多个定义使用同一bean类的情况下)。@Order值可能会影响注入点的优先级,但请注意,它们不会影响单例启动顺序,单例启动顺序是由依赖关系和@DependsOn声明决定的正交问题。
注意,标准javax.annotation.Priority注释在@Bean级别不可用,因为它不能在方法上声明。它的语义可以通过在每个类型的单个bean上结合@Order值和@Primary来建模。
即使是类型化的Map实例也可以自动连接,只要预期的键类型是String。映射值包含所有期望类型的bean,键包含相应的bean名称,如下例所示:
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,当给定注入点没有匹配的候选bean可用时,自动装配将失败。对于声明的数组、集合或映射,至少需要一个匹配的元素。
默认的行为是将注释的方法和字段视为必需的依赖项。你可以像下面的例子中演示的那样改变这个行为,通过将一个非必需的注入点标记为非必需的,从而使框架跳过一个不可满足的注入点(例如,通过将@Autowired中的required属性设置为false):
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么它根本不会被调用。在这种情况下,根本不会填充非必需字段,只保留其默认值。
注入构造函数和工厂方法参数是一种特殊情况,因为@Autowired中的required属性有一些不同的含义,因为Spring的构造函数解析算法可能会处理多个构造函数。默认情况下,构造函数和工厂方法参数实际上是必需的,但在单构造函数场景中有一些特殊规则,比如如果没有匹配的bean可用,多元素注入点(数组、集合、映射)解析为空实例。这允许使用一种通用的实现模式,在这种模式下,所有依赖项都可以在一个唯一的多参数构造函数中声明——例如,声明为一个没有@Autowired注释的公共构造函数。
注意:
任何给定bean类只有一个构造函数可以声明@Autowired,并将所需属性设置为true,这表明作为Spring bean使用时要自动连接的构造函数。因此,如果required属性保留其默认值true,则只有一个构造函数可以使用@Autowired进行注释。如果多个构造函数声明了注释,那么它们都必须声明required=false,以便被认为是自动装配的候选对象(类似于XML中的autotowire =constructor)。
将选择具有最多依赖项的构造函数,这些依赖项可以通过在Spring容器中匹配bean来满足。如果所有候选函数都不满意,那么将使用主/默认构造函数(如果存在)。类似地,如果一个类声明了多个构造函数,但是没有一个构造函数被@Autowired注释,那么将使用主/默认构造函数(如果存在)。如果一个类只声明了一个构造函数,那么即使没有注释,它也总是会被使用。注意,带注释的构造函数不一定是公共的。
建议使用@Autowired的required属性,而不是setter方法上已弃用的@Required注释。将required属性设置为false表示该属性不是自动连接所需的,如果无法自动连接,则忽略该属性。另一方面,@Required更强,因为它强制使用容器支持的任何方法设置属性,如果没有定义值,则会引发相应的异常。
或者,您可以通过Java 8的Java .util.Optional来表示特定依赖项的非必需性质。示例如下:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始,你也可以使用@Nullable注释(任何包中的任何类型的注释——例如JSR-305中的javax.annotation.Nullable),或者仅仅利用Kotlin内置的空安全支持:
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实现处理的。
这意味着您不能在自己的BeanPostProcessor或BeanFactoryPostProcessor类型(如果有的话)中应用这些注释。这些类型必须通过使用XML或Spring @Bean方法显式地“连接”起来。
2.9.3. 使用@Primary微调基于注释的自动装配
由于按类型自动装配可能会导致多个候选人,因此通常有必要对选择过程有更多的控制。实现这一点的一种方法是使用Spring的@Primary注释。@Primary表示当多个bean是自动连接到单值依赖项的候选bean时,应该优先考虑某个特定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"/>
</beans>
2.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>
The bean with the main qualifier value is wired with the constructor argument that
is qualified with the same value.
The bean with the action qualifier value is wired with the constructor argument that
is qualified with the same value.
对于回滚匹配,bean名称被认为是默认限定符值。因此,可以使用id为main而不是嵌套限定符元素来定义bean,从而得到相同的匹配结果。然而,尽管您可以使用此约定按名称引用特定的bean, @Autowired从根本上讲是关于使用可选语义限定符的类型驱动注入。这意味着限定符值(即使使用bean名称回退)在类型匹配集中始终具有收缩语义。它们在语义上并不表示对唯一bean id的引用。
好的限定符值是main、EMEA或persistent,它们表示独立于bean id的特定组件的特征,在匿名bean定义(如前面示例中的bean)的情况下,bean id可能是自动生成的。
限定符也应用于类型化集合,如前所述—例如,设置
注意:
让限定符值在类型匹配候选对象中针对目标bean名进行选择,不需要在注入点使用@Qualifier注释。如果没有其他解析指示符(例如限定符或主标记),对于非惟一依赖关系情况,Spring将注入点名称(即字段名称或参数名称)与目标bean名称匹配,并选择同名的候选对象(如果有的话)。
也就是说,如果您打算通过名称表示注释驱动的注入,那么不要主要使用@Autowired,即使它能够在类型匹配的候选对象中通过bean名称进行选择。
相反,使用JSR-250 @Resource注释,它在语义上定义为通过其惟一名称标识特定的目标组件,声明的类型与匹配过程无关。
@Autowired具有相当不同的语义:在按类型选择候选bean之后,指定的String限定符值只在这些类型选择的候选bean中考虑(例如,将帐户限定符与标记有相同限定符标签的bean相匹配)。
对于本身定义为集合、Map或数组类型的bean, @Resource是一个很好的解决方案,它通过唯一名称引用特定的集合或数组bean。
也就是说,从4.3开始,只要元素类型信息保存在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的@Autowired类型匹配算法匹配集合、Map和数组类型。
在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如上一段所述。
从4.3开始,@Autowired还将自身引用视为注入(即对当前被注入的bean的引用)。
请注意,自注入是一种后备方法。
对其他组件的常规依赖总是具有优先级。
从这个意义上说,自我引用并不参与常规的候选人选择,因此它从来都不是主要的。
相反,它们的优先级总是最低。
在实践中,您应该仅将自身引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其他方法)。
在这种情况下,请考虑将受影响的方法分解到单独的委托bean中。
或者,您可以使用@Resource,它可以通过当前bean的唯一名称获取回当前bean的代理。
注意:
尝试将来自@Bean方法的结果注入同一个配置类实际上也是一个自引用场景。
要么在方法签名中惰性地解析此类引用(与配置类中的自动连接字段相反),要么将受影响的@Bean方法声明为静态方法,将它们与包含的配置类实例及其生命周期解耦。
否则,只在回退阶段考虑此类bean,而选择其他配置类上的匹配bean作为主要候选(如果可用)。
@Autowired应用于字段、构造函数和多参数方法,允许在参数级别通过限定符注释缩小范围。
相比之下,@Resource只支持具有单个参数的字段和bean属性设置方法。
因此,如果注入目标是构造函数或多参数方法,则应该坚持使用限定符。
您可以创建自己的自定义限定符注释。
为此,定义一个注释并在定义中提供@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定义提供信息。您可以添加
<?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>
在Classpath Scanning和Managed Components中,您可以看到一种基于注释的替代方法,以替代在XML中提供限定符元数据。具体地说,请参见使用注释提供限定符元数据。
在某些情况下,使用没有值的注释可能就足够了。当注释用于更通用的目的,并且可以跨几种不同类型的依赖项应用时,这一点非常有用。例如,您可以提供一个脱机目录,在没有Internet连接时可以搜索它。首先,定义简单的注释,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后将注释添加到要自动连接的字段或属性,如下面的示例所示:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
This line adds the @Offline
annotation.
现在bean定义只需要一个限定符类型,如下例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
This element specifies the qualifier.
您还可以定义自定义限定符注释,除了接受简单的value属性外,还接受命名属性。如果在要自动连接的字段或参数上指定了多个属性值,那么bean定义必须匹配所有这样的属性值,将其视为自动连接候选值。例如,考虑以下注释定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这种情况下,Format是一个enum,定义如下:
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元属性来代替
<?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>
2.9.5.使用泛型作为自动装配限定符
除了@Qualifier注释外,还可以使用Java泛型类型作为限定的隐式形式。例如,假设您有以下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设前面的bean实现了一个泛型接口(即Store
@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;
2.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通过以下方法确定自动装配候选对象:
-
每个bean定义的自动连接候选值
-
元素上可用的任何默认自动连线候选模式 -
@Qualifier注释和在CustomAutowireConfigurer注册的任何自定义注释的存在
当多个bean限定为自动装配候选对象时,“primary”的确定如下:如果候选对象中恰好有一个bean定义的primary属性设置为true,则选择该bean。
2.9.7. 注射用@Resource
Spring还通过在字段或bean属性设置器方法上使用JSR-250 @Resource注释(javax.annotation.Resource)来支持注入。这是Java EE中的一种常见模式:例如,在jsf管理的bean和JAX-WS端点中。Spring也支持Spring管理对象的这种模式。
@Resource接受一个name属性。默认情况下,Spring将该值解释为要注入的bean名。
换句话说,它遵循by-name语义,如下例所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
This line injects a @Resource
.
如果没有显式指定名称,则默认名称派生自字段名或setter方法。对于字段,它接受字段名。
对于setter方法,它接受bean属性名。下面的例子将把名为movieFinder的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注意:
与注释一起提供的名称由CommonAnnotationBeanPostProcessor感知的ApplicationContext解析为bean名称。如果显式配置Spring的SimpleJndiBeanFactory,则可以通过JNDI解析这些名称。
但是,我们建议您依赖默认行为,并使用Spring的JNDI查找功能来保持间接级别。
在没有明确指定名称的@Resource使用情况下,与@Autowired类似,@Resource会找到一个主类型匹配,而不是特定的命名bean,并解析众所周知的可解析依赖项:BeanFactory、ApplicationContext、ResourceLoader、ApplicationEventPublisher和MessageSource接口。
因此,在下面的示例中,customerPreferenceDao字段首先查找名为“customerPreferenceDao”的bean,然后返回到与customerPreferenceDao类型匹配的主类型:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
The context
field is injected based on the known resolvable dependency type: ApplicationContext
.
2.9.8. 使用 @Value
@Value通常用于注入外部化属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
配置如下:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
以及下面的应用程序。application.properties文件:
catalog.name=MovieCatalog
在这种情况下,catalog参数和字段将等于MovieCatalog值。
Spring提供了一个默认的、宽松的嵌入式值解析器。它将尝试解析属性值,如果不能解析,属性名(例如${catalog.name})将作为值注入。如果你想严格控制不存在的值,你应该声明一个PropertySourcesPlaceholderConfigurer bean,如下面的例子所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
注意:
当使用JavaConfig配置PropertySourcesPlaceholderConfigurer时,@Bean方法必须是静态的。
如果无法解析任何${}占位符,使用上述配置可确保Spring初始化失败。
也可以使用setPlaceholderPrefix、setPlaceholderSuffix或setvalueseseparator等方法来定制占位符。
注意:
Spring Boot configures by default a PropertySourcesPlaceholderConfigurer
bean that will get properties from application.properties
and application.yml
files.
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如到Integer或int)。多个逗号分隔的值可以自动转换为字符串数组而不需要额外的努力。
可以提供如下默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor在幕后使用ConversionService来处理将@Value中的String值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的ConversionService bean实例,如下例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value包含一个SpEL表达式时,该值将在运行时动态计算,如下例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL还支持使用更复杂的数据结构:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
2.9.9. 使用 @PostConstruct
and @PreDestroy
CommonAnnotationBeanPostProcessor不仅可以识别@Resource注释,还可以识别JSR-250生命周期注释:javax.annotation.PostConstruct和javax.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获得,只需像其他库一样添加到应用程序的类路径中。
2.10. 类路径扫描和托管组件
本章中的大多数示例使用XML指定在Spring容器中生成每个BeanDefinition的配置元数据。
上一节(基于注释的容器配置)演示了如何通过源级注释提供大量配置元数据。
然而,即使在这些示例中,“基本”bean定义也是在XML文件中显式定义的,而注释仅驱动依赖项注入。
本节描述一个通过扫描类路径隐式检测候选组件的选项。
候选组件是符合筛选条件的类,并且具有在容器中注册的相应bean定义。
这消除了使用XML执行bean注册的需要。
相反,您可以使用注释(例如@Component)、AspectJ类型表达式或您自己的自定义过滤条件来选择哪些类具有已注册到容器中的bean定义。
注意:
从Spring 3.0开始,Spring JavaConfig项目提供的许多特性都是核心Spring框架的一部分。
这允许您使用Java定义bean,而不是使用传统的XML文件。
查看@Configuration、@Bean、@Import和@DependsOn注释,了解如何使用这些新特性。
1.10.1. @Component
和进一步构造型注解
@Repository注释是任何实现存储库角色或原型(也称为数据访问对象或DAO)的类的标记。
该标记的使用包括异常的自动翻译,如异常翻译中所述。
Spring提供了进一步的原型注解:@Component、@Service和@Controller。
@Component是任何spring托管组件的通用构造型。
@Repository、@Service和@Controller是@Component用于更具体用例的专门化(分别在持久性、服务和表示层中)。
因此,你可以用@Component来标注你的组件类,但是,通过用@Repository、@Service或@Controller来标注,你的类更适合通过工具进行处理或与方面相关联。
例如,这些构造型注释是切入点的理想目标。
@Repository、@Service和@Controller也可以在Spring框架的未来版本中携带额外的语义。
因此,如果要在服务层使用@Component还是@Service之间进行选择,@Service显然是更好的选择。
类似地,如前所述,@Repository已经被支持作为持久性层中自动异常转换的标记。
1.10.2.使用元注释和复合注释
Spring提供的许多注释都可以在您自己的代码中用作元注释。元注释是可以应用于另一个注释的注释。例如,前面提到的@Service注释是用@Component元注释的,如下例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Service {
// ...
}
The @Component
causes @Service
to be treated in the same way as @Component
.
您还可以组合元注释来创建“组合注释”。例如,来自Spring MVC的@RestController注释由@Controller和@ResponseBody组成。
此外,组合注释可以选择性地重新声明元注释的属性,以允许自定义。当您只想公开元注释属性的子集时,这可能特别有用。例如,Spring的@SessionScope注释将作用域名称硬编码为会话,但仍然允许定制proxyMode。下面的清单显示了SessionScope注释的定义:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后你可以使用@SessionScope而不需要声明proxyMode,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
你也可以覆盖proxyMode的值,如下例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
2.10.3.自动检测类和注册Bean定义
Spring可以自动检测构造型类并向ApplicationContext注册相应的BeanDefinition实例。
例如,以下两个类符合这种自动检测条件:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
要自动检测这些类并注册相应的bean,需要将@ComponentScan添加到@Configuration类中,其中basePackages属性是这两个类的公共父包。(或者,您可以指定一个逗号或分号或空格分隔的列表,其中包括每个类的父包。)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
// ...
}
注意:
为简洁起见,前面的示例可以使用注释的value属性(即@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
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:component-scan base-package="org.example"/>
</beans>
注意:
使用<context:component-scan>
隐式地启用了<context:annotation-config>
的功能。
当使用<context:component-scan>
时,通常不需要包含<context:annotation-config>
元素。
注意:
类路径包的扫描需要类路径中存在相应的目录项。当您使用Ant构建JAR时,请确保您没有激活JAR任务的只文件开关。此外,在某些环境中,基于安全策略,类路径目录可能不会公开——例如,JDK 1.7.0_45或更高版本上的独立应用程序(这需要在您的清单中设置“Trusted-Library”——参见https://*.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)上,Spring的类路径扫描通常按照预期工作。但是,请确保在模块信息描述符中导出了组件类。如果希望Spring调用类的非公共成员,请确保它们是“打开的”(也就是说,它们在模块信息描述符中使用一个“打开”声明,而不是一个“导出”声明)。
此外,当您使用组件扫描元素时,AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor都是隐式包含的。这意味着这两个组件被自动检测并连接在一起—所有这些都不需要在XML中提供任何bean配置元数据。
注意:
通过包含值为false的注释配置属性,可以禁用AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor的注册。
2.10.4.使用筛选器自定义扫描
默认情况下,使用@Component、@Repository、@Service、@Controller、@Configuration标注的类,或者使用@Component标注的自定义标注是唯一检测到的候选组件。但是,您可以通过应用自定义过滤器来修改和扩展此行为。将它们添加为@ComponentScan注释的includeFilters或excludeFilters属性(或在XML配置中作为<context:component-scan>
元素的<context:include-filter />或<context:exclude-filter />子元素)。每个筛选器元素都需要type和expression属性。
过滤选项如下表所示:
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) | org.example.SomeAnnotation |
An annotation to be present or meta-present at the type level in target components. |
assignable | org.example.SomeClass |
A class (or interface) that the target components are assignable to (extend or implement). |
aspectj | org.example..*Service+ |
An AspectJ type expression to be matched by the target components. |
regex | org\.example\.Default.* |
A regex expression to be matched by the target components' class names. |
custom | org.example.MyTypeFilter |
A custom implementation of the org.springframework.core.type.TypeFilter interface. |
下面的示例显示了忽略所有@Repository注释并使用“存根”存储库的配置:
@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>
注意:
您还可以通过在注释上设置useDefaultFilters=false或通过提供use-default-filters="false"作为
2.10.5. 在组件中定义 Bean 元数据
Spring组件还可以向容器提供bean定义元数据。您可以使用与在@Configuration注释类中定义bean元数据相同的@Bean注释来实现这一点。下面的例子展示了如何做到这一点:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
前面的类是一个Spring组件,它的doWork()方法中包含特定于应用程序的代码。但是,它还提供了一个bean定义,该bean定义有一个引用方法publicInstance()的工厂方法。@Bean注释标识工厂方法和其他bean定义属性,例如通过@Qualifier注释标识一个限定符值。其他可以指定的方法级注释有@Scope、@Lazy和自定义限定符注释。
注意:
除了它在组件初始化中的作用外,您还可以将@Lazy注释放置在用@Autowired或@Inject标记的注入点上。在这个上下文中,它会导致惰性解析代理的注入。
如前所述,支持自动连接字段和方法,还支持@Bean方法的自动连接。下面的例子展示了如何做到这一点:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
该示例将String方法参数country自动连接到另一个名为privateInstance的bean上的age属性的值。
Spring Expression Language元素通过符号#{< Expression >}定义属性的值。
对于@Value注释,表达式解析器被预先配置为在解析表达式文本时查找bean名称。
从Spring Framework 4.3开始,您还可以声明一个类型为InjectionPoint(或其更具体的子类:DependencyDescriptor)的工厂方法参数,以访问触发当前bean创建的请求注入点。
注意,这只适用于bean实例的实际创建,而不适用于现有实例的注入。
因此,这个特性对于原型范围的bean最有意义。
对于其他作用域,工厂方法只看到在给定作用域中触发创建新bean实例的注入点(例如,触发创建惰性单例bean的依赖项)。
在这种情况下,您可以使用提供的注入点元数据并注意语义。
下面的例子展示了如何使用InjectionPoint:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
常规Spring组件中的@Bean方法的处理方式与Spring @Configuration类中的对应方法不同。
不同之处在于@Component类没有使用CGLIB进行增强以拦截方法和字段的调用。
CGLIB代理是通过调用@Configuration类中的@Bean方法中的方法或字段创建到协作对象的bean元数据引用的方法。
这些方法不是用普通的Java语义调用的,而是通过容器来提供Spring bean的常用生命周期管理和代理,甚至在通过对@Bean方法的编程调用引用其他bean时也是如此。
相比之下,在普通的@Component类中调用@Bean方法中的方法或字段具有标准的Java语义,不需要应用特殊的CGLIB处理或其他约束。
注意:
可以将@Bean方法声明为静态方法,这样就可以在不将包含它们的配置类创建为实例的情况下调用它们。
这在定义后处理器bean(例如,类型为BeanFactoryPostProcessor或BeanPostProcessor)时特别有意义,因为这样的bean在容器生命周期的早期被初始化,应该避免在那个时候触发配置的其他部分。
对静态@Bean方法的调用从来不会被容器拦截,甚至在@Configuration类中也不会(如本节前面所述),这是由于技术限制:CGLIB子类化只能覆盖非静态方法。
因此,直接调用另一个@Bean方法具有标准的Java语义,导致直接从工厂方法本身返回独立的实例。
@Bean方法的Java语言可见性对Spring容器中的结果bean定义没有直接影响。
您可以*地在non-@Configuration类中声明您的工厂方法,也可以在任何地方声明静态方法。
但是,@Configuration类中的常规@Bean方法需要重写—也就是说,它们不能声明为private或final。
在给定组件或配置类的基类上,以及在组件或配置类实现的接口中声明的Java 8默认方法上,也会发现@Bean方法。
这允许在组合复杂的配置安排时具有很大的灵活性,甚至可以通过Spring 4.2中的Java 8默认方法实现多重继承。
最后,单个类可能包含同一个bean的多个@Bean方法,这是根据运行时可用的依赖关系安排的多个工厂方法。
这与在其他配置场景中选择“最贪婪的”构造函数或工厂方法的算法相同:在构造时选择具有最大数量可满足依赖的变量,类似于容器在多个@Autowired构造函数之间的选择。
2.10.6. 命名自动检测组件
当一个组件作为扫描过程的一部分被自动检测时,它的bean名称将由该扫描程序已知的BeanNameGenerator策略生成。默认情况下,任何包含name值的Spring构造型注释(@Component、@Repository、@Service和@Controller)因此将该名称提供给相应的bean定义。
如果这样的注释不包含任何名称值或任何其他检测到的组件(例如自定义过滤器发现的组件),默认bean名称生成器将返回未大写的非限定类名。例如,如果检测到以下组件类,则名称为myMovieLister和movieFinderImpl:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果不想依赖默认的bean命名策略,可以提供自定义的bean命名策略。首先,实现BeanNameGenerator接口,并确保包含一个默认的无参数构造函数。然后,在配置扫描程序时提供完全限定的类名,如下面的注释和bean定义示例所示。
注意:
如果由于多个自动检测的组件具有相同的非限定类名(即具有相同名称但驻留在不同包中的类)而导致命名冲突,则可能需要配置一个BeanNameGenerator,默认为生成的bean名的完全限定类名。
从Spring Framework 5.2.3开始,位于package org.springframework.context.annotation中的fulllyqualifiedannotationbeannamegenerator就可以用于这些目的。
@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>
作为一般规则,考虑在其他组件显式引用它时使用注释指定名称。另一方面,只要容器负责连接,自动生成的名称就足够了。
2.10.7. 为自动检测组件提供范围
与spring管理的组件一样,自动检测组件的默认和最常见的作用域是单例的。然而,有时您需要一个可以由@Scope注释指定的不同范围。您可以在注释中提供作用域的名称,如下面的示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
注意:
@Scope注释只在具体bean类(对于带注释的组件)或工厂方法(对于@Bean方法)上自省。
与XML bean定义相反,没有bean定义继承的概念,类级别的继承层次对于元数据的目的是无关的。
关于特定于web的作用域的详细信息,如Spring上下文中的“请求”或“会话”,请参见请求、会话、应用和WebSocket作用域。与那些作用域的预构建注释一样,您也可以通过使用Spring的元注释方法来编写自己的作用域注释:例如,使用@Scope(“prototype”)注释的自定义注释元,还可能声明自定义作用域代理模式。
注意:
为了提供范围解析的自定义策略,而不是依赖于基于注释的方法,您可以实现ScopeMetadataResolver接口。确保包含一个默认的无参数构造函数。然后,您可以在配置扫描程序时提供完全限定的类名,如下面的注释和bean定义示例所示:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用某些非单例作用域时,可能需要为作用域对象生成代理。其原因在作用域bean中描述为依赖关系。为此目的,可以在组件扫描元素上使用作用域代理属性。三个可能的值是:no、interfaces和targetClass。例如,下面的配置将导致标准JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
2.10.8. 提供带有注释的限定符元数据
@Qualifier注释在使用qualifier的基于微调注释的自动装配中进行了讨论。
该节中的示例演示了@Qualifier注释和自定义限定符注释的使用,以便在解析自动装配候选项时提供细粒度控制。
因为这些示例是基于XML bean定义的,所以限定符元数据是通过使用XML中bean元素的限定符或元子元素在候选bean定义上提供的。
当依赖类路径扫描来自动检测组件时,您可以在候选类上提供带有类型级别注释的限定符元数据。
下面三个例子演示了这种技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
注意:
与大多数基于注释的替代方法一样,请记住注释元数据是绑定到类定义本身的,而XML的使用允许同一类型的多个bean在其限定符元数据中提供变化,因为元数据是按实例而不是按类提供的。
2.10.9. 生成候选组件的索引
虽然类路径扫描非常快,但是可以通过在编译时创建一个静态候选列表来提高大型应用程序的启动性能。在此模式下,所有作为组件扫描目标的模块都必须使用此机制。
注意:
您现有的@ComponentScan或<context:component-scan/>
指令必须保持不变,以请求上下文扫描某些包中的候选包。当ApplicationContext检测到这样的索引时,它会自动使用它,而不是扫描类路径。
要生成索引,请向每个包含组件扫描指令目标组件的模块添加额外的依赖项。下面的例子展示了如何使用Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.3.10</version>
<optional>true</optional>
</dependency>
</dependencies>
The spring-context-indexer
artifact generates a META-INF/spring.components
file that is included in the jar file.
注意:
在IDE中使用此模式时,必须将spring上下文索引器注册为注释处理器,以确保在更新候选组件时索引是最新的。
当在类路径中找到META-INF/spring.components文件时,该索引将自动启用。如果索引是部分可用于一些图书馆(或者用例),但不能为整个应用程序,你可以回到常规类路径安排(好像没有索引在场)通过设置spring.index.ignore为true,要么作为一个JVM系统属性或通过SpringProperties机制。