前言
在看小傅哥 的《重学java设计模式》的抽象工厂,之前理解的只是面试题要背的知识点,知道是通过反射调用方法的,spring中的AOP 和 事务也是用的动态代理。
在看 这篇文章又有了新的收获,怕忘了,就立即写出来。
问题场景
根据小傅哥的例子–改造升级redis 多集群配置,大致就是,随着系统升级,qps提高,之前单机版本的redis 无法满足需求,现在要升级至集群模式,且要平稳过渡,具体可以看上面链接。
为了说明更加方便,附上小傅哥例子中的目录结构:
itstack-demo-design-2-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── factory
│ │ ├── impl
│ │ │ ├── EGMCacheAdapter.java
│ │ │ └── IIRCacheAdapter.java
│ │ ├── ICacheAdapter.java
│ │ ├── JDKInvocationHandler.java
│ │ └── JDKProxy.java
│ ├── impl
│ │ └── CacheServiceImpl.java
│ └── CacheService.java
└── test
└── java
└── org.itstack.demo.design.test
└── ApiTest.java
关键代码
CacheService 缓存接口
public interface CacheService {
/**
* 获取数据
* @param key
* @return
*/
String get(final String key);
/**
* 设置数据
* @param key
* @param value
*/
void set(String key, String value);
/**
* 根据时间,设置数据
* @param key
* @param value
* @param timeout
* @param timeUnit
*/
void set(String key, String value, long timeout, TimeUnit timeUnit);
/**
* 删除数据
* @param key
*/
void del(String key);
}
ICacheAdapter 适配器 接口
public interface ICacheAdapter {
String get(String key);
void set(String key, String value);
void set(String key, String value, long timeout, TimeUnit timeUnit);
void del(String key);
}
InvocationHandler类中方法
/**
* 当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用
*/
public class JDKInvocationHandler implements InvocationHandler {
private ICacheAdapter cacheAdapter;
/**
* 构造器目的:引入缓存适配器对象实例,
* 在invoke方法中,可以使用反射机制,
* 对某一个实例执行方法
* @param cacheAdapter
*/
public JDKInvocationHandler(ICacheAdapter cacheAdapter) {
this.cacheAdapter = cacheAdapter;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// return ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
// 获取ICacheAdapter 接口实例的方法对象,再使用invoke方法 类反射调用具体方法
Object invoke = ICacheAdapter.class.getMethod(method.getName(), ClassLoaderUtils.getClazzByArgs(args)).invoke(cacheAdapter, args);
return invoke;
}
}
Proxy类中的方法
/**
* 得到一个动态的代理对象
* 原材料:缓存适配器
*
* @param interfaceClass
* @param cacheAdapter 作用:实际上真正的对象,通过动态代理生成出的对象,调用对象方法,实际调用的就是InvocationHandler对象的invoke方法
* @return 生成一个动态代理对象
* @param <T>
* @throws Exception
*/
public static <T> T getProxy(Class<T> interfaceClass, ICacheAdapter cacheAdapter) throws Exception {
InvocationHandler handler = new JDKInvocationHandler(cacheAdapter);
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// [CacheService.class]
// 为什么 jdk动态代理要传入实现类了,因为这个方法失效,更深层次影响是,Proxy.newProxyInstance会失败
// 传入一个 接口有实现类,则
Class<?>[] classes = interfaceClass.getInterfaces();
/**用来动态创建一个代理对象的类
* 1、loader:一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
* 2、interfaces:一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
* 3、handler:一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上;在代理实例调用方法时,方法调用被编码分派到InvocationHandler的invoke方法。
*/
// return (T) Proxy.newProxyInstance(classLoader, new Class[]{classes[0]}, handler);
return (T) Proxy.newProxyInstance(classLoader, new Class[]{interfaceClass}, handler);
}
疑惑
正常的动态代理是使用相同接口传入InvocationHandler和接口,但是这里小傅哥使用的分别是ICacheAdapter和CacheService这两个接口,上面代码中也可能看到,这两个接口完全没有任何继承关系,那么他们是如何关联上的呢?
解答
关键点是 相同方法名称
在看了这两个接口的方法名,发现这两个接口的方法名称是一样的,就想到了JDK动态代理对象调用方法时,是通过方法名称和参数进行调用的,也就是说,在调用代理对象方法时,会自动跳转到InvocationHandler的invoke方法中,然后通过查找传入InvocationHandler中的ICacheAdapter的相同方法名称并传入参数,就完成了。
总结
反过来也就理解了为什么这里要创建ICacheAdapter 适配器了,目的就是为了与CacheService 进行适配,用以动态代理,以前一直对适配器很模糊,现在经过这样发现,对适配器这个概念也就更具体了。
同时,动态代理配置适配器使用,也就更加灵活多变了,可能就本来就是动态代理的意义,只是我之前一直没有弄明白。