spirng实战(三:高级装配)

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实战》












猜你喜欢

转载自blog.csdn.net/qq_37598011/article/details/80635697
今日推荐