高级装配
1.Spring profile
- 作用:能够在不同的环境下对应用程序执行不同的默认配置信息
- 配置profile bean
- 能够在指定的部署的环境中确定创建对应环境的bean
- 将所有不同的bean定义并整理到一个或多个profile之中,并且在应用部署到每个环境的时候,激活对应的profile状态
- Spring4可以支持@Bean与@Profile一同在Java配置中声明
- 基于Java配置
@Configuration
public class DataSourceConfig{
// 将Dev 和 Product的配置整合成一个Bean中进行配置
@Bean(destroyMethod = "shutdown")
@Profile("dev")
public DataSource devDataSource() {
// 开发环境
System.out.println("创建开发环境的数据源");
return null;
}
@Bean(destroyMethod = "shutdown")
@Profile("production")
public DataSource proDataSource() {
// 生产环境
System.out.println("创建生产环境的数据源");
return null;
}
}
- 基于xml配置
...
<!--
重复使用多元素来定义bean
-->
<beans profile="dev">
<jdbc:embedded-database id="dataSource">
<jdbc:script location="classpath:sql/test.sql"></jdbc:script>
</jdbc:embedded-database>
</beans>
<!--
Qa测试环境
-->
<beans profile="qa">
<bean id="datasource_qa" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:url="jdbc:mysql://localhost:3306/study_mysql?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=true&prepStmtCacheSqlLimit=256&cachePrepStmts=true&prepStmtCacheSize=256&rewriteBatchedStatements=true"
p:driverClassName="com.mysql.jdbc.Driver"
p:username="root"
p:password="root123"
p:initialSize="20"
p:maxActive="2">
</bean>
</beans>
<beans profile="production">
<jee:jndi-lookup id="datasource_pro"
jndi-name="jdbc/mydb"
resource-ref="true"
proxy-interface="javax.sql.DataSource">
</jee:jndi-lookup>
</beans>
...
- 激活profile
- spring.profiles.active
- spring.profiles.default
- 设置这两个属性的方式
- 作为DispatcherServlet的初始化参数
- 作为Web应用的上下文参数
- 作为JNDI条目
- 作为环境变量
- 作为JVM的系统属性
- 在集成测试类上,使用@ActiveProfiles注解设置
条件化的bean声明
- 作用:根据指定的条件化来创建bean
- 实现:
- 实现Spring的接口Condition
- 在Java配置的bean中声明@Conditional
- 代码示例:
// 配置bean
@Configuration
public class ConditionalConfig {
@Bean
@Conditional(StringConditional.class)
public String getString(){
return new String("conditional is true");
}
}
// 声明条件
public class StringConditional implements Condition{
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return true;
}
}
- ConditionContext在spring中的源码
// spring4.0开始
public interface ConditionContext {
// 返回的BeanDefinitionRegistry检查bean定义
BeanDefinitionRegistry getRegistry();
//返回的ConfigurableListableBeanFactory检查bean是 否存在,甚至探查bean的属性
ConfigurableListableBeanFactory getBeanFactory();
//返回的Environment检查环境变量是否存在以及它的值是什 么
Environment getEnvironment();
//读取并探查getResourceLoader()返回的ResourceLoader所加载的资源
ResourceLoader getResourceLoader();
//借助getClassLoader()返回的ClassLoader加载并检查类是否存在
ClassLoader getClassLoader();
}
- AnnotatedTypeMetadata在spring的源码
// spring 4.0之后
public interface AnnotatedTypeMetadata {
// 判断带有@Bean注解的方法是不是还有其他特定的注解
boolean isAnnotated(String annotationName);
// 判断给定类型注解的属性,即是属于元注解还是直接注解的修饰
Map<String, Object> getAnnotationAttributes(String annotationName);
// 同上
Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
// 根据指定的类型来获取其的所有属性
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
自动装配的歧义性
- 歧义性:在spring容器中使用自动装配的方式来创建bean实例,而当有多个子类实现一个共同接口时,此时注入接口的实例对象无法知道对应于哪个子类实现
- 解决歧义性的方式
- 标示首选的bean,使用@Primary,@Primary能够与@Component组合用在组件扫描的bean上,也可以与@Bean组合用在Java 配置的bean声明中,但是无法将可选方案的范围限定到唯一一个无歧义性的选项 中。它只能标示一个优先的可选方案
// 摘录自《spring实战》
public interface Dessert {}
@Component
public class Cookie implements Dessert{}
@Component
@Primary // spring会优先装配当前bean
public class Cake implements Dessert{}
// 自动装配的时候如果遇到相同的装配bean,将会以标示@Primary优先注入
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.identifier")
public class IdentifierBeanConfig {
private Dessert dessert;
@Autowired
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}
// xml配置
<bean id="datasource_qa" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:url="jdbc:mysql://localhost:3306/study_mysql?useUnicode=true&characterEncoding=UTF-8&useServerPrepStmts=true"
p:driverClassName="com.mysql.jdbc.Driver"
p:username="root"
p:password="root123"
p:initialSize="20"
p:maxActive="2"
primary="true">
- 限定自动装配的bean,@Qualifier注解是使用限定符的主要方式,注解所设置的参数就是想要注入的bean的ID,所引用的bean要具有String类型的“beanId”作为限定符
// 在对应的子类使用限定符指明
@Component
@Qualifier("cookie")
public class Cookie implements Dessert{}
@Component
//@Primary
@Qualifier("cake")
public class Cake implements Dessert{}
// 在配置bean的时候同样指明要注入的bean
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.identifier")
public class IdentifierBeanConfig {
private Dessert dessert;
@Autowired
@Qualifier("cake")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
}
- 自定义限定符
- 作用:解决多个bean具备相同的特性的问题,即有多个bean具有相同定义的@Qualifier,为了区分,将@Qualifier设定的修饰符拆分成为多个独立的特性,每个bean各个特性的组合(仅当一个特性除外)
// 使用自定义注解定义限定符
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold {}
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Sweet {}
....
// 通过自定义的限定符,可以进行自由组合,但是如果仅用一个来修饰bean的时候,建议不用自定义修饰符,比如这种情况便会出现错误:
@Component
@Cold
@Sweet
public class Cookie implements Dessert{} // 使用两个限定符修饰
@Component
@Sweet
public class Cake implements Dessert{} // 使用一个限定符修饰
// test
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = IdentifierBeanConfig.class)
public class DessertTest {
@Autowired
@Sweet
private Dessert dessert;
/**
* 这时候spring定位到有两个bean有相同的修饰符就会出错
* NoUniqueBeanDefinitionException:
* No qualifying bean of type 'com.dtrees.spring.core.assembly.identifier.Dessert' available: expected single matching bean but found 2: cake,cookie
*/
@Test
public void testDessert(){
Assert.assertNotNull(dessert);
System.out.println(dessert.getClass() == Cake.class);;
}
}
bean的作用域
- 作用域
- 单例(Singleton):在整个应用中,只创建bean的一个实例,spring默认作用域
- 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的 bean实例
- 会话(Session):在Web应用中,为每个会话创建一个bean实例
- 请求(Rquest):在Web应用中,为每个请求创建一个bean实例
- 实践
- spring容器使用ConfigurableBeanFactory来声明作用域的单例和多例,避免出错
// 单例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public class SingletonBean {}
// 多例
@Component
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class PrototypeBean {}
- spring web中使用WebApplicationContext声明请求和会话的作用域,对于Web应用,有5种对应的作用域
@Component
//@Scope(value = WebApplicationContext.SCOPE_GLOBAL_SESSION)
//@Scope(value = WebApplicationContext.SCOPE_APPLICATION)
//@Scope(value = WebApplicationContext.SCOPE_SESSION)
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
public class OrderBean {}
- WebApplicationContext源码,定义的作用域如下
// web的request作用域,支持标准作用域的单例和多例
String SCOPE_REQUEST = "request";
// web的session作用域,支持标准作用域的单例和多例
String SCOPE_SESSION = "session";
// web的全局session作用域,支持标准作用域的单例和多例
String SCOPE_GLOBAL_SESSION = "globalSession";
// web的上下文application作用域,支持标准作用域的单例和多例
String SCOPE_APPLICATION = "application";
- 作用域的代理模式proxyMode的使用场景
// 需求:在web应用中,需要在服务层获取当前会话或请求中一个实例bean对象,而不是每次一个会话创建一个bean,这样会产生一个会话多个bean实例,相当于session或者request中的单例
// spring做法是做一个bean的代理注入到单例bean中,并由这个代理bean委托给实际的bean去处理业务逻辑
// 基于Java的配置
@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST,proxyMode = ScopedProxyMode.INTERFACES)
public class OrderBean {}
// 基于xml的配置
<bean id="oderBean" class="com.dtrees.springmvc.bean.OrderBean" scope="session">
<aop:scoped-proxy proxy-target-class="true"/>
</bean>
- ScopedProxyMode源码
public enum ScopedProxyMode {
// 默认是NO的值
DEFAULT,
// 不创建代理对象
NO,
// 基于JDK的动态代理模式
INTERFACES,
// 基于CGLIB的动态代理模式
TARGET_CLASS;
}
运行时注入bean
属性占位符(Property placeholder):
* 通过Spring的Environment来检索属性
* 解析属性占位符,即${ ... }包装的属性名称
// 使用spring的Environment来检索属性
@Configuration
@ComponentScan("com.dtrees.spring.core.assembly.placeholder")
@PropertySource("classpath:spring/app.properties")
public class EnvironmentConfig {
@Autowired
private Environment environment; //app.properties 的数据注入到spring的Environment中
/**
* 希望这个属性必须要定义,使用getRequiredProperty
* @return
*/
@Bean
public PropertiesBean propertiesBean(){
return new PropertiesBean(environment.getProperty("title"),
environment.getProperty("age",Integer.class));
}
}
// 上述注释掉propertiesBean也可以正常注入,对于$表达式而言是直接从环境变量environment中取值
// 使用占位符注入
// 2. 自动扫描组件并注入组件属性信息
@Component
public class PropertiesBean implements Serializable{
public PropertiesBean(@Value("${title}") String title, @Value("${age}") int age) {
this.title = title;
this.age = age;
}
//其他的属性
private List<Songs> songs;
...
}
Spring表达式语言(SpEL)
- SpEL特征
- 使用bean的ID来引用bean;
- 调用方法和访问对象的属性
- 对值进行算术、关系和逻辑运算;
- 正则表达式匹配;
- 集合操作
- SpEL表达式:#{ … }
- 样例
// 使用SpEL表达式注入属性和构造器参数
// 1. 定义配置文件
@Configuration
@PropertySource("classpath:spring/app.properties")
@ComponentScan("com.dtrees.spring.core.assembly.placeholder")
public class SpELConfig {
/**
* 注意这里使用SpEl表达式的时候需要在配置文件配置PropertySourcesPlaceholderConfigurer,如果没有配置将会报错
* @return
*/
@Bean
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
return new PropertySourcesPlaceholderConfigurer();
}
}
// 2.这时候才能在bean中使用SpEl表达式注入
@Component
public class SpELBean implements Serializable{
private String title;
private String age;
public SpELBean(@Value("#{systemProperties['title']}") String title,
@Value("#{systemProperties['age']}") String age) {
this.title = title;
this.age = age;
}
}
// 其他SpEl表达式的样例,在Java的Config文件下
@Value("#{3.1444}") //表示字面值
private float v1;
@Value("#{3.1444E}") //使用科学记数法
private float v2;
@Value("#{'使用字符串'}") //字符串
private String v3;
@Value("#{false}") // 使用boolean
private boolean isBean;
// 引用bean、属性和方法
@Value("#{propertiesBean}") // 注入bean
private PropertiesBean propertiesBean;
@Value("#{propertiesBean.title}")
private String title;
@Value("#{propertiesBean.getTitle()}") // 使用方法
private String title2;
@Value("#{propertiesBean.getTitle().toUpperCase()}") // 使用方法
private String title3;
// 安全类型检查,防止出现空指针
@Value("#{propertiesBean?.getTitle()?.toUpperCase()}") // 使用方法
private String title4;
// 类作用域的方法和常量,要用T()这个关键的运算符进行转换
@Value("#{T(java.lang.Math).PI}") //
private double pi;
// 执行运算符
@Value("#{T(java.lang.Math).PI * 2.3}") // 基本运算符
private double result;
@Value("#{T(java.lang.Math).PI > 5}") // 比较运算符
private boolean gt;
@Value("#{propertiesBean.age == 20}") // 比较运算符
private boolean gt2;
@Value("#{propertiesBean.age eq 20}") // 比较运算符
private boolean gt3;
@Value("#{propertiesBean.title + ' to ' + propertiesBean.age}") // 字符串连接
private String str;
// 三元运算符
@Value("#{propertiesBean.age > 20?'old':'young'}") // 字符串连接
private String ageState;
@Value("#{propertiesBean?.email?.matches(''[\\s._%+-]+@[\\s.-]+\\.com)}") // 使用正则表达式
private boolean emailChecked;
// 从集合或者数组获取元素
@Value("#{'string'[2]}") // 下标基于0开始
private String subString;
- 集合查询运算和投影运算
// 1.定义存放的对象
public class Songs implements Serializable{
private String name;
private String artist;
// get and set 方法...
}
// 2.在Java的Config文件下
// 查询运算,查找所有的歌中的名称包含有keithl的歌曲列表,
@Value("#{propertiesBean.songs.?[name eq 'keithl']}")
private List<Songs> songsList;
// 查找集合中的第一个匹配的歌曲包含keithl的
@Value("#{propertiesBean.songs.^[name eq 'keithl']}")
private List<Songs> firstSong;
// 查找集合中的第一个匹配的歌曲包含keithl的
@Value("#{propertiesBean.songs.$[name eq 'keithl']}")
private List<Songs> lastSong;
// 投影运算,从集合的每个成员中选择特定的属性放到另外一个集合,下面将集合中song对象的name取出来并存放到新的集合中
@Value("#{propertiesBean.songs.![name]}")
private List<String> titleList;