lvhouhou

 

 

 

 

嵌入式数据库

在开发环境中,我们可能会使用嵌入式数据库,并预先加载测试数据。例如,在Spring配置类中,我们可能会在一个带有@Bean注解的方法上使用EmbeddedDatabaseBuilder

@Bean(destroyMethod="shutdown")
public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .addScript("classpath:schema.sql")
        .addScript("classpath:test-data.sql")
        .build();
}

这会创建一个类型为javax.sql.DataSource的bean,这个bean是如何创建出来的才是最有意思的。使用EmbeddedDatabaseBuilder会搭建一个嵌入式的Hypersonic数据库,他的模式(schema)定义在schma.sql中,测试数据则是通过test—data.sql加载的。

 

我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最为合适的配置。

其中一种方式就是在单独的配置类(或XML文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中。这种方式的问题在于要为每种环境重新构建应用。当从开发阶段迁移到QA阶段时,重新构建也许算不上什么大问题。但是,从QA阶段迁移到生产阶段时,重新构建可能会引入bug并且会在QA团队的成员中带来不安的情绪。

值得庆幸的是,Spring所提供的解决方案并不需要重新构建。

 根据环境决定该创建哪个bean和不创建哪个bean。不过Spring并不是在构建的时候做出这样的决策,而是等到运行时再来确定。这样的结果就是同一个部署单元(可能会是WAR文件)能够适用于所有的环境,没有必要进行重新构建。

 

要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)的状态。

在Java配置中,可以使用@Profile注解指定某个bean属于哪一个profile。

例如,在配置类中,嵌入式数据库的DataSource可能会配置成如下所示:

package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import
  org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import
  org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

@Configuration
@Profile("dev")
public class DevelopmentProfileConfig {

  @Bean(destroyMethod="shutdown")
  public DataSource dataSource() {
      return new EmbeddedDatabaseBuilder()
          .setType(EmbeddedDatabaseType.H2)
          .addScript("classpath:schema.sql")
          .addScript("classpath:test-data.sql")
          .build();
  }

}

@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。如果dev profile没有激活的话,那么带有@Bean注解的方法都会被忽略掉。

同时,你可能还需要有一个适用于生产环境的配置,如下所示:

package com.myapp;
import javax.activation.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jndi.JndiObjectFactoryBean;

@Configuration
@Profile("prod")
public class ProductionProfileConfig {

  @Bean
  public DataSource dataSource() {
    JndiObjectFactoryBean jndiObjectFactoryBean =
        new JndiObjectFactoryBean();
    jndiObjectFactoryBean.setJndiName("jdbc/myDS");
    jndiObjectFactoryBean.setResourceRef(true);
    jndiObjectFactoryBean.setProxyInterface(
        javax.sql.DataSource.class);
    return (DataSource) jndiObjectFactoryBean.getObject();
  }

}

在本例中,只有prod profile激活的时候,才会创建对应的bean。

在Spring 3.1中,只能在类级别上使用@Profile注解。不过,从Spring 3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解一同使用。这样的话,就能将这两个bean的声明放到同一个配置类之中,如下所示:

程序清单3.1 @Profile注解基于激活的profile实现bean的装配

 

 但是可能会有其他的bean并没有声明在一个给定的profile范围内。没有指定profile的bean始终都会被创建,与激活哪个profile没有关系。

 

也可以在XML中配置profile。此处省略。

 

Spring在确定哪个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.activespring.profiles.default。如果设置了spring.profiles.active属性的话,那么它的值就会用来确定哪个profile是激活的。但如果没有设置spring.profiles.active属性的话,那Spring将会查找spring.profiles.default的值。如果spring.profiles.activespring.profiles.default均没有设置的话,那就没有激活的profile,因此只会创建那些没有定义在profile中的bean。

使用profile进行测试

Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时要激活哪个profile。例如,下面的测试类片段展现了使用@ActiveProfiles激活dev profile:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest {
  ...
}

 

在条件化创建bean方面,Spring的profile机制是一种很棒的方法,这里的条件要基于哪个profile处于激活状态来判断。Spring 4.0中提供了一种更为通用的机制来实现条件化的bean定义,在这种机制之中,条件完全由你来确定。让我们看一下如何使用Spring 4和@Conditional注解定义条件化的bean。

Spring 4引入了一个新的@Conditional注解,它可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean会被忽略。

例如,假设有一个名为MagicBean的类,我们希望只有设置了magic环境属性的时候,Spring才会实例化这个类。

 

 

 可以看到,@Conditional中给定了一个Class,它指明了条件——在本例中,也就是MagicExistsCondition@Conditional将会通过Condition接口进行条件对比:

设置给@Conditional的类可以是任意实现了Condition接口的类型。

 

 

 

当确实发生歧义性的时候,你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean。

例如:

 

 

 因为这三个实现均使用了@Component注解,在组件扫描的时候,能够发现它们并将其创建为Spring应用上下文里面的bean。然后,当Spring试图自动装配setDessert()中的Dessert参数时,但是Spring却无法做出选择。Spring此时别无他法,只好宣告失败并抛出异常。更精确地讲,Spring会抛出NoUniqueBeanDefinitionException

在声明bean的时候,通过将其中一个可选的bean设置为首选(primary)bean能够避免自动装配时的歧义性。当遇到歧义性的时候,Spring将会使用首选的bean,而不是其他可选的bean。

在Spring中,可以通过@Primary来表达最喜欢的方案。

