Spring IoC容器:基于注解的容器配置

https://docs.spring.io/spring-framework/reference/core/beans/annotation-config.html

使用注解配置Spring比使用XML更好吗?

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

无论选择哪种方式,Spring都能适应这两种风格,甚至可以将它们混合在一起。值得一提的是,通过JavaConfig选项,Spring允许以非侵入性的方式使用注解,而无需触及目标组件的源代码。在工具方面,所有配置风格都得到了Spring Tools for Eclipse、Visual Studio Code和Theia的支持。

基于注解的配置提供了XML设置的替代方案,它依赖于字节码元数据来连接组件,而不是XML声明。开发人员不再使用XML来描述bean的连接,而是通过在相关类、方法或字段声明上使用注解,将配置移动到组件类本身中。例如,结合使用BeanPostProcessor和注解是扩展Spring IoC容器的常见手段。此外,Spring还支持JSR-250注解,如@PostConstruct@PreDestroy,以及支持JSR-330(Java的依赖注入)注解,这些注解包含在jakarta.inject包中,如@Inject@Named

注解注入在XML注入之前执行。因此,对于通过这两种方法注入的属性,XML配置会覆盖注解。

像往常一样,你可以将后处理器(post-processors)注册为单个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>

<context:annotation-config/>元素隐式注册了以下后处理器(post-processors:):

  • ConfigurationClassPostProcessor
  • AutowiredAnnotationBeanPostProcessor
  • CommonAnnotationBeanPostProcessor
  • PersistenceAnnotationBeanPostProcessor
  • EventListenerMethodProcessor

<context:annotation-config/>只会查找在其定义的同一应用程序上下文中的bean上的注解。这意味着,如果将<context:annotation-config/>放在DispatcherServletWebApplicationContext中,它只会检查控制器中的@Autowired bean,而不是你的服务。

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

	// ...
}

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

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

	// ...
}

确保目标组件(例如,MovieCatalogCustomerPreferenceDao)始终由用于@Autowired注解注入点的类型声明。否则,由于运行时出现“未找到类型匹配”的错误,注入可能会失败。

对于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声明确定的正交问题。

请注意,标准的jakarta.annotation.Priority注解在@Bean级别不可用,因为它不能在方法上声明。其语义可以通过@Order值与每个类型的单个bean上的@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注解的方法和字段视为表示必需的依赖关系。可以像下面的例子所示那样改变这种行为,通过将其标记为非必需(例如,将@Autowired中的required属性设置为false),使框架能够跳过无法满足的注入点:

public class SimpleMovieLister {

	private MovieFinder movieFinder;

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

	// ...
}

如果non-required 方法的依赖关系(或者在多个参数的情况下,其中一个依赖关系)不可用,那么根本不会调用该方法。在这种情况下,non-required 字段根本不会被填充,保持其默认值不变。

换句话说,将required属性设置为false表示相应的属性对于自动装配是可选的,如果该属性无法自动装配,那么将被忽略。这允许属性被分配默认值,可以通过依赖注入选择性地覆盖。

注入的构造函数和工厂方法参数是一个特例,因为由于Spring的构造函数解析算法可能会处理多个构造函数,@Autowired中的required属性有着稍微不同的含义。构造函数和工厂方法参数默认是必需的,但在单构造函数场景下有一些特殊规则,例如如果没有匹配的bean可用,多元素注入点(数组、集合、映射)将解析为空实例。这允许实现一种常见模式,即所有依赖项可以在一个独特的多参数构造函数中声明——例如,声明为单个公共构造函数而无需@Autowired注解。

任何给定bean类只能有一个构造函数声明@Autowired,并将required属性设置为true,表示在用作Spring bean时自动装配该构造函数。因此,如果required属性保持其默认值true,只有单个构造函数可以用@Autowired注解。如果多个构造函数声明了该注解,它们都必须声明required=false,以便被视为自动装配的候选者(类似于XML中的autowire=constructor)。将选择能满足最多依赖关系的构造函数,这些依赖关系可以通过匹配Spring容器中的bean来满足。如果没有候选者可以被满足,那么将使用主/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但没有一个用@Autowired注解,那么将使用主/默认构造函数(如果存在)。如果一个类一开始就只声明了一个构造函数,即使没有注解,也总会使用它。请注意,带注解的构造函数不必是public的。

