2021-07-20:Spring IOC 之 模块装配&条件装配实战

1、模块装配与条件装配如何能同时兼容多种场景的需求?

首先谈到模块装配,我们可以利用自定义注解和 @Import 来完成某个模块所有所需的组件的导入,即将模块需要的所有组件都注入到IOC容器中,例如我们要对数据库模块进行统一装配:

  • 自定义一个注解,例如 @EnableJdbc
  • 接着可以编写一个 Configuration 类,里面包含所有组件的 Bean 实例;
  • 接着在自定义注解处使用 @Import 注解将步骤2中的 Java配置类 注入到IOC容器中。
  • 最后在启动类上加入自定义注解,即可将整个模块进行装配了

条件装配:条件装配可以理解为,我们可根据当前环境信息、配置信息或组件之间的依赖来决定加载哪些组件。

Spring 提供了 @Profile 支持环境变量条件装配、提供了 @Condition 支持自定义条件装配,如组件之间的依赖、组件依赖配置、组件依赖环境等,更加丰富。

那么模块装配和条件装配的混合使用必定能支持非常多的场景。

2、如何理解 SPI ?JDK 原生的 SPI 与 Spring 的 SPI 有什么区别?

SPI 全程叫 Service Provider Interface 服务提供接口,它可以通过一个指定的接口 / 抽象类,寻找到预先配置好的实现类(并创建实现类对象)。

jdk1.6 中有 SPI 的具体实现,SpringFramework 3.2 也引入了 SPI 的实现,而且比 jdk 的实现更加强大,下面我们分别来讲解这两种 SPI 的实现。

JDK 原生 SPI 和 Spring SPI 的区别:

  • JDK SPI 扫描的路径是:META-INF/services/ 下的文件,文件名为接口/抽象类的全路径;而 Spring 的扫描路径是:META-INF 下的所有名为 spring.factories 的文件
  • JDK SPI 文件内容为实现类全路径、支持多个换行、文件名和文件内容需是继承或实现关系;而 Spring SPI 的文件内容为 key/value 对,key 支持注解、接口、类,value 支持为全路径名,key 和 value 不需是继承或实现关系。
  • JDK 的 SPI 实现类为 ServiceLoader,支持根据接口/抽象类获取 Iterator,然后遍历获取所有实现类实例(load);而 Spring SPI 的实现类为 SpringFactoriesLoader,支持根据注解、接口、类获取全路径名列表(loadFactoryNames)或实例列表(loadFactories)。

3、模块装配&条件装配综合例子

代码github地址:code demo

目标:

  • 支持数据库链接的模块装配
  • 支持DataSource的条件装配
  • 支持配置外部化

3.1 依赖

我们这里用 MySQL 做例子

<!-- db -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.20</version>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.1.22</version>
</dependency>
<dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.7</version>
</dependency>

3.2 自定义注解:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(JdbcBeanDefinitionRegistryPostProcessor.class)
@PropertySource("enablejdbc/jdbc.properties")
public @interface EnableJdbc {
}

我们可以看到,我们在自定义注解中利用 @Import 注入 JdbcBeanDefinitionRegistryPostProcessor 到容器中,并且利用 @PropertySource 注解读取外部配置文件 enablejdbc/jdbc.properties。

3.3 文件

spring.factories:

com.github.howinfun.demo.ioc.conditon_demo.version3.EnableJdbc=\
  com.mysql.cj.jdbc.Driver,\
  oracle.jdbc.driver.OracleDriver,\
  org.h2.Driver

jdbc.properties:

jdbc.url=jdbc:mysql://localhost:3306/test?characterEncoding=utf8
jdbc.username=root
jdbc.password=123456

3.4 动态注册 BeanDefinition

逻辑:

  1. 我们利用 Spring 提供的 SPI 机制,读取 spring.factories 文件的支持的数据库Driver全路径名
  2. 利用 Class.forName 来判断是否引入了 Driver 的依赖
  3. 如果引入了对应的依赖,注册 DataSource 对应的 BeanDefinition 到 IOC 容器中
  4. 利用 @PropertySource 和 Environment 支持读取外部配置文件
/**
 * 动态注入数据库链接
 * @author winfun
 * @date 2021/7/19 8:53 上午
 **/
public class JdbcBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {

    private Environment environment;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        // 利用Spring的SPI读取支持的数据库的Driver,这里我们利用的是 loadFactoryNames 方法来读取 spring.factories 的配置
        List<String> driverClassNames = SpringFactoriesLoader.loadFactoryNames(EnableJdbc.class, this.getClass().getClassLoader());
        if (!CollectionUtils.isEmpty(driverClassNames)){
            AtomicReference<String> finalDriverClassName = new AtomicReference<>();
            driverClassNames.forEach(driverClassName ->{
                try {
                    // 判断是否引入了对应的 Driver
                    Class.forName(driverClassName);
                    finalDriverClassName.set(driverClassName);
                }catch (Exception e){
                    System.out.println("不支持:"+driverClassName);
                }
            });
            BeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(DruidDataSource.class)
                    .addPropertyValue("url",environment.getProperty("jdbc.url"))
                    .addPropertyValue("username",environment.getProperty("jdbc.username"))
                    .addPropertyValue("password",environment.getProperty("jdbc.password"))
                    .addPropertyValue("driverClassName",finalDriverClassName.get())
                    .getBeanDefinition();
            registry.registerBeanDefinition("dataSource",beanDefinition);
        }
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }

    /**
     * Set the {@code Environment} that this component runs in.
     * @param environment
     */
    @Override
    public void setEnvironment(Environment environment) {
        this.environment = environment;
    }
}

3.5 启动类

最后我们在启动类上加上自定义注解 @EnableJdbc 即可

@EnableJdbc
@Configuration
public class ApplicationV3 {

    public static void main(String[] args) throws SQLException {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(ApplicationV3.class);
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        DataSource mysqlDataSource = applicationContext.getBean(DataSource.class);
        query(mysqlDataSource).forEach(System.out::println);

    }

    private static List<Object> query(DataSource dataSource) throws SQLException {
        QueryRunner queryRunner = new QueryRunner();
        return queryRunner.execute(dataSource.getConnection(),"select * from user", new BeanListHandler(Object.class));
    }
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值