【Spring】高级装配

前言

前面讲解了bean的核心装配技术,其可应付不少中装配状况,但Spring提供了高级装配技术,以此实现更为高级的bean装配功能。java

高级装配

配置profile bean

将全部不一样bean定义放置在一个或多个profile中,在将应用部署到每一个环境时,要确保对应的profile处于激活状态。如配置了以下数据源,并使用profile注解定义。web

JavaConfig配置profile

  • 开发环境中的数据源配置
package com.hust.grid.leesf.ch3;

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;

@Configuratoin
@Profile("dev")
public class DevelopmentProfileConcifg {
    @Bean(destroyMethod = "shutdown")
    public DataSource embeddedDataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.H2)
            .addScript("classpath:schema.sql")
            .addScript("classpath:test-data.sql")
            .build();
    }   
}
  • 生产环境下的数据源配置
package com.hust.grid.leesf.ch3;

import javax.sql.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
public class ProductionProfileConfig {
 
  @Bean
  @Profile("prod")
  public DataSource jndiDataSource() {
    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注解一块儿使用,上述放在两个不一样配置类能够转化为两个方法放在同一个配置类中。spring

package com.hust.grid.leesf.ch3;

import javax.sql.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;
import org.springframework.jndi.JndiObjectFactoryBean;

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

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

}

注意:尽管配置类中配置了不一样的Profile,但只有规定的profile激活时,对应的bean才会被激活。sql

XML配置profile

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd"
  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元素定义多个profileapache

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee" xmlns:p="http://www.springframework.org/schema/p"
  xsi:schemaLocation="
    http://www.springframework.org/schema/jee
    http://www.springframework.org/schema/jee/spring-jee.xsd
    http://www.springframework.org/schema/jdbc
    http://www.springframework.org/schema/jdbc/spring-jdbc.xsd
    http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">

  <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:driverClassName="org.h2.Driver"
          p:username="sa"
          p:password="password"
          p:initialSize="20"
          p:maxActive="30" />
  </beans>
  
  <beans profile="prod">
    <jee:jndi-lookup id="dataSource"
      jndi-name="jdbc/myDatabase"
      resource-ref="true"
      proxy-interface="javax.sql.DataSource" />
  </beans>
</beans>

三个beanID都是dataSource,在运行时会动态建立一个bean,这取决激活的哪一个profilesession

激活profile

Spring依赖spring.profiles.activespring.profiles.default两个属性肯定哪一个profile处于激活状态,若是设置了spring.profiles.active,那么其值用于肯定哪一个profile是激活状态,若是未设置,则查找spring.profiles.defaults的值;若是均未设置,则没有激活的profile,只会建立那些没有定义在profile中的bean。以下是在web.xml中设置spring.profiles.defaultapp

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
  ...>

<context-param>
    <param-name>spring.profiles.default</param-name>
    <param-value>dev</param-value>
</context-param>


<servlet>
    <init-param>
        <param-name>spring.profiles.default</param-name>
        <param-value>dev</param-value>
    </init-param>
</servlet>

Spring提供了@ActiveProfiles注解启用profiletcp

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

条件化的bean

使用@Conditional注解,若是给定条件计算结果为true,那么建立bean,不然不建立。学习

  • MagicBean
@Bean
@Condition(MagicExistsCondition.class)
public MagicBean magicBean() {
    return new MagicBean();
}
  • MagicExistsCondition
package com.hust.grid.leesf.ch3;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

public class MagicExistsCondition implements Condition {
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");
    }
}

处理自动装配的歧义性

以下代码网站

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

其中Dessert为一个接口,其有多个子类。

@Component
public class Cake implements Dessert {}

@Component
public class Cookies implements Dessert {}

@Component
public class IceCream implements Dessert {}

此时,会发现不止一个bean能够匹配,Spring会抛出异常,能够将某个bean设置为首选的bean或使用限定符。

标识首选bean

使用Primary注解标识首选bean。

@Component
@Primary
public class IceCream implements Dessert {}

或者使用xml配置首选bean

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

若是配置多个首选bean,那么也将没法工做。

限定自动装配的bean

使用@Qualifier注解进行限定。

