Spring 5 中文解析核心篇 -IoC 容器之基于注解的容器配置
1.9 基于注解的容器配置
Spring配置注解比XML配置更好?
基于注解的配置介绍抛出一个问题,是否比XML方式更好。简单的回答是看场景。具体的描述是每种方式各有利弊,通常的,这个由开发者去决定更适合他们的策略。由于这种定义的方式,注解在声明中提供了大量的上下文,导致更短和更简洁的配置。XML擅长于连接组件,而不需要修改它们的源代码或重新编译它们。一些开发人员倾向于闭源,而另一些人则认为被注释的类不再是pojo,而且配置变得分散且更难控制。
无论怎么选择,Spring能够兼容两种风格甚至是混合使用。值得指出的是通过JavaConfig,Spring允许以无侵入式方式使用,不需要接触目标组件源代码。在工具方面,所有的配置风格通过Spring的Tools Eclipse工具支持。
基于注解的配置提供了XML设置的替代方法,它依靠字节码元数据来连接组件,而不是尖括号声明(不需要xml的格式配置)。替换使用XML去描述bean,开发者只需移动配置到类本身并通过在关联的类、方法、字段上使用注解。在RequiredAnnotationBeanPostProcessor中提到,结合使用BeanPostProcessor
和注释是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required
注解 强制执行必需属性的可能性。Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。实际上,@Autowired
注解提供了相同的能力,在 Autowiring Collaborators中被描述,但是提供了更细粒度和更多的能力。Spring2.5增加对JSR250支持 ,例如:@PostConstruct
、@PreDestroy
。Spring3.0增加对JSR-330 (Java的依赖注入)注解包含在包javax.inject
,例如@Inject
和@Named
。更多关于注解详情能在相关的文章中找到。
<u>注解注入在XML注入之前被执行。因此,XML配置覆盖注解对属性的两种连接方式。</u>
你可以注册他们作为独立的bean定义,但是他们也可以通过基于XML包含下面的标签被隐的注册(注意包含context命名空间)。
隐式的注册后置处理器包括:
AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor, PersistenceAnnotationBeanPostProcessor和前面提到的RequiredAnnotationBeanPostProcessor
<context:annotation-config />
仅在定义它的相同应用程序上下文中查找关于bean的注解。意思是,如果你在WebApplicationContext
中为DispatcherServlet
配置< context:annotation-config/>
,它仅仅检查@Autowired
在你的Controller层并且不会在你的Service层。查看DispatcherServlet 更多信息。
1.9.1 @Required
@Required
注解应用到bean属性的Setter方法,类似下面例子:
这个注解表示bean属性在运行时必须被填充赋值,在一个bean定义中显示赋值或自动装配。如果受影响的bean属性没有被填充值容器抛出一个异常。这允许更早的显示异常,避免以后再出现NullPointerException
实例等。我们仍然推荐你把断言放入到bean类本身中(例如,放入初始化方法)。这样做会强制执行那些必需的引用和值,即使你在容器外部使用该类也是如此。
@Required
注解在Spring5.1中被正式的不推荐使用,更好的方式是,使用构造函数注入要求的设置(或InitializingBean.afterPropertiesSet()
的自定义实现以及bean属性设置器方法)。
1.9.2 @Autowired
JSR 330的
@Inject
注解能够使用在Spring的@Autowired
注解的地方。
可以在构造方法上使用@Autowired
,类似下面例子:
从Spring4.3开始,如果目标bean仅仅定义以一个构造函数开始,那么构造函数上的
@Autowired
注解是不必要的。然而,如果有一些构造函数是有效的并且没有主要默认的构造函数,则至少有一个构造函数必须被注解为@Autowired
,目的是引导容器选择其中一个去使用。查看构造方法解析详情。
你也可以应用@Autowired注解到传统的Setter方法,类似下面例子:
你可以应用@Autowired
注解到任意方法名和多个参数,类似下面例子:
你可以应用@Autowired
注解到字段甚至混合到构造方法,类似下面例子:
确保你的目标组件(例如,
MovieCatalog
或CustomerPreferenceDao
)是一致性的通过类型声明,也就是使用基于@Autowired
注入点。否则,注入可能由于在运行时出现“找不到类型匹配”错误而失败。对于基于XML定义的bean或者组件类通过类路径扫描,容器通常知道具体的类型。然而,对于
@Bean
工厂方法,你需要确保声明的返回类型足够的表达。对于实现多个接口的组件,或者对于实现类型可能引用的组件,请考虑在工厂方法中声明最具体的返回类型。至少与指向bean的注入点所要求的一样具体(备注:尽可能返回具体的实现类型)。
你还可以引导Spring从ApplicationContext
中提供特定类型的所有bean,通过将@Autowired
注解添加到需要该类型数组的字段或方法中,类似下面例子:
类型化集合也是如此,类似下面例子:
如果你想数组或集合中的元素在指定的顺序下被排序,那么你的目标bean可以实现
org.springframework.core.Ordered
接口、@Order
或标准的@Priority
注解。否则,它们的顺序遵循在容器中对应目标bean定义注册的顺序。你可以在目标类级别和在
@Bean
方法上申明@Order
注解,可能是针对单个bean定(多个定义情况下使用相同的bean类)。@Order
值可能会影响注入点的优先级,不会影响单例bean的启动顺序,这是由依赖关系和@DependsOn
声明确定。注意:标准的
javax.annotation.Priority
注解在@Bean
级别是无效的,因为它不能被声明在方法上。它的语义可以通过@Order
值与@Primary
结合在每种类型的单个bean上。
甚至类型Map实例也能被自动装配只要期望的key类型是String。map的值包含了所有期望类型的bean,并且这些key包含对应bean的名称,像下面的例子展示:
默认情况下,当没有匹配的候选bean对应给定的注入点的时候自动装配将失败。在这种情况下申明数组、集合或map至少有一个期望匹配的元素。
默认行为是将带注解的方法和字段视为指示所需的依赖项。你可以改变这种行为,像下面这个例子,通过将框架标记为不需要,从而使框架可以跳过不满意的注入点(通过在@Autowired
属性中的required
设置为false)。说明:满足注意条件就注入,不满足条件就跳过注入。
如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不需要填充非必需字段,将其默认值保留在适当的位置。注入构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能要处理多个构造函数,@Autowired
中的required
属性有一些不同的含义。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能会处理多个构造函数,因此@Autowired
中必填属性的含义有所不同。默认情况下,有效地需要构造函数和工厂方法参数,但是在单构造函数场景中有一些特殊规则,例如,如果没有匹配的Bean可用,则将多元素注入点(数组,集合,映射)解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明-例如,申明一个简单的公共构造函数不需要@Autowired
注解。
任何给定的bean类只有一个构造函数可以声明
@Autowired
并设置属性required
为true,指示当使用Spring Bean时构造函数自动装配。因此,如果必填属性保留为默认值true,则只能使用@Autowired
注解单个构造函数。如果有多个构造函数声明注解,那么它们都必须声明required=false
,以便被认为是自动装配的候选对象(类似于XML中的autowire=constructor
)。将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数如果没有一个候选者满意,则将使用主要的/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但都没有使用@Autowired
进行注解,则将使用主要的/默认构造函数(如果存在)。如果一个类仅声明一个单一的构造函数开始,即使没有注释,也将始终使用它。请注意,带注解的构造函数不必是公共的。
在setter方法上,建议使用@Autowired
的required
属性,而建议使用@Required
注解。设置required
属性为false则表明这个属性不是必须的对于自动装配意图,并且这个属性如果不能被自动装配将被忽略。换句话说,@Required
更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。
另外,你可以通过Java 8的java.util.Optional
来表达特定依赖项的非必需性质,类似以下示例所示:
从Spring5.0后,你可以使用@Nullable
注解(任何包装中的任何种类-例如,JSR-305的javax.annotation.Nullable
)或者只是利用Kotlin
内置的null
安全支持:
你可以为这些接口使用@Autowired
,这是众所周知的可注入的依赖:BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
,ApplicationEventPublisher
, 和 MessageSource
。这些接口和它们的拓展接口,例如,ConfigurableApplicationContext
或ResourcePatternResolver
是自动地解析,没有特殊的安装需要。下面的例子主动注入一个ApplicationContext
对象:
@Autowired
、@Inject
、@Value
和@Resource
注解是通过Spring的BeanPostProcessor
实现处理的。这意味着你不能应用这些注解在你自己的BeanPostProcessor
和BeanFactoryPostProcessor
类中。这些类型必须被通过XML或者Spring@Bean
方法连接。
参考代码:
com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer
1.9.3 使用@Primary
对基于注解的自动装配调整
因为通过类型的自动装配可能导致多个候选者,通常有必要需要更多的选择处理。一种可以完成的方式是使用Spring的@Primary
注解。@Primary
表示当多个bean自动装配到单值依赖时这个bean是我们更期望的值。如果主要的bean在限定符候选bean中时,它将被自动装配。
考虑下面的配置,这个配置定义firstMovieCatalog
作为主要的MovieCatalog
类型候选值:
通过前面的配置,下面的MovieRecommender
类中自动装配firstMovieCatalog
:
对应bean的定义如下:
1.9.4 使用Qualifiers对基于注解的自动装配调整
@Primary
是一种根据类型使用自动装配的有效方法,当可以确定一个主要候选对象时,可以使用多个实例。当你需要更多的控制选择处理时,你可以使用Spring的@Qualifier
注解。你可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便每个参数选择特定的bean。在这个简单的例子中,这可以是简单的描述性值,如以下示例所示:
你也可以指定@Qualifier
注解在各个构造函数参数或方法参数上,类似下面的例子:
下面的例子显示对应的bean定义:
具有
main
限定符值的Bean与构造函数参数连接,构造函数参数使用相同的值进行限定带有
action
限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
为了回退匹配,bean的名字是默认的限定符。因此,你可以定义bean的id为main替代嵌入的限定符元素,导致相同的匹配结果。然而,即使你能使用这个约定通过名字指定bean,@Autowired
基本上是关于带有可选语义限定符的类型驱动的注入(意思是@Autowired
中带有require限定符)。这意味着限定符值总是有缩小类型匹配集合的语义,即使bean名为回退名称。他们从语义上不能表达引用一个唯一bean的id。好的限定符值是main
、EMEA
或persistent
,它们表示独立于bean id
的特定组件的特征,对于匿名bean定义(如前面示例中的定义),可以自动生成这些特征。
限定符也能应用到类型化的集合,像前面讨论的-例如,Set<MovieCatalog>。在这种例子中,所有匹配的bean,根据声明的限定符,作为一个集合被注入。这暗示限定符没有必要是唯一的。相反,它们构成了过滤标准。例如,你可以定义具有相同限定符值action
的多个MovieCatalog Bean,所有这些都注入到以@Qualifier(“ action”)
注解的Set <MovieCatalog>中。
在类型匹配的候选者中,让限定符值选择目标bean名称,在注入点不需要@Qualifier注解。如果没有其他解析指示器(例如限定符或primary标记),对于非唯一依赖情况,Spring将注入点名(即字段名或参数名)与目标bean名相匹配,并选择同名的候选项(如果有的话)。
也就是说,如果你打算试图通过名称表示注解驱动注入,不要主要地使用@Autowired,即使它有通过名称在类型匹配的候选者间选择的能力。相反,使用JSR-250@Resource
注解,它是语义地通过使用唯一名称定义标识一个指定目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:按类型选择候选bean之后,指定的字符串限定符值仅在那些类型选择的候选中被考虑(例如,匹配一个account
限定符与bean被标记相同的限定符标签)。
对于定义为集合、Map、数组类型的bean,@Resource
是一个好的解决方案,通过唯一的名称引用指定的集合或数组bean。也就是说,在Spring4.3以后你可以匹配一个Map和数组类型通过Spring的@Autowired
类型匹配算法,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中。在这种场景中,你可以使用限定符值去相同类型集合选择,如前一段所述。
在Spring4.3以后,@Autowired
还考虑了注入的自我引用(也就是说,引用回当前注入的bean)。注意自我注入是一种后备。对其他组件的常规依赖始终优先。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其是绝不是主要的。相反,它们总是以最低优先级结束。实际上,你应该仅将自我引用用作最后的手段(例如,在相同实例上通过bean的事物代理调用其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托Bean。或者,你可以使用@Resource
,它可以通过其唯一名称获取返回到当前bean的代理。
尝试将
@Bean
方法的结果注入相同的配置类也是一种有效的自引用方案。要么在实际需要的方法签名中懒惰地解析此类引用(与配置类中的自动装配字段相对),要么将受影响的@Bean
方法声明为静态,将其与包含的配置类实例及其生命周期<u>脱钩</u>。否则,仅在回退阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。
@Autowired
应用到字段、构造函数和多参数方法,在参数级别允许使用限定符注解缩小选择范围。相反,@Resource
仅仅支持字段和bean属性Setter方法简单参数。因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。
你可以创建你自己的自定义限定符注解。为了这样做,定义一个注解和提供@Qualifier
注解在你的定义中,类似下面例子:
然后,你可以提供自定义限定符在自动装配字段和参数上,类似下面例子:
接下来,你可以为候选者bean定义提供信息。你可以增加<bean/>标签的子标签<qualifier/>并且指定type
和value
为匹配你的自定义限定符注解。这个类型是注解的全限定类名的匹配。或者,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面例子展示两种方式:
在Classpath扫描和组件管理中,你可以看到在XML中基于注解另类的提供限定符元数据,查看Providing Qualifier Metadata with Annotations。
在某些场景中,使用注解不需要值可能就满足。这是非常有用的,当注解应用更一般的用途和可以应用于几种不同类型的依赖项时。例如,你可以提供一个脱机目录,当没有Internet连接可用时可以进行搜索。首先,定义相同的注解,类似下面的例子:
然后,增加注解到字段或者属性去自动装配,类似下面例子:
这一行添加
@Offline
注解
接下来,bean的定义仅需要一个限定符type
,类似下面例子:
这个元素指定了限定符
你还可以定义自定义限定符注解,这些注解除了接受简单value
属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则Bean定义必须与所有此类属性值匹配才能被视为自动装配候选。例如,请考虑以下注释定义:
在这个例子中Format是一个枚举,定义如下:
字段装配字段是被自定义限定符注解并且保护genre和format属性值,类似下面例子:
最后,bean定义应该包含匹配的限定符值。这个例子展示你可以使用bean元数据属性替换<qualifier/>元素。如果可用,<qualifier />元素及其属性优先,但是如果不存在这样的限定符,则自动装配机制将回退到<meta />标记内提供的值,如下面示例中的最后两个bean定义:
1.9.5 使用泛型作为自定装配限定
除了@Qualifier
注解,你也可以使用Java泛型类型作为隐式的限定。例如,假设你有下面的配置:
假设前面的bean实现一个泛型接口,(也就是说,Store<String>和Store<Integer>),你可以使用@Autowire装配Store接口并且泛型作为一个限定符,类似下面的例子:
当自动装配集合、Map接口和数组的时泛型限定符也可以使用。下面的例子装配Store泛型为integer的List:
参考代码:
com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer
1.9.6 使用CustomAutowireConfigurer
CustomAutowireConfigurer是一个BeanFactoryPostProcessor
,它允许你注册你自己的自定义限定符注解类型,甚至他们不被注解Spring的@Qualifier
。下面的例子展示怎样去使用CustomAutowireConfigurer
:
AutowireCandidateResolver
通过下面确定自动装配候选者:
每个bean定义的
autowire-candidate
值在<beans/>元素上任何
default-autowire-candidates
模式有效@Qualifier
注解和任何自定义注解存在注册CustomAutowireConfigurer
当多个bean限定符作为自动装配候选者时,primary
的确定类似下面:如果候选者bean中有一个bean被定义primary
属性值被设置为true,它将被选择。
参考代码:
com.liyong.ioccontainer.starter.XmlQualifierIocContainer
1.9.7 @Resource
注入
Spring也支持通过使用JSR-250注解(javax.annotation.Resource
)在字段或bean属性Setter方法上注入。这是在JavaEE通用的模式,在JSF管理bean和JAX-WS端点。Spring提供这个模式去管理Spring的bean。
@Resource
采用一个名字属性。默认情况下,Spring将该值解释为要注入的Bean名称。换而言之,它遵循通过名字的语义,如下所示:
这行注入
@Resource
。
如果显示指定名称,这个默认名是从字段或Setter方法名获取。如果是字段,则采用字段名称。在使用setter方法的情况下,它采用bean属性名称。以下示例将把名为movieFinder
的bean注入其setter方法中:
与注解一起提供的名称被
CommonAnnotationBeanPostProcessor
所感知的ApplicationContext
解析为bean名称。如果你显示的配置Spring的SimpleJndiBeanFactory,这个名字能通过JNDI解析。然而,我们推荐你依赖默认的行为并且使用Spring的JNDI能力去保留间接的级别。
在没有明确指定名称的@Resource
使用的特殊情况下(类似于@Autowired), @Resource会找到一个主类型匹配而不是一个特定的命名bean,并解析我们熟知的可解析依赖项:BeanFactory
、ApplicationContext
、 ResourceLoader
, ApplicationEventPublisher
和MessageSource
接口。
因此,在下面的例子中,customerPreferenceDao
字段首先查找bean名字为customerPreferenceDao
然后回退查找CustomerPreferenceDao
主要匹配的类型:
context字段是基于已知的可解析依赖项类型:
ApplicationContext
注入的。
1.9.8 使用@Value
@Value
典型的应用去注入外部属性:
下面是配置:
application.properties配置
在这种情况下,catalog参数和字段将等于MovieCatalog值。
Spring提供了一个默认的宽松内嵌值解析器。它将尝试去解析属性值并且如果不能被解析,属性名(例如,${catalog.name})
将被作为值注入。如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurer
bean,如以下示例所示:
当使用JavaConfig配置
PropertySourcesPlaceholderConfigurer
,@Bean
方法必须是static。
如果任何${}
占位符不能被解析,使用上面的配置能够确保Spring初始化失败。它也可以使用方法类似setPlaceholderPrefix
、setPlaceholderSuffix
或setValueSeparator
去自定义占位符。
Spring Boot 通过默认的
PropertySourcesPlaceholderConfigurer
bean配置,它能从application.properties
和application.yml
文件获取属性。
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如,转换为Integer或int)。多个逗号分隔的值能够自动的转换为String数组不需要额外的操作。
它也可能提供一个默认值类似下面:
Spring BeanPostProcessor
在使用ConversionService
处理将@Value
中的String值转换为目标类型的过程。如果你想提供转换支持为你自己的自定义类型,你可以提供你自己的ConversionService
bean实例类似下面例子:
当@Value
包含SpEl
表达式时候,这个值将被在运行时动态地计算,类似下面例子:
SpEL
还可以使用更复杂的数据结构:
参考代码:
com.liyong.ioccontainer.starter.XmlValueIocContainer
1.9.9 使用@PostConstruct
和@PreDestroy
CommonAnnotationBeanPostProcessor
不仅仅识别@Resource
注解也能识别JSR-250生命周期注解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
。在Spring 2.5中引入的对这些注解的支持为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。CommonAnnotationBeanPostProcessor
在Spring的ApplicationContext
中被注入,在生命周期的同一点上(备注:同一类回调方法,比如:销毁方法),与相应的Spring生命周期接口方法或显式声明的回调方法调用带有其中一个注解的方法。在下面的例子中,在以下示例中,缓存在初始化时预先填充,并在销毁时清除。
有关组合各种生命周期机制的详细信息,查看组合生命周期机制。
类似
@Resource
一样,@PostConstruct
和@PreDestroy
注解类型是从JDK6到JDK8的标准Java库的一部分。然而,整个javax.annotation
包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api
可以通过Maven中央库获取,简单地增加到应用的类路径下面和其他库一样。
参考代码:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址: http://youngitman.tech
CSDN: https://blog.csdn.net/liyong1028826685
微信公众号:
版权声明: 本文为 InfoQ 作者【青年IT男】的原创文章。
原文链接:【http://xie.infoq.cn/article/8d8f7eaf0bac23981469650a6】。
本文遵守【CC BY-NC】协议,转载请保留原文出处及本版权声明。
评论