spring实战——3、高级装配

3.1、环境与profile

在软件开发中,有一个难题是将应用程序从一个环境中迁移到另外一个环境。开发阶段中,某些环境的相关配置可能并不适合实际生产环境中的需求,甚至迁移过去也无法正常工作。如数据库配置、加密算法以及与外部系统的继承是跨环境部署时会发生变化的几个典型例子。

比如数据库的配置:

a、开发环境中使用嵌入式数据库,我们会在一个带有@Bean注解的方法中使用EmbeddedDatabaseBuilder来创建DataSource

b、在生产环境配置中,我们使用JNDI从容器中获取一个DataSource。

JNDI(Java Naming and Directory Interface)Java命名和目录接口:避免了程序与数据库之间的紧耦合,使得跨环境部署更加容易。

c、在QA环境中,我们会使用Commons DBCP连接池来获取一个DataSource。

QA(quality assurance): 质量保证

3.1.1、配置profile bean

在spring3.1中,引入了bean profile功能,要使用profile,我们首先需要将不同的bean的定义整理到一个或多个profile之中,在将应用部署到每个环境时,要确保对应的profile处于激活(active)状态。

使用@Profile注解指定某个bean属于哪一个profile。

1、为部署在不同环境下但功能相同的bean添加@profile("profile名")注解

2、在xml中配置profile

<beans …… profile=“上面定义的profile名”/>

3、激活

spring容器在确定哪个使用哪个profile修饰的bean时,需要依赖两个独立的属性:spring.profiles.default和spring.profile.active,通过这两个属性确定在当前环境中使用哪个bean。如果设置了active,那么它的值就被用来确定哪个profile是激活的,如果没有设置active,那么spring则通过default的值来确定当前环境中的profile。

3.2、条件化bean

条件化bean的意思是指spring在创建一个bean的时候这个bean需要满足一定的条件才会被创建。spring4引入了@Conditional注解来标记条件化bean,它可以作用到带有@Bean注解的方法上,根据@Conditional(条件)中条件的计算结果来判断是否创建这个bean。

@Bean("testBean")

@Condition(TestBeanCondition.class)——TestBeanCondition.java类实现了Condition接口,接口如下所示,在这个接口中,我们通过ConditionContext和AnnotionTypeMetadata来判断该bean是否满足创建条件。

public interface Condition {
   boolean matches(ConditionContext ctxt, AnnotionTypeMetadata metadata);
}

ConditionContext的使用方法:


AnnotionTypeMetadata可以让我们检查带有@Bean注解的方法上还有什么其他的注解。


@Profile注解本身也是借助了@Condition来实现的,实现如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional({ProfileCondition.class})//通过profileCondition中的matches方法的返回值来判断该bean是否需要判断
public @interface Profile {
    String[] value();
}

class ProfileCondition implements Condition {
    ProfileCondition() {
    }

    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        if (context.getEnvironment() != null) {//取得当前spring的环境变量
            MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());//获取所修饰方法上的注解的属性
            if (attrs != null) {
                Iterator var4 = ((List)attrs.get("value")).iterator();

                Object value;
                do {
                    if (!var4.hasNext()) {
                        return false;
                    }

                    value = var4.next();
                } while(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));

                return true;
            }
        }

        return true;
    }
}

 

3.3、处理自动装配的歧义性

问题描述:spring在进行依赖注入时,根据接口类型注入bean时可能会遇到多个满足条件的bean,那么这时候spring就无法判断应该注入哪个满足条件的bean了,只好宣布失败并抛出异常NoUniqueBeanDefinitionException。

问题解决:我们可以将可选bean中的某一个设置为首选(primary)bean,或者使用限定符(qualifier)来帮助spring缩小可选bean的范围。

1、在声明一个bean的时候使用@Primary注解将其声明首选bean,那么在注入的时候这个bean就会从多个bean中优先挑选出来注入。

2、使用@Primary无法将可选方案范围限定到唯一一个无歧义性的选项中,当首选bean的数量超过一个时,spring还是不能判断选择哪个bean;这时,我们应该使用限定符的方式来缩小范围,限定符可以将可选bean缩小范围并最终能达到一个bean,使用如下:

