是为了方便环境迁移时,简化修改配置的过程。也就是说我们有一个项目在开发环境时,可能用到的是windons系统,mysql数据库等。但是到生产环境,项目部署在Linux系统上,也有可能是使用Oracle数据库。这个时候你也许在发布到生产环境时,需要修改跟环境有关的诸多代码,这样不安全。
Spring就提供了一个解决方案,使用profile bean ,等到运行时,Spring会识别到active的profile,自动帮你装配bean。
我们需要做的:
1.整理bean到一个或多个profile中
2.激活当前环境的profile为active状态
在java配置类中,可以使用@Profile注解指定某个bean属于哪一个Profile。
@Configuration
@Profile("dev")
public class DevelopmentProfileConfig{
@Bean(id="devDataSource" destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql").build();
}
}
@Configuration
@Profile("prod")
public class ProductionProfileConfig{
@Bean(id="prodDataSource")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:test-data.sql").build();
}
}
从上面两段代码我们可以看到,@Profile注解配置了一个关于数据源的bean,该注解作用在配置类上,当我们激活了这个名叫"dev"的profile的时候,激活的就是devDataSource这个bean,并且自动装配到spring的IOC容器中。如果激活的的是prod这个Profile,那么激活的bean就是prodDataSource这个bean,装配到容器中。
还有一个问题就是,@Profile注解是类级别的注解,但是从Spring3.2开始,@Profile可以作用在方法上。也就是说我们可以在一个配置类里整理多个不同环境下的bean,不需要为每一种环境都整理一个配置类。
@Configuration
public class DataSourceConfig{
@Bean(destroyMethod="shutdown")
@Profile("dev")
public DataSource embeddedDataSource(){
return new .....
}
@Bean
@Profile("prod")
public DataSource jndiDataSource(){
return new ......
}
}
上述是在Java类中配置的profile,接下来看看在XML文件中使用profile。
在一个XML文件中,使用多个标签,每个标签中指定profile属性的值。
p:url="数据库链接"
p:username="root"
p:password="123456"
p:driverClassName="com.mysql.cj.Driver"
知道了使用profile可以为不同的环境配置bean,那么如何激活profile呢?
首先激活profile依赖于两个属性:
1.spring.profiles.active
2.spring.profiles.default
如果直接设置spring.profiles.active为dev,那么所有标注了dev的bean就会被激活。
如果没有设置spring.profiles.active,而是设置了spring.profiles.default,那么这种情况就是spring找不到spring.profiles.active,它就会去找spring.profiles.default从而去激活default值设置的bean。
如果spring.profiles.active和spring.profiles.default都没有设置,spring找不到这两个值,那么spring便不会激活这些值所对应的bean,它只会创建那些没有定义profile的普通bean。
激活profile bean
1.在web.xml中DispatcherServlet中激活
dispatcherServlet
org.springframework.web.servlet.DispatcherServlet
spring.profiles.active
dev
1
2.也可以使用@ActiveProfiles注解
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes={XXXConfig.class})
@ActiveProfiles("dev")
public class XXXTest{
......
}
就是我们在创建某个bean的时候,希望是满足了一个条件这个bean才会被创建,如果这个条件不满足,那么我们就希望spring不创建这个bean。
先来看一个简单的示例:
public class Student {
private String name;
private int age;
private String score;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getScore() {
return score;
}
public void setScore(String score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", score='" + score +
'}';
}
}
public class House {
private String name;
public House(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "House{" +
"name='" + name + '\'' +
'}';
}
}
@Configuration
public class JavaConfig {
@Bean
public House house(){
return new House("我有学区房");
}
@Bean
@Conditional(IsHouse.class)
public Student student(){
Student student = new Student();
student.setName("aa");
student.setAge(12);
student.setScore("A");
return student;
}
}
只有有了学区房才能上一小,那么我们这里就是只有先创建了house这个bean,才能注册成为一小的学生。所以在Student这个bean上添加了@Conditional注解,它指向IsHouse.class这个类,实现了Condition接口。
public class IsHouse implements Condition {
@Override
public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) {
ConfigurableListableBeanFactory beanFactory = conditionContext.getBeanFactory();
return beanFactory.containsBean("house");
}
}
实现Condition接口,重写matches方法,实现自己需要的条件逻辑即可。
测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {JavaConfig.class})
public class ConditionTest {
@Test
public void test1(){
ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
Student student = ac.getBean(Student.class);
System.out.println(student.getName());
}
}
如果把JavaConfig中的house这个bean注释掉,那么也无法创建student这个bean。
####什么是自动装配的歧义性?
就是当a这个bean依赖于另一个bean时,这个被依赖的bean有多种实现,那么spring就不知道把哪一个实现装配到a上。
举个例子,当某个人要吃甜品,现在有蛋糕,冰淇淋,巧克力都是甜品,那spring就不能知道这个人要吃哪种甜品,它就无法自动的给这个人甜品。换言之如果只有一种甜品冰淇淋,那么spring自然而然的会将冰淇淋"端到"这个人面前。
解决这个问题的方案:
1.标示首选的bean
Java配置中使用@Primary注解标示
public class JavaConfig{
@Bean
@Primary
public IceCream iceCream(){
return new IceCream();
}
}
XML配置中使用属性 primary=true标示
分析存在的问题:如果代码中,有两个类型相同的bean被标注了primary属性怎么办?这时spring又不知道如何自动装配了。
所以这就引出了@Qualifier注解:该注解是使用限定符的主要方式,它可以和@Autowired,@Inject协同使用,在注入的时候指定想要注入进去的是哪个bean。
假设我们希望将iceCream这个bean注入到dessert中:
@Autowired
@Qualifier("iceCream")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
使用@Qualifier注解在bean的类上
@Component
@Qualifier("cold")
public class IceCream implments Dessert{
......
}
在需要自动装配的地方,使用@Qualifier注解
例如:我们要使用上面的IceCream进行自动装配bean:
@Autowired
@Qualifier("cold")
public void setDessert(Dessert dessert){
this.dessert = dessert;
}
这样就使用了自定义的限定符自动装配了。
如果是在Java配置显式的定义bean的时候,也可以@Qualifier和@Bean注解一起使用。
@Bean
@Qualifier("cold")
public Dessert iceCream(){
return new IceCream();
}
为什么要使用自定义的限定符注解?
就是因为如果出现了两个@Qulifier(“cold”)的限定符,spring又不知道该如何区分了,所以引入了自定义的限定符注解。
@Component
@Qualifier("cold")
//@Qualifier("creamy")
public class IceCream implements Dessert{
......
}
@Component
@Qualifier("cold")
//@Qualifier("fruity")
public class Popsicle implements Dessert{
......
}
看上面两段代码,这两个bean都被标注了@Qualifier(“cold”),说明它们都是冰的甜品,那么spring又该如何区分它们呢?难道再次使用@Qualifier注解继续对它们予以区分?在Java8之前不允许出现重复注解,所以就引入了自定义限定符注解。
如何创建自定义的限定符注解?
先创建一个@Cold注解
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Cold{
}
再创建一个@Creamy注解
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Creamy{
}
再创建一个@Fruity注解
@Target({ElementType.CONSTRUCTOR,ElementType.FIELD,ElementType.METHOD,ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Fruity{
}
有了这三个自定义的限定符注解,我们再来看看如何使用?
@Autowired
@Cold
@Creamy
public Dessert setDessert(Dessert dessert){
this.dessert = dessert;
}
@Autowired
@Cold
@Fruity
public Dessert setDessert(Dessert dessert){
this.dessert = dessert;
}
这样就避免了重复注解,但是事实上Java8虽然允许了重复注解,只要某个注解本身定义的时候带有@Repeatable,就允许重复。但是Spring却没有对@Qualifier注解添加@Repeatable。所以最好还是使用自定义的限定符注解。
综上所述,这一篇我们了解了以下几点:
1.什么是profile?为什么要有profile?
2.如何配置profile的bean?
3.如何激活profile?
4.什么是条件化的bean?
5.如何实现条件化的bean?
6.通过ConditionContext可以得到什么?
7.自动装配有什么歧义性?如何处理这些歧义性?
8.自定义的限定符注解有什么作用?如何创建并使用自定义的限定符注解?