1.9。基于注解的容器配置
注解在配置Spring方面比XML更好吗? 基于注解的配置的引入提出了一个问题,即这种方法是否比XML“更好”。 简短的答案是“取决于状况”。 长话短说,每种方法都有其优缺点,一般,由开发人员决定哪一种策略更适合他们。 因为定义方式的不一样,注解在声明中提供了不少上下文,从而使配置更短,更简洁。 可是,XML擅长链接组件而不接触其源代码或从新编译它们。 一些开发人员更喜欢将布线放置在靠近源的位置,而另外一些开发人员则认为带注解的类再也不是POJO, 并且,该配置变得分散且难以控制。 不管选择如何,Spring均可以容纳两种样式,甚至能够将它们混合在一块儿。 值得指出的是,经过其JavaConfig选项,Spring容许以非侵入方式使用注解, 而无需接触目标组件的源代码。
注解是XML配置的替代方法,该配置依赖字节码元数据来链接组件,而不是尖括号声明。
经过使用相关的 类,方法或字段 声明上的注解,开发人员无需使用XML来描述bean的链接,而是将配置移入组件类自己。java
如示例中所述:将RequiredAnnotationBeanPostProcessor,经过BeanPostProcessor的方式与注解结合使用是扩展Spring IoC容器的经常使用方法。web
例如,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配置将覆盖经过注解注入的属性spring
与往常同样,您能够根据类名将它们注册为单独的bean定义,但也能够经过在基于XML的Spring配置中包含如下标记来隐式注册它们:api
<?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。spring-mvc
上边的这段话意思不是很明确,须要解释一下之前用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属性,容器将抛出异常。tomcat
这样显式的失败,避免了实例在应用的时候出现NullPointerException的状况。数据结构
@Required注解在Spring Framework 5.1时正式被弃用, Spring更赞同使用构造函数注入来进行必需参数的设置 (或者使用InitializingBean.afterPropertiesSet()的自定义实现来进行bean属性的设置)。
1.9.2。@Autowired
在本节包含的示例中,JSR330的@Inject注释能够用来替代Spring的@Autowired注释。
您能够将@Autowired注解应用于构造函数,如如下示例所示:mvc
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<MovieCatalog>,在这种状况下,根据声明的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></qualifier>标记做为<bean></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元属性来代替<qualifier></qualifier>元素。
若是可用,<qualifier></qualifier>元素及其属性优先,可是若是没有这样的限定符,自动装配机制就会回到<meta>标签中提供的值上,就像下面例子中的最后两个bean定义同样:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Action"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Comedy"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="DVD"/> <meta key="genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="BLURAY"/> <meta key="genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> </beans>
1.9.5。将泛型用做自动装配限定符
除了@Qualifier注解以外,您还能够将Java泛型类型用做资格的隐式形式。例如,假设您具备如下配置:
@Configuration public class MyConfiguration { @Bean public StringStore stringStore() { return new StringStore(); } @Bean public IntegerStore integerStore() { return new IntegerStore(); } }
假设前面的bean实现了一个通用接口(即Store<String>和 Store<Integer>)
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不参于候选
- <beans/>元素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获取,只需像其余任何库同样将其添加到应用程序的类路径中便可。