参照SpringBoot的自动配置

欢迎访问配色更好看的原文,啦啦啦

“无感”就是最棒的设计

问题

想直接解决问题不愿听故事的,进屋坐坐看第二节。

最近在公司领到一个任务,优化公司的缓存框架的使用体验。公司的缓存框架封装成一个JAR文件,提供基于注解的对几种缓存的一致性使用。覆盖的缓存方案有ehcache memcached redis,先向公司大牛致敬。
要使用缓存框架,需要做这么几件事:

  1. 引入缓存框架的JAR文件

  2. 引入想要使用的缓存JAR,比如memcached.jar

  3. 做相关的Spring Bean配置,比如要使用Memcached缓存就要有如下的配置:

    <!-- memcache配置示例 -->
    <bean name="ljmemcachedClient" class="net.rubyeye.xmemcached.utils.XMemcachedClientFactoryBean" destroy-method="shutdown">
     <!-- 多个地址空格分隔 -->
     <property name="servers" value="${cache.memcacheAddress}" />
     <!-- 连接池大小一般5已经足够用,根据项目组实际情况调整 -->
     <property name="connectionPoolSize" value="${cache.memcachePoolSize}" />
     <!-- 一致哈希分布 -->
     <property name="sessionLocator">
       <bean class="net.rubyeye.xmemcached.impl.KetamaMemcachedSessionLocator"></bean>
     </property>
     <!-- 二进制协议,提高数据传输效率,支持touch等 -->
     <property name="commandFactory">
       <bean class="net.rubyeye.xmemcached.command.BinaryCommandFactory"></bean>
     </property>
    </bean>
  4. 必要的配置文件:

    cache.memcacheAddress=10.8.1.107:11211
    cache.memcachePoolSize=1

如果要使用redis缓存,则要引入另外一套配置。每个新项目都要配置,这显然是令人沮丧的。能不能简化这些配置呢?

  • 方案一:在缓存框架中引入三种缓存的JAR包,并做三种缓存的Bean配置,不提供配置文件,由使用缓存框架的项目提供。
  • 方案二:在缓存框架中提供三种缓存的“自动配置”,由使用缓存框架的项目提供配置文件和相关缓存JAR包,根据引入的缓存JAR包“自动配置”适合的Bean对象,完成缓存的使用。

来看下两种方案的优缺:

  • 方案一
    • 优点:简单易实现
    • 缺点:JAR包臃肿,无论实际使用哪种缓存,都引入了三种缓存JAR,并且需要提供三种的配置文件。不符合程序员美学–简约
  • 方案二:
    • 优点:“智能”发现缓存,按需引入,语义明确,使用方便
    • 缺点:程序实现复杂

我选择方案二

然而Spring Boot 已经有成熟的自动配置实现方案,如果直接引入 spring-boot-autoconfigure.jar 会面临额外问题:

  1. spring-boot-autoconfigure.jar 没有“直接”提供针对 memcahced 的自动配置;
  2. spring-boot-autoconfigure.jar 提供了太多自动配置支持,而我只想要关于缓存的几个;

那就只好模仿Spring Boot搞一搞了。


终焉

撒花…完结

在开始赘述之前,先给出最终的成品。

如何使用自动配置?

  1. 引入自动配置JAR包

  2. 使用注解 @EnableAutoConfigure 开启自动配置。建议写在基础类上或者专门提供一个类,如:

    @EnableAutoConfigure
    public class AutoConfigure {
         
    }
  3. 根据需要选择具体缓存,引入JAR并给出配置信息。比如memcached.jar

    cache.memcached.servers=10.8.1.107:11211
    cache.memcached.connectionPoolSize=2

PS: 这里有一个纠结的地方,其实可以做到省略第2步,也就是在自动配置JAR包里把这事做了。但笔者感觉这样不好,好像是降低了使用的成本,但是损失了使用的自由。所以最终还是没有进一步封装。这里仅代表笔者个人感受,未必是对的。

PS2:然而什么是对的呢?世界本来就不是0和1.

赘述

接下来详细说说如何实现的。

有条件的创建