@Qualifier("限定词")——使用在需要注入的地方,只有当注入的bean的限定词等于该限定词时,才会将该bean注入。

@Qualifier("限定词")——使用在bean上,表明该bean的限定词


如果以上两种方式解决不了你的需求(spring不允许一个方法上使用多个相同的注解)的话,我们则需要使用自定义限定注解来满足需求。自定义注解实现如下:

/**
 * Create by 康茜 on 2018/6/1.
 * 自定义限定注解,表示在原来注解的基础上,增加了cold、worm属性
 */
@Qualifier
public @interface attrCold {}

@Qualifier
public @interface attrWorm {}

这样我们就可以通过自定义注解来标记bean了。

3.4、bean的作用域

spring中所有的自定义bean默认都是以单例创建的。

bean被污染:有的时候你会发现你的一个bean是易变的,他们会保持一些状态,因此对于他的每个使用者来说需要记录这些状态,但是所有的使用者共用一个bean,这样肯定会乱套,因此我们需要对每个使用者都提供一个这样的bean。spring定义了多种作用域,可以根据这些作用域创建bean,如下所示:

使用会话和请求作用域:

分析:在web应用中,如果能实例化在会话和请求范围内共享bean,那将是非常有价值的。例如,在典型的电子商务应用中,我们可能会有一个bean代表用户的购物车。如果购物车是单例的话,那么将会导致所有用户都向同一个购物车中添加商品;另一方面,如果购物车是原型多例的话,那么用户在应用中一个地方添加商品,在另一个地方想要使用该bean就不可用了。



3.5、运行时值注入

我们讨论依赖注入的时候,通常所说的是将一个bean的引用注入到另一个bean中,通常指将一个对象与另一个对象进行关联。当时bean装配中的另一个方面则是将一个值注入到bean的属性,现在我们来讨论下如何将一个值注入到bean的属性中。

注入外部配置文件中的值

package stu.kx.test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.env.Environment;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * Create by 康茜 on 2018/6/1.
 */
@Configuration
@PropertySource("classpath:/stu/kx/config/database.properties")//声明配置文件位置,并将配置文件中的变量注入到当前环境变量中
public class ExpressiveConfig {
    @Autowired
    Environment environment;//获取当前环境变量

    @Bean
    public DataSource getDataSource() throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(environment.getProperty("db.url"));//通过环境变量获取配置的数据库参数
        dataSource.setDriverClass(environment.getProperty("db.driver"));
        dataSource.setUser(environment.getProperty("db.user"));
        dataSource.setPassword(environment.getProperty("db.password"));
        return dataSource;
    }
}

解析属性占位符——${……}

spring支持将属性定义到外部的属性文件中,并使用占位符将其值插入到spring bean中。若是使用占位符的话,可以将上面的例子改成如下所示:

@Configuration
@PropertySource("classpath:/stu/kx/config/database.properties")
public class ExpressiveConfig {
    @Autowired
    Environment environment;

    @Bean
    public DataSource getDataSource(@Value("${db.url}") String url, @Value("${db.driver}") String dirver,
                                 @Value("${db.user}") String user, @Value("${db.password}") String password) 
                                 throws PropertyVetoException {
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl(url);
        dataSource.setDriverClass(dirver);
        dataSource.setUser(user);
        dataSource.setPassword(password);
        return dataSource;
    }
}
个人理解:在我看来spring在解析这个占位符${……}时所使用的方式还是调用当前系统的Environment来取得变量值,然后再将其覆盖给方法的参数,只不过对程序员隐藏了此操作而已。

总结:

1、spring profile解决了spring bean要跨各种部署环境的通用问题。在运行时,通过将环境相关的bean与当前激活的profile进行匹配,spring能够让相同的部署单元跨多种环境运行,而不需要进行重新构建。

2、通过使用@Conditional注解和spring Condition接口的实现可以为开发人员提供一种强大和灵活的机制,实现条件化创建bean。

3、两种解决自动装配歧义性的方法:首选bean、限定符

4、bean的作用域可以帮助应用更好的运行。

依赖注入能够将组件及其写作的其他组件解耦,与之类似,AOP有助于将应用组件与跨多个组件的任务进行解耦。

猜你喜欢

转载自blog.csdn.net/kangxidagege/article/details/80532309
今日推荐