Spring Bean的高级装配

Spring除了能够提供一些基本的bean装配,它还提供了更多技巧,借助他们可以实现更为高级的bean装配功能

一、环境与Profile
  • @Profile 注解可以根据当前环境,动态的激活和切换一系列组件的功能

  • @Profile 指定组件在哪个环境的情况下才能被注册到容器中,不指定,任何环境下都能注册这个组件

1.在类上使用@Profile注解
  • 在类上使用@Profile注解可以声明当前bean被激活时使用的环境,它会告诉Spring配置类中的Bean只有配置文件环境为dev激活使才会被创建,如果dev profile没有被激活,那么所有带Bean注解的方法将会被忽略
  • 实际上,开发时会有多个环境,那么类似的可以指定prod,test,master等等
@Configuration
@Profile("dev")
public class DataSourceProfileConfig{
	
	@Bean
    public DataSource getDataSource(){
        // 创建数据库连接的一些逻辑
        // .....
        // .....
        return dataSoure;
    }

 
 
}

2.在方法上使用@Profile注解
  • 在Spring 3.1版本中只能在类上使用@Profile注解,不过从Spring 3.2版本开始,可以在方法上使用该注解了 ,这样的话只需在一个配置类中配置多个环境了
@Configuration
public class DataSourceProfileConfig{
	
	@Bean
    @Profile("dev")
    public DataSource getDataSource(){
        // 创建数据库连接的一些逻辑
        // .....
        // .....
        return dataSoure;
    }

 	@Bean
    @Profile("prod")
    public DataSource initDataSource(){
        // 创建数据库连接的一些逻辑
        // .....
        // .....
        return dataSoure;
    }
    
    @Bean
    public void beforeCreateDataSoure(){
        // do other something
    }
 
}
  • 在这里,只有当规定的profile激活时,相应的bean才会被创建。如果一个bean并没有给定@Profile注解的范围,那么这个Bean始终会被创建,与激活哪个环境的Profile没有关系
3.使用Xml方式的配置
  • 这和注解产生的效果是一样的,也可以在标签里直接指定环境,例如
<?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:aop="http://www.springframework.org/schema/aop"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task.xsd
           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    <task:annotation-driven/>

    <import resource="spring-datasource.xml"/>
    <import resource="spring-hessian-server.xml"/>
    <import resource="spring-remoting-dis.xml"/>
    <import resource="spring-remoting-worldeye.xml"/>
    <import resource="spring-activemq.xml"/>
    <import resource="spring-cxf-client.xml"/>

    <!-- 开发配置 -->
    <beans profile="dev">
        <context:property-placeholder location="classpath:config/application.properties, classpath:config/application-dev.properties"/>
        <import resource="spring-hadoop-dev.xml"/>
    </beans>

    <!-- 测试配置 -->
    <beans profile="test">
        <context:property-placeholder location="classpath:config/application.properties, classpath:config/application-prd.properties, classpath:config/application-test.properties"/>
        <import resource="spring-hadoop-test.xml"/>
    </beans>

    <!-- 线上配置 -->
    <beans profile="prd">
        <context:property-placeholder location="classpath:config/application.properties, classpath:config/application-prd.properties"/>
        <import resource="spring-hadoop.xml"/>
    </beans>
    
</beans>
二、条件化的Bean装配
  • 从Spring 4版本开始,引入的bean的条件化装配,有些使用场景下,我们可能需要条件化的装配某些bean,即当满足某一条件时,装配某些bean,当不满足某一条件时,就忽略掉某些bean。这样一来,提高了Bean装配的灵活性
1.使用@Conditional类
  • 只有满足了MagicExistsCondition类这个条件,Bean才会被装配
//MagicBean.java

@Bean
@Conditional(MagicExistsCondition.class)
public MagicBean magicBean(){
    return new MagicBean();
}
  • 设置给@Conditional的类可以是任意实现了Condition接口的类型。可以看出来,这个接口实现起来很简单直接,只需要提供matches()方法的实现即可。如果matches()方法返回true,那么就会创建带有@Conditional注解的bean。若返回false,将不会创建这些bean。
public class MagicExistsCondition implements Condition {
    pulic boolean matches(ConditionContext context,AnnotatedTypeMetadata metadata) {
        Environment env = context.getEnvironment();
        return env.containsProperty("magic");
    }
}
  • 在上面的程序清单中,matches()方法很简单但功能强大。它用过给定的ConditionContext对象进而得到Environment对象,并使用这个对象检查环境中是否存在名为magic的环境属性。在本例中,属性的值是什么无所谓,只要属性存在即可满足需求。如果满足这个条件的话,matches()方法就会返回true。所带来的结果就是条件能够得到满足,所有@Conditional注解上引用MagicExistsCondition的bean都会被创建。
    话说回来,如果这个属性不存在的话,就无法满足条件,matches()方法会返回false,这些bean都不会被创建。

  • Spring中常见的Conditional注解

    Conditions描述
    @ConditionalOnBean在存在某个bean的时候
    @ConditionalOnMissingBean不存在某个bean的时候
    @ConditionalOnClass当前classpath可以找到某个类型的类时
    @ConditionalOnMissingClass当前classpath不可以找到某个类型的类时
    @ConditionalOnResource当前classpath是否存在某个资源文件
    @ConditionalOnProperty当前jvm是否包含某个系统属性为某个值
    @ConditionalOnWebApplication当前spring context是否是web应用程序
