spring boot自动配置和自定义配置

spring boot自动配置和自定义配置

本文介绍spring boot的自动配置原理,redis自动配置示例,如何配置多数据源。

一、Spring Boot 自动配置分析

spring Boot很大的改变就是xml配置改成配置类。并且为很多常用的中间件引入了自动配置,用户只需要依赖对应jar包并在properties种配置即可使用。

我们为spring boot项目添加一个启动类,使用@SpringBootApplication注解,即可右键启动项目。先看看这个注解的源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class))
public @interface SpringBootApplication {
    
        ...
}

继续找到@EnableAutoConfiguration源码:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};
}

对于这段代码,有几个需要关注的地方:
1. @Import:功能等效于xml配置文件中的,xml中import引入的xml文件也将被解析,@import主要用来引入@Configuration注解的类。注意,如果要引入的就是xml配置问题,请使用@ImportResource。
2. @AutoConfigurationPackage:存储自动配置类的包通过@Import(EnableAutoConfigurationImportSelector.class)查看EnableAutoConfigurationImportSelector类的部分源码:

/**
*返回自动配置类的名字
*/
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
		AnnotationAttributes attributes) {
	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(
			getSpringFactoriesLoaderFactoryClass(), getBeanClassLoader());
	Assert.notEmpty(configurations,
			"No auto configuration classes found in META-INF/spring.factories. If you "
					+ "are using a custom packaging, make sure that file is correct.");
	return configurations;
}

自动配置类位于 META-INF/spring.factories文件中,内容如下(只列出部分):

# Initializers
org.springframework.boot.autoconfigure.data.neo4j.Neo4jDataAutoConfiguration,\
org.springframework.boot.autoconfigure.data.neo4j.Neo4jRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.solr.SolrRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,\
org.springframework.boot.autoconfigure.data.redis.RedisRepositoriesAutoConfiguration,\
org.springframework.boot.autoconfigure.data.rest.RepositoryRestMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.data.web.SpringDataWebAutoConfiguration,\
org.springframework.boot.autoconfigure.elasticsearch.jest.JestAutoConfiguration,\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerAutoConfiguration,\
org.springframework.boot.autoconfigure.gson.GsonAutoConfiguration,\
org.springframework.boot.autoconfigure.h2.H2ConsoleAutoConfiguration,\
org.springframework.boot.autoconfigure.hateoas.HypermediaAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastAutoConfiguration,\
org.springframework.boot.autoconfigure.hazelcast.HazelcastJpaDependencyAutoConfiguration,\
org.springframework.boot.autoconfigure.info.ProjectInfoAutoConfiguration,\
org.springframework.boot.autoconfigure.integration.IntegrationAutoConfiguration,\
org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JdbcTemplateAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.JndiDataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.XADataSourceAutoConfiguration,\
org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JmsAutoConfiguration,\
org.springframework.boot.autoconfigure.jmx.JmxAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.JndiConnectionFactoryAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.activemq.ActiveMQAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.artemis.ArtemisAutoConfiguration,\
org.springframework.boot.autoconfigure.jms.hornetq.HornetQAutoConfiguration,\
org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration,\
org.springframework.boot.autoconfigure.groovy.template.GroovyTemplateAutoConfiguration

以redis 为例子,在文件搜redis,发现配置类为:org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration,根据它找到源码:

@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(RedisProperties.class)
public class RedisAutoConfiguration {
    
        .....
}

解读:
1. @Configuration:表示声明一个或者多个方法,生成spring容器的bean。
2. @ConditionalOnClass :注解参数对应的类(JedisConnection,RedisOperations,Jedis)必须存在,否则不解析该注解修饰的配置类。也就是说,不引入redis客户端相关包,自动配置不会生效。
3. @EnableConfigurationProperties:让RedisAutoConfiguration以RedisProperties所定义的配置标准读取配置。

先看下RedisProperties部分代码:

@ConfigurationProperties(prefix = "spring.redis")
public class RedisProperties {
    //application.properties中 spring.redis.port 的值会赋给port属性。
	private int database = 0;
	private String host = "localhost";
	private String password;
	private int port = 6379;

	@Configuration
	protected static class RedisConfiguration {

		@Bean
		@ConditionalOnMissingBean(name = "redisTemplate")
		public RedisTemplate<Object, Object> redisTemplate(
				RedisConnectionFactory redisConnectionFactory)
						throws UnknownHostException {
			RedisTemplate<Object, Object> template = new RedisTemplate<Object, Object>();
			template.setConnectionFactory(redisConnectionFactory);
			return template;
		}

