目录
Spring中的缓存
Spring中缓存中间件的切换
Spring中的缓存管理器的设计
适配器模式
适配器的定义
适配器的结构
基础适配器样例代码
Spring缓存管理中的适配器
适配器的实现
适配多个Adaptee
适配器的复杂程度
对象适配和类适配器
关于适配器的思考
适配器的优缺点
适配器的适用场景
Spring中的缓存
Spring中缓存中间件的切换
缓存管理是日常开发中不可或缺的部分,也是很多性能问题、技术方案的核心点。作为Java系框架的老大哥,Spring的缓存管理是其一个非常重要的功能,Spring除了完成了缓存所需的基本能力外还保证了与其他缓存组件的兼容能力,以redis为例,我们在Spring中使用redis作为缓存组件,核心流程是这样的:
配置一个缓存管理器:
//创建一个配置类来启用缓存并配置Redis作为缓存管理器
@Configuration
@EnableCaching
public class CacheConfig {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}
}
使用缓存注解:
@Service
public class UserServiceImpl implements UserService {private final UserRepository userRepository;@Autowiredpublic UserServiceImpl(UserRepository userRepository) {this.userRepository = userRepository;}@Override@Cacheable(value = "users", key = "#id")public User getUserById(Long id) {// 这里假设userRepository.findById(id)是一个数据库调用return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}
}
十分的轻便,如果不再使用redis作为缓存的中间件,而使用 EhCache当作缓存的容器,除了pom依赖的改变,在上述代码中只需要修改缓存管理器的配置代码即可:
//将CacheConfig稍作修改即可
@Configuration
@EnableCaching
public class EhCacheConfig {//将redis的缓存管理器注销掉即可,重新实现一个EhCache的缓存管理器即可/*@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig().serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())));return RedisCacheManager.builder(connectionFactory).cacheDefaults(config).build();}*/@Beanpublic CacheManager cacheManager() {// 使用EhCache的配置文件EhCacheManagerFactoryBean bean = new EhCacheManagerFactoryBean();bean.setConfigLocation(new ClassPathResource("ehcache.xml"));bean.afterPropertiesSet();return bean.getObject();}
}
仅仅对Config类修改,类似于UserServiceImpl中已经使用了的地方全然不需要改动。
Spring中的缓存管理器的源码
我们通过Spring的源码来看一下Spring是如何做到无感切换缓存中间件的。Spring缓存相关的内容均在:org.springframework.cache下
通过org.springframework.cache代码分析,可以发现,主要涉及缓存内容操作的主要代码包括Cache和CacheManager,其中CacheManager接口主要是负责管理所有的 Cache
实例,而Cache接口的内容则是定义了缓存相关操作:
public interface Cache {String getName();Object getNativeCache();@NullableValueWrapper get(Object var1);@Nullable<T> T get(Object var1, @Nullable Class<T> var2);@Nullable<T> T get(Object var1, Callable<T> var2);void put(Object var1, @Nullable Object var2);//省略部分代码
}public interface CacheManager {@NullableCache getCache(String var1);Collection<String> getCacheNames();
}
org.springframework.cache其他代码涉及完整的注解动态代理、缓存相关操作与Spring上下文的互联。而实际涉及内存中间件数据的部分主要集中在上述的接口及中间类中。
然后我们从Redis的适配再往回看,在redis适配中发现,适配过程中主要是RedisCacheManager类,来看下此部分源码:
public class RedisCacheManager extends AbstractTransactionSupportingCacheManager {private final RedisCacheWriter cacheWriter;private final RedisCacheConfiguration defaultCacheConfig;private final Map<String, RedisCacheConfiguration> initialCacheConfiguration;private final boolean allowInFlightCacheCreation;private RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation) {Assert.notNull(cacheWriter, "CacheWriter must not be null!");Assert.notNull(defaultCacheConfiguration, "DefaultCacheConfiguration must not be null!");this.cacheWriter = cacheWriter;this.defaultCacheConfig = defaultCacheConfiguration;this.initialCacheConfiguration = new LinkedHashMap();this.allowInFlightCacheCreation = allowInFlightCacheCreation;}public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {this(cacheWriter, defaultCacheConfiguration, true);}public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, String... initialCacheNames) {this(cacheWriter, defaultCacheConfiguration, true, initialCacheNames);}public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, boolean allowInFlightCacheCreation, String... initialCacheNames) {this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation);String[] var5 = initialCacheNames;int var6 = initialCacheNames.length;for(int var7 = 0; var7 < var6; ++var7) {String cacheName = var5[var7];this.initialCacheConfiguration.put(cacheName, defaultCacheConfiguration);}}public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations) {this(cacheWriter, defaultCacheConfiguration, initialCacheConfigurations, true);}public RedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration, Map<String, RedisCacheConfiguration> initialCacheConfigurations, boolean allowInFlightCacheCreation) {this(cacheWriter, defaultCacheConfiguration, allowInFlightCacheCreation);Assert.notNull(initialCacheConfigurations, "InitialCacheConfigurations must not be null!");this.initialCacheConfiguration.putAll(initialCacheConfigurations);}public static RedisCacheManager create(RedisConnectionFactory connectionFactory) {Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");return new RedisCacheManager(new DefaultRedisCacheWriter(connectionFactory), RedisCacheConfiguration.defaultCacheConfig());}public static RedisCacheManagerBuilder builder() {return new RedisCacheManagerBuilder();}public static RedisCacheManagerBuilder builder(RedisConnectionFactory connectionFactory) {Assert.notNull(connectionFactory, "ConnectionFactory must not be null!");return RedisCacheManager.RedisCacheManagerBuilder.fromConnectionFactory(connectionFactory);}public static RedisCacheManagerBuilder builder(RedisCacheWriter cacheWriter) {Assert.notNull(cacheWriter, "CacheWriter must not be null!");return RedisCacheManager.RedisCacheManagerBuilder.fromCacheWriter(cacheWriter);}protected Collection<RedisCache> loadCaches() {List<RedisCache> caches = new LinkedList();Iterator var2 = this.initialCacheConfiguration.entrySet().iterator();while(var2.hasNext()) {Map.Entry<String, RedisCacheConfiguration> entry = (Map.Entry)var2.next();caches.add(this.createRedisCache((String)entry.getKey(), (RedisCacheConfiguration)entry.getValue()));}return caches;}protected RedisCache getMissingCache(String name) {return this.allowInFlightCacheCreation ? this.createRedisCache(name, this.defaultCacheConfig) : null;}public Map<String, RedisCacheConfiguration> getCacheConfigurations() {Map<String, RedisCacheConfiguration> configurationMap = new HashMap(this.getCacheNames().size());this.getCacheNames().forEach((it) -> {RedisCache cache = (RedisCache)RedisCache.class.cast(this.lookupCache(it));configurationMap.put(it, cache != null ? cache.getCacheConfiguration() : null);});return Collections.unmodifiableMap(configurationMap);}protected RedisCache createRedisCache(String name, @Nullable RedisCacheConfiguration cacheConfig) {return new RedisCache(name, this.cacheWriter, cacheConfig != null ? cacheConfig : this.defaultCacheConfig);}public static class RedisCacheManagerBuilder {//省略此部分逻辑源码}
}// AbstractTransactionSupportingCacheManager 类
public abstract class AbstractTransactionSupportingCacheManager extends AbstractCacheManager{
}
//AbstractTransactionSupportingCacheManager 类
public abstract class AbstractCacheManager implements CacheManager, InitializingBean{}
从源码上来看:
RedisCacheManager扩展了AbstractCacheManager,这是一个抽象类,实现了CacheManager接口。这意味着RedisCacheManager继承了AbstractCacheManager的所有方法,包括getCache(String name)方法。
在AbstractCacheManager中,getCache(String name)方法使用CacheManager接口来获取缓存实例:
public Cache getCache(String name) {Cache cache = this.cacheMap.get(name);if (cache == null && this.dynamic) {synchronized (this.cacheMap) {cache = this.cacheMap.get(name);if (cache == null) {cache = this.createCache(name);this.cacheMap.put(name, cache);}}}return cache;
}
在RedisCacheManager中,createCache(String name)方法创建并返回一个RedisCache实例
RedisCacheManager使用RedisTemplate来操作Redis缓存,它实现了Cache接口,提供了基本的缓存操作方法。
能够无感实现中间件的切换,设计的核心来着于以下两点
- 面向接口编程:之前一直提过,面向对象中封装后使用接口唯一对外暴露,这样内部是实现便是透明不可见的,只有满足接口的诉求,内部代码是随便改动的。这样使用组件时基于接口的编程则对组件的改动是无感的。
- 某种类结构的设计:基于此设计,可以将不同缓存技术适配到统一的缓存接口上。
适配器模式
适配器的定义
将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
适配器的结构
Client:客户端,调用自己需要的领域接口Target。
Target:定义客户端需要的跟特定领域相关的接口。
Adaptee:已经存在的接口,通常能满足客户端的功能要求,但是接口与客户端要求的特定领域接口不一致,需要被适配。
Adapter:适配器,把Adaptee 适配成为Client 需要的 Target。
基础适配器样例代码
/**
* 定义组件对外服务的接口
*/
public interface Target {/*** 示意方法,客户端请求处理的方法*/public void request();
}/*** 已经存在的接口(待适配接口),这个接口需要被适配*/
public class Adaptee {/*** 示意方法,原本已经存在,已经实现的方法*/public void specificRequest() {// 具体的功能处理}
}/*** 具体的适配器*/
public class Adapter implements Target {/*** 持有需要被适配的接口对象*/private Adaptee adaptee;/*** 构造方法,传入需要被适配的对象* @param adaptee 需要被适配的对象*/public Adapter(Adaptee adaptee) {this.adaptee = adaptee;}/*** 可能转调已经实现了的方法,进行适配*/@Overridepublic void request() {adaptee.specificRequest();}
}/*** 使用适配器的客户端*/
public class Client {public static void main(String[] args) {// 创建需要被适配的对象Adaptee adaptee = new Adaptee();// 创建客户端需要调用的接口对象Target target = new Adapter(adaptee);// 请求处理target.request();}
}
Spring缓存管理中的适配器
基于以上的定义 ,我们可以基于Redis的集成捋一捋Spring中的缓存管理的实现思路了。
RedisCacheManager作为适配器(Adapter),将RedisTemplate(Adaptee)适配到Cache接口(Target),使得Spring的缓存抽象可以与Redis缓存进行交互。
具体类 | 适配器模式角色 |
RedisCacheManager | Adapter |
RedisTemplate | Adaptee |
Cache | Target |
RedisCacheManager通过继承AbstractCacheManager并实现createCache方法来适配RedisTemplate,从而实现了与Cache接口的关联。这种适配器模式的应用使得Spring的缓存抽象可以与Redis缓存进行交互。同理,采用此种结构,也可以适配任何缓存中间件。
适配器的实现
适配多个Adaptee
适配器在适配的时候,可以适配多个Adaptee,也就是说实现某个新的Target 的功能的时候,需要调用多个模块的功能,适配多个模块的功能才能满足新接口的要求。
适配器的复杂程度
适配器 Adapter 实现的复杂程度取决于 Target和 Adaptee 的相似程度。如果相似程度很高,比如只有方法名称不一样,那么Adapter只需要简单地转调一下接口就可以了。
如果相似程度低,比如两边接口的方法所定义的功能完全不一样,在Target中定义的一个方法,可能在 Adaptee中定义了三个更小的方法,那么这个时候在实现 Adapter 的时候,就需要组合调用了。
对象适配和类适配器
在标准的适配器模式里面,根据适配器的实现方式,把适配器分成了两种,一种是对象适配器,另一种是类适配器。
- 对象适配器的实现:依赖于对象组合。就如同前面的标准实现示例,都是采用对象组合的方式,也就是对象适配器实现的方式。
- 类适配器的实现:采用多重继承对一个接口与另一个接口进行匹配。由于Java不支持多重继承,所以Java中很少甚至不使用。下面着重介绍一下类适配
类适配器
从结构图上可以看出,类适配器是通过继承来实现接口适配的,标准的设计模式中,类适配器是同时继承 Target和Adaptee的,也就是一个多重继承,这在Java里面是不被支持的,也就是说Java中是不能实现标准的类适配器的。
但是Java中有一种变通的方式,也能够使用继承来实现接口的适配,那就是让适配器去实现 Target 的接口,然后继承 Adaptee 的实现,虽然不是十分标准,但是意思差不多。
关于适配器的思考
适配器的优缺点
适配器的优势:
- 更好的复用性
如果功能是已经有了的,只是接口不兼容,那么通过适配器模式就可以让这些功能得到更好的复用。
- 更好的可扩展性
在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能。
劣势:
过多地使用适配器,会让系统非常零乱,不容易整体进行把握比如,明明看到调用的是A接口,其实内部被适配成了B接口来实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。
适配器的适用场景
- 如果你想要使用一个已经存在的类,但是它的接口不符合你的需求,这种情况可以使用适配器模式,来把已有的实现转换成你需要的接口。
- 如果你想创建一个可以复用的类,这个类可能和一些不兼容的类一起工作,这种情况可以使用适配器模式,到时候需要什么就适配什么。
- 如果你想使用一些已经存在的子类,但是不可能对每一个子类都进行适配,这种情况可以选用对象适配器,直接适配这些子类的父类就可以了。