2.处理装配的歧义性
  • 当Spring装配一个Bean时仅有一个结果,那么就不会出现Bean的歧义性;相反,存在多个Bean相同时(比如类名一样),这种歧义性会阻碍Spring Bean的自动装配,会抛出NoUnqiueBeanDe-

    fitionException异常

**例如:**这里有个自动装配方法,设置Dessert的值,但是这个接口有三个子类实现了,那么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{ ... }
  • 使用@Primary标示首选的Bean

    有多个Bean时,通过将其中一个可选的Bean设置为首选,能够避免自动装配的歧义性

// 方式一:类上声明
@Compoent
@Primary
public class IceCream implements Dessert{ ... }

// 方式二:在方法上使用(通过Java配置显示声明时)
@Bean
@Primary
public Dessert iceCream(){
    return new IceCream();
}

// 方式三:通过XML声明
<bean id="iceCream" class="com.demo.IceCream" primary="true">
  • 使用@Qualifier注解限定自动装配的Bean

    值得注意的是,@Primary注解可以在多个Bean上使用,如果标示了多个Bean,那么又出现了歧义性,此时

    可以通过@Qualifier来限定唯一个Bean的装配

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

@Qualifier注解所给的参数是Bean的ID(如果不给,默认的也是Bean的ID),同样的你也可以自定义这个参数

示例:

在这里将IceCream的Bean ID 声明为 cold,这样有一个好处就是,即使以后你修改了类名也不会受到影响

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

在注入的地方,只需引用cold限定符就可以

@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
    this.dessert = dessert;
}
三、Spring Bean的作用域
  • 在默认情况下,Spring应用上下文中所有Bean都是以单例(Singleton)形式创建的,也就是说,不管给定的Bean被注入到其他bean多少次,每次注入的都是同一个实例。
  • 采用单例模式的好处就是,在初始化对象或者垃圾回收对象时系统资源开销很小。但在一些特殊业务场景下,单例会产生一些问题。比如你所使用的某个类是易变的,将其声明为单例的bean就会产生意想不到的问题,因为这个Bean可能会保持之前的某种状态。

Spring 对于Bean定义了很多的作用域:

作用域(Scope)说明
单例 - Singleton在Spring IOC容器中仅存在一个Bean实例,Spring的默认值
原型 - Prototype每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的Bean实例
请求 - Request每次HTTP请求都会创建一个新的Bean,该作用域仅适合WebApplicationContext环境
会话 - Session在Web应用中,为每一个会话创建一个Bean实例,即同一个Http Session 共享一个
bean,不同Session使用不同Bean,仅适用于WebApplicationContext环境
全局会话 - global session类似标准的http session作用域,不过仅仅在基于portlet的web应用当中才有意义。
Portlet规范定义了全局的Session的概念。他被所有构成某个portlet外部应用中的
各种不同的portlet所共享。
在global session作用域中所定义的bean被限定于全局的portlet session的生命周期范围之内
声明bean的作用域的方式:
  • 使用@Scope注解

    **1.**如果你使用组件扫描来发现和声明Bean,那么你可以在Bean的类上使用@Scope注解声明

    @Component
    @Scope("singleton")
    public class Demo(){ ... }
    

    在这里@Scope注解的参数也可以通过ConfigurableBeanFactory类的常量设置

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

    采用常量更加安全和不易出错

    **2.**如果通过Java配置方式,那么可以通过@Scope,@Bean组合使用来指定作用域

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Demo getDemo(){
        return new Demo();
    }
    
  • 通过XML配置

    可以通过</bean/>标签元素的scope属性来设置作用域

    <bean id="demo" class="com.test.demo" scope="prototype" />
    

在ConfigurableBeanFactory源码中定义了单例,原型两个作用域常量

public interface ConfigurableBeanFactory extends HierarchicalBeanFactory, SingletonBeanRegistry {
    String SCOPE_SINGLETON = "singleton";
    String SCOPE_PROTOTYPE = "prototype";
    
    ....
}

在WebApplicationContext源码中定义了请求,会话等作用域常量

public interface WebApplicationContext extends ApplicationContext {
    String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
    String SCOPE_REQUEST = "request";
    String SCOPE_SESSION = "session";
    String SCOPE_GLOBAL_SESSION = "globalSession";
    String SCOPE_APPLICATION = "application";
    String SERVLET_CONTEXT_BEAN_NAME = "servletContext";
    String CONTEXT_PARAMETERS_BEAN_NAME = "contextParameters";
    String CONTEXT_ATTRIBUTES_BEAN_NAME = "contextAttributes";

    ServletContext getServletContext();
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值