抽象工厂模式和工厂方法模式虽然主要的目的都是为了解决接口选择问题。 但是在实现上, 抽象工厂是一个中心工厂, 它能够创建其他的工厂。

业务背景介绍

随着业务超过预期的快速发展,系统的负载能力也要随着跟上。原有的单机 Redis 已经满足不了系统需求。这时候就需要更换为更为健壮的Redis集群服务,虽然需要修改但是不能影响目前系统的运行,还要平滑过渡过去。
随着这次的升级,可以预见的问题会有;

  1. 很多服务用到了Redis需要一起升级到集群。
  2. 需要兼容集群A和集群B,便于后续的灾备。
  3. 两套集群提供的接口和方法各有差异,需要做适配。
  4. 不能影响到目前正常运行的系统。

单机的Redis服务操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 单机Redis服务操作
*/
public class RedisUtils {

private Logger logger = LoggerFactory.getLogger(RedisUtils.class);

private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

public String get(String key) {
logger.info("Redis获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
System.out.println("写数据");
logger.info("Redis写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void set(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("Redis写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void del(String key) {
logger.info("Redis删除数据 key:{}", key);
dataMap.remove(key);
}

}

业务实现逻辑

准备了两种集群, EGM 和 IIR

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 模拟Redis集群二
public class EGM {
private Logger logger = LoggerFactory.getLogger(IIR.class);

private Map<String, String> dataMap = new ConcurrentHashMap<String, String>();

public String get(String key) {
logger.info("IIR获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
logger.info("IIR写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void setExpire(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("IIR写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void del(String key) {
logger.info("IIR删除数据 key:{}", key);
dataMap.remove(key);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// 模拟Redis集群一
@Slf4j
public class IIR {
private Logger logger = LoggerFactory.getLogger(IIR.class);
private Map<String, String> dataMap= new ConcurrentHashMap<>();

public String gain(String key) {
logger.info("EGM获取数据 key:{}", key);
return dataMap.get(key);
}

public void set(String key, String value) {
logger.info("EGM写入数据 key:{} val:{}", key, value);
dataMap.put(key, value);
}

public void setEx(String key, String value, long timeout, TimeUnit timeUnit) {
logger.info("EGM写入数据 key:{} val:{} timeout:{} timeUnit:{}", key, value, timeout, timeUnit.toString());
dataMap.put(key, value);
}

public void delete(String key) {
logger.info("EGM删除数据 key:{}", key);
dataMap.remove(key);
}

}

两个适配器的实现:

都实现了ICacheAdapter适配器接口,也是为了统一适配器的服务

EGMCacheAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* EGM 实现适配器接口,实现集群的使用服务
*/
public class EGMCacheAdapter implements ICacheAdapter {
private EGM egm = new EGM();

public String get(String key) {
return egm.gain(key);
}

public void set(String key, String value) {
egm.set(key, value);
}

public void set(String key, String value, long timeout, TimeUnit timeUnit) {
egm.setEx(key, value, timeout, timeUnit);
}

public void del(String key) {
egm.delete(key);
}
}

IIRCacheAdapter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* IIR 实现适配器接口, 并且实现相关的方法
*/
public class IIRCacheAdapter implements ICacheAdapter {


private IIR iir = new IIR();

public String get(String key) {
return iir.get(key);
}

public void set(String key, String value) {
System.out.println("写书据");
iir.set(key, value);
}

public void set(String key, String value, long timeout, TimeUnit timeUnit) {
iir.setExpire(key, value, timeout, timeUnit);
}

public void del(String key) {
iir.del(key);
}
}

定义服务的统一入口

定义了CacheService作为服务的统一接口, (这里为了服务入口的统一才实现了接口, 其实也可以不用)
并且CacheService中实现的方法应该要和适配器接口中实现的一样, 这样才能够通过invoke找得到适配器中的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/**
* 定义服务使用的统一接口
*/
public interface CacheService {

String get(final String key);

void set(String key, String value);

void set(String key, String value, long timeout, TimeUnit timeUnit);

void del(String key);
}

同时还需要实现统一接口的实现类, 这样才能够拿到服务对象。

这里就不做实现了, 和前面的单体实现redis一样

通过代理类调用对象方法的流程


image.png

JDK接口 代理的抽象工厂getProxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* JDK接口 代理的抽象工厂
*/
public class JDKProxy {
/**
* 在运行时创建一个实现了指定接口的代理对象,这个代理对象可以在调用接口方法时进行拦截和自定义处理。
* @param interfaceClass 需要被代理的接口的实现类对象
* @param cacheAdapter 被代理的对象需要使用的哪个代理适配器
* @return 返回一个代理实例
* @param <T>
* @throws Exception
*/
public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
//创建了JDKInvocationHandler的实例,传入cacheAdapter作为参数,这个handler将用于处理所有代理方法的调用。
InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
// 获取当前线程的上下文类加载器,用于之后创建代理实例。
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = interfaceClass.getInterfaces();
//该接口将方法调用调度到指定的调用处理程序。, handler:调用处理程序,用于将方法调用调度
return (T) Proxy.newProxyInstance(classLoader, classes, handler);
// 这里如果是EGMCacheAdapter调用, 那么就可以是EGMCacheAdapter来实现处理
}
}

抽象工厂代理的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
/**
* JDK代理的抽象工厂实现
*/
public class JDKInvocationHandler implements InvocationHandler {

private ICacheAdapter cacheAdapter; // 调用的实际执行者, 比如传入的是EGMCacheAdapter

public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
this.cacheAdapter = cacheAdapter;
}
/**
* @param proxy 调用该方法的代理实例
* @param method 对应于代理实例上调用的接口方法的 {@code Method} 实例。{@code Method} 对象的声明类将是声明方法的接口,该接口可能是代理类继承方法的代理接口的超接口
* @param args 一个对象数组,其中包含在代理实例上的方法调用中传递的参数的值,如果接口方法不带参数,则为 {@code null}。基元类型的参数包装在相应基元包装类的实例中,例如 {@code java.lang.Integer} 或 {@code java.lang.Boolean}。
* @return 返回代理的结果
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 这里就可以直接对传入的EGMCacheAdapter进行调用其中需要的方法
System.out.println("调用希望调用的方法: " + method.getName() + " 并且传递参数 ");
return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
}

}

抽象工厂模式的代理类抽象场景测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 使用抽象工厂的方式
@Test
public void test_Factory() throws Exception {
// 获取代理对象
CacheService proxy_EGM = JDKProxy.getProxy(CacheServiceImpl.class, new EGMCacheAdapter());
// 通过代理对象调用方法, 而不是通过if -else 来判断调用的方法
System.out.println("完成代理对象的获取");
proxy_EGM.set("user-EGM", "rayce-EGM");
System.out.println("完成代理方法的调用");
String val01 = proxy_EGM.get("user-EGM");
System.out.println("测试结果:" + val01);

CacheService proxy_IIR = JDKProxy.getProxy(CacheServiceImpl.class, new IIRCacheAdapter());
proxy_IIR.set("user-IIR", "rayce-IIR");
String val02 = proxy_IIR.get("user-IIR");
System.out.println("测试结果:" + val02);


}

image.png