【Spring Boot】(17)、Spring Boot数据源属性注入与创建

一、 前言

    在Spring Boot中常常会用到配置文件,最常见的莫过于数据源,基本上都使用配置文件的方式进行配置了,即在application.properties/yml中配置连接数据库的基本信息。

#driver配置不需要配置,Spring Boot会从配置的url中试图获取driver名
spring.datasourc.driver=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql:///springboot
spring.datasource.username=root
spring.datasource.password=admin

具体在DataSourceBuilder类的build()方法中。

DataSourceBuilder#build():
public DataSource build() {
   Class<? extends DataSource> type = getType();
   DataSource result = BeanUtils.instantiate(type);

   //试图尝试获取driver名
   maybeGetDriverClassName();
   bind(result);
   return result;
}
DataSourceBuilder#maybeGetDriverClassName():
private void maybeGetDriverClassName() {
   
if (!this.properties.containsKey("driverClassName")
       &&
this.properties.containsKey("url")) {
       String url =
this.properties.get("url");
       
//从url中获取driver
       String driverClass = DatabaseDriver.fromJdbcUrl(url).getDriverClassName();
       
this.properties.put("driverClassName", driverClass);
   }
}
DatabaseDriver#fromJdbcUrl():

DatabaseDriver是个Enum枚举类,列举了所有数据库的驱动。

private static DatabaseDriver fromJdbcUrl(String url) {
   if (StringUtils.hasLength(url)) {
       Assert.isTrue(url.startsWith("jdbc"), "URL must start with 'jdbc'");
       String urlWithoutPrefix = url.substring("jdbc".length()).toLowerCase();

       //values()返回Spring Boot配置的所有数据库驱动
       for (DatabaseDriver driver : values()) {
           for (String urlPrefix : driver.getUrlPrefixes()) {
               String prefix = ":" + urlPrefix + ":";
               if (driver != UNKNOWN && urlWithoutPrefix.startsWith(prefix)) {
                   return driver;
               }
           }
       }
   }
   return UNKNOWN;
}
DatabaseDriver:
public enum DatabaseDriver {

   UNKNOWN(null, null),

   DERBY("Apache Derby", "org.apache.derby.jdbc.EmbeddedDriver",
           "org.apache.derby.jdbc.EmbeddedXADataSource",
           "SELECT 1 FROM SYSIBM.SYSDUMMY1"),

   H2("H2", "org.h2.Driver", "org.h2.jdbcx.JdbcDataSource", "SELECT 1"),

   HSQLDB("HSQL Database Engine", "org.hsqldb.jdbc.JDBCDriver",
           "org.hsqldb.jdbc.pool.JDBCXADataSource",
           "SELECT COUNT(*) FROM INFORMATION_SCHEMA.SYSTEM_USERS"),

   SQLITE("SQLite", "org.sqlite.JDBC"),

   MYSQL("MySQL", "com.mysql.jdbc.Driver",
           "com.mysql.jdbc.jdbc2.optional.MysqlXADataSource", "SELECT 1"),

   MARIADB("MySQL", "org.mariadb.jdbc.Driver", "org.mariadb.jdbc.MariaDbDataSource",
           "SELECT 1") {

       //other code...
   },

   GAE(null, "com.google.appengine.api.rdbms.AppEngineDriver"),


   ORACLE("Oracle", "oracle.jdbc.OracleDriver",
           "oracle.jdbc.xa.client.OracleXADataSource", "SELECT 'Hello' from DUAL"),

   POSTGRESQL("PostgreSQL", "org.postgresql.Driver", "org.postgresql.xa.PGXADataSource",
           "SELECT 1"),

   JTDS(null, "net.sourceforge.jtds.jdbc.Driver"),

   SQLSERVER("Microsoft SQL Server", "com.microsoft.sqlserver.jdbc.SQLServerDriver",
           "com.microsoft.sqlserver.jdbc.SQLServerXADataSource", "SELECT 1") {

       //other code...
   },

   FIREBIRD("Firebird", "org.firebirdsql.jdbc.FBDriver",
           "org.firebirdsql.ds.FBXADataSource",
           "SELECT 1 FROM RDB$DATABASE") {

       //other code...
   },

   DB2("DB2", "com.ibm.db2.jcc.DB2Driver", "com.ibm.db2.jcc.DB2XADataSource",
           "SELECT 1 FROM SYSIBM.SYSDUMMY1") {

       //other code...
   },

   DB2_AS400("DB2 UDB for AS/400", "com.ibm.as400.access.AS400JDBCDriver",
           "com.ibm.as400.access.AS400JDBCXADataSource",
           "SELECT 1 FROM SYSIBM.SYSDUMMY1") {

       //other code...
   },

   TERADATA("Teradata", "com.teradata.jdbc.TeraDriver"),

