1.配置profile bean
在java配置中使用@profile注解指定某个bean属于哪一个profile。例如,在配置类中,嵌入式数据库的DataSource可能会配置成如下所示:
在这里@Profile注解应用在类级别上,它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。否则带有@Bean注解的方法都会被忽略。在Spring3.1以前@Profile只能在类级别上使用。在Spring3.2以后可以在方法级别上使用,如:
只有在规定的profile激活时,相应的bean才会被创建。没有指明profile的bean始终会被创建,与激活哪个profile没有关系。
在XML中配置profile:
重复使用元素来指定bean:
除了所有的bean定义到了同一个XML文件之中,这种配置方式与定义在单独的XML文件中的实际效果是一样的。这里有三个bean,类型都是javax.sql.DataSource,并且ID都是dataSource。但是在运行时,只会创建一个bean,这取决于处于激活状态的是哪个profile
激活profile:
Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.active和spring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。
设置这两个属性:
1.作为DispatcherServlet的初始化参数;
2.作为Web应用的上下文参数;
3.作为JNDI条目;
4.作为环境变量;
5.作为JVM的系统属性;
6.在集成测试类上,使用@ActiveProfiles注解设置。
下面看一下通过使用DispatcherServlet的参数将spring.profiles.default设置为开发环境的profile.web.xml如下:
2.条件化的Bean
如果想要一个或多个bean只有在应用的类路径下包含特定的库时才创建,或者希望某个bean只有当某个特定的bean也声明了之后才创建。等等……
在Spring4之前很难实现这种级别的条件化配置。但是在spring4引入了一个新的注解@Conditional注解,它可以用到带有@Bean注解的方法上。如果条件计算结果为true,则创建这个bean,否则这个bean会被忽略。
@Conditional中给定了一个Class,它指明了条件-在本例中MagicExistsCondition为条件。@Conditional将会通过Condition接口进行条件比对:
设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。如果matches()方法返回false,将不会创建这些bean。
本例中我们要创建Condition的实现并跟姐姐环境中是否存在magic属性做决策。
matches()方法很简单且功能强大,它通过给定的ConditionContext对象得到Environment对象,并使用这个对象检查环境中是否存在magic的环境属性。如果满足要求则会返回true。否则返回false。
通过ConditionContext我们可做如下几点:
1.借助getRegistry()返回的BeanDefinitionRegistry检查bean定义;
2.借助getBeanFactory()返回的ConfigurableListableBeanFactory检查Bean是否存在甚至探查bean属性;
3.借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;
4.读取并探查getResourceLoader()返回的ResourceLoader所加载的资源;
5.借助getClassLoader()返回的ClassLoader加载并检查类是否存在;
AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他的注解。
借助isAnnotated()方法,我们能判断带有@Bean注解的方法是不是还有其他注解,借助其他方法我们能检查@Bean方法上其他注解属性。
3.处理自动装配的歧义性
通过自动装配让Spring完全负责将bean引用注入到构造参数和属性中,但是只有在一个bean匹配到所需的结果时,自动装配才是有效的。为了展示自动装配的歧义性:
在本例中Dessert是一个接口,并且有三个类实现这个接口:
三个实现类均使用了@Component注解,在组件扫描的时候,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。因为Spring无法做出选择所以会抛出Spring会抛出NoUniqueBeanDefinitionException。解决方案有将可选bean中的某一个设为首选Bean,或者使用限定符将bean的范围缩小到一个Bean。
1.通过@Primary来表达最喜欢的方案:
需要注意的是不能同时标示两个或更多的首选Bean。
2.限定自动装配的bean
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如我们想要确保将IceCream注入到setDessert()中:
创建自定义的限定符
在bean声明上添加@Qualifier注解
然后在注入的地方引用cold限定符就行了
当Java配置现实定义的bean时,@Qualifier可以与@bean一起使用:
如果此时有两个带有cold限定符的Bean那么可以在注入点和定义bean的地方同时添加另一个@Qualifier注解:
注入点:
但是java不允许同一个条目上重复出现相同类型的注解!!!!!!
所以我们可以创建自定义的限定符注解:
等价于@Qualifier("cold")
同理@Qualifier("creamy"):
最后如图所示:
4.bean的作用域
在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:单例(Singleton):在整个应用中,只创建bean的一个实例。
原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
会话(Session):在Web应用中,为每个会话创建一个bean实例。
请求(Rquest):在Web应用中,为每个请求创建一个bean实例。
单例是默认的作用域,但是不适用于易变的类型。如果要选择其他作用域要使用@Scope注解,它可以与@Component或者@Bean一起使用。
如果你使用组件扫描来发现和声明bean,那么你可以在bean的类上使用@Scope注解,将其声明为原型bean:
也可以使用@Scope("prototype"),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。
在JavaConfig中配置Bean作用域:
在XML中配置bean作用域:
举个例子:购物车Bean
我们将value设置成了webApplicationContext中的SCOPE_SESSION常量(当前值为session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。会创建多个ShoppingCartbean实例,但是对于当前会话只会创建一个实例,在当前会话的相关操作中,这个bean相当于单例。
除此之外还有个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES,这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。
假设我们要将ShoppingCart bean注入到单例的StoreService bean的Setter方法中,如:
因为ShoppingCart bean是会话作用域,而且系统中会有多个ShoppingCart实例,所以我们并不像让Spring注入某个固定的ShoppingCart实例,我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例是当前会话对应的那个。
Spring不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCart bean的代理:
这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCartbean。
proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现Shopping接口,并调用委托给实现bean.如果ShoppingCart是接口不是类的话这是可以的(最理想的代理模式)。但是如果ShoppingCart是一个具体的类Spring没有办法创建基于接口的代理。此时它必须使用CGLib来生成基于类的代理。并且将要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。
请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入。
在XML中声明作用域
<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CGLib创建目标类的代理。但是我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:
如果要使用<aop:scoped-proxy>元素,要在XML中声明Spring aop的命名空间:
3.运行时注入
Spring提供了两种运行时求值的方式:
1.属性占位符(Property placceholder)。
2.spring表达式语言(SpEL)。
注入外部的值
在Spring中,处理外部值得最简单方式就是声明属性源并通过Spring的Environment来检索属性,如:
@PropertySource引用类路径中的一个app.properties文件,大致如下所示:
这个属性文件会加载到Spring的Environment中,稍后可以从这里检索属性。同时,在disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这是通过调用getProperty()实现的。
getProperty()方法有四个重载的变种形式:
如果你希望这个属性必须要定义,那么可以使用getRequiredProperty()方法.
如果disc.title或disc.artist没有定义会抛出IllegalStaeException异常。
如果想检查某个属性是否存在可以调用Environment的containsProperty()方法:
如果想将属性解析为类的话,可以使用getPropertyAsClass()方法:
检查哪些profile处于激活状态:
解析属性占位符
Spring一直支持将属性定义到外部的属性的文件中,并使用占位符值将其插入到Spring bean中。在Spring装配中,占位符的形式为使用“${... }”包装的属性名称。
如果我们依赖于组件扫描和自动装配来创建和初始化应用组件的话,那么就没有指定占位符的配置文件或类了。在这种情况下,我们可以使用@Value注解,它的使用方式与@Autowired注解非常相似。
为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurerbean或PropertySourcesPlaceholderConfigurerbean。从Spring3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。
如果要使用XML配置,Spring context命名空间中的<context:propertyplaceholder>元素会生成 PropertySourcesPlaceholderConfigurer bean:
使用Spring表达式语言进行装配
Spring 3引入了Spring表达式语言(Spring Expression Language,SpEL),它能够以一种强大和简洁的方式将值装配到bean属性和构造器参数中,在这个过程中所使用的表达式会在运行时计算得到值。
SpEL有很多特性,包括:
1.使用Bean的ID引用Bean;
2.调用方法和访问关系的属性;
3.对值进行算术,关系,和逻辑运算;
4.正则表达式匹配;
5.集合操作;
SpEL表达式要放到“#{ ... }”之中,如:#{1},除去“#{ ... }”标记之后,剩下的就是SpEL表达式体了,也就是一个数字常量。最后结果是1。
它的最后计算表达式是那一刻时间的毫秒数。T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用static修饰的currentTimeMillis()方法。
SpEL表达式也可以引用其他的bean或其他bean的属性。
通过systemPropertis对象引用系统属性:
如果通过组件扫描创建bean的话,在注入属性和构造器参数时,我们可以使用@Value注解,这与之前看到的属性占位符非常类似。而用SpEl表达式则是:
在XML中,可以将SpEL表达式传入<property>或<constructor-arg>的value属性中:
表示字面量:
#{3.12},#{'hello'},#{true}分别对应浮点数,String类型,Boolean类型。
引用bean,属性和方法:
SpEL所能做的另外一件基础的事情就是通过ID引用其他的bean。例如,你可以使用SpEL将一个bean装配到另外一个bean的属性中,此时要使用bean ID作为SpEL
表达式(在本例中,也就是sgtPeppers):
如果想引用其内部属性则:
引用其方法则:
如果selectArtist()返回的不是null那上面的表达式没有问题,但是为了避免空指针异常我们使用类型安全的运算符:
“?”运算符能够在访问它右边的内容之前确保它所对应的元素不是null。如果是null则不调用右侧方法,并且返回值是null。
在表达式中使用类型
如果要在SpEL中访问类作用域的方法和常量的话,要依赖T()这个关键的运算符。例如,为了在SpEL中表达Java的Math类,需要按照如下的方式使用T()运算符:
这里所示的T()运算符的结果会是一个Class对象,代表了java.lang.Math。如果需要的话,我们甚至可以将其装配到一个Class类型的bean属性中。但是T()运算符的真正价值在于它能够访问目标类型的静态方法和常量,如:
将PI值装配到Bean属性中。
例子:(获取一个0-1的随机数)
SpEL运算符
例子:
解析:在这里PI的值乘以2,然后再乘以radius属性的值,这个属性来源于ID为circle的bean。实际上,它计算了circlebean中所定义圆的周长。
计算圆的面积:
与String类型一样+运算符执行连接操作:
比较运算符的使用,如:(比较两个数字是不是相等)
结果返回是一个Boolean类型的值。
三元表达式的使用:(如果socoreboard.score的值大于1000则返回Winner否则返回Loser)
常用的方式:(判断null值)
正则表达式的用法:(邮箱地址是否有效)
SpEL引用列表元素的方式:
解析:这个表达式会计算songs集合中第五个(基于零开始)元素的title属性,这个集合来源于ID为jukeboxbean。
'[]'运算符用来从集合或数组中按照索引获取元素,实际上,它还可以从String中获取一个字符。比如:
也就是's'。
SpEL还提供了查询运算符(.?[]),它会用来对集合进行过滤,得到集合的一个子集。作为阐述的样例,假设你希望得到jukebox中artist属性为Aerosmith的所有歌曲。如下的表达式就使用查询运算符得到了Aerosmith的所有歌曲:
SpEL还提供了另外两个查询运算符:“.^[]”和“.$[]”,它们分别用来在集合中查询第一个匹配项和最后一个匹配项。
如查找列表中第一个artist属性为Aerosmith的歌曲:
SpEL还提供了投影运算符(.![]),它会从集合的每个成员中选择特定的属性放到另外一个集合中。如:假设我们不想要歌曲对象的集合,而是所有歌曲名称的集合。如下的表达式会将title属性投影到一个新的String类型的集合中:
并且投影可以与其他任意SpEL运算符一起使用,如获取Aerosmith所有歌曲的名称:
4.小结
Spring profile解决了Spring bean要跨各种部署环境的通用问题。通过将环境相关的bean与当前激活的profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。
Profile bean是在运行时条件化创建bean的一种方式,在Spring4后结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件化地创建bean。
两种解决自动装配歧义性的方法:首选bean以及限定符。
Spring能够让bean以单例、原型、请求作用域或会话作用域的方式来创建。在声明请求作用域或会话作用域的bean的时候,如何创建作用域代理,它分为基于类的代理和基于接口的代理的两种方式。
最后,讲述了Spring表达式语言,它能够在运行时计算要注入到bean属性中的值。
注:Java 8允许出现重复的注解,只要这个注解本身在定义的时候带有@Repeatable注解就可以。不过,Spring的@Qualifier注解并没有在定义时添加@Repeatable注解。
参考:《Spring实战》