Spring In Action 02 ---高级装配

原创 2016年08月29日 14:45:05

上一章了解了一些最为核心的bean装配技术,但是bean装配所涉及的领域并不仅仅局限于上一章的内容,Spring提供了多种技巧,借助他们能实现更为高级的bean装配功能。虽然这些技术不会经常用到,但这并不意味着他们的价值会因此而降低。

环境与Profile

在开发的时候有一个很大的挑战就是将应用程序从一个环境迁移到另一个环境。举一个栗子,比如配置数据库(有关数据库配置的详细会在后面的章节)。在开发阶段,我们可能会使用嵌入式数据库,并预先加载测试数据,如下:
@Bean(destroyMethod = "shutdown")
    public EmbeddedDatabase embeddedDatasource(){
      return new EmbeddedDatabaseBuilder()
              .setType(EmbeddedDatabaseType.H2)
              .addScript("classpath:schema.sql")
              .addScript("classpath:test-data.sql")
              .build();
    }
这会创建一个类型为javax.sql.DataSource的bean,他使用EmbeddedDatabaseBuilder会搭建一个嵌入式的Hypersonic数据库,他的模式(schema)定义在shcema.sql中,测试数据通过test-data.sql加载,这种方式对于开发环境来说是非常适合的,每次启动他的时候都能让数据库处于一个给定的状态。但是对于生产环境来说却是一个糟糕的选择,更多的会选择JNDI从容器中获取一个DataSource,对于这种情况我们应该使用如下的bean
@Bean
    public DataSource jndidataDataSource(){
        JndiObjectFactoryBean jndiObjectFactoryBean = 
                new JndiObjectFactoryBean();
        jndiObjectFactoryBean.setJndiName("jdbc/myDS");
        jndiObjectFactoryBean.setResourceRef(true);
        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndiObjectFactoryBean.getObject();
    }
通过JNDI获取DataSource能够让容器决定该如何创建这个DataSource,JNDI更加适合的是生产环境,然而对于测试环境来说,会带来不必要的复杂性。在测试环境中,可以选择配置Commons DBCP连接池,具体的方式就不写了大同小异。这是一个很好的栗子,他表现了在不同的环境中某个bean会有所不同,我们必须要有一种方法来配置使其在每种环境中都会选择最合适的配置。

注解方式配置

在Spring中提供了@Profile注解,可以指定某个bean属于哪个profile,使用的方式很简单,只需要在相应的方法上添加@Profile注释并且在属性中指定环境,例如@Profile("dev")这样会告诉Spring这个bean只有在dev的profile激活时才会被创建,@Profile注解可以用在类级别上,也可以用在方法级别上。只有当规定的profile被激活时,相应的bean才会被创建,没有指定profile的bean始终都会被创建于激活哪个profile无关。dev表示开发环境,prod表示生产环境,qa表示测试环境

XML方式配置

同样在XML中也可以做配置,通过<beans>元素的profile属性。我们可以在<beans>元素中嵌套<beans>元素,在一个XML文件中创建多个profile环境
<beans profile="dev">
        <bean id="dataSource" class="com.dataSource">
            <value>....</value>
        </bean>
</beans>

如何激活

指定了profile环境之后那么该如何激活proflie呢?Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性,spring.profiles.active和spring.profiles.defaultz。active的值就是确定哪个profile是激活的,如果没有设置active的值的话就会使用default的值,如果都没有设置的话,那就没有激活的profile,那就只会创建那些没有定义在profile中的bean。有多种方式来设置这两个属性
  • 作为DispatcherServlet的初始化参数
  • 作为Web应用的上下文参数
  • 作为JND条目
  • 作为环境变量
  • 作为JVM的系统属性
  • 在集成测试类上使用@ActiveProfiles注解属性

条件化的bean

在Spring4中引入了一个新的@Conditional注解,他可以用到带有@Bean注解的方法上,如果给定的条件计算结果为true就会创建这个bean否则这个bean会被忽略。
假设现在有一个MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类,如果环境中没有这个属性,那么MagicBean将会被忽略。
/**
     * 在@Conditional中给定一个class,他会指明条件
     * 在@Conditional中是通过Condition接口进行对比的
     * 也就是传入@Conditional注解中的类必须要实现Condition接口
     * 在Condition接口中有个matches方法用来匹配条件
     * 只有这个方法返回true才会创建这个bean这就是条件化的bean
     */
    @Bean
    @Conditional(MagicExistsCondition.class)
    public MagicBean magicBean(){
        return new MagicBean();
    }