   INFORMIX("Informix Dynamic Server", "com.informix.jdbc.IfxDriver", null,
           "select count(*) from systables") {

       //other code...
   };

   //other code...
}


二、属性注入与Bean创建

    在【Spring Boot】(4)、配置文件值注入文中简单地讲述了配置文件中属性值的注入,本文重新讲述几种属性注入的方法:

  • @Value注解

  • Environment环境变量

  • @ConfigurationProperties@PropertySource注解

    • 注入全局配置

    • 注入非全局(即外部)配置

    在讲述各种属性注入方式的同时,顺便使用该注入方式进行Bean的创建,本文以创建DataSource数据源为例。

2.1、@Value注解

【Spring Boot】(4)、配置文件值注入文中也讲了使用@Value注解的优缺点。

#dbcp2.properties
dbcp2.driver=com.mysql.jdbc.Driver
dbcp2.url=jdbc:mysql:///springboot
dbcp2.username=root
dbcp2.password=admin


@Configuration
@PropertySource("classpath:dbcp2.properties")//如果配置信息不在全局配置文件application.properties/yml中,则需要显式加载外部properties
public class Dbcp2Config {

   @Value("${dbcp2.driver}")
   private String driver;

   @Value("${dbcp2.url}")
   private String url;

   @Value("${dbcp2.username}")
   private String username;

   @Value("${dbcp2.password}")
   private String password;

   //...
}

注意点:如果dbcp2的配置放置在外部配置文件中,必须显式地加载外部配置文件;否则的话,使用@Value就会提示 java.lang.IllegalArgumentException: Could not resolve placeholder 'dbcp2.driver' in value "${dbcp2.driver}" 的异常信息。

    最后创建dbcp2的DataSource的时候,可以直接使用最基本的四个参数值就可以完成Bean的创建。

@Configuration
@PropertySource("classpath:dbcp2.properties")//如果配置信息不在全局配置文件application.properties/yml中,则需要显式加载外部properties
public class Dbcp2Config {

   @Value("${dbcp2.driver}")
   private String driver;

   @Value("${dbcp2.url}")
   private String url;

   @Value("${dbcp2.username}")
   private String username;

   @Value("${dbcp2.password}")
   private String password;

   @Bean("dbcp2")
   public DataSource dataSource(){
       BasicDataSource dataSource = new BasicDataSource();
       System.out.println("@Value ===================:" + driver);

       dataSource.setDriverClassName(driver);
       dataSource.setUrl(url);
       dataSource.setUsername(username);
       dataSource.setPassword(password);

       return dataSource;
   }
}
2.2、Environment环境变量

其实使用Environment变量和使用@Value注解类似,不同的是:@Value是直接将值注入到具体的属性上,而Environment是把所有的配置项添加到环境变量中,然后在取值的时候使用getProperty(key)方法即可。

#c3p0.properties
c3p0.driver=com.mysql.jdbc.Driver
c3p0.url=jdbc:mysql:///springboot
c3p0.username=root
c3p0.password=admin


@Configuration
@PropertySource("classpath:c3p0.properties")//引入外部配置文件
public class C3p0Config {

   @Autowired
   private Environment env;

}

    注意点:使用Environment环境变量和使用@Value注解一样,对于外部配置文件需要显式加载。

@Configuration
@PropertySource("classpath:c3p0.properties")//引入外部配置文件
public class C3p0Config {

   @Autowired
   private Environment env;

   @Bean("c3p0")
   public DataSource dataSource() throws PropertyVetoException {
       ComboPooledDataSource dataSource = new ComboPooledDataSource();
       System.out.println("env ==========:" + env.getProperty("c3p0.driver"));

       dataSource.setDriverClass(env.getProperty("c3p0.driver"));
       dataSource.setJdbcUrl(env.getProperty("c3p0.url"));
       dataSource.setUser(env.getProperty("c3p0.username"));
       dataSource.setPassword(env.getProperty("c3p0.password"));
       return dataSource;
   }
}

EnvironmentgetProperty()有个重载的方法:

String getProperty(String key);
String getProperty(String key, String defaultValue);

如果使用getProperty(key),如果key不存在的时候,方法返回null,而不会报异常,这跟@Value有些区别。如果使用getProperty(key, defaultValue),见名思意,即当key不存在的时候,可以使用指定的默认值代替。

2.3、@ConfigurationProperties和@PropertySource注解
2.3.1、注入全局配置属性
#application.properties
druid.driver-class-name=com.mysql.jdbc.Driver
druid.url=jdbc:mysql:///springboot
druid.username=root
druid.password=admin


@ConfigurationProperties(prefix = "druid")
public class DruidProperties {

   private String driverClassName;
   private String url;
   private String username;
   private String password;

