Spring之高级装配(二)

上一节提到Spring之装配bean(一),我们已经了解到了装配的基础知识,这部分是更为高级的bean装配技术。

高级装配内容:

  • spring profile
  • 有条件的bean
  • 处理自动装配的歧义性
  • bean的作用域

1.spring profile

应用程序从一个环境迁移到另一个环境。开发阶段,某些环境相关做法可能并不适合迁移到生产环境中,甚至几遍迁移过去也无法正常工作,所以在不同的环境中使用的bean可能不同。下面以datasource为例:

在Java配置类中配置(“dev”和“prod”生产环境):

@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();
    }
}

在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>

激活(使用)哪一个profile:

<!--为应用山下文设置默认的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>

2.有条件的bean

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

@Configuration
public class ConditionalConfig {
    @Bean
    @Conditional(MagicExistsConditional.class) //条件化的创建bean
    public MagicBean magicBean() {
        return new MagicBean();
    }
}
public class MagicExistsConditional implements Condition{
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Environment evn = context.getEnvironment();

        return evn.containsProperty("magic");
    }

}

如你所见,设置给@Conditional的类可以是任意实现了Condition接口的的类型。而实现这个接口只需要实现matches方法,如果matches方法返回true就创建该bean,如果返回false则不创建bean,上例中我们就是根据环境变量中是否存在magic变量,来决定matches的返回值,进而决定是否创建MagicBean的。

但是在实际生产中,ConditionContext context, AnnotatedTypeMetadata metadata这两个参数可以对类中的其他信息进行判断,有兴趣可以查一下这两个类的api。

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

如果不仅有一个bean能够匹配结果的话,这种歧义性阻碍Spring自动装配属性、构造器参数或方法参数。 下面是一个例子:

@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{...}

在这里,当Spring试图自动装配setDessert()中的Dessert参数时,它并没有唯一、无歧义的可选值。因而,Spring会抛出NoUniqueBeanDefinitionException;

解决方案1:将其中一个bean设置为首选

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

或者在xml中

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

解决方法2:使用限定符(qualifier)来帮助Spring将可选的bean的范围缩小

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

4、bean的作用域

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

单例是默认的作用域,但是正如之前所述,对于易变的类型,这并不合适。如果选择其他的作用域,要使用@Scope注解,他可以与@Component(自动装配)或@Bean(JavaConfig)一起使用。

例如,如果你使用组件扫描来发现声明bean,那么你可以在bean的类上使用@Scope注解,将其声明为原型bean,如下:

@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
//@Scope("prototype")
public class Notepad{...}

或者

<bean id="notepad"
      class="com.myapp.Notepad"
      scope="prototype"  />
使用会话和请求作用域
@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中所遇到的问题。作用域代理能够延迟注入请求和会话作用域的bean。

在xml中注入:

<bean id="cart"
         class="com.myapp.ShoppingCart"
         scope="session">
     <aop:scoped-proxy />
</bean>
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值