		@Bean
		@ConditionalOnMissingBean(StringRedisTemplate.class)
		public StringRedisTemplate stringRedisTemplate(
				RedisConnectionFactory redisConnectionFactory)
						throws UnknownHostException {
			StringRedisTemplate template = new StringRedisTemplate();
			template.setConnectionFactory(redisConnectionFactory);
			return template;
		}
}

解读:
1. 获取前缀为spring.redis的配置,如果没有配置,host 默认localhost,端口默认6379。
2. @ConditionalOnMissingBean(StringRedisTemplate.class): 如果StringRedisTemplate这个bean不存在配置才生效,意思就是说如果我们自己配置了StringRedisTemplate,自动配置就不会生效。

二、spring boot redis 自动配置使用示例

2.1 maven 添加依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 添加配置

这儿使用redis伪集群,在properties中添加如下配置:

spring.redis.cluster.nodes=192.168.99.100:7001,192.168.99.100:7002,192.168.99.100:7003,192.168.99.100:7004,192.168.99.100:7005,192.168.99.100:7006
spring.redis.pool.max-active=8
spring.redis.pool.max-idle=8
spring.redis.pool.max-wait=-1
spring.redis.pool.min-idle=0
spring.redis.timeout=0

2.3 使用

@SpringBootApplication
public class UserApplication implements CommandLineRunner {
    @Autowired
    private StringRedisTemplate redisTemplate;

    public static void main(String[] args) {
        SpringApplication.run(UserApplication.class, args);
    }

    @Override
    public void run(String... args) throws Exception {
        ValueOperations<String, String> opsForValue = redisTemplate.opsForValue();
        String key = "name";
        opsForValue.set(key, "erdaoya");
        System.out.println("redis key " + key + ", value=" + opsForValue.get(key));
    }
}

说明:
StringRedisTemplate就相当于RedisTemplate<String,String>,如果只是简单的key-value 字符串操作,推荐用前者。后者提供更丰富的功能,如对java对象序列化存储。

三、Spring Boot + Jdbc + Mybatis 多数据源自定义配置

3.1 添加依赖

  <!--mysql-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>${druid.version}</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>${mybatis-spring-boot-starter.version}</version>
</dependency>

3.2 编写配置类

@Configuration
public class DataSourceConfig {

    @Primary
    @Bean(name = "primaryDataSource")
    @Qualifier("primaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.user.primary")
    public DataSource primaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "secondaryDataSource")
    @Qualifier("secondaryDataSource")
    @ConfigurationProperties(prefix = "spring.datasource.user.secondary")
    public DataSource secondaryDataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "primarySqlSessionFactory")
    @Primary
    public SqlSessionFactory sqlSessionFactory(@Qualifier("primaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
         bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
        return bean.getObject();
    }

    @Bean(name = "primaryTransactionManager")
    @Primary
    public DataSourceTransactionManager transactionManager(@Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "primarySqlSessionTemplate")
    @Primary
    public SqlSessionTemplate sqlSessionTemplate(
            @Qualifier("primarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }

    @Bean(name = "secondarySqlSessionFactory")
    public SqlSessionFactory secondarySqlSessionFactory(@Qualifier("secondaryDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(dataSource);
         bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis/mybatis-config.xml"));
        return bean.getObject();
    }

    @Bean(name = "secondaryTransactionManager")
    public DataSourceTransactionManager secondaryTransactionManager(@Qualifier("secondaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "secondarySqlSessionTemplate")
    public SqlSessionTemplate secondarySqlSessionTemplate(
            @Qualifier("secondarySqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

可以看出,其实配置类跟xml相差无几。

3.3 添加配置

spring.datasource.user.primary.url=jdbc:mysql://192.168.99.100:3306/boot?useUnicode=true&characterEncoding=UTF-8
spring.datasource.user.primary.username=root
spring.datasource.user.primary.password=1234
spring.datasource.user.primary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.user.primary.type=com.alibaba.druid.pool.DruidDataSource

spring.datasource.user.secondary.url=jdbc:mysql://192.168.99.100:3306/boot?useUnicode=true&characterEncoding=UTF-8
spring.datasource.user.secondary.username=root
spring.datasource.user.secondary.password=1234
spring.datasource.user.secondary.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.user.secondary.type=com.alibaba.druid.pool.DruidDataSource

3.4 使用

@Repository
public class UserDao {

    @Autowired
    @Qualifier("primarySqlSessionTemplate")
    private SqlSessionTemplate primarySqlSessionTemplate;

    @Autowired
    @Qualifier("secondarySqlSessionTemplate")
    private SqlSessionTemplate secondarySqlSessionTemplate;

    public User selectUserFromDB1ById(Long id) {
        return primarySqlSessionTemplate.selectOne("selectUserById", id);
    }

    public User selectUserFromDB2ById(Long id) {
        return secondarySqlSessionTemplate.selectOne("selectUserById", id);
    }
}

访问/health监控端口,可以看到2个配置都UP

CONTENTS