   //getter和setter
}

@Configuration
@EnableConfigurationProperties(DruidProperties.class)
public class DruidConfig {

   private DruidProperties properties;

   public DruidConfig(DruidProperties properties){
       this.properties = properties;
   }
}

@EnableConfigurationProperties启动配置功能,将对应的DruidProperties配置类与全局配置文件(默认)中的以druid为前缀的配置项进行绑定并注入,并将DruidProperties配置类注入到Spring的IOC环境中。这个时候就可以创建一个DruidDataSource的Bean。

@Configuration
@EnableConfigurationProperties(DruidProperties.class)
public class DruidConfig {

   private DruidProperties properties;

   public DruidConfig(DruidProperties properties){
       this.properties = properties;
   }

   @Bean("druid")
   public DruidDataSource dataSource(){
       System.out.println("properties =====================:" + properties.getDriverClassName());

       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setDriverClassName(properties.getDriverClassName());
       dataSource.setUrl(properties.getUrl());
       dataSource.setUsername(properties.getUsername());
       dataSource.setPassword(properties.getPassword());
       return dataSource;
   }
}

此时创建的DataSource中包含了基本的四个属性值,就能执行数据库操作了。

2.3.2、注入外部配置属性

还是同全局配置中的属性值一样,只是将其提取到druid.properties中了。

#druid.properties
druid.driver-class-name=com.mysql.jdbc.Driver
druid.url=jdbc:mysql:///springboot
druid.username=root
druid.password=admin

DruidProperties中加入 @PropertySource("classpath:druid.properties") 一行,提示Spring Boot从外部druid.properties中读取属性值。

@ConfigurationProperties(prefix = "druid")
@PropertySource("classpath:druid.properties")
public class DruidProperties {

   private String driverClassName;
   private String url;
   private String username;
   private String password;

   //getter和setter
}

DruidConfig.java同上小节的一样,保持不变。

    在运行的时候,却会发现DruidProperties并未注入,即数据源的四个属性值都未正常注入。在调试的时候,发现DruidProperties的属性setter方法并未进入。

    其实注入外部配置属性的时候,需要手动将该类注册为组件,即使用@Component或者@Configuration注解才行。与此同时,由于DruidConfig类上有个 @EnableConfigurationProperties(DruidProperties.class) 注解,既然已经手动在DruidProperties上添加了@Component或者@Configuration注解使其成为组件,那么就无需再在DruidConfig上通过@EnableConfigurationProperties(DruidProperties.class)再将DruidProperties注册为组件,否则会在自动注入使用DruidProperties的时候会找到两个Bean,导致出现org.springframework.beans.factory.NoUniqueBeanDefinitionException唯一性异常。

代码如下:

@Component
@ConfigurationProperties(prefix = "druid")
@PropertySource("classpath:druid.properties")
public class DruidProperties {

   private String driverClassName;
   private String url;
   private String username;
   private String password;

   //other code...
}


@Configuration
@EnableConfigurationProperties//(DruidProperties.class)
public class DruidConfig {

   private DruidProperties properties;

   public DruidConfig(DruidProperties properties){
       this.properties = properties;
   }

   @Bean("druid")
   public DruidDataSource dataSource(){
       System.out.println("properties =====================:" + properties.getDriverClassName());

       DruidDataSource dataSource = new DruidDataSource();
       dataSource.setDriverClassName(properties.getDriverClassName());
       dataSource.setUrl(properties.getUrl());
       dataSource.setUsername(properties.getUsername());
       dataSource.setPassword(properties.getPassword());
       return dataSource;
   }
}


三、疑问?

在第2.3.1小节中讲述到,使用@EnableConfigurationProperties会将其属性value对应的配置类与配置文件绑定,并将其注入到Spring IOC容器中。而对于第2.3.2小节中,既然有这个功能,为什么还要在配置类上加上组件注解吗?

其实博主在调试的时候,发现Spring IOC容器中确实有配置类的Bean,但是就是没办法进入到属性注入的setter方法中。在博主历经N次调试后才发现,在使用@PropertySource(s)注解引入外部配置文件的时候,如果不在配置类上手动添加组件注解的话,该配置文件就无法加载到Spring环境中,导致想要给配置类属性进行注入的时候就不能成功执行,只能是null了。

下图是不使用注解时,环境中的数据:

如果使用注解的话,环境中就多了外部配置文件信息:


====================打个广告,欢迎关注====================

QQ:
412425870
微信公众号:Cay课堂

csdn博客:
http://blog.csdn.net/caychen
码云:
https://gitee.com/caychen/
github:
https://github.com/caychen

点击群号或者扫描二维码即可加入QQ群:

328243383(1群)




点击群号或者扫描二维码即可加入QQ群:

180479701(2群)




  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值