class MagicExistsCondition implements Condition{
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");
        //检查环境中是否包含magic属性,如果满足就会返回true
    }
}
如上所示在magicBean方法上有@bean注解,同时也有@Conditional注解,表示这个bean需要指定条件才会被创建,那么条件是什么呢,具体的要求条件在@Conditional注解的参数中,这个参数要求是一个实现了Condition接口的类,实现这个接口需要实习其中的matches方法,这个方法才是真正返回true或false的方法,用来判断是否创建bean,当在matches中满足条件时,他会返回true,这样注解@Conditional注解就会满足条件,创建bean。在提一点,在matches方法中有两个参数ConditionContext和AnnotatedTypeMetadata,这两个都是接口,其中包含了很多的方法,借助他们可以实现更多的条件判断。

处理自动装配的歧义性

如果Spring中匹配出多个适合的bean的话就会发生异常,这种自动装配的歧义性该如何解决。现在描述一个场景,有一个Dessert接口,他表示甜品,并有三个类实现了这个接口,分别时Cake,Cookies和IceCream表示具体的甜品。
@Autowired
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}

@Component
public class Cake implements Dessert{...}
@Component
public class Cookies implements Dessert{...}
@Component
public class IceCream implements Dessert{...}
这三个类都实现了@Component注解,在组件扫描的时候能够发现他们并将他们创建为Spring应用上下文中的bean,当Spring试图自动装配Dessert参数时,他没有唯一的无歧义的一个bean对应,Spring会抛出异常NoUniqueBeanDefinitionException;Spring有两种方式可以解决歧义性
  • 标识首选
  • 使用限定符
标识首选很简单,就是在你希望成为首选bean的带有@Component注解的bean上添加@Primary注解就可以了,当Spring遇到同类型的bean时会自动选择首选的bean。很明显这种方法太过于简陋,Spring提供了更加强大的限定符来限定条件。一个最简单的栗子,如果你直接希望在Dessert中注入IceCream,那么在只需要在方法上添加注释即可
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}
@Qualifier中的参数就是要指定的bean的ID,更准确的说其实时bean的限定符,因为所有的bean都会有要给默认的限定符,于ID相同,所以这里引用的时"iceCream"实际上引用的时限定符为"iceCream"的bean,也就是IceCream类将第一个字母小写。除了使用默认的限定符,我们还可以自己指定自定义限定符,这样就可以避免重构修改类名带来的变化。
@Autowired
@Qualifier("cold")//使用限定符
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}
@Component
@Qualifier("cold")//指定限定符
public class IceCream implements Dessert{...}
更加极端的情况是,我们使用这个描述特性的限定符的时候,如果两个bean有同样的特性呢,这个时候是不是需要在添加一个@Qualifier注解来表示更多的特性,但是这里有一个小问题,在Java中不允许在同一个条目上重复出现相同类型的多个注解(Java8允许重复注解只要这个注解本身带有@Repeatable注解就可以,但是Spring的@Qualifier并没有带有这个注解,所有不可以重复)。这个时候我们需要自定义一个注解,在定义的时候添加@Qualifier注解,他们就具体有了Qualifier特性。
@Target(ElementType.FIELD,ElementType.CONSTRUCTOR,EleentType.METHOD,ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy{}
就是这样然后这个@Creamy就变成了一个注解了你可以在bean上使用@Creamy表示他是一个限定符,表示具体Creamy特性,同时你可以为另外的一个@Cold创建自定义注解,这样这两个注解都表示限定,而且具有不同的特性,最重要的时候你可以同时使用这两个注解在同一个条目上。

bean的作用域

默认情况下,Spring应用上下文中的所有bean都是作为单例(singleton)创建的的,也就是一个bean不管被注入到其他的bean多少次,每次注入的都是同一个实例。单例的情况有好也有坏,所以在不同的情况下需要创建基于不同作用域的bean,Spring中定义了四种作用域
  • 单例(Singleton)在整个应用中只创建bean的一个实例
  • 原型(Prototype)每次注入或者获取的时候都会创建一个新的bean实例
  • 会话(Session)在WEB应用中,为每一个会话创建一个bean实例
  • 请求(Request)在WEB应用中,为每一个请求创建一个bean实例
对于前面两种,Singleton是默认的不需要设置,设置Prototype非常简单,你可以在类上或者方法上添加@Scope注解,@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)或者@Scope("protoype")前者更加安全不易出错,这样的话每次注入或者从Spring上下文中检索该bean的时候都会创建新的实例。对于会话和请求作用域来说稍微有点复杂,先来描述一个场景。在WEB应用的电子商务应用中典型的场景是,可能会有一个bean代表用户的购物车,如果这个购物车是单例的话那么将会导致所有的用户都会向同一个购物车添加商品。如果购物车是原型的话,那么在应用的一个地方添加商品,在应用的另一个地方就不能再用了因为将会是另一个原型作用域的购物车。最好的解决办法就是使用会话作用域,他会告诉Spring为WEB应用的每个会话创建一个购物车,这会创建多个购物车实例,对于的给定的会话只会创建一个实例,对于当前会话的相关操作来说,这个bean实际上是单例的。
@Component
@Scope(
        value = WebApplicationContext.SCOPE_SESSION,
        proxyMode = ScopedProxyMode.INTERFACES)