@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java配置的bean声明中。

比如,下面的代码展现了如何将@Component注解的IceCream bean声明为首选的bean:

@Component
@Primary
public class IceCream implements Dessert { ... }

或者,如果你通过Java配置显式地声明IceCream,那么@Bean方法应该如下所示:

@Bean
@Primary
public Dessert iceCream() {
    return new IceCream();
}

如果你使用XML配置bean的话,同样可以实现这样的功能。<bean>元素有一个primary属性用来指定首选的bean:

<bean id="iceCream"
      class="com.desserteater.IceCream"
      primary="true" />

 

@Qualifier注解是使用限定符的主要方式。它可以与@Autowired@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。例如,我们想要确保要将IceCream注入到setDessert()之中:

@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

@Qualifier注解所设置的参数就是想要注入的bean的ID。@Qualifier("iceCream")指向的IceCream类的实例。

 这里的问题在于setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。对类名称的任意改动都会导致限定符失效。如果你重构了IceCream类,将其重命名为Gelato的话,就出问题了。

解决:

创建自定义的限定符

我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的就是在bean声明上添加@Qualifier注解。例如,它可以与@Component组合使用,如下所示:

@Component
@Qualifier("cold")
public class IceCream implements Dessert { ... }

在这种情况下,cold限定符分配给了IceCreambean。因为它没有耦合类名,因此你可以随意重构IceCream的类名,而不必担心会破坏自动装配。在注入的地方,只要引用cold限定符就可以了:

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

当通过Java配置显式定义bean的时候,@Qualifier也可以与@Bean注解一起使用:

@Bean
@Qualifier("cold")
public Dessert iceCream() {
    return new IceCream();
}

当使用自定义的@Qualifier值时,最佳实践是为bean选择特征性或描述性的术语,而不是使用随意的名字。

 在默认情况下,Spring应用上下文中所有bean都是作为以单例(singleton)的形式创建的。

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例(Singleton):在整个应用中,只创建bean的一个实例。
  • 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。
  • 会话(Session):在Web应用中,为每个会话创建一个bean实例。
  • 请求(Rquest):在Web应用中,为每个请求创建一个bean实例。

单例是默认的作用域。如果选择其他的作用域,要使用@Scope注解,它可以与@Component@Bean一起使用。

例如,在典型的电子商务应用中,可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有的用户都会向同一个购物车中添加商品。

就购物车bean来说,会话作用域是最为合适的。

@Component
@Scope(
    value=WebApplicationContext.SCOPE_SESSION,
    proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart() { ... }

value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean的实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例的。

@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。在描述proxyMode属性之前,我们先来看一下proxyMode所解决问题的场景。

假设我们要将ShoppingCart bean注入到单例StoreService bean的Setter方法中,如下所示:

@Component
public class StoreService {

  @Autowired
  public void setShoppingCart(ShoppingCart shoppingCart) {
    this.shoppingCart = shoppingCart;
  }
  ...
}

因为StoreService是一个单例的bean,会在Spring应用上下文加载的时候创建。当它创建的时候,Spring会试图将ShoppingCart bean注入到setShoppingCart()方法中。但是ShoppingCart bean是会话作用域的,此时并不存在。直到某个用户进入系统,创建了会话之后,才会出现ShoppingCart实例。

另外,系统中将会有多个ShoppingCart实例:每个用户一个。我们并不想让Spring注入某个固定的ShoppingCart实例到StoreService中。我们希望的是当StoreService处理购物车功能时,它所使用的ShoppingCart实例恰好是当前会话所对应的那一个。

Spring并不会将实际的ShoppingCart bean注入到StoreService中,Spring会注入一个到ShoppingCartbean的代理,如图3.1所示。这个代理会暴露与ShoppingCart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用ShoppingCart的方法时,代理会对其进行懒解析并将调用委托给会话作用域内真正的ShoppingCart bean。

现在,我们带着对这个作用域的理解,讨论一下proxyMode属性。如配置所示,proxyMode属性被设置成了ScopedProxyMode.INTERFACES,这表明这个代理要实现ShoppingCart接口,并将调用委托给实现bean。

如果ShoppingCart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果ShoppingCart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。

尽管我主要关注了会话作用域,但是请求作用域的bean会面临相同的装配问题。因此,请求作用域的bean应该也以作用域代理的方式进行注入

 

 作用域代理能够延迟注入请求和会话作用域的bean。

省略。

bean装配的时候,bean属性的值可以不是写死的。

Spring提供了两种在运行时求值的方式:

  • 属性占位符(Property placeholder)。
  • Spring表达式语言(SpEL)。

省略。

 

Spring profile,它解决了Spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,Spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。

Profile bean是在运行时条件化创建bean的一种方式,但是Spring 4提供了一种更为通用的方式,通过这种方式能够声明某些bean的创建与否要依赖于给定条件的输出结果。结合使用@Conditional注解和Spring Condition接口的实现,能够为开发人员提供一种强大和灵活的机制,实现条件化地创建bean。

分类:

技术点:

相关文章:

  • 2021-07-31
  • 2021-12-06
  • 2021-12-16
  • 2022-02-08
  • 2021-06-22
  • 2021-11-06
  • 2022-12-23
  • 2022-02-26
猜你喜欢
  • 2021-09-28
  • 2021-12-06
  • 2021-12-06
  • 2021-12-06
  • 2021-12-09
相关资源
相似解决方案