阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

禁用自动配置

55次阅读
没有评论

共计 5064 个字符,预计需要花费 13 分钟才能阅读完成。

Spring Boot 大量使用自动配置和默认配置,极大地减少了代码,通常只需要加上几个注解,并按照默认规则设定一下必要的配置即可。例如,配置 JDBC,默认情况下,只需要配置一个spring.datasource

spring:
  datasource:
    url: jdbc:hsqldb:file:testdb
    username: sa
    password:
    dirver-class-name: org.hsqldb.jdbc.JDBCDriver

Spring Boot 就会自动创建出DataSourceJdbcTemplateDataSourceTransactionManager,非常方便。

但是,有时候,我们又必须要禁用某些自动配置。例如,系统有主从两个数据库,而 Spring Boot 的自动配置只能配一个,怎么办?

这个时候,针对 DataSource 相关的自动配置,就必须关掉。我们需要用 exclude 指定需要关掉的自动配置:

@SpringBootApplication
// 启动自动配置,但排除指定的自动配置:
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
public class Application {...}

现在,Spring Boot 不再给我们自动创建 DataSourceJdbcTemplateDataSourceTransactionManager了,要实现主从数据库支持,怎么办?

让我们一步一步开始编写支持主从数据库的功能。首先,我们需要把主从数据库配置写到 application.yml 中,仍然按照 Spring Boot 默认的格式写,但 datasource 改为 datasource-masterdatasource-slave

spring:
  datasource-master:
    url: jdbc:hsqldb:file:testdb
    username: sa
    password:
    dirver-class-name: org.hsqldb.jdbc.JDBCDriver
  datasource-slave:
    url: jdbc:hsqldb:file:testdb
    username: sa
    password:
    dirver-class-name: org.hsqldb.jdbc.JDBCDriver

注意到两个数据库实际上是同一个库。如果使用 MySQL,可以创建一个只读用户,作为 datasource-slave 的用户来模拟一个从库。

下一步,我们分别创建两个 HikariCP 的DataSource

public class MasterDataSourceConfiguration {@Bean("masterDataSourceProperties")
    @ConfigurationProperties("spring.datasource-master")
    DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}

    @Bean("masterDataSource")
    DataSource dataSource(@Autowired @Qualifier("masterDataSourceProperties") DataSourceProperties props) {return props.initializeDataSourceBuilder().build();
    }
}

public class SlaveDataSourceConfiguration {@Bean("slaveDataSourceProperties")
    @ConfigurationProperties("spring.datasource-slave")
    DataSourceProperties dataSourceProperties() {return new DataSourceProperties();}

    @Bean("slaveDataSource")
    DataSource dataSource(@Autowired @Qualifier("slaveDataSourceProperties") DataSourceProperties props) {return props.initializeDataSourceBuilder().build();
    }
}

注意到上述 class 并未添加 @Configuration@Component,要使之生效,可以使用 @Import 导入:

@SpringBootApplication
@EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
@Import({MasterDataSourceConfiguration.class, SlaveDataSourceConfiguration.class})
public class Application {...}

此外,上述两个 DataSource 的 Bean 名称分别为 masterDataSourceslaveDataSource,我们还需要一个最终的 @Primary 标注的DataSource,它采用 Spring 提供的AbstractRoutingDataSource,代码实现如下:

class RoutingDataSource extends AbstractRoutingDataSource {@Override
    protected Object determineCurrentLookupKey() {// 从 ThreadLocal 中取出 key:
        return RoutingDataSourceContext.getDataSourceRoutingKey();}
}

RoutingDataSource本身并不是真正的 DataSource,它通过 Map 关联一组DataSource,下面的代码创建了包含两个DataSourceRoutingDataSource,关联的 key 分别为 masterDataSourceslaveDataSource

public class RoutingDataSourceConfiguration {@Primary
    @Bean
    DataSource dataSource(@Autowired @Qualifier("masterDataSource") DataSource masterDataSource,
            @Autowired @Qualifier("slaveDataSource") DataSource slaveDataSource) {var ds = new RoutingDataSource();
        // 关联两个 DataSource:
        ds.setTargetDataSources(Map.of("masterDataSource", masterDataSource,
                "slaveDataSource", slaveDataSource));
        // 默认使用 masterDataSource:
        ds.setDefaultTargetDataSource(masterDataSource);
        return ds;
    }

    @Bean
    JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {return new JdbcTemplate(dataSource);
    }

    @Bean
    DataSourceTransactionManager dataSourceTransactionManager(@Autowired DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
    }
}

仍然需要自己创建 JdbcTemplatePlatformTransactionManager,注入的是标记为 @PrimaryRoutingDataSource

这样,我们通过如下的代码就可以切换 RoutingDataSource 底层使用的真正的DataSource

RoutingDataSourceContext.setDataSourceRoutingKey("slaveDataSource");
jdbcTemplate.query(...);

只不过写代码切换 DataSource 即麻烦又容易出错,更好的方式是通过注解配合 AOP 实现自动切换,这样,客户端代码实现如下:

@Controller
public class UserController {@RoutingWithSlave // <-- 指示在此方法中使用 slave 数据库
	@GetMapping("/profile")
	public ModelAndView profile(HttpSession session) {...}
}

实现上述功能需要编写一个 @RoutingWithSlave 注解,一个 AOP 织入和一个 ThreadLocal 来保存 key。由于代码比较简单,这里我们不再详述。

如果我们想要确认是否真的切换了 DataSource,可以覆写determineTargetDataSource() 方法并打印出 DataSource 的名称:

class RoutingDataSource extends AbstractRoutingDataSource {
    ...

    @Override
    protected DataSource determineTargetDataSource() {DataSource ds = super.determineTargetDataSource();
        logger.info("determin target datasource: {}", ds);
        return ds;
    }
}

访问不同的 URL,可以在日志中看到两个 DataSource,分别是HikariPool-1hikariPool-2

2020-06-14 17:55:21.676  INFO 91561 --- [nio-8080-exec-7] c.i.learnjava.config.RoutingDataSource   : determin target datasource: HikariDataSource (HikariPool-1)
2020-06-14 17:57:08.992  INFO 91561 --- [io-8080-exec-10] c.i.learnjava.config.RoutingDataSource   : determin target datasource: HikariDataSource (HikariPool-2)

我们用一个图来表示创建的 DataSource 以及相关 Bean 的关系:

┌────────────────────┐       ┌──────────────────┐
│@Primary            │<──────│   JdbcTemplate   │
│RoutingDataSource   │       └──────────────────┘
│ ┌────────────────┐ │       ┌──────────────────┐
│ │MasterDataSource│ │<──────│DataSource        │
│ └────────────────┘ │       │TransactionManager│
│ ┌────────────────┐ │       └──────────────────┘
│ │SlaveDataSource │ │
│ └────────────────┘ │
└────────────────────┘

注意到 DataSourceTransactionManagerJdbcTemplate引用的都是 RoutingDataSource,所以,这种设计的一个限制就是:在一个请求中,一旦切换了内部数据源,在同一个事务中,不能再切到另一个,否则,DataSourceTransactionManagerJdbcTemplate操作的就不是同一个数据库连接。

练习

禁用 DataSourceAutoConfiguration 并配置多数据源。

下载练习

小结

可以通过 @EnableAutoConfiguration(exclude = {...}) 指定禁用的自动配置;

可以通过 @Import({...}) 导入自定义配置。

正文完
星哥玩云-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2024-08-05发表,共计5064字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中