除非满足条件,不然。。。哼!

最本质的需求是根据条件加载Bean。 Spring4 提供了实现方案 — @Conditional ,可以通过条件判断创建 Bean

示例:

// java config
@Configuration
public class TestBeanConfig {
   

    // 根据条件创建, 条件写在TestConditional类里
    @Bean
    @Conditional(TestConditional.class)
    public TestBean createTestBean() {
        return new TestBean();
    }
}

// 配套的条件类实现
public class TestConditional implements Condition {
   

    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        return false;
    }
}

只有当 TestConditional.matches() 结果为 true 时才会创建 TestBean

注解 @Conditional
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface Conditional {
   

    Class<? extends Condition>[] value();
}
  • 作用范围: 类,方法
  • 包含参数: value, 接口Condition的实现类数组。数组内所有的条件都要满足哟!

如果value中的条件都满足,就创建接下来的Bean。没啥好说的了;

接口 Condition
public interface Condition {
   

    // 当返回值为true时,创建Bean;否则忽略。
    boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata);
}

接口很简单只有一个matches方法,可以通过context和metadata获得相应判断信息,辅助判断;

参数 ConditionContext
public interface ConditionContext {
   
    // 检查Bean定义
    BeanDefinitionRegistry getRegistry();
    // 检查Bean是否存在,甚至探查Bean的属性
    ConfigurableListableBeanFactory getBeanFactory();
    // 检查环境变量是否存在以及它的值是什么
    Environment getEnvironment();
    // 检查加载的资源
    ResourceLoader getResourceLoader();
    // 加载并检查类是否存在
    ClassLoader getClassLoader();
}
AnnotatedTypeMetadata
public interface AnnotatedTypeMetadata {
   

    boolean isAnnotated(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName);
    Map<String, Object> getAnnotationAttributes(String annotationName, boolean classValuesAsString);
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName);
    MultiValueMap<String, Object> getAllAnnotationAttributes(String annotationName, boolean classValuesAsString);
}
  • 借助 isAnnotated() 能够判断带有 @Bean 注解的方法是不是还有其他特定注解;
  • 借助其他方法可以检查 @Bean 注解的方法上其他注解的属性;

尝试自动配置

判断条件

至此,基础技能已经 GET ,要解决第一节的问题,只要做判断:当前类路径下是否存在某类,有就创建无则忽略。
Spring Boot@Conditional 基础上扩展了几个注解方便做此类判断:@ConditionalOnClass, @ConditionalOnMissingClass , @ConditionalOnBean , @ConditionalOnMissingBean ;
这里就不贴源码了,想看的朋友去这里 org.springframework.boot.autoconfigure.condition;
当然可以自行编写条件类实现判断,我这里就懒省事了,直接使用 Spring Boot 的实现。(才没有这么简单)

编写自动配置类

编写 memcachedredis 的Java Config类。核心代码:

/**
 * memcached 缓存自动配置
 * 在引入googlecode-xmemcached.jar时启用
 * Created by mw4157 on 16/7/6.
 */
@Configuration
@ConditionalOnClass(MemcachedClient.class)
public class MemcachedAutoConfiguration {
   
    /**
     * 创建一个工厂Bean
     */
    @Bean
    @ConditionalOnMissingBean
    public XMemcachedClientFactoryBean memcachedClientFactory() {
        return ...;
    }
}

/**
 * redis 自动配置
 * 在引入redis-clients.jar和spring-data-redis.jar时启用
 * Created by mw4157 on 16/7/8.
 */
@Configuration
@ConditionalOnClass({ JedisConnection.class, RedisOperations.class, Jedis.class })
public class RedisAutoConfiguration {
   

    /**
     * Redis 连接配置
     */
    @Bean
    @ConditionalOnMissingBean(RedisConnectionFactory.class)
    public JedisConnectionFactory redisConnectionFactory() throws UnknownHostException {
        return ...;
    }
}

代码的核心功能是创造需要的Bean,同时辅以@Conditional相关的注解,就可以在大意上解决问题。

赘述2.0

之所以说大意上,是因为还有其他内容没有注

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值