Java后台框架篇--Spring高级装配

内容概述

  • Spring Profile
  • 条件化的bean声明
  • 自动装配与歧义性
  • bean的作用域
  • Spring表达式语言

1、Spring Profile

在开发软件的时候,有一个很大的挑战就是将应用程序从一个环境迁移到另一个环境。开发阶段,某些环境相关做法可能并不适合迁移到生产环境中,甚至几遍迁移过去也无法正常工作。数据库配置、加密算法以及与外部系统的集成是跨环境部署时会发生变化的几个典型例子。 
以dataSource bean为例,在开发环境中,使用一个嵌入式数据库会大大提高开发效率,减少dataSource配置的复杂性,并且可以使得每次启动数据库时,它都处于一个给定的状态:

@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
    return new EmbeddedDatabaseBuilder()
               .addScript("classpath:schema.sqsl")
               .addScript("classpath:test-data.sql")
               .build();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

使用EmbeddedDatabaseBuilder会搭建一个嵌入式的Hypersonic数据库,它的模式(schema)定义在schema.sql中,测试数据则是通过test-data.sql加载的。当在开发环境中运行集成测试或者启动应用进行手动测试的时候,这个DataSource是很有用的。 
尽管该嵌入式数据库创建的DataSource非常适合开发环境,但是对于生产环境来说,这会是一个糟糕的选择。在生产环境中,你可能会希望使用JCDI从容器中获取一个DataSource。在这样的场景下,如下的@Bean方法会更加合适:

@Bean
public DataSource dataSource(){
    JndiObjectFactoryBean jndi=new JndiObjectFactoryBean();
    jndi.setJndiName("jdbc/myDS");
    jndi.setResourceRef(true);
    jndi.setProxyInterface(javax.sql.DataSource.class);
    return (DataSource) jndi.getObject();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

显然,这里展现的连个版本的dataSource()方法互不相同。虽然他们都会生成一个类型为javax.sql.DataSource的bean,但他们的相似点也仅限于此了。每个方法都使用了完全不同的策略来生成DataSource bean。这是一个很好的例子,它表现了在不同的环境中某个bean会有所不同。我们必须要有一种方法来配置DataSource,使其在每种环境下都会选择最适合的配置。 
其中一种方式就是在单独的配置类(或XML文件)中配置每个bean,然后在构建阶段(可能会使用Maven的profiles)确定要将哪一个配置编译到可部署的应用中。这种方式在QA阶段迁移到生产阶段时会带来诸多弊端,并且工作量也会大增。而Spring提供一种更为简便的方式,那就是配置profile bean。

1.1在JavaConfig配置profile bean

Spring为环境相关的bean所提供的解决方案其实与构建时的方案没有太大的差别。当然,在这个过程中需要根据环境决定该创建那个bean和不创建哪个bean。不过Sprig并不是在构建的时候做出这样的决策的,而是等到运行时再来确定(这样的话就必须确保每一种环境的配置都必须随时存在,这会增加项目文件体积的吧?一般配置文件都相对较小,而且拿空间换效率也是当下流行的做法)。这样的结果是同一个部署单元(可能会是war包)能够适用于所有的环境,没有必要进行重新构建。 
在3.1版本中,Spring引入了bean profile功能。要使用profile,你首先要将所有不同的bean定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)状态。 
在Java配置中,可以使用@Profile注解指定那个bean属于哪一个profile。例如,在配置类中,嵌入式数据库的DataSource可能会配置成如下所示:

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

    @Bean(destroyMethod="shutdown")
    public DataSource dataSource(){
        return new EmbeddedDatabaseBuilder()
               .addScript("classpath:schema.sqsl")
               .addScript("classpath:test-data.sql")
               .build();
}
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

我希望你能够注意的是@Profile注解应用在了类级别上。它会告诉Spring这个配置类中的bean只有在dev profile激活时才会创建。同时你能还需要一个适用于生成环境的配置,如下所示:

