一.Spring profile
在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁 移到另外一个环境。开发阶段中,某些环境相关做法可能并不适合迁 移到生产环境中,甚至即便迁移过去也无法正常工作。数据库配置、 加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典 型例子。例
--配置profile bean
Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有 太大的差别。当然,在这个过程中需要根据环境决定该创建哪个bean 和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决 策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可 能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。
--我们还是一样建立一个多媒体播放器,以其其CD播放器的实现
1 package 高级配置.profile.player;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public interface MediaPlayer { //定义一个多媒体播放器
8 /**
9 * 播放多媒体文件
10 */
11 void play();
12 }
1 package 高级配置.profile.player;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public interface CompactDisc {
8 /**
9 * 播放内容
10 */
11 void play();
12 }
1 package 高级配置.profile.player;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public class CDorDVDPlayer implements MediaPlayer {
8 private CompactDisc compactDisc;
9
10 public CDorDVDPlayer(CompactDisc compactDisc) {
11 this.compactDisc = compactDisc;
12 }
13
14 @Override
15 public void play() {
16 compactDisc.play();
17 }
18 }
1 package 高级配置.profile.player;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public class MyDVD implements CompactDisc {
8 @Override
9 public void play() {
10 System.out.println("播放我最喜欢的DVD");
11 }
12 }
1 package 高级配置.profile.player;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public class MyCD implements CompactDisc{
8 @Override
9 public void play() {
10 System.out.println("播放我最喜爱的CD..");
11 }
12 }
--现在我们有这样的区别,在我们的测试环境中,此时团队的资金比较充足,需要实现的是播放DVD,但是对于线上环境来说,由于客户使用量较大,昂贵的DVD虽然稳定但是无法承担起这个费用,因此需要使用的是CD,我们可以使用@Profile注解来指定当前的这个bean所装配的环境:
1 package 高级配置.profile.player;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Configuration;
5 import org.springframework.context.annotation.Profile;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Configuration
12 public class PlayerConfig {
13 @Bean("compactDisc")
14 @Profile("test") //此时是在测试开发环境之中
15 public CompactDisc testCompact() {
16 return new MyDVD();
17 }
18
19 @Bean("compactDisc")
20 @Profile("run") //此时是在线上运行环境之中
21 public CompactDisc runCompact() {
22 return new MyCD();
23 }
24
25 @Bean
26 public MediaPlayer player(CompactDisc compactDisc) {
27 return new CDorDVDPlayer(compactDisc);
28 }
29 }
--在我们的测试类中,需要使用到Springtest
1 package 高级配置.profile.player;
2
3 import org.junit.Test;
4 import org.junit.runner.RunWith;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.context.ApplicationContext;
7 import org.springframework.test.context.ActiveProfiles;
8 import org.springframework.test.context.ContextConfiguration;
9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10
11 /**
12 * @author : S K Y
13 * @version :0.0.1
14 */
15 @RunWith(SpringJUnit4ClassRunner.class)
16 @ContextConfiguration(classes = {PlayerConfig.class})
17 @ActiveProfiles("test")
18 public class PlayerTest {
19 @Autowired //在Spring test中使用此方法可以自动装配我们的配置文件,为了确保成功我们应该使用接口ApplicationContext
20 private ApplicationContext context;
21 @Test
22 public void testPlayer(){
23 MediaPlayer player = context.getBean("player", MediaPlayer.class);
24 player.play();
25 }
26 }
--使用@ActiveProfiles注解指定当前的环境为"test"测试环境,当然你也可以指定为使用"run"环境来实现.此时Spring并不是在构建的时候作出这样的决策,而是等到运行时来确定,这样一来我们的同一个部署单元,在这里就是player bean,就能适用于所有的环境,没有必要进行重构建.当然,我们也可以在XML配置文件中实现这样的profiles部署:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5 <bean id="player" class="高级配置.profile.player.CDorDVDPlayer">
6 <constructor-arg ref="compactDisc"/>
7 </bean>
8 <beans profile="test">
9 <bean class="高级配置.profile.player.MyDVD" id="compactDisc"/>
10 </beans>
11 <beans profile="run">
12 <bean class="高级配置.profile.player.MyCD" id="compactDisc"/>
13 </beans>
14 </beans>
1 package 高级配置.profile.player;
2
3 import org.junit.Test;
4 import org.junit.runner.RunWith;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.context.ApplicationContext;
7 import org.springframework.test.context.ActiveProfiles;
8 import org.springframework.test.context.ContextConfiguration;
9 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
10
11 /**
12 * @author : S K Y
13 * @version :0.0.1
14 */
15 @RunWith(SpringJUnit4ClassRunner.class)
16 @ContextConfiguration(locations = {"classpath:高级配置/profile/player/player.xml"})
17 @ActiveProfiles("test")
18 public class XMLConfigTest {
19 @Autowired
20 private ApplicationContext context;
21 @Test
22 public void testXMLPlayer() {
23 MediaPlayer player = context.getBean("player", MediaPlayer.class);
24 player.play();
25 }
26 }
--事实上激活Spring的profile,并不只有上述的@ActiveProfiles注解,想要激活profile,需要依赖两个独立的属性:
1.spring.profiles.active
2.spring.profiles.default
--如果设置了spring.profiles.active属性的话,那么它的值就会用来确定那个哪个profile是激活的,但是如果没有设置spring.profiles.active的话,那么Spring将会查找spring.profiles.default的值.如果这两个值都没有进行设置的话,那么就没有激活的profile,因此只会创建那些没有定义在profile中的bean,在Spring中,提供了多种方式来设置这两个属性(更多的是在Web应用中):
1.作为DispatcherServlet的初始化参数;
2.作为Wen应用的上下文参数;
3.作为环境变量;
4.作为JVM的系统属性;
5.在集成测试类上,使用@ActiveProfiles注解来设置.
--使用Spring4和@Conditional注解来自定义条件化的bean
假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才 创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之 后才会创建。我们还可能要求只有某个特定的环境变量设置之后,才 会创建某个bean。
--在@Condition注解中,定义了一个Class,他指明了条件,@Condition将会通过Condition接口进行条件比对:
public interface Condition {
boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}
--我们可以自定义一个Condition接口的实现:
1 package 高级配置.profile.player.condition;
2
3 import org.springframework.context.annotation.Condition;
4 import org.springframework.context.annotation.ConditionContext;
5 import org.springframework.core.env.Environment;
6 import org.springframework.core.type.AnnotatedTypeMetadata;
7
8 import java.util.Arrays;
9
10
11 /**
12 * @author : S K Y
13 * @version :0.0.1
14 */
15 public class MagicExistsCondition implements Condition {
16 @Override
17 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
18 Environment environment = context.getEnvironment();
19 String[] activeProfiles = environment.getActiveProfiles();//获取当前环境中的Profile属性
20 for (String activeProfile : activeProfiles) {
21 if (activeProfile.equalsIgnoreCase("magic")) {
22 return true;
23 }
24 }
25 return false; //检查magic属性
26 }
27 }
1 package 高级配置.profile.player.condition;
2
3 import org.springframework.context.annotation.Bean;
4 import org.springframework.context.annotation.Conditional;
5 import org.springframework.context.annotation.Configuration;
6 import org.springframework.context.annotation.Profile;
7 import 高级配置.profile.player.*;
8
9 /**
10 * @author : S K Y
11 * @version :0.0.1
12 */
13 @Configuration
14 public class PlayerConfig {
15 @Bean("player")
16 @Conditional(MagicExistsCondition.class)
17 public MediaPlayer player(CompactDisc compactDisc) {
18 return new CDorDVDPlayer(compactDisc);
19 }
20
21 @Bean("compactDisc")
22 @Profile("magic")
23 public CompactDisc myDVD() {
24 return new MyDVD();
25 }
26
27 @Bean("compactDisc")
28 @Profile("test")
29 public CompactDisc myCD() {
30 return new MyCD();
31 }
32 }
--此时只有在我们当前的Profiles中存在magic,才能正确的初始化我们的bean,否则bean将不会被装配.可以发现在MagicExistsCondition中,使用了ConditionContext来获取当前的environment,ConditionContext是一个接口:
1 package org.springframework.context.annotation;
2
3 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
4 import org.springframework.beans.factory.support.BeanDefinitionRegistry;
5 import org.springframework.core.env.Environment;
6 import org.springframework.core.io.ResourceLoader;
7
8 /**
9 * Context information for use by {@link Condition}s.
10 *
11 * @author Phillip Webb
12 * @since 4.0
13 */
14 public interface ConditionContext {
15
16 BeanDefinitionRegistry getRegistry();
17
18 ConfigurableListableBeanFactory getBeanFactory();
19
20 Environment getEnvironment();
21
22 ResourceLoader getResourceLoader();
23
24 ClassLoader getClassLoader();
25
26 }
--通过ConditionContext我们可以做到如下几点:
1.借助getRegistry()返回的BeanDefinitionRegistry检查 bean定义;
2.借助getBeanFactory()返回的 ConfigurableListableBeanFactory检查bean是否存在, 甚至探查bean的属性;
3.借助getEnvironment()返回的Environment检查环境变量 是否存在以及它的值是什么;
4.读取并探查getResourceLoader()返回的ResourceLoader 所加载的资源;
5.借助getClassLoader()返回的ClassLoader加载并检查类 是否存在。
--而matches()方法的另一个属性AnnotatedTypeMetadata 则能够让我们检查带有@Bean注解的方法上还有什么其他的注解:
1 package org.springframework.core.type;
2
3 import java.util.Map;
4
5 import org.springframework.util.MultiValueMap;
6
7 /**
8 * Defines access to the annotations of a specific type ({@link AnnotationMetadata class}
9 * or {@link MethodMetadata method}), in a form that does not necessarily require the
10 * class-loading.
11 *
12 * @author Juergen Hoeller
13 * @author Mark Fisher
14 * @author Mark Pollack
15 * @author Chris Beams
16 * @author Phillip Webb
17 * @author Sam Brannen
18 * @since 4.0
19 * @see AnnotationMetadata
20 * @see MethodMetadata
21 */
22 public interface AnnotatedTypeMetadata {
23
24 boolean isAnnotated(String annotationName);
25
26 Map<String, Object> getAnnotationAttributes(String annotationName);
27
28 Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
29
30 MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
31
32 MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
33
34 }
--借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解,借助其他的方法,我们可以判断@Bean注解的方法上是否还有其他注解的属性.@Profile本身也使用了@Conditional注解,并且引用ProfileCondition作为Condition实 现。如下所示,ProfileCondition实现了Condition接口,并且在做出决策的过程中,考虑到了 ConditionContext和AnnotatedTypeMetadata中的多个因素:
1 package org.springframework.context.annotation;
2
3 import java.lang.annotation.Documented;
4 import java.lang.annotation.ElementType;
5 import java.lang.annotation.Retention;
6 import java.lang.annotation.RetentionPolicy;
7 import java.lang.annotation.Target;
8
9 import org.springframework.core.env.AbstractEnvironment;
10 import org.springframework.core.env.ConfigurableEnvironment;
11 @Retention(RetentionPolicy.RUNTIME)
12 @Target({ElementType.TYPE, ElementType.METHOD})
13 @Documented
14 @Conditional(ProfileCondition.class)
15 public @interface Profile {
16
17 /**
18 * The set of profiles for which the annotated component should be registered.
19 */
20 String[] value();
21
22 }
1 class ProfileCondition implements Condition {
2
3 @Override
4 public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
5 if (context.getEnvironment() != null) {
6 MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());
7 if (attrs != null) {
8 for (Object value : attrs.get("value")) {
9 if (context.getEnvironment().acceptsProfiles(((String[]) value))) {
10 return true;
11 }
12 }
13 return false;
14 }
15 }
16 return true;
17 }
18
19 }
--ProfileCondition通过 AnnotatedTypeMetadata得到了用于@Profile注解的所有属 性。借助该信息,它会明确地检查value属性,该属性包含了bean的 profile名称。然后,它根据通过ConditionContext得到的 Environment来检查[借助acceptsProfiles()方法]该profile 是否处于激活状态。
二.处理自动装配的歧义
在之前已经介绍了如何使用自动装配让Spring完成bean的装配和使用(点击查看原文),但是也存在一个问题,在仅有一个bean匹配所需的结果的时候,自动装配才是有效的,如果不仅有一个bean能够匹配结果的haunted,这种歧义会阻碍Spring自动装配属性,构造器参数或方法参数:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 /**
4 * @author : S K Y
5 * @version :0.0.1
6 */
7 public interface Dessert {
8 /**
9 * 吃甜点
10 */
11 void eat();
12 }
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.stereotype.Component;
4
5 /**
6 * @author : S K Y
7 * @version :0.0.1
8 */
9 @Component
10 public class Cake implements Dessert {
11 @Override
12 public void eat() {
13 System.out.println("吃蛋糕....");
14 }
15 }
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.stereotype.Component;
4
5 /**
6 * @author : S K Y
7 * @version :0.0.1
8 */
9 @Component
10 public class Cookies implements Dessert {
11 @Override
12 public void eat() {
13 System.out.println("吃巧克力...");
14 }
15 }
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.stereotype.Component;
4
5 /**
6 * @author : S K Y
7 * @version :0.0.1
8 */
9 @Component
10 public class IceCream implements Dessert {
11 @Override
12 public void eat() {
13 System.out.println("吃冰激凌...");
14 }
15 }
--此时我们存在有3个Dessert的实现,那么此时如果我们进行自动化装配:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.junit.Test;
4 import org.junit.runner.RunWith;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.test.context.ContextConfiguration;
7 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
8
9 /**
10 * @author : S K Y
11 * @version :0.0.1
12 */
13 @RunWith(SpringJUnit4ClassRunner.class)
14 @ContextConfiguration(classes = {AutoWiredConfig.class})
15 public class DessertTest {
16 @Autowired
17 private Dessert dessert;
18 @Test
19 public void eatDessert(){
20 dessert.eat();
21 }
22 }
--运行结果(会出现如下异常):
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name '高级配置.profile.player.自动装配中的歧义.DessertTest': Unsatisfied dependency expressed through field 'dessert'; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type '高级配置.profile.player.自动装配中的歧义.Dessert' available: expected single matching bean but found 3: cake,cookies,iceCream
--可以发现在自动装配的时候,发现了三个和Dessert相关的bean,因此由于歧义的存在,本次的自动装配将无法成功.从而抛出异常.在Spring中提供了解决歧义的多种方案:我们可以将一个bean设置为首选(primary),或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean.
--标示首选的bean
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.context.annotation.Primary;
4 import org.springframework.stereotype.Component;
5
6 /**
7 * @author : S K Y
8 * @version :0.0.1
9 */
10 @Component
11 @Primary
12 public class Cake implements Dessert {
13 @Override
14 public void eat() {
15 System.out.println("吃蛋糕....");
16 }
17 }
--如果使用的是XML的配置方式,那么可以设置profile属性为true:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5 <bean id="cake" class="高级配置.profile.player.自动装配中的歧义.Cake"/>
6 <bean id="cookies" class="高级配置.profile.player.自动装配中的歧义.Cookies" primary="true"/>
7 <bean id="iceCream" class="高级配置.profile.player.自动装配中的歧义.IceCream"/>
8 </beans>
--如果说设置了不止一个的bean为首选的bean,那么就相当于没有设置这个首选属性了,Spring还是找不到我们所需要自动装配的bean,依然存在歧义.
--限定自动装配的bean
设置首选bean的局限性在于@Primary无法将可选方案的范围限定到 唯一一个无歧义性的选项中。它只能标示一个优先的可选方案。当首 选bean的数量超过一个时,我们并没有其他的方法进一步缩小可选范围。
--Spring的限定符能够在所有可选的bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件,如果将所有的限定符都用上后依然存在歧义,那么可以继续使用更多的限定符来缩小选择的范围.@Qualifier注解使使用限定符的主要方式,他可以与@Autowired注解协同使用:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.junit.Test;
4 import org.junit.runner.RunWith;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.beans.factory.annotation.Qualifier;
7 import org.springframework.test.context.ContextConfiguration;
8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
9
10 /**
11 * @author : S K Y
12 * @version :0.0.1
13 */
14 @RunWith(SpringJUnit4ClassRunner.class)
15 @ContextConfiguration(classes = {AutoWiredConfig.class})
16 public class DessertTest {
17 @Autowired
18 @Qualifier("iceCream") //优先级高于primary属性即@Primary注解
19 private Dessert dessert;
20
21 @Test
22 public void eatDessert() {
23 dessert.eat();
24 }
25 }
--更准确地 讲,@Qualifier("iceCream")所引用的bean要具有String类型 的“iceCream”作为限定符。如果没有指定其他的限定符的话,所有的 bean都会给定一个默认的限定符,这个限定符与bean的ID相同。因 此,框架会将具有“iceCream”限定符的bean注入到setDessert()方 法中。这恰巧就是ID为iceCream的bean,它是IceCream类在组件 扫描的时候创建的。但是这样依然存在一个问题,如果哪一天进行代码的重构将IceCream修改了名称,那么此时所定义的@Qualifier("iceCream")就将失去了作用.因此实际上我们在进行自动装配的时候可以自定义限定符:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.beans.factory.annotation.Qualifier;
4 import org.springframework.stereotype.Component;
5
6 /**
7 * @author : S K Y
8 * @version :0.0.1
9 */
10 @Component
11 @Qualifier("myFavoriteDessert") //使用自定义的限定符
12 public class Cookies implements Dessert {
13 @Override
14 public void eat() {
15 System.out.println("吃巧克力...");
16 }
17 }
--但是如果这个人比较贪心,同时为多个都设置了相同的自定义的限定符,那么歧义将会再次产生,此时我们可以对其中一个设置@Primary注解,那么可以消除歧义:
1 @Component
2 @Primary
3 @Qualifier("myFavoriteDessert") //使用自定义的限定符
4 public class Cake implements Dessert {
5 @Override
6 public void eat() {
7 System.out.println("吃蛋糕....");
8 }
9 }
--除此之外,我们还能定义多个@Qualifiter注解来消除歧义,但是这里会有一个小问题:Java不允许在同一个条目上重复出现相同类型的多个注解,但是我们可以创建自定义的限定符注解来解决这个问题:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.beans.factory.annotation.Qualifier;
4
5 import java.lang.annotation.*;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE, ElementType.ANNOTATION_TYPE})
12 @Retention(RetentionPolicy.RUNTIME)
13 @Qualifier
14 public @interface MyFavoriteDessert {
15 String value();
16 }
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.junit.Test;
4 import org.junit.runner.RunWith;
5 import org.springframework.beans.factory.annotation.Autowired;
6 import org.springframework.beans.factory.annotation.Qualifier;
7 import org.springframework.test.context.ContextConfiguration;
8 import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
9
10 /**
11 * @author : S K Y
12 * @version :0.0.1
13 */
14 @RunWith(SpringJUnit4ClassRunner.class)
15 @ContextConfiguration(classes = {AutoWiredConfig.class})
16 public class DessertTest {
17 @Autowired
18 @Qualifier("myFavoriteDessert") //优先级高于primary属性即@Primary注解
19 @MyFavoriteDessert("cookies")
20 private Dessert dessert;
21
22 @Test
23 public void eatDessert() {
24 dessert.eat();
25 }
26 }
三.bean的作用域
在默认情况下,Spring应用上下文中所有的bean都是以单例(singleton)的形式创建的.也就是说,不管给定的一个bean被注入到其他的bean中多次,每次所注入的都是熊一个实例.但是有时候所使用的一个类是易变的(mutable),他们会保持一些状态,因此重用是不安全的.这样一来,将class声明为单例的bean就不是声明好主意;额,因为对象会被污染,稍后重用的时候会出现意想不到的情况.Spring中定义了多种作用域,可以基于这些作用域创建bean:
1.单例(Singleton):在整个应用中,只创建bean的一个实例。
2.原型(Prototype):每次注入或者通过Spring应用上下文获取的 时候,都会创建一个新的bean实例。
3.会话(Session):在Web应用中,为每个会话创建一个bean实 例。
4.请求(Rquest):在Web应用中,为每个请求创建一个bean实 例。
--单例是默认的作用域,如果要选择其他的作用域,要使用@Scope注解,他可以与@Component或@Bean注解一起使用
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.beans.factory.annotation.Qualifier;
4 import org.springframework.beans.factory.config.ConfigurableBeanFactory;
5 import org.springframework.context.annotation.Scope;
6 import org.springframework.stereotype.Component;
7
8 /**
9 * @author : S K Y
10 * @version :0.0.1
11 */
12 @Component
13 @Qualifier("myFavoriteDessert") //使用自定义的限定符
14 @MyFavoriteDessert("cookies")
15 @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) //声明为原型
16 public class Cookies implements Dessert {
17 @Override
18 public void eat() {
19 System.out.println("吃巧克力...");
20 }
21 }
1 @Test
2 public void testScope(){
3 Dessert cakeA = context.getBean("cake", Dessert.class);
4 Dessert cookiesA = context.getBean("cookies", Dessert.class);
5 Dessert cakeB = context.getBean("cake", Dessert.class);
6 Dessert cookiesB = context.getBean("cookies", Dessert.class);
7 System.out.println(cakeA + " " + cakeB);
8 System.out.println(cookiesA + " " + cookiesB );
9 }
--运行结果
高级配置.profile.player.自动装配中的歧义.Cake@3d74bf60 高级配置.profile.player.自动装配中的歧义.Cake@3d74bf60
高级配置.profile.player.自动装配中的歧义.Cookies@4f209819 高级配置.profile.player.自动装配中的歧义.Cookies@15eb5ee5
九月 08, 2019 10:35:12 上午 org.springframework.context.support.GenericApplicationContext doClose
信息: Closing org.springframework.context.support.GenericApplicationContext@3cbbc1e0: startup date [Sun Sep 08 10:35:11 CST 2019]; root of context hierarchy
Process finished with exit code 0
--此外,也可以直接使用@Scope("prototype")来进行声明,但是使用常来会更加的安全不易出错,在XML配置文件中则可以使用scope属性来设置其类型:
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
5 <bean id="cake" class="高级配置.profile.player.自动装配中的歧义.Cake"/>
6 <bean id="cookies" class="高级配置.profile.player.自动装配中的歧义.Cookies" primary="true"/>
7 <bean id="iceCream" class="高级配置.profile.player.自动装配中的歧义.IceCream" scope="prototype"/>
8 </beans>
四.运行时值注入
当讨论依赖注入的时候,我们通常所讨论的是将一个bean引用注入到 另一个bean的属性或构造器参数中。它通常来讲指的是将一个对象与 另一个对象进行关联。但是还存在将属性或者构造参数进行赋值的情况,此时在我们之前的装配中使用的都是硬编码的装配(显示的指明了所需要装配的值),那么如何在运行时去决定所需要装配的值呢,为了实现这个功能,Spring提供了两种运行时求值的方式:
1.属性占位符(Property placeholder);
2.Spring表达式语言(SpEL);
--利用属性占位符注入外部的值,此时我们需要准备一个properties属性文件:
1 //@Component
2 @Qualifier("myFavoriteDessert") //使用自定义的限定符
3 @MyFavoriteDessert("cake")
4 public class Cake implements Dessert {
5 private String name;
6
7 public Cake(String name) {
8 this.name = name;
9 }
10
11 @Override
12 public void eat() {
13 System.out.println("吃" + name + "....");
14 }
15 }
1 @Configuration
2 //@ComponentScan(basePackages = {"高级配置.profile.player.自动装配中的歧义"})
3 @PropertySource("classpath:高级配置/profile/player/自动装配中的歧义/dessert.properties") //引入外部的属性文件
4 public class AutoWiredConfig {
5 @Autowired
6 private Environment environment;
7
8 @Bean
9 public Dessert cake() {
10 return new Cake(environment.getProperty("name"));
11 }
12 }
1 @Test
2 public void testProperties(){
3 Dessert cake = context.getBean("cake", Dessert.class);
4 cake.eat();
5 }
--运行结果
吃超级无敌大蛋糕....
Process finished with exit code 0
--需要注意的是在属性文件中,中文字符不能直接输入,需要输入Unicode编码,可以使用JDK的自带工具native2ascii.exe来实现转化.
--深入学习Spring的Environment:我们可以发现getProperty()方法存在四种重载的实现:
1.String getProperty(String key);
2.String getProperty(String key, String defaultValue);
3.<T> T getProperty(String key, Class<T> targetType);
4.<T> T getProperty(String key, Class<T> targetType, T defaultValue);
--前两种方法都会返回一个String类型的参数,但是使用第二个方法,当属性不存在的时候,我们可以自定义默认值,3 4 两种方法则可以在获取属性的时候将内容转化为指定的类型,例如我们知道所需要获取的属性是数字(当前配置的是连接池所维持的最大连接数量),那么可以直接指定返回类型为Integer.class;当然,如果对于获取的属性,希望他必须存在定义,那么我们可以使用String getRequiredProperty(String key) throws IllegalStateException;方法来完成,此时如果当前的属性没有并定义的话,就会抛出异常信息;同时还可以使用boolean containsProperty(String key);方法来判断一个属性是否存在;此外还提供了一些方法来检查有哪些profile处于激活状态:
1.String[] getActiveProfiles():返回当前激活的profile名称的数组;
2.String[] getDefaultProfiles():返回默认profile名称的数组
3.boolean acceptsProfiles(String... profiles): 如果environment支持所给定的profile,那么则返回true;
--属性占位符:我们可以使用属性占位符来完成我们的定义,当然想要使用属性占位符的话,我们必须配置PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean:
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.beans.factory.annotation.Qualifier;
4 import org.springframework.beans.factory.annotation.Value;
5 import org.springframework.stereotype.Component;
6
7 /**
8 * @author : S K Y
9 * @version :0.0.1
10 */
11 @Component
12 @Qualifier("myFavoriteDessert") //使用自定义的限定符
13 @MyFavoriteDessert("cake")
14 public class Cake implements Dessert {
15 private String name;
16
17 public Cake(@Value("${name}") String name) {
18 this.name = name;
19 }
20
21 @Override
22 public void eat() {
23 System.out.println("吃" + name + "....");
24 }
25 }
1 package 高级配置.profile.player.自动装配中的歧义;
2
3 import org.springframework.beans.factory.annotation.Autowired;
4 import org.springframework.context.annotation.Bean;
5 import org.springframework.context.annotation.ComponentScan;
6 import org.springframework.context.annotation.Configuration;
7 import org.springframework.context.annotation.PropertySource;
8 import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
9
10 /**
11 * @author : S K Y
12 * @version :0.0.1
13 */
14 @Configuration
15 @ComponentScan(basePackages = {"高级配置.profile.player.自动装配中的歧义"})
16 @PropertySource("classpath:高级配置/profile/player/自动装配中的歧义/dessert.properties") //引入外部的属性文件
17 public class AutoWiredConfig {
18 @Bean
19 public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
20 return new PropertySourcesPlaceholderConfigurer();
21 }
22
23 }
--如果想要在XML中使用属性占位符进行定义的话,Spring context命名空间中的<context:propertyPlaceholder>元素可是实现PropertySourcesPlaceholderConfigurer bean的功能
1 <?xml version="1.0" encoding="UTF-8"?>
2 <beans xmlns="http://www.springframework.org/schema/beans"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xmlns:context="http://www.springframework.org/schema/context"
5 xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
6 <!--引入外部属性文件-->
7 <context:property-placeholder location="classpath:高级配置/profile/player/自动装配中的歧义/dessert.properties"`/>
8 <bean id="cake" class="高级配置.profile.player.自动装配中的歧义.Cake" primary="true">
9 <constructor-arg value="${name}"/>
10 </bean>
11 <bean id="cookies" class="高级配置.profile.player.自动装配中的歧义.Cookies"/>
12 <bean id="iceCream" class="高级配置.profile.player.自动装配中的歧义.IceCream" scope="prototype"/>
13 </beans>
--使用Spring表达式语言进行装配
Spring 3引入了Spring表达式语言(Spring Expression Language, SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造 器参数中,在这个过程中所使用的表达式会在运行时计算得到值。SpELl拥有如下特性:
1.使用bean的ID来引用bean;
2.调用方法和访问对象的属性;
3.对值进行算数,关系和逻辑运算;
4.正则表达式匹配;
5.集合操作;
--SpELl样例
SpEL表达式要放到"#{...}"中(需要主要的是,属性占位符是放在${..}中),例如我们定义SpEL表达式#{1},那么其表达式体就是数字1,一个数字常量,那么我们所计算得到的结果就是数字1.#{T(System).currentTimeMillis()}:T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其static修饰的currentTimeMillis()方法.SpEL还可以引用其他bean的属性#{student.name},那么就可以得到ID为student的bean的name属性.
--在上述的示例中,在注入属性和构造器参数时,我们可以使用@Value注解,这与属性占位符非常的相似,但是我们所使用的实质上并不是占位符表达式,而是属于SpEL表达式:
1 @Component
2 @Qualifier("myFavoriteDessert") //使用自定义的限定符
3 @MyFavoriteDessert("cake")
4 public class Cake implements Dessert {
5 private String name;
6
7 public Cake(@Value("#{systemProperties['os.name']}") String name) {
8 this.name = name;
9 }
10
11 @Override
12 public void eat() {
13 System.out.println("吃" + name + "....");
14 }
15 }
--这样我们就可以顺利获取到当前的系统名称来作为们要吃的蛋糕名称....同样在XML配置文件中,我们也可以通过这样的方法来进行我们参数的设置,接下来我们看如何使用SpEL来设置字面值:
1.设置浮点数: #{3.14159};
2.设置数值98700: #{9.87E4};
3.设置String字符串: #{'hello'};
4.设置Boolean类型的值: #{false}
--引用bean,属性和方法
1.使用beanID:#{student};
2.引用bean属性: #{student.name};
3.引用方法: #{student.getTeacher()};
4.操作引用方法的返回值: #{studnet.getTeacher().getName()};
5.防止空异常(在不是null的情况下执行?后的操作): #{student.getTeacher()?.getName()};
6.访问类作用域的方法和常量: T{java.lang.Math}
7.将PI(π)装配到bean属性中: #{T{java.lang.Math}.PI};
8.调用static方法: #{T{java.lang.Math}.random()}
--SpEL运算符:
SpEl三目表达式的运算: #{studentA.age > studentB.age?true : false};
查询学生集合中名称第一个匹配MIke的人: #{teacher.students.^[name eq 'Mike']};
选择学生集合中名字最后一匹配MIke的人:#{teacher.students.$[name eq 'Mike']};
投影运算,将一个教师的学生的姓名存放入一个新的集合: #{teacher.students.![name]};
获取所有年龄为18岁的学生的名称集合: #{teacher.students.?[age eq 18].![name]};
获取字符串的指定字符(获取第四个字符): #{"hello"[3]}
--事实上,在Java8中允许出现重复的注解,只要将这个注解本身在定义的时候带有@Repeatable注解就可以了,但是Spring的@Qualifier注解在定义的时候并没有添加@Repeatable注解.