或者,可以通过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内置的null-safety 支持:

public class SimpleMovieLister {

	@Autowired
	public void setMovieFinder(@Nullable MovieFinder movieFinder) {
		...
	}
}

还可以使用@Autowired来自动装配众所周知的可解析依赖接口:BeanFactoryApplicationContextEnvironmentResourceLoaderApplicationEventPublisherMessageSource。这些接口及其扩展接口,如ConfigurableApplicationContextResourcePatternResolver,会自动解析,无需特殊设置。以下示例自动装配了一个ApplicationContext对象:

public class MovieRecommender {

	@Autowired
	private ApplicationContext context;

	public MovieRecommender() {
	}

	// ...
}

@Autowired@Inject@Value@Resource注解由Spring BeanPostProcessor实现处理。这意味着不能在自己的BeanPostProcessorBeanFactoryPostProcessor类型(如果有)中使用这些注解。这些类型必须通过使用XML或Spring @Bean方法显式配置。

通过@Primary微调基于注解的自动装配

由于按类型自动装配可能会导致多个候选者,通常需要对选择过程有更多的控制。一种实现方式是使用Spring的@Primary注解。@Primary表示在多个bean成为自动装配到单一值依赖项的候选者时,应优先考虑特定的bean。如果候选者中恰好存在一个primary bean,它将成为自动装配的值。

考虑以下配置,它将firstMovieCatalog定义为primary 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>

通过Qualifiers微调基于注解的自动装配

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

public class MovieRecommender {

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

	// ...
}

还可以在单个构造函数参数或方法参数上指定@Qualifier注解,如下例所示:

public class MovieRecommender {

	private final MovieCatalog movieCatalog;

	private final 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的名称被视为默认的限定符值。因此,可以使用idmain定义bean,而不是使用嵌套的qualifier元素,这将导致相同的匹配结果。然而,尽管可以使用这种约定按名称引用特定的bean,@Autowired本质上是关于类型驱动的注入,带有可选的语义限定符。这意味着限定符值,即使在bean名称作为后备的情况下,总是在类型匹配集合中具有缩小范围的语义。它们在语义上并不表达对唯一bean id的引用。好的限定符值是mainEMEApersistent,它们表达了特定组件的特性,这些特性独立于bean id,bean id可能是自动生成的,如前面示例中的匿名bean定义。

限定符也适用于之前讨论的类型集合,例如Set<MovieCatalog>。在这种情况下,根据声明的限定符,所有匹配的bean都会被注入为一个集合。这意味着限定符不必是唯一的。相反,它们构成了过滤条件。例如,可以定义多个具有相同限定符值“action”的MovieCatalog bean,所有这些bean都会被注入到一个用@Qualifier("action")注解的Set中。

在类型匹配的候选项中,让限定符值与目标bean名称相对应,不需要在注入点使用@Qualifier注解。如果没有其它解析指示器(如qualifier 或primary 标记),对于非唯一的依赖情况,Spring会将注入点名称(即字段名或参数名)与目标bean名称进行匹配,并在有相同名称的候选项时选择它。

自6.1版本起,这需要加上-parameters Java编译器标志。

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

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

自4.3版本起,@Autowired也考虑了对当前被注入bean的自身引用(即,引用回当前正在注入的bean)。请注意,自身注入是一种后备方案。对其它组件的常规依赖总是优先。在这个意义上,自身引用不参与常规的候选选择,因此永远不会是主要的。相反,它们总是作为最低优先级结束。在实践中,应该仅将自身引用作为最后的手段(例如,通过bean的事务代理调用同一实例上的其它方法)。在这种情况下,可以考虑将受影响的方法分离到一个单独的委托bean中。或者,可以使用@Resource,它可以通过唯一名称获取回当前bean的代理。

在同一配置类中尝试注入@Bean方法的结果实际上也是一种自身引用场景。要么在真正需要的地方(与配置类中的自动装配字段相反)的方法签名中延迟解析这些引用,要么将受影响的@Bean方法声明为静态的,从而将它们与包含的配置类实例及其生命周期解耦。否则,这样的bean只会在后备阶段被考虑,而其它配置类上的匹配bean将作为主要候选者被选中(如果有的话)。

@Autowired适用于字段、构造函数和多参数方法,允许通过参数级别的qualifier 注解进行缩小范围。相比之下,@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>

在某些情况下,使用没有值的注解可能就足够了。当注解用于更通用的目的并且可以应用于几种不同类型的依赖项时,这可能会很有用。例如,你可能提供了一个离线目录,在没有互联网连接时可以进行搜索。首先,定义简单的注解,如下例所示:

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

还可以定义接受命名属性的自定义限定符注解,这些属性可以作为简单值属性的补充或替代。如果在要自动装配的字段或参数上指定了多个属性值,则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 meta 属性而不是<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>

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

除了@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>),你可以@Autowire 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;

