1.9。基于注解的容器配置
注解在配置Spring方面比XML更好吗?
基于注解的配置的引入提出了一个问题,即这种方法是否比XML“更好”。
简短的答案是“取决于情况”。
长话短说,每种方法都有其优缺点,通常,由开发人员决定哪种策略更适合他们。
由于定义方式的不同,注解在声明中提供了很多上下文,从而使配置更短,更简洁。
但是,XML擅长连接组件而不接触其源代码或重新编译它们。
一些开发人员更喜欢将布线放置在靠近源的位置,而另一些开发人员则认为带注解的类不再是POJO,
而且,该配置变得分散且难以控制。
无论选择如何,Spring都可以容纳两种样式,甚至可以将它们混合在一起。
值得指出的是,通过其JavaConfig选项,Spring允许以非侵入方式使用注解,
而无需接触目标组件的源代码。
注解是XML配置的替代方法,该配置依赖字节码元数据来连接组件,而不是尖括号声明。
通过使用相关的 类,方法或字段 声明上的注解,开发人员无需使用XML来描述bean的连接,而是将配置移入组件类本身。
如示例中所述:将RequiredAnnotationBeanPostProcessor,通过BeanPostProcessor的方式与注解结合使用是扩展Spring IoC容器的常用方法。
例如,Spring 2.0引入了使用@Required注解强制执行必需属性的可能性。
Spring 2.5引入@Autowired注解,提供的功能与自动装配协作器中所述的功能相同,但具有更细粒度的控制和更广泛的适用性。
Spring 2.5还添加了对JSR-250批注(例如 @PostConstruct和@PreDestroy)的支持。
Spring 3.0增加了对javax.inject包中包含的JSR-330(Java依赖性注入)注解的支持,例如@Inject 和@Named。
注解注入在XML注入之前执行。因此,XML配置将覆盖通过注解注入的属性
与往常一样,您可以根据类名将它们注册为单独的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/> 隐式注册后置处理器包括 :
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
RequiredAnnotationBeanPostProcessor
并且当使用<context:component-scan/>后,即可将<context:annotation-config/>省去
context:annotation-config/只在定义它的相同应用程序上下文中查找关于bean的注解。
这意味着,如果你把context:annotation-config/定义在WebApplicationContext的DispatcherServlet中,它只是检查controllers中的@Autowired注解,而不是你的services。
上边的这段话意思不是很明确,需要解释一下以前用web.xml配置时的Spring启动流程
拿出几段配置
<!--配置开始 -->
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/service/*</url-pattern>
</servlet-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/spring-base.xml
</param-value>
</context-param>
<!--配置结束 -->
上边的配置应该是多年前webi应用的基础配置,理一下tomcat启动后如何调用Spring的大概流程
1. tomcat读取web.xml文件(此处不管tomcat如何找到xml),解析内容并分组,
分成ServletContainerInitializer,servlet,listener,context-param等多个数组
2.逐个进行解析,先解析ServletContainerInitializer
//这个就相当典型了 这个东西就是之前的文章讲过的ServletContainerInitializer
//Tomcat启动会查找ServletContainerInitializer实现类并执行其中的onStartup方法。
//Spring-web模块存在ServletContainerInitializer实现类,所以Tomcat启动会调用Spring-web的代码。
//但是我们用Spring框架的话不需要实现这个接口,实现一个Spring的接口WebApplicationInitializer。
//就可以由Tomcat调用Spring-web的ServletContainerInitializer实现类
Iterator i$ = this.initializers.entrySet().iterator();
while(i$.hasNext()) {
Entry entry = (Entry)i$.next();
try {
((ServletContainerInitializer)entry.getKey()).onStartup((Set)entry.getValue(), this.getServletContext());
} catch (ServletException var22) {
log.error(sm.getString("standardContext.sciFail"), var22);
ok = false;
break;
}
}
但是这里我们并没有用这种方式而是用了listener的方式继续往下看
3. 解析listener,这里this.listenerStart()会解析我们配置的ContextLoaderListener
if (ok && !this.listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
就在这里tomcat关联上了Spring的ApplicationContext,会实例化XmlWebApplicationContext,
实例化时取出context-param中的name为contextConfigLocation的配置文件,来进行解析注册
4.解析servlet,this.loadOnStartup(this.findChildren())来解析servlet,
if (ok && !this.loadOnStartup(this.findChildren())) {
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
这里就会进入DispatcherServlet的init方法,
init方法中会根据当前的ServletContext查找在此之前有没有初始化过Spring的ApplicationContext,
然后再判断当前DispatcherServlet有没有ApplicationContext,
如果没有就初始化一个并把之前初始化ApplicationContext的设置为父节点
总结一下,也就是说用了上边的配置的话,tomcat在启动过程中,会初始化两遍并生成两个ApplicationContext对象,
第一遍解析context-param中param-name 为contextConfigLocation的配置文件,
并以此配置文件生成一个ApplicationContext ROOT
第二遍是解析DispatcherServlet servlet的spring-mvc.xml配置文件,
再以此配置文件生成一个ApplicationContext,并将ROOT设置为父节点
因此就产生了一个问题,当你在两个ApplicationContext都可以扫描到同一个Bean的时候,
那么这个bean在连个ApplicationContext中都各存在一个实例,并且实例不一样
举一个之前遇到的问题:
之前想给某个controller加一个AOP,拦截某些方法进行特殊处理,但是我把 <aop:aspectj-autoproxy/>这个注解
放到了下面这个层次的配置文件中了
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:spring/spring-base.xml
</param-value>
</context-param>
最后我的AOP并没有生效,后来又把注解挪到了spring-mvc.xml中,才生效。
之前百度搜到说:spring-mvc 的配置扫描优先于spring的配置文件
通过调试才理解这句话:
我的controller在spring的ApplicationContext中有一份被AOP代理的对象
在spring-mvc的ApplicationContext中还有一份没被代理的普通对象
因为spring-mvc配置加载的晚,所以用到的都是没有被代理的对象
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的情况。
@Required注解在Spring Framework 5.1时正式被弃用,
Spring更赞同使用构造函数注入来进行必需参数的设置
(或者使用InitializingBean.afterPropertiesSet()的自定义实现来进行bean属性的设置)。
1.9.2。@Autowired
在本节包含的示例中,JSR330的@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以指示容器使用哪个构造函数。
您还可以将@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)与带@Autowired注解的注入点的类型一致地声明。
否则,注入可能会由于运行时出现“no type match found”错误而失败。
对于通过类路径扫描找到的xml定义的bean或组件类,容器通常预先知道具体的类型。
但是,对于@Bean工厂方法,您需要确保声明的返回类型具有足够的表达能力。
对于实现多个接口的组件,或者对于可能由其实现类型引用的组件,
考虑在您的工厂方法上声明最特定的返回类型(至少与引用您的bean的注入点所要求的那样特定)。
您还可以将@Autowired注解添加到需要该类型数组的字段或方法中,指示Spring提供特定类型的所有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定义的注册顺序。
您可以使用@Order在目标类级别和@Bean方法上声明注解。
@Order值可能会影响注入点的优先级,但请注意它们不会影响单例启动顺序,
这是由依赖关系和@DependsOn声明确定的正交关系。(举例:a,b,c三个bean设置的order分别为1,2,3,
但是a依赖c,所以a在初始化的时候会初始化c,导致c比b提前初始化)
请注意,标准javax.annotation.Priority注解在该@Bean级别不可用 ,因为无法在方法上声明它。
可以通过将@Order值与@Primary每个类型的单个bean结合使用来对其语义进行建模。
即使是类型化的Map实例也可以自动注入,键包含相应的bean名称是String类型,值是对应的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 = false)用在方法上时
当存在任何一个参数不可注入,则根本不会调用该方法。 在这种情况下,完全不需要填充非必需字段,而保留其默认值。
当方法有多个参数时,可以使用该注解标识其中的某个参数可以不被注入
public class ServiceController {
private ServiceTwo serviceTwo;
private CusService serviceOne;
public ServiceController(CusService cusService,
@Autowired(required = false) ServiceTwo serviceTwo){
this.serviceOne = cusService;
this.serviceTwo = serviceTwo;
}
}
在任何给定bean类中,只有一个构造函数可以声明@Autowired,并将required属性设置为true,以指示当作为Spring bean使用时要自动装配的构造函数。
因此,如果required属性的默认值为true,那么只有一个构造函数可以使用@Autowired注解。
如果有多个构造函数声明注解,那么它们都必须声明required=false,才能被认为是自动装配的候选者(类似于XML中的autowire=constructor)。
通过在Spring容器中匹配bean可以满足的依赖关系最多的构造函数将被选择。
如果没有一个候选函数可以满足,那么将使用主/默认构造函数(如果存在)。
类似地,如果一个类声明了多个构造函数,但是没有一个是用@Autowired注解的,那么一个主/默认构造函数(如果有的话)将会被使用。
如果一个类只声明了一个构造函数,那么它将始终被使用,即使没有@Autowired注解。
请注意,带注解的构造函数不必是public的。
建议在setter方法上的已弃用的@Required注释上使用@Autowired属性。
将required属性设置为false表示该属性对于自动装配目的是不需要的,并且如果该属性不能自动装配,则忽略它。
另一方面,@Required更强制,因为它强制用容器支持的任何方法设置属性,如果没有定义值,则会引发相应的异常。
另外,您可以通过Java8来表达特定依赖项的非必需性质java.util.Optional,如以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始,您还可以使用@Nullable注解(任何包中的Nullable注解,例如,javax.annotation.Nullable来自JSR-305的注解)。 使用此注解标识该参数不一定会被注入,有可能会是空值
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
您还可以对这些接口(BeanFactory,ApplicationContext,Environment,ResourceLoader, ApplicationEventPublisher,和MessageSource)使用@Autowired。
这些接口及其扩展接口(例如ConfigurableApplicationContext或ResourcePatternResolver)将自动解析,而无需进行特殊设置。
以下示例自动装配ApplicationContext对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
在@Autowired,@Inject,@Value,和@Resource注解由Spring注册的BeanPostProcessor实现。
这意味着您不能在自己的类型BeanPostProcessor或BeanFactoryPostProcessor类型(如果有)中应用这些注解。
必须通过使用XML或Spring@Bean方法显式地“连接”这些类型。
不仅相当上一章的内容:
您应该看到一条参考性日志消息:
Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。
这条消息的意思大概就是说这个bean没有得到所有BeanPostProcessor的处理
如果您自定义的BeanPostProcessor或BeanFactoryPostProcessor在自动注入的BeanPostProcessor之前实例化那么就无法为您注入您想要的参数。
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;
// ...
}
1.9.4。@Qualifier
@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"/> <!-- 指定qualifier属性 -->
</bean>
</beans>
bean名称被认为是默认的qualifier值。
也可以不使用qualifier而是将该bean的id定义为main,达到相同的匹配效果。
然而,尽管您可以使用这种约定来按名称引用特定的bean,但@Autowired基本上是关于类型驱动的注入,qualifier只是在类型之上的可选选项,这意味着,即使使用了bean名称来进行qualifier的限定,qualifier 值也总是在类型匹配集中选择相同名称的bean。
qualifier 也适用于collections, 如前所述—例如 Set
,在这种情况下,根据声明的qualifier值,所有匹配的bean都作为一个集合注入。
这意味着qualifier不必是惟一的。相反,它们构成了过滤标准。例如,您可以定义具有相同qualifier值“action”的多个MovieCatalog bean,
所有这些bean都被注入到带有@Qualifier(“action”)注释的集合中。
public class ServiceController {
@Autowired
@Qualifier("main")
private List<MovieCatalog> serviceList;
}
<bean class="example.SimpleMovieCatalogOne">
<qualifier value="main"/> <!-- 指定相同的qualifier属性 -->
</bean>
<bean class="example.SimpleMovieCatalogTwo">
<qualifier value="main"/> <!-- 指定相同的qualifier属性 -->
</bean>
<bean class="example.SimpleMovieCatalogThree">
<qualifier value="action"/> <!-- 指定相同的qualifier属性 -->
</bean>
如果没有其他注解(例如qualifier或primary ),
对于非唯一依赖情况,Spring将注入点名称(即字段名称或参数名称)与目标bean名称或者bean id匹配,
并选择同名的候选对象(如果有同名的的话,没有同名的话则依然抛出异常)。
如果您打算通过名称进行依赖注入,请不要主要使用@Autowired,即使它能够通过bean名称在类型匹配的候选者中进行选择。
使用JSR-250 @Resource注解:
1. 如果同时指定了name和type,按照bean Name 和 bean 类型同时匹配
2. 如果指定了name,就按照bean Name 匹配
3. 如果指定了type,就按照类型匹配
4. 如果既没有指定name,又没有指定type,就先按照beanName匹配;
如果没有匹配,再按照类型进行匹配;
测试 @Resource的时候还发现一个有意思的东西,
当被注入的是个List的时候,不管是什么类型的List,
只要@Resource加了name条件,都能被注入进去,
比如 List<String> 会被注入到List<MovieCatalog> 大家可以试一下
@Autowired注解:
在按类型选择候选bean之后,再在候选者bean中选择相同名称的。
@Autowired适用于 字段,构造方法,和多参数方法,允许通过qualifier注解在参数级别上缩小范围。
相比之下,@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定义提供信息。您可以添加
标记作为
标记的子元素,然后指定类型和值来匹配您的自定义qualifier注解。该类型与注释的全限定类名匹配。
另外,为了方便起见,如果不存在名称冲突的风险,您可以使用简短的类名。
下面的例子演示了这两种方法:
<?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>
在某些情况下,使用没有值的注解就足够了。当注解用于更通用的目的,并且可以跨几种不同类型的依赖项应用时,这一点非常有用。例如,您可以提供一个脱机目录,当没有可用的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>
您还可以定义自定义qualifier注解,自定义的注解可以定义除了简单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
}
要自动装配的字段将用自定义qualifier进行注解,并包括这两个属性的值: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元属性来代替
元素。
如果可用,
元素及其属性优先,但是如果没有这样的限定符,自动装配机制就会回到
标签中提供的值上,就像下面例子中的最后两个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 和 Store )
class StringStore implements Store<String>{
}
class IntegerStore implements Store<Integer>{
}
则可以@Autowire将该Store接口和通用用作限定符,如以下示例所示:
@Autowired
private Store<String> s1; // <String> qualifier, 注入 stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, 注入 the integerStore bean
在自动装配列表,Map实例和数组时,通用限定符也适用。下面的示例自动连接泛型List:
// 只注入 Store<Integer> 类型
// Store<String> 不会被注入
@Autowired
private List<Store<Integer>> s;
1.9.6。使用CustomAutowireConfigurer
CustomAutowireConfigurer 是一个BeanFactoryPostProcessor
您可以注册自己的自定义限定符注解类型,即使它们未使用Spring的@Qualifier注解进行注解。
像之前我们定义的注解
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String value();
}
这种写法主要就是托@Qualifier的福气
但我们也可以不依赖它
以下示例显示如何使用CustomAutowireConfigurer:
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
example.CustomQualifier Spring会根据这个类路径加载这个类,
并将这个类作为和@Qualifier同作用来对待
自动注入是如何处理候选对象的?
- bean definition 的 autowire-candidate 值,值为false表示该bean不参于候选
- 元素default-autowire-candidates上可用的任何模式,值为false表示该组的bean不参与候选
- @Qualifier注解 和 任何在customautowiresfigurer注册的自定义注解的存在会被优先使用
当多个bean符合自动装配候选条件时,
确定“primary”的步骤如下:如果候选中恰好有一个bean定义将primary属性设置为true,则将其选中。
1.9.7。@Resource
Spring还通过在字段或bean属性设置器方法上使用JSR-250@Resource批注(javax.annotation.Resource)支持注入。
1. 如果同时指定了name和type,按照bean Name 和 bean 类型同时匹配
2. 如果指定了name,就按照bean Name 匹配
3. 如果指定了type,就按照类型匹配
4. 如果既没有指定name,又没有指定type,就先按照beanName匹配;
如果没有匹配,再按照类型进行匹配;
@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;
}
}
因此,在下例中,customerPreferenceDao字段首先查找名为“customerPreferenceDao”的bean,找不到的话然后返回到与类型customerPreferenceDao匹配的bean:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
}
1.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} 则将被当做值注入到属性中。
例如:catalog="${catalog.name}"
如果要严格控制不存在的值,则应声明一个PropertySourcesPlaceholderConfigurerbean,如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
当配置PropertySourcesPlaceholderConfigurer使用JavaConfig,该@Bean方法必须是static。
如果${} 无法解析任何占位符,则使用上述配置可确保Spring初始化失败。
默认情况下,Spring Boot配置一个PropertySourcesPlaceholderConfigurer
从application.properties和application.yml获取bean的属性。
Spring提供的内置转换器支持允许自动处理简单的类型转换(例如转换为Integer 或转换为简单的类型int)。
多个逗号分隔的值可以自动转换为String数组,而无需付出额外的努力。
可以像下边一样提供默认值:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor在后台使用ConversionService来处理将@Value中的字符串值转换为目标类型的过程。如果你想为自己的自定义类型提供转换支持,你可以提供自己的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;
}
}
1.9.9。使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor不仅处理@Resource注解
也处理javax.annotation.PostConstruct和 javax.annotation.PreDestroy。
在Spring 2.5中引入了对这些注解的支持,为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。
如果在Spring ApplicationContext中注册了CommonAnnotationBeanPostProcessor,带有这两个注解的方法将会被回调执行。
在下面的例子中,缓存在初始化时被预填充,在销毁时被清除:
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注解是JDK6到8的标准Java库的一部分。
但是,整个javax.annotation 程序包都与JDK 9中的核心Java模块分开,并最终在JDK 11中删除了。
如果需要,需要对javax.annotation-api工件进行处理。
现在可以通过Maven Central获取,只需像其他任何库一样将其添加到应用程序的类路径中即可。