Spring高级装配(一)

Spring高级装配

Spring Profile

什么是Spring profile?

是为了方便环境迁移时,简化修改配置的过程。也就是说我们有一个项目在开发环境时,可能用到的是windons系统,mysql数据库等。但是到生产环境,项目部署在Linux系统上,也有可能是使用Oracle数据库。这个时候你也许在发布到生产环境时,需要修改跟环境有关的诸多代码,这样不安全。
Spring就提供了一个解决方案,使用profile bean ,等到运行时,Spring会识别到active的profile,自动帮你装配bean。

1.1配置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

就是我们在创建某个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.自定义的限定符注解有什么作用?如何创建并使用自定义的限定符注解?

你可能感兴趣的:(读书笔记,spring)