自动配置(
auto-configuration
)
一项简化配置的功能,比如在classpath中发现有spring security的jar包,则自动创建相关的bean等starters(
简化依赖
)
这个比较关键,方便spring去集成各类组件,比如redis、mongodb等等。
core(
security、aop
)web(
web、websocket、ws、vaadin、rest、mobile
)template(
freemarker、velocity、groovy templates、thymeleaf
)data(
jdbc、jpa、mongodb、redis、gemfire、solr、elasticsearch
)database(
h2、hsqldb、mysql、postgresql
)social(
facebook、linkedin、twitter
)io(
batch、integration、jms、amqp
)ops(
actuator、remote shell
)
我们只需要引入spring-boot-starter-data-redis包就可以使用redis了,不用关心其他依赖的包
这个包中会把我们需要的其他包一起引入
看spring-data-redis中源码的自定义配置
可以知道,它给redis自动配置了一些默认属性,如:
maxActive = 8
maxIdle = 8
minIdle = 0
maxWait = -1
然后我们在引入了autoconfigure和spring-boot-starter-data-redis后,这些默认配置会自动到redis的连接池配置中。如果需要修改指定值,则在application.yml中配置
spring:
redis:
database: 0
host: 192.168.2.240
password: 123123
port: 6379
pool:
maxActive: 60
maxIdle: 60
maxWait: 3000
application.yml没有配置,则Redis的配置用源码中自动配置的属性,如果application.yml有配置,则会被application.yml中的配置覆盖。比如:没有application.yml,则maxToal=8,有application.yml,则maxActive=60
使用场景:
工作中有接到如下需求:
1.封装apache http client ,让业务开发的组可以开箱即用,不用关心配置。(这个不介绍,类似的)
2.封装redis,让业务开发组开箱即用,不用关心连接池的配置。这里就有个问题了,spring-data-redis已经做了自动配置了,为什么还要再封装,因为spring-data-redis做的自动配置的那些属性,跟我们期待的标准配置太不一样了。
比如,我们期待的默认配置是
private int maxIdle = 60;
private int minIdle = 0;
private int maxActive = 60;
private int maxWait = 3000;
private boolean testOnCreate = false;
private boolean testOnBorrow = false;
private boolean testOnReturn = false;
private boolean testWhileIdle = true;
private long minEvictableIdleTimeMillis = 70000;
private long timeBetweenEvictionRunsMillis = 40000;
private int numTestsPerEvictionRun = -1;
各个业务系统根据实际情况,自己还可以再改。
创建一个项目common-redis-spring-boot
在这个项目中创建两个modules
common-redis-spring-boot-autoconfigure
common-redis-spring-boot-starter
结构如下
这个工程中不要引入扫描相关的注解,如@Autowire, @componet, @Service, @Controller等
这个工程主要定义子模块的需要用到的jar。
pom.xml
4.0.0
com.sid
common-redis-spring-boot
pom
1.0-SNAPSHOT
1.8
1.5.8.RELEASE
UTF-8
1.0-SNAPSHOT
1.0-SNAPSHOT
common-redis-spring-boot-autoconfigure
common-redis-spring-boot-starter
org.springframework.boot
spring-boot-starter-web
${spring-boot.version}
org.springframework.boot
spring-boot-dependencies
${spring-boot.version}
pom
import
org.springframework.boot
spring-boot-starter-data-redis
${spring-boot.version}
com.lee
common-redis-spring-boot-autoconfigure
${common-redis.springboot.verison}
com.sid
common-redis
${common-redis.version}
pom.xml
common-redis-spring-boot
com.sid
1.0-SNAPSHOT
../../common-redis-spring-boot/pom.xml
2.8.10
4.0.0
common-redis-spring-boot-autoconfigure
org.springframework.boot
spring-boot-configuration-processor
true
org.springframework.boot
spring-boot-autoconfigure
true
org.springframework.boot
spring-boot-starter-data-redis
true
org.springframework.boot
spring-boot-starter-web
true
com.sid
common-redis
true
com.alibaba
fastjson
1.2.47
true
org.springframework.boot
spring-boot-starter-test
test
spring-boot-configuration-processor 的作用是编译时生成spring-configuration-metadata.json,在intellij idea中,当配置此jar相关配置属性在application.properties,你可以用ctlr+鼠标左键,IDE会跳转到你配置此属性的类中
MyRedisProperties.java 属性类
自动从配置文件中解析属性并生成对象
package common.redis.spring.boot.autoconfigure.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.List;
/**
* @program: common-redis-spring-boot
* @description:
* @author: Sid
* @date: 2018-11-01 17:18
* @since: 1.0
**/
//注入application.yml中spring.redis开头的属性
/**
* 通过@EnableConfigurationProperties可以将被@ConfigurationProperties注解的类生成一个bean
* */
@ConfigurationProperties(prefix = "spring.redis")
public class MyRedisProperties {
/**
* Database index used by the connection factory.
*/
private int database = 0;
/**
* Redis url, which will overrule host, port and password if set.
*/
private String url;
/**
* Redis server host.
*/
private String host = "localhost";
/**
* Login password of the redis server.
*/
private String password;
/**
* Redis server port.
*/
private int port = 6379;
/**
* Enable SSL.
*/
private boolean ssl;
/**
* Connection timeout in milliseconds.
*/
private int timeout;
private Pool pool = new Pool();
private Sentinel sentinel;
private Cluster cluster;
public int getDatabase() {
return this.database;
}
public void setDatabase(int database) {
this.database = database;
}
public String getUrl() {
return this.url;
}
public void setUrl(String url) {
this.url = url;
}
public String getHost() {
return this.host;
}
public void setHost(String host) {
this.host = host;
}
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
public int getPort() {
return this.port;
}
public void setPort(int port) {
this.port = port;
}
public boolean isSsl() {
return this.ssl;
}
public void setSsl(boolean ssl) {
this.ssl = ssl;
}
public void setTimeout(int timeout) {
this.timeout = timeout;
}
public int getTimeout() {
return this.timeout;
}
public Sentinel getSentinel() {
return this.sentinel;
}
public void setSentinel(Sentinel sentinel) {
this.sentinel = sentinel;
}
public Pool getPool() {
return this.pool;
}
public void setPool(Pool pool) {
this.pool = pool;
}
public Cluster getCluster() {
return this.cluster;
}
public void setCluster(Cluster cluster) {
this.cluster = cluster;
}
/**
* Pool properties.
*/
public static class Pool {
/**
* Max number of "idle" connections in the pool. Use a negative value to indicate
* an unlimited number of idle connections.
*/
private int maxIdle = 60;
/**
* Target for the minimum number of idle connections to maintain in the pool. This
* setting only has an effect if it is positive.
*/
private int minIdle = 0;
/**
* Max number of connections that can be allocated by the pool at a given time.
* Use a negative value for no limit.
*/
private int maxActive = 60;
/**
* Maximum amount of time (in milliseconds) a connection allocation should block
* before throwing an exception when the pool is exhausted. Use a negative value
* to block indefinitely.
*/
private int maxWait = 3000;
private boolean testOnCreate = false;
private boolean testOnBorrow = false;
private boolean testOnReturn = false;
private boolean testWhileIdle = true;
private long minEvictableIdleTimeMillis = 60000;
private long timeBetweenEvictionRunsMillis = 30000;
private int numTestsPerEvictionRun = -1;
public int getMaxIdle() {
return this.maxIdle;
}
public void setMaxIdle(int maxIdle) {
this.maxIdle = maxIdle;
}
public int getMinIdle() {
return this.minIdle;
}
public void setMinIdle(int minIdle) {
this.minIdle = minIdle;
}
public int getMaxActive() {
return this.maxActive;
}
public void setMaxActive(int maxActive) {
this.maxActive = maxActive;
}
public int getMaxWait() {
return this.maxWait;
}
public void setMaxWait(int maxWait) {
this.maxWait = maxWait;
}
public boolean getTestOnCreate() {
return testOnCreate;
}
public void setTestOnCreate(boolean testOnCreate) {
this.testOnCreate = testOnCreate;
}
public boolean getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public boolean getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public boolean getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public long getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(long minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public long getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(long timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public int getNumTestsPerEvictionRun() {
return numTestsPerEvictionRun;
}
public void setNumTestsPerEvictionRun(int numTestsPerEvictionRun) {
this.numTestsPerEvictionRun = numTestsPerEvictionRun;
}
}
/**
* Cluster properties.
*/
public static class Cluster {
/**
* Comma-separated list of "host:port" pairs to bootstrap from. This represents an
* "initial" list of cluster nodes and is required to have at least one entry.
*/
private List nodes;
/**
* Maximum number of redirects to follow when executing commands across the
* cluster.
*/
private Integer maxRedirects;
public List getNodes() {
return this.nodes;
}
public void setNodes(List nodes) {
this.nodes = nodes;
}
public Integer getMaxRedirects() {
return this.maxRedirects;
}
public void setMaxRedirects(Integer maxRedirects) {
this.maxRedirects = maxRedirects;
}
}
/**
* Redis sentinel properties.
*/
public static class Sentinel {
/**
* Name of Redis server.
*/
private String master;
/**
* Comma-separated list of host:port pairs.
*/
private String nodes;
public String getMaster() {
return this.master;
}
public void setMaster(String master) {
this.master = master;
}
public String getNodes() {
return this.nodes;
}
public void setNodes(String nodes) {
this.nodes = nodes;
}
}
}
RedisAutoConfig.java 自动配置类
package common.redis.spring.boot.autoconfigure;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import com.alibaba.fastjson.JSON;
import common.redis.RedisUtil;
import common.redis.spring.boot.autoconfigure.properties.MyRedisProperties;
import common.redis.spring.boot.autoconfigure.properties.MyRedisProperties.Sentinel;
import common.redis.spring.boot.autoconfigure.properties.MyRedisProperties.Cluster;
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPoolConfig;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisClusterConfiguration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisNode;
import org.springframework.data.redis.connection.RedisSentinelConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnection;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import javax.annotation.PostConstruct;
/**
* @program: common-redis-spring-boot
* @description:
* @author: Sid
* @date: 2018-11-01 17:02
* @since: 1.0
**/
@Configuration //定义配置类
@AutoConfigureBefore(RedisAutoConfiguration.class)
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
@EnableConfigurationProperties(MyRedisProperties.class)
public class RedisAutoConfig {
/**
* Redis connection configuration.
*/
@Configuration
@ConditionalOnClass(GenericObjectPool.class)
protected static class RedisConnectionConfiguration {
private final MyRedisProperties properties;
private final RedisSentinelConfiguration sentinelConfiguration;
private final RedisClusterConfiguration clusterConfiguration;
public RedisConnectionConfiguration(MyRedisProperties properties,
ObjectProvider sentinelConfiguration,
ObjectProvider clusterConfiguration) {
this.properties = properties;
this.sentinelConfiguration = sentinelConfiguration.getIfAvailable();
this.clusterConfiguration = clusterConfiguration.getIfAvailable();
}
@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
public JedisConnectionFactory redisConnectionFactory() {
return applyProperties(createJedisConnectionFactory());
}
protected final JedisConnectionFactory applyProperties(JedisConnectionFactory factory) {
configureConnection(factory);
if (this.properties.isSsl()) {
factory.setUseSsl(true);
}
factory.setDatabase(this.properties.getDatabase());
if (this.properties.getTimeout() > 0) {
factory.setTimeout(this.properties.getTimeout());
}
return factory;
}
private void configureConnection(JedisConnectionFactory factory) {
if (StringUtils.hasText(this.properties.getUrl())) {
configureConnectionFromUrl(factory);
}
else {
factory.setHostName(this.properties.getHost());
factory.setPort(this.properties.getPort());
if (this.properties.getPassword() != null) {
factory.setPassword(this.properties.getPassword());
}
}
}
private void configureConnectionFromUrl(JedisConnectionFactory factory) {
String url = this.properties.getUrl();
if (url.startsWith("rediss://")) {
factory.setUseSsl(true);
}
try {
URI uri = new URI(url);
factory.setHostName(uri.getHost());
factory.setPort(uri.getPort());
if (uri.getUserInfo() != null) {
String password = uri.getUserInfo();
int index = password.lastIndexOf(":");
if (index >= 0) {
password = password.substring(index + 1);
}
factory.setPassword(password);
}
}
catch (URISyntaxException ex) {
throw new IllegalArgumentException("Malformed 'spring.redis.url' " + url,
ex);
}
}
protected final RedisSentinelConfiguration getSentinelConfig() {
if (this.sentinelConfiguration != null) {
return this.sentinelConfiguration;
}
Sentinel sentinelProperties = this.properties.getSentinel();
if (sentinelProperties != null) {
RedisSentinelConfiguration config = new RedisSentinelConfiguration();
config.master(sentinelProperties.getMaster());
config.setSentinels(createSentinels(sentinelProperties));
return config;
}
return null;
}
/**
* Create a {@link RedisClusterConfiguration} if necessary.
* @return {@literal null} if no cluster settings are set.
*/
protected final RedisClusterConfiguration getClusterConfiguration() {
if (this.clusterConfiguration != null) {
return this.clusterConfiguration;
}
if (this.properties.getCluster() == null) {
return null;
}
Cluster clusterProperties = this.properties.getCluster();
RedisClusterConfiguration config = new RedisClusterConfiguration(
clusterProperties.getNodes());
if (clusterProperties.getMaxRedirects() != null) {
config.setMaxRedirects(clusterProperties.getMaxRedirects());
}
return config;
}
private List createSentinels(Sentinel sentinel) {
List nodes = new ArrayList();
for (String node : StringUtils.commaDelimitedListToStringArray(sentinel.getNodes())) {
try {
String[] parts = StringUtils.split(node, ":");
Assert.state(parts.length == 2, "Must be defined as 'host:port'");
nodes.add(new RedisNode(parts[0], Integer.valueOf(parts[1])));
}
catch (RuntimeException ex) {
throw new IllegalStateException(
"Invalid redis sentinel " + "property '" + node + "'", ex);
}
}
return nodes;
}
private JedisConnectionFactory createJedisConnectionFactory() {
JedisPoolConfig poolConfig = this.properties.getPool() != null
? jedisPoolConfig() : new JedisPoolConfig();
if (getSentinelConfig() != null) {
return new JedisConnectionFactory(getSentinelConfig(), poolConfig);
}
if (getClusterConfiguration() != null) {
return new JedisConnectionFactory(getClusterConfiguration(), poolConfig);
}
return new JedisConnectionFactory(poolConfig);
}
private JedisPoolConfig jedisPoolConfig() {
JedisPoolConfig config = new JedisPoolConfig();
MyRedisProperties.Pool props = this.properties.getPool();
config.setMaxTotal(props.getMaxActive());
config.setMaxIdle(props.getMaxIdle());
config.setMinIdle(props.getMinIdle());
config.setMaxWaitMillis(props.getMaxWait());
//sid since1.0 允许用户自定义这几个属性
config.setTestOnCreate(props.getTestOnCreate());
config.setTestOnBorrow(props.getTestOnBorrow());
config.setTestOnReturn(props.getTestOnReturn());
config.setTestWhileIdle(props.getTestWhileIdle());
config.setMinEvictableIdleTimeMillis(props.getMinEvictableIdleTimeMillis());
config.setTimeBetweenEvictionRunsMillis(props.getTimeBetweenEvictionRunsMillis());
config.setNumTestsPerEvictionRun(props.getNumTestsPerEvictionRun());
return config;
}
}
/**
* Standard Redis configuration.
*/
@Configuration
protected static class RedisConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate
注解介绍
@Configuration:定义配置类
@EnableConfigurationProperties:属性类
@AutoConfigureAfter: 自动配置应在XXX的自动配置类之后应用。
@AutoConfigureBefore: 自动配置应在XXX的自动配置类之前应用
@AutoConfigureOrder:定义配置类执行的顺序
@ConditionalOnBean:当容器里有指定的Bean 时才生成
@ConditionalOnMissingBean:当容器里没有指定Bean 时才生成
@ConditionalOnClass:当类路径下有指定的类时才生成
@ConditionalOnMissingClass:当类路径下没有指定的类时才生成
@ConditionalOnExpression:基于SpEL 表达式作为判断条件。
@ConditionalOnJava:基于JVM 版本作为判断条件。
@ConditionalOnJndi:在JNDI存在的条件下查找指定的位置。
@ConditionalOnProperty:指定的属性是否有指定的值。
@ConditionalOnResource:类路径是否有指定的值。
@ConditionalOnSingleCandidate:当指定Bean 在容器中只有一个,或者虽然有多个但是指定首选的Bean。
@ConditionalOnWebApplication:当前项目是Web 项目的条件下。
@ConditionalOnNotWebApplication:当前项目不是Web 项目的条件下。
spring.factories
src/resources/META-INF/spring.factories:
配置RedisAutoConfig在此文件,则spring boot会自动生成初始化此类
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
common.redis.spring.boot.autoconfigure.RedisAutoConfig
pom.xml
common-redis-spring-boot
com.sid
1.0-SNAPSHOT
../../common-redis-spring-boot/pom.xml
4.0.0
common-redis-spring-boot-starter
com.sid
common-redis-spring-boot-autoconfigure
com.sid
common-redis
common-redis包中大致代码
public class RedisUtil {
/**
* @Description: 这个redisTemplate 自动配置了连接池
* maxIdle = 60
* maxActive = 60
* maxWait = 3000
*
* 默认使用序列化:
* template.setKeySerializer(new StringRedisSerializer());
* template.setHashKeySerializer(new StringRedisSerializer());
* template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
* template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
* @Author: Sid
* @Date: 2018-11-08 15:16
* @since: 1.0
*/
private static RedisTemplate redisTemplate;
private static final AtomicBoolean init = new AtomicBoolean(false);
public static void init(RedisTemplate redisTemplate) {
if (init.compareAndSet(false, true)) {
RedisUtil.redisTemplate = redisTemplate;
}
}
public static V get(String key) {
return (V)redisTemplate.opsForValue().get(key);
}
public static void set(String key ,V value){
redisTemplate.opsForValue().set(key,value);
}
}
新建一个项目,引入common-redis-spring-boot-starter包
然后直接使用common-redis中的RedisUtil
ModelTest modelTest = new ModelTest();
modelTest.setName("小李");
modelTest.setAge(27);
RedisUtil.set("common-redis:test:modelTest1",modelTest);
RedisUtil.set("common-redis:test:string1","测试");
RedisUtil.get("common-redis:test:string1");