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