使用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定义的autowire-candidate
  • <beans/>元素上可用的任何default-autowire-candidates模式
  • @Qualifier注解的存在以及使用CustomAutowireConfigurer注册的任何自定义注解

当多个bean符合自动装配候选者的条件时,“primary” bean的确定如下:如果候选者中恰好有一个bean定义的primary属性设置为true,则选择它。

使用@Resource进行注入

Spring还支持使用JSR-250 @Resource注解(jakarta.annotation.Resource)在字段或bean属性setter方法上进行注入。这是Jakarta EE中的一个常见模式:例如,在JSF管理的bean和JAX-WS端点中。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;
	}
}

注解提供的名称由CommonAnnotationBeanPostProcessor所知的ApplicationContext解析为bean名称。如果你明确配置了Spring的SimpleJndiBeanFactory,这些名称可以通过JNDI解析。但是,建议依赖默认行为,并使用Spring的JNDI查找功能以保持间接层次。

@Resource使用中没有明确指定名称的特殊情况下,与@Autowired类似,@Resource会找到主要类型匹配而不是特定的命名bean,并解析众所周知的可解析依赖项:BeanFactoryApplicationContext、ResourceLoader、ApplicationEventPublisherMessageSource接口。

因此,在下面的例子中,customerPreferenceDao字段首先寻找名为“customerPreferenceDao”的bean,然后回退到CustomerPreferenceDao类型的主要类型匹配:

public class MovieRecommender {

	@Resource
	private CustomerPreferenceDao customerPreferenceDao;

	@Resource
	private ApplicationContext context;

	public MovieRecommender() {
	}

	// ...
}

context 字段基于已知的可解析依赖类型进行注入:ApplicationContext

使用@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初始化失败。还可以使用setPlaceholderPrefixsetPlaceholderSuffixsetValueSeparator等方法来自定义占位符。

Spring Boot默认配置了一个PropertySourcesPlaceholderConfigurer bean,它会从application.propertiesapplication.yml文件中获取属性。

Spring提供的内置转换器支持允许自动处理简单类型转换(例如转换为Integerint)。多个逗号分隔的值可以自动转换为String数组,无需额外努力。

可以提供默认值,如下所示:

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

使用@PostConstruct 和 @PreDestroy

CommonAnnotationBeanPostProcessor不仅识别@Resource注解,还识别JSR-250生命周期注解:jakarta.annotation.PostConstructjakarta.annotation.PreDestroy。在Spring 2.5中引入的支持这些注解提供了一种替代方案,即初始化回调和销毁回调中描述的生命周期回调机制。只要在Spring ApplicationContext中注册了CommonAnnotationBeanPostProcessor,就会在生命周期中的相同点调用带有这些注解之一的方法,就像相应的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中被移除。自Jakarta EE 9起,该包现在位于jakarta.annotation中。如果需要,现在可以通过Maven Central获取jakarta.annotation-api组件,就像任何其他库一样简单地添加到应用程序的类路径中。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值