public shoppingCart cart(){...}
如上设置了session会话作用域,这里重点要解释一下proxyMode代理模式。假设我们需要将ShoppingCart bean注入到StoreService bean中去,因为StoreService是单例的,当他创建的时候Spring试图将ShoppingCart注入进去,但是他是会话作用域的,此时并不存在,直到某个用户进入系统创建会话之后才会出现ShoppingCart实例。更重要的是系统在之后可能会出现多个ShoppingCart实例每个用户一个,我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中,我们希望StoreService处理购物车功能的时候他所使用的ShoppingCart实例恰好是当前会话对应的那个。Spring是如何解决的?
Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个ShoppingCart bean的代理如下图所示。这个代理会暴露于于ShoppingCart相同的方法,所以StoreService会认为他是一个购物车,但是当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并调用委托给会话作用域内的真正ShoppingCart bean。在proxyMode属性中我们设置为INTERFACES,这表明这个代理要实现ShoppingCart接口并将调用委托给实现bean。补充一点如果ShoppingCart是接口而不是类的话(这是最理想的代理模式),但他如果是具体类的话就必须使用CGLib来生成基于类的代理,这时候就需要将proxyMode属性设置为TARGET_CLASS,以此来表示要以生成目标类扩展的方式创建代理。


运行时值注入

就目前来说我们对bean的设值都是在将值硬编码在配置类中(也就是提前指定好值是什么),有时候硬编码是可以的,但有时候我们希望可以避免硬编码能够让这些值在运行的时候在确定,为了实现这些功能,Spring提供了两种在运行时求值的方式
  • 属性占位符
  • Spring表达式语言(SpEL)
先看一下比较简单的属性占位符。一种方式是声明属性源并通过Spring的Environment来检索属性。可以通过@PropertySource("classpath:app.properties")来声明属性源
@Autowired
Environment env;

@Bean
public BlankDisc disc(){
    return new BlankDisc(env.getProperty("disc.title"),env.getProperty("disc.artist"));
}
在本例中引入的配置文件应该有这两个属性
disc.title = "title";
disc.artist = "artist";
这个属性会加载到Environment中通过env取得,getProperty有四种重载形式。另外一种方式是Spring表达式语言,在Spring3中引入了SpEL,他能够以一种强大和简洁的方式将值装配到bean属性和构造器参数当中,在这个过程中使用表达式会在运行时计算得到值。SpEL具有以下特性
  • 使用bean的ID来引用bean
  • 调用方法和访问对象的属性
  • 对值进行算术、关系和逻辑运算
  • 正则表达式
  • 集合操作
最基本需要了解的是SpEL将表达式放在#{...}之中,放括号中可以放入很多类型,常量#{1},包括布尔类型字符串类型科学记数法等,Java对象#{T(System).currenTimeMills()},T()表达式会将java.lang.System视为Java中对应的类型,因此可以调用其静态static修饰的currentTimeMills()方法,SpEL也可以引用其他的bean或其他bean的属性#{sgtPeppers.artist},还可以引用系统对象#{systemProperties['disc.title']}。

总结

本章介绍一些高级的装配技巧,根据不同环境激活不同的profile。解决自动装配歧义性的办法,首选项以及限定符。Spring bean的不同作用域,还有Spring语言表达式。
版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

xml形式装配bean——spring in action chapter 2

1、声明bean 利用beans命名空间: 当spring容器加载该bean时,spring将调用默认的构造器实例化beanClass,相当于 new beanClass()...

Spring_in_Action中文版part02

  • 2012-11-23 16:30
  • 14.29MB
  • 下载

Spring in action 01 -- 装配 Bean(@Autowired)

昨天走马观花,看一遍Spring,当然也比较的片面,今天就从第一个知识点详细些再学习。温故而知新,可以为师也,如此而而一个概念 装配(wiring) : 创建应用对象之间协作关系的行为通常称为装配。它...

Spring in Action入门之装配管理Bean

(该部分是Spring的入门和Spring容器装配管理Bean的方法)第一章 开始Spring之旅Applet可以用来创建动态的Web应用,在html文件中通过标识,一种已经被淘汰的技术。<!-- g...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)