@Configuration
@Profile("prod")
public class ProductionProfileConfig{
    @Bean
    public DataSource dataSource(){
       JndiObjectFactoryBean jndi=new JndiObjectFactoryBean();
        jndi.setJndiName("jdbc/myDS");
        jndi.setResourceRef(true);
        jndi.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndi.getObject();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

在Spring3.1中,只能在类级别上使用@Profile注解。不过,从Spring3.2开始,你也可以在方法级别上使用@Profile注解,与@Bean注解一起使用(这下可就方便多了!!!)。这样的话,就能将这两个bean的声明放在同一个配置类中,如下所示:

@Configuration
public class DataSourceConfig{

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

    @Bean
    @Profile("prod")
    public DataSource dataSource(){
       JndiObjectFactoryBean jndi=new JndiObjectFactoryBean();
        jndi.setJndiName("jdbc/myDS");
        jndi.setResourceRef(true);
        jndi.setProxyInterface(javax.sql.DataSource.class);
        return (DataSource) jndi.getObject();
    }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

这里有个问题需要注意,尽管每个DataSource bean都被声明在一个profile中,并且只有当规定的profile激活时,相应的bean才会被创建,但是可能会有其他的bean并没有声明在一个给定的profile范围内。没有指定profile的bean始终都会被创建,与激活那个profile没有关系。

1.2在XML中配置profile

我们也可以通过元素的profile属性,在XML中配置profile bean。例如,为了在XML中定义适用于开发阶段的嵌入式数据库DataSource bean,我们可以创建如下所示的XML文件:

<beans xmlns="....." profile="dev">
<jdbc:embedded-database id="dataSource">
  <jdbc:script location="classpath:schema.sql" />
  <jdbc:script location="classpath:test-data.sql" />
</jdbc:embedded-database>
</beans>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

除此之外,可以在跟,<beans>元素中嵌套定义<beans>元素,而不是为每个环境都创建一个profile XML文件。这能够将所有的profile bean定义放在一个XML文件中,如下所示:

<beans xmlns=".......">
     <beans profile="dev">
         <jdbc:embedded-database id="dataSource">
         <jdbc:script location="classpath:schema.sql" />
         <jdbc:script location="classpath:test-data.sql" />
         </jdbc:embedded-database>
     </beans>
     <beans profile="qa">
       <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destory-method="close"
            p:url="jdbc:h2:tcp://dbserver/~/test"
            .......
            p:maxActive="30"/>
     </beans>
</beans>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

1.3、激活profile

Spring在确定那个profile处于激活状态时,需要依赖两个独立的属性:spring.profiles.active和spring.profiles.default。如果active属性未设置,则使用default属性;若两个均未设置,那就没有激活Profile,因此只会创建那些没有定义在Profile中的bean。 
有多种方式来设置这两个属性:

  • a、作为DispatcherServlet的初始化参数;

  • b、作为web应用上下文参数

  • c、作为JNDI条目;

  • d、作为环境变量;

  • e、作为JVM的系统属性;

  • f、在集成测试类上,使用@ActiveProfiles注解设置

可以在web.xml中设置spring.profiles.default

<!--为应用山下文设置默认的profile-->
<context-param>
     <param-name>spring.profiles.default</param-name>
     <param-name>dev</param-name>
</context-param>
<!--为Servlet设置默认的profile-->
<servlet>
     <servlet-name>appServlet</servlet-name>
     <servlet-class>
      org.springframework.web.servlet.DispatcherServlet
     </servlet-class>
     <init-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
     </init-param>
        <load-on-startup>1</load-on-startup>
</servlet>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

按照这种方式设置spring.profiles.default,所有的开发人员都能从版本控制软件中获得应用程序源码,并使用开发环境设置(如嵌入式数据库)运行代码,而不需要任何额外的配置。

1.4使用profile进行测试

当运行集成测试时,通常会希望采用与生产环境相同的配置进行测试。但是,如果配置中的bean定义在了profile中,那么在运行测试时,我们就需要有一种方式来启用合适的profile。 
Spring提供了@ActiveProfiles注解,我们可以使用它来指定运行测试时需要激活哪个profile。

@RunWith(SpringJunit4ClassRunner.class)
@ContextConfiguration(classes={PersistenceTestConfig.class})
@ActiveProfiles("dev")
public class PersistenceTest{
   ......
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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

2、条件化的bean

假设你希望一个或多个bean只有在应用的类路径下包含特定的库时才创建。或者我们希望某个bean只有当另外某个特定的bean也声明了之后才会创建。我们还可能要求只有某个特定的环境变量设置之后才会创建某个bean。在Spring4之前,很难实现这种级别的条件化配置,但是Spring4引入了一个新的@Conditioinal注解,他可以用到带有@Bean注解的方法上。如果给定的条件计算结果为true,就会创建这个bean,否则的话,这个bean就会被忽略。

@Bean
@Conditional(MagicExistsCondition.class)  //条件化地创建bean,根据该类的返回值
public MagicBean magicBean(){
   return new MagicBean();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

public interface Condition{
  boolean matches(ConditionContext ctxt,AnnotatedTypeMetadata metadata);
}
 
 
  • 1
  • 2
  • 3

设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需要提供matches()方法的实现即可。如果maches()方法返回true,那么就会创建带有@Conditional注解的bean。如果返回false,将不会创建这些bean。 
在Condition接口参数中,ConditionContext属性可以用来获取Environment对象,通过这个对象检查环境中是否存名为magic的环境属性。通过ConditionContext,我们可以做到以下几点:

  • a、借助getRegistry()返回的BeanDefinitionRegistry检查bean的定义;

  • b、借助getBeanFactory()返回ConfiurableListableBeanFactory检查bean是否存在,甚至探查bean的属性;

  • c、’借助getEnvironment()返回的Environment检查环境变量是否存在以及它的值是什么;

  • d、读取并检查getResourceLoader()返回的ResourceLoader所加载的资源;

  • e、借助getClassLoader()返回的ClassLoader加载并检查类是否存在。

AnnotatedTypeMetadata则能够让我们检查带有@Bean注解的方法上还有什么其他注解。像ConditionContext一样,AnnotatedTypeMetadata也是一个接口。借助isAnnotated()方法,我们能够判断带有@Bean注解的方法是不是还有其他特定的注解。借助其他的那些方法,我们能够检查@Bean注解的方法上其他注解的属性。非常有意思的是,从Spring4开始,@Profile注解进行了重构,使其基于@Conditional和Condition接口实现。

3、处理自动装配的歧义性

仅有一个bean匹配所需的结果时,自动装配才是最有效的。如果不仅有一个bean能够匹配结果的话,这种歧义性阻碍Spring自动装配属性、构造器参数或方法参数。 
为了阐述自动装配的歧义性,假设我们使用@Autowired注解标注了setDessert方法:

@Autowired
public void setDessert(Dessert dessert){
   this.dessert = dessert;
}
 
 
  • 1
  • 2
  • 3
  • 4

在本例中,Dessert是一个接口,并且有三个类实现了这个接口,分别为Cake、Cookies和IceCream:

@Component
public class Cake implements Dessert{...}
@Component
public class Cookies implements Dessert{...}
@Component
public class IceCream implements Dessert{...}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

因为这三个实现均使用了@Component注解,在组件扫描的时候,能够发现他们并将其创建为Spring应用上下文里面的bean。 
然后,当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。因而,Spring会抛出NoUniqueBeanDefinitionException; 
当确实发生歧义性的时候,Spring提供了多种可选方案来解决这样的问题。你可以将可选bean中的某一个设为首选(primary)的bean,或者使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小到只有一个bean。

3.1标示首选的bean

在声明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{...}
 
 
  • 1
  • 2
  • 3

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

@Bean
@Primary
public Dessert iceCream(){
    return new IceCream();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

<bean id="iceCream"
      class="com.desserteater.IceCream"
      primary="true"/>
 
 
  • 1
  • 2
  • 3

不管你采用什么方式来标示首选bean,效果都是一样的,都是告诉Spring在遇到歧义性的时候要选择首选的bean。当然,这个注解只能出现一次,如果你标示了两个或更多的首选bean,那么它就无法正常工作了。就解决歧义性问题而言,限定符是一种更为强大的机制,下面就将对其进行介绍。

3.2、限定自动装配bean

Spring的限定符不同于@Primary只能标示一个优先的可选方案,与之相反,Spring限定符能够在所有可选的Bean上进行缩小范围的操作,最终能够达到只有一个bean满足所规定的限制条件。如果将所有的限定符都用上后仍然存在歧义性,那么你可以继续使用更多的限定符来缩小范围。 
@Qualifier注解是使用限定符的主要方式。它可以与@Autowired和@Inject协同使用,在注入的时候指定想要注入进去得是哪个bean。例如,我们想要确保要将IceCream注入到setDessert()之中:

@Autowired
@Quelifier("iceCream")
public void setDessert(Dessert dessert)}{
   this.dessert = dessert;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

这是使用限定符的最简单的例子。为@Qualifier注解所设置的参数就是想要注入的bean的ID。所有使用@Component注解声明的类都会创建为bean,并且bean的ID为首字母变为小写的类名。因此,@Qualifier(“iceCream”)指向的是组件扫描时所创建的bean,并且这个bean是IceCream类的实例。 
基于默认的bean ID作为限定符是非常简单的,但这有可能会引入一些问题。如果你重构了IceCream类,将其重命名为Gelato的话,那此时会发生什么情况呢?如果这样的话,bean的ID和默认的限定符会变为gelato,这就无法匹配setDessert()方法中的限定符。自动装配会失败。这里的问题在于setDessert()方法上所指定的限定符与要注入的bean的名称是紧耦合的。这类名的任意改动都会导致限定符失效。为了解决这个问题,我们可以创建自定义的限定符。 
(思考:为什么不通过为每个bean指定别名来实现呢?这和Quelifier的原理不是一样的么?猜测:@Autowired注解没有name属性,不能通过bean的名字来装配) 
我们可以为bean设置自己的限定符,而不是依赖于将bean ID作为限定符。在这里所需要做的就是在bean声明上添加@Quelifier注解。例如,他可以与@Component组合使用,如下所示:

@Component
@Qualifier("cold")
public class IceCream implements Dessert{...}
 
 
  • 1
  • 2
  • 3

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

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
    thid.dessert = dessert;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

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

@Bean
@Qualifier("cold")
public Dessert iceCream(){
    return new IceCream();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

注意:Java不允许在同一条目上重复出现相同类型的多个注解(Java8允许出现重复的注解,但要求这个注解本身在定义的时候带有@Repeatable注解就可以了。不过,Spring的@Qualifier注解并没有在定义的时候添加@Repeatable注解。)。如果你试图这样做的话,编译器会提示错误。因此,在这里没办法直接应用多个@Qualifier注解来层层筛选。我们可以通过自定义注解来解决这个问题。 
我们可以创建一个自定义的@Cold注解:

@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{}
 
 
  • 1
  • 2
  • 3
  • 4

4、bean的作用域

在默认的情况下,Spring应用上下文中所有的bean都是作为以单例(singleton)的形式创建的。也就是说,不管给定的一个bean被注入到其他bean多少次,每次所注入的都是同一个实例。在大多数情况下,单例bean是很理想的方案。初始化和垃圾回收对象实例所带来的成本只留给一些小规模任务,在这些任务中,让对象保持无状态并且在应用中反复重用这些对象可能并不合理。 
有时候,可能会发现,你所使用的类是易变的(mutable),他们会保持一些状态,因此重用是不安全的。在这种情况下,将class声明为单例的bean就不是什么好主意了,因为对象会被污染,稍后重用的时候回出现意想不到的问题。
Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • a、单例(singleton):在整个应用中,只创建bean的一个实例。

  • b、原型(prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

  • c、会话(Session):在Web应用中,为每个会话创建一个bean实例。

  • d、请求(Request):在Web应用中,为每个请求创建一个bean实例。

    单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,他可以与@Component(自动装配)或@Bean(JavaConfig)一起使用。例如,如果你使用组件扫描来发现声明bean,那么你可以在bean的类上使用@Scope注解,将其声明为原型bean:
    
       
       
    • 1
    • 2
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class Notepad{...}
 
 
  • 1
  • 2
  • 3

这里,使用ConfigurableBeanFactory类的SCOPE_PROTOTYPE常量设置了原型作用域。你当然也可以使用@Scope(“prototype”),但是使用SCOPE_PROTOTYPE常量更加安全并且不易出错。 
如果你想在Java配置中将Notepad声明为原型bean,那么可以组合使用@Scope和@Bean注解来指定所需的作用域:

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Notepad notepad(){
    return new Notepad();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

同样,如果你使用XML来配置bean的话,可以使用元素的scope属性来设置作用域:

<bean id="notepad"
      class="com.myapp.Notepad"
      scope="prototype"  />
 
 
  • 1
  • 2
  • 3

不管使用哪种方式来声明原型作用域,每次注入或从Spring应用上下文中检索该bean的时候,都会创建新的实例。这样所导致的结果就是每次操作都能得到自己的Notepad实例。

4.1、使用会话和请求作用域

在Web应用中,如果能够实例化在会话和请求范围内共享的bean,那将是非常有价值的事情。例如,在典型的电子商务应用中,购物车bean既不适合单例模式,也不适合原型模式。就购物车而言,会话作用域是最合适的,因为它与给定的用户关联性最大。要指定会话作用域,我们可以使用@Scope作用域:

@Component
@Scope(
      value=WebApplicationContext.SCOPE_SESSION,
      proxyMode=ScopedProxyMode.INTERFACES)
public ShoppingCart cart(){...}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

这里,我们将value设置成了WebApplicationContext中的SCOPE_SESSION常量(它的值是session)。这会告诉Spring为Web应用中的每个会话创建一个ShoppingCart。这会创建多个ShoppingCart bean 实例,但是对于给定的会话只会创建一个实例,在当前会话相关的操作中,这个bean实际上相当于单例。 
@Scope同时还有一个proxyMode属性,它被设置成了ScopedProxyMode.INTERFACES。这个属性解决了将会话或请求作用域的bean注入到单例bean中所遇到的问题。作用域代理能够延迟注入请求和会话作用域的bean。 
4.2、在XML中声明作用域代理

<bean id="cart"
         class="com.myapp.ShoppingCart"
         scope="session">
     <aop:scoped-proxy />
</bean>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

<aop:scoped-proxy>是与@Scope注解的proxyMode属性功能相同的Spring XML配置元素。它会告诉Spring为bean创建一个作用域代理。默认情况下,它会使用CDLib创建目标类的代理。但我们也可以将proxy-target-class属性设置为false,进而要求它生成基于接口的代理:

<bean id="cart"
         class="com.myapp.ShoppingCart"
         scope="session">
     <aop:scoped-proxy proxy-target-class="false" />
</bean>
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

注意,要使用<aop:scoped-proxy>元素,我们必须在XML配置中声明Spring的aop命名空间,即beans头里面加入“xmlns:aop=’http://www.springframework.org/schema/aop’ ”。

5、运行时值注入

当讨论依赖注入的时候,我们通常所讨论的是将一个bean引用注入到另一个bean的属性或构造器参数中。它通常来讲指的是将一个对象与另一个对象进行关联。但是bean装配的另外一个方面是将一个值(字面量)注入到bean的属性或者构造器参数中。在前面的章节中,我们也试着这样装配值:

@Bean
public CompactDisc sgtPeppers(){
    return new BlankDisc("Sgt.Pepper's Lonely Hearts",
    "The Beatles");
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

尽管这实现了需求,也就是为BlankDisc bean设置title和artist,但它在实现的时候是将值硬编码在配置类中的。与之类似,如果使用XML的话,那么值也会是硬编码的。有时候硬编码是可以的,但有的时候,我们会希望避免硬编码值,而是想让这些值在运行时在确定。为了实现这些功能,Spring提供了两种在运行时求值的方式(重点!!!):

  • a、属性占位符(Property placeholder)

  • b、Spring表达式语言(SpEL)

其中,属性占位符比较简单,而SpEL更为强大。

5.1注入外部的值

在Spring中,处理外部值的最简单方式就是声明属性源并通过Spring的Environment来检索属性。例如,如下展示了一个基本的Spring配置类,它使用外部的属性来装配BlankDisc bean。

@Configuration
@PropertySource("classpath:/com/soundsystem/app.properties")  //声明属性源
public class ExpressiveConfig{
    @Autowired
    Environment env;
   @Bean
   public BlankDisc disc(){
      return new BlankDisc(env.getProperty("disc.title"),
                           env.getProperty("disc.artist"));
  }
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

在本例中,@PropertySource引用了类路径中一个名为app.properties的文件。它大致会如下所示:

disc.title=Sgt.Peppers Lonly Hearts Club Band
disc.artist=The Beatles
 
 
  • 1
  • 2

这个属性会加载到Spring的Environment中,稍后可以从这里检索属性。同时,在disc()方法中,会创建一个新的BlankDisc,它的构造器参数是从属性文件中获取的,而这时通过调用getProperty()实现的。

5.2、深入学习Spring的Environment

当我们去了解Environment的时候会发现,程序清单3.7所示的getProperty()方法并不是获取属性值的唯一方法,getProperty()方法有四个重载的变种形式:

  • a、String getProperty(String key)

  • b、String getProperty(String key,String defaultValue)

  • c、T getProperty(String key,Class type)

  • d、T getProperty(String key,Class type,T defaultValue)

Environment还提供了几个与属性相关的方法:

返回值 方法 说明
String env.getRequiredProperty(“”) 希望将要获取的属性必须要定义,如果没有定义,将会抛出异常
boolean env.containsProperty(“”) 检查某个属性是否存在
String[] getActiveProfiles() 返回激活profile名称的数组
String[] getDefaultProfiles() 返回默认profile名称的数组
boolean acceptsProfiles(String … profiles) 如果environment支持给定profile的话,就返回true

通常来讲,我们并不会频繁地使用Environment相关的方法,但是知道有这些方法还是有好处的。直接从Environment中检索属性是非常方便的,尤其是在Java配置中装配bean的时候。但是,Spring也提供了通过占位符装配属性的方法,这些占位符的值会来源于一个属性源。

5.3解析属性占位符

Spring 一直支持将属性定义到外部的属性文件中,并使用占位符将其插入到Spring bean中。在Spring装配中,占位符的形式为使用”${…}”包装的属性名称。我们可以在XML中按照如下的方式解析BlankDisc构造器参数:

<bean id="sgtPeppers" class="soundsystem.BlankDisc" c:_title="${disc.title}" c:_artist="${disc.artist}" />
 
 
  • 1

按照这种方式,XML配置没有使用任何硬编码的值,它的值是从配置文件意外的一个源中解析得到的。 
如果我们依赖于组件扫描和自动装配类创建和初始化应用组件的话,那么久没有指定占位符的配置文件或类了。在这种情况下,我么你可以使用@Value注解,它的使用方式与@Autowired注解非常相似。如,在BlankDisc类中,构造器可以改成如下所示:

public BlankDisc(@Value("${disc.title}") String title,
                 @Value("${disc.artist}") String artist){
    this.title=title;
    this.artist=artist;
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

为了使用占位符,我们必须要配置一个PropertyPlaceholderConfigurer bean或PropertySourcesPlaceholderConfigurer bean。从Spring 3.1开始,推荐使用PropertySourcesPlaceholderConfigurer,因为它能够基于Spring Environment及其属性源来解析占位符。 
如下的@Bean方法在Java中配置了PropertySourcesPlaceholderConfigurer:

//启用占位符功能
@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer(){
    return new PropertySourcesPlaceholderConfigurer();
}
 
 
  • 1
  • 2
  • 3
  • 4
  • 5

如果你想使用XML配置的话,Spring context命名空间中的<context:property-placeholder>元素将会为你生成PropertySourcesPlaceholderConfigurer bean:

<context:property-placeholder><!--启用占位符功能-->
 
 
  • 1

解析外部属性能够将值的处理推迟到运行时,但是他的关注点在于根据名称解析来自Spring Environment和属性源的属性。而Spring 表达式语言提供了一种更通用的方式在运行时计算所要注入的值。SpEL基础参照另一篇笔记。 
http://blog.csdn.net/zhoucheng05_13/article/details/54603985

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值