@Autowired
@Qualifier("iceCream")
pulbic void setDessert(Dessert dessert) {
    this.dessert = dessert;
}
  • 建立自定义限定符

能够为bean设置本身的限定符,而不依赖将bean ID做为限定符,在bean的声明上使用@Qualifier注解,其能够与@Component组合使用。

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

这样,使用以下。

@Autowired
@Qualifier("cold")
pulbic void setDessert(Dessert dessert) {
    this.dessert = dessert;
}
  • 使用自定义的限定符注解

若是多个bean都具有相同特性的话,那么也会出现问题,没法肯定惟一bean,如定义@Cold注解

@Target({ElementType.CONSTRUCTOR, ElementType.FIELD,
            ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
#Qualifier
public @interface Cold {}

这样就可使用以下注解进行定义

@Component
@Cold
@Creamy
public class IceCream implements Dessert {}

经过自定义注解后,而后能够经过多个注解的组合肯定惟一一个符合条件的bean

@Autowired
@Cold
@Creamy
pulbic void setDessert(Dessert dessert) {
    this.dessert = dessert;
}

bean的做用域

默认状况下,Spring上下文中全部bean都是做为以单例形式建立的。但有时候须要多个不一样的bean实例,Spring定义了多种做用域,包括:

  • 单例,整个应用中,只建立一个bean实例。
  • 原型,每次注入或者经过Spring应用上下文获取时,都会建立一个新的bean实例。
  • 会话,在Web应用中,为每一个会话建立一个bean实例。
  • 请求,在Web应用中,为每一个请求建立一个bean实例。

使用@Scope注解肯定bean的做用域,如将以下bean声明为原型。

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class NotePad {}

当使用xml文件配置时以下

<bean id="notepad"
      class="com.hust.grid.leesf.Notepad"
      scope="prototype" />

使用会话和请求做用域

Web应用中,可能须要实例化在会话和请求范围内共享的bean,如电商网站,须要会话做用域。

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

须要将ShoppingCart bean注入到单例StoreService bean中。

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

此时,因为ShoppingCart是会话做用域,直到某个用户建立了会话后,才会出现ShoppingCart实例,而且Spring会注入一个代理至StoreService中,这个代理与ShoppingCart有相同的方法,当处理时须要将调用委托给会话做用域内真正的ShoppingCart

在XML中声明做用域代理

须要使用Spring aop命名空间的新元素

<bean id="cart"
      class="com.hust.grid.leesf.ShoppingCart"
      scope="session">
  <aop:scoped-proxy />
</bean>

上述状况会使用CGLib建立目标类的代理,但也可将proxy-target-class属性设置为false,进而要求它生成基于接口的代理。

<bean id="cart"
      class="com.hust.grid.leesf.ShoppingCart"
      scope="session">
  <aop:scoped-proxy proxy-target-class="false" />
</bean>

为使用<aop:scoped-proxy>元素,须要在XML中声明spring-aop.xsd命名空间。

运行时值注入

不使用硬编码注入,想让值在运行时肯定,Spring提供了以下两种方式。

  • 属性占位符
  • Spring表达式语言

注入外部的值

声明属性源并经过SpringEnvironment来检索属性。

...
@Configuration
@PropertySource("classpath:/com/hust/gird/leesf/app.properties")
public class ExpressiveConfig {
    @Autowired
    Environment env;
    
    @Bean
    public BlankDisc disc() {
        return new BlankDisc(
            env.getProperty("disc.title"),
            env.getProperty("disc.artist"));
    }
}

经过在app.properties中配置对应的属性完成注入。还可以使用占位符完成注入。

public BlankDisc(
    @Value("${disc.title}") String title,
    @Value("${disc.artist}") String artist) {
  this.title = title; 
  this.artist = artist;
}

为使用占位符,须要配置PropertySourcesPlaceholderConfigurer

@Bean
public static PropertySourcesPlaceholderConfigurer placeholderConfigurer() {
    return new PropertySourcesPlaceholderConfigurer();
}

或者在XML配置文件中使用<context:property-placeholder />,这样会生成一个PropertySourcesPlaceholderConfigurerbean

总结

本篇学习了更为高级的装配技巧,如Spring profile,还有条件化装配bean,以及bean的做用域等等。

相关文章
相关标签/搜索