Bean初始化前,对基属性进行一些校验,不满足校验时就不去装配数据源。
@Conditional注解+一个实现了Condition接口(org.springframework.context.annotation.Condition)的类,例子:
使用属性初始化数据库连接池:加入了@Conditional注解,并且配置了类DatabaseConditional
@Bean(name = "dataSource", destroyMethod = "close")
@Conditional(DatabaseConditional.class)
public DataSource getDataSource(
@Value("${database.driverName}") String driver,
@Value("${database.url}") String url,
@Value("${database.username}") String username,
@Value("${database.password}") String password
) {
Properties props = new Properties();
props.setProperty("driver", driver);
props.setProperty("url", url);
props.setProperty("username", username);
props.setProperty("password", password);
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
DatabaseConditional类:实现了Condition接口,重写matches方法,matches方法首先读取其上下文环境,然后判定是否已经配置了对应的数据库信息。当这些都已经配置好后则返回true。这个时候Spring会装配数据库连接池的Bean,否则是不装配的。
public class DatabaseConditional implements Condition {
/**
* 数据库装配条件
*
* @param context 条件上下文
* @param metadata 注释类型的元数据
* @return true装配Bean,否则不装配
*/
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
// 取出环境配置
Environment env = context.getEnvironment();
// 判断属性文件是否存在对应的数据库配置
return env.containsProperty("database.driverName")
&& env.containsProperty("database.url")
&& env.containsProperty("database.username")
&& env.containsProperty("database.password");
}
}
在介绍IoC容器最顶级接口BeanFactory的时候,可以看到isSingleton和isPrototype两个方法。
其中,isSingleton方法如果返回true,则Bean在IoC容器中以单例存在,这也是Spring IoC容器的默认值;
如果isPrototype方法返回true,则当我们每次获取Bean的时候,IoC容器都会创建一个新的Bean,这便是Spring Bean的作用域的问题。
在一般的容器中,Bean都会存在单例(Singleton)和原型(Prototype)两种作用域,
Java EE广泛地使用在互联网中,而在Web容器中,则存在页(page)、请求(request)、会话(session)和应用(application)4种作用域。对于页面(page),是针对JSP当前页面的作用域,Spring是无法支持的。
下面我们探讨单例(Singleton)和原型(Prototype)的区别
定义作用域类
@Component
// @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public class ScopeBean {
}
测试作用域
AnnotationConfigApplicationContext ctx
= new AnnotationConfigApplicationContext(AppConfig.class);
ScopeBean scopeBean1 = ctx.getBean(ScopeBean.class);
ScopeBean scopeBean2 = ctx.getBean(ScopeBean.class);
System.out.println(scopeBean1 == scopeBean2);
测试Spring作用域
如果加了注释,默认单例,结果true。
如果不加注释,IoC容器在每次获取Bean时,都新建一个Bean的实例返回给调用者,结果false。
这里的ConfigurableBeanFactory只能提供单例(SCOPE_SINGLETON)和原型(SCOPE_PROTOTYPE)两种作用域供选择,
如果是在Spring MVC环境中,还可以使用WebApplicationContext去定义其他作用域,如请求(SCOPE_REQUEST)、会话(SCOPE_SESSION)和应用(SCOPE_APPLICATION)。
@Component
@Scope(WebApplicationContext.SCOPE_REQUEST)
public class ScopeBean {
}
这样同一个请求范围内去获取这个Bean的时候,只会共用同一个Bean,第二次请求就会产生新的Bean。因此两个不同的请求将获得不同的实例的Bean。
Profile机制:实现各个环境之间的切换。
假设存在dev_spring_boot和test_spring_boot两个数据库,这样可以使用注解@Profile定义两个Bean:
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("dev")
public DataSource getDevDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/dev_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
@Bean(name = "dataSource", destroyMethod = "close")
@Profile("test")
public DataSource getTestDataSource() {
Properties props = new Properties();
props.setProperty("driver", "com.mysql.jdbc.Driver");
props.setProperty("url", "jdbc:mysql://localhost:3306/test_spring_boot");
props.setProperty("username", "root");
props.setProperty("password", "123456");
DataSource dataSource = null;
try {
dataSource = BasicDataSourceFactory.createDataSource(props);
} catch (Exception e) {
e.printStackTrace();
}
return dataSource;
}
在Spring中存在两个参数以修改启动Profile机制,两个属性都没有配置的情况下,Spring将不会启动Profile机制,即被@Profile标注的Bean将不会被Spring装配到IoC容器中:
一个是spring.profiles.active,
一个是spring.profiles.default。
两个属性存在优先级,先判定是否存在spring.profiles.active配置后,再去查找spring.profiles.default配置的
在Java启动项目中,我们只需要如下配置就能够启动Profile机制:
JAVA_OPTS="-Dspring.profiles.active=dev"
注解@ImportResource:可以引入对应的XML文件,用以加载Bean。有时候有些框架(如Dubbo,Cat)是基于Spring的XML方式进行开发的,这个时候需要引入XML的方式来实现配置。先来建一个POJO:
public class Squirrel implements Animal {
@Override
public void use() {
System.out.println("松鼠可以采集松果");
}
}
注意,这个POJO所在的包并不在@ComponentScan定义的扫描包com.springboot.chapter3.*之内,而且没有标注@Component,所以不会被扫描机制所装配。
在这里,我们使用XML的方式来装配它,于是就可以定义一个XML文件
使用XML配置POJO——spring-other.xml
在Java配置文件中就可以直接载入它,装配XML定义的Bean
@Configuration
@ComponentScan(basePackages = "com.springboot.chapter3.*")
@ImportResource(value = {"classpath:spring-other.xml"})
public class AppConfig {
......
}
这样就可以引入对应的XML,从而将XML定义的Bean也装配到IoC容器中。
Spring EL拥有更为强大的运算规则来更好地装配Bean。
1.读取属性文件的值,@Value中的${......}代表占位符
,它会读取上下文的属性值装配到属性中,例如:
@Value("${database.driverName}")
String driver
2.@Value还能够调用方法,采用#{......}
代表启用Spring表达式,它将具有运算的功能,例如,我们记录一个Bean的初始化时间:
@Value("#{T(System).currentTimeMillis()}")
private Long initTime = null;
这里T(…)代表的是引入类;System是java.lang.*包的类,这是Java默认加载的包,因此可以不必写全限定名,如果是其他的包,则需要写出全限定名才能引用类;
currentTimeMillis是它的静态(static)方法,也就是我们调用一次System.currentTimeMillis()方法来为这个属性赋值。
3.给属性直接进行赋值
:
@Value("#{‘使用Spring EL赋值字符串’}")
private String str = null;
4.科学计数法赋值
@Value("#{9.3E3}")
private double d;
5.获取其他Spring Bean的属性来给当前的Bean属性赋值
@Value("#{beanName.str}")
private String otherBeanProp = null;
注意,这里的beanName是Spring IoC容器Bean的名称。str是其属性,代表引用对应的Bean的属性给当前属性赋值
6.Spring EL进行计算
#浮点数比较运算
@Value("#{beanName.pi == 3.14f}")
private boolean piFlag;
#字符串比较运算
@Value("#{beanName.str eq ‘Spring Boot’}")
private boolean strFlag;
#字符串连接
@Value("#{beanName.str + ’ 连接字符串’}")
private String strApp = null;