目錄
一、簡介
這篇文章總結redis緩存鏈路跟蹤器的實現
二、思路
redis的客戶端本身是沒有提供攔截器的。此外,緩存操作一般也不是一個獨立的方法,而是嵌入在某業務方法內部,因此也不可能用AOP來攔截。這裏的思路是通過動態代理,提供一個代理類,由它來完成redis緩存操作的攔截。
給redis操作提供定義攔截器的功能
我們準備通過動態代理來給redis操作提供定義攔截器的功能,類似於mybatis。
我們一步一步來,先看看需要哪些儲備知識。
-
靜態代理
我們可以看到,有3個角色,Action爲操作的接口,Operator執行真正的操作邏輯,Proxy引用了Operator的對象,它也實現了Action接口,但是它在doSomething方法中調用Operator對象的該方法,並在調用前後附加自己的邏輯。
public class Proxy implements Action {
private Operator operator;
public Proxy(Operator operator) {
this.operator = operator;
}
public void doSomething() {
preHandle();
operator.doSomething();
postHandle();
}
private void preHandle() {
System.out.println("前置操作");
}
private void postHandle() {
System.out.println("後置操作");
}
}
調用的時候如下
Proxy proxy = new Proxy(new Operator());
proxy.doSomething();
靜態代理有一個很大的缺點,就是需要新代理一個接口,就必須修改Proxy類,或者新增一個Proxy類。
比如上面的例子中,我需要新代理一個Handle接口,第一種方式,我們的uml圖變爲如下所示
很明顯Proxy類需要實現Handle接口,並且增加成員變量handler,增加方法doHandle
還有一種方式是新建一個Proxy類
我們希望不用修改Proxy類的代碼,也不新增一個Proxy類。這時候就需要動態代理了。
-
動態代理
動態代理解決了新增一個需要代理的接口,也不用修改Proxy類或者新增一個Proxy類。
Proxy類通過反射自動得分析出了需要代理的接口,方法,實際實現類,並把這些對象作爲p參數傳遞給了InvocationHandler接口的實現類的invoke方法。
示例代碼如下
public class ActionProxy implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
preHandle();
Object result = method.invoke(target, args);
postHandle();
return result;
}
private void preHandle() {
System.out.println("前置操作");
}
private void postHandle() {
System.out.println("前置操作");
}
}
Action action =
Proxy.newProxyInstance(
this.getClass().getClassLoader(),
Action,
new Operator());
action.doSomething();
這樣的話,新增一個需要被代理的接口,只需要在調用的時候修改Proxy.newProxyInstance傳遞的參數即可,不需要修改Proxy類的代碼,或者新增一個Proxy類。
-
mybatis的interceptor實現
mybatis的interceptor的實現就是通過動態代理實現的,我們來看看它是怎麼實現的吧
1、啓動的時候通過解析配置文件,實例化interceptor,並保存下來
其中紫色代表類,綠色代表方法,藍色代表方法中的關鍵代碼
XMLConfigBuilder從xml中解析出ingerceptor的類名,並通過反射將其實例化,最後保存到Configuration的interceptorChain成員變量中。
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
2、執行sql操作的時候使用動態代理,代理如下接口,Executor,ParameterHandler,ResultHandler,StatementHandler
其中紫色代表類,綠色代表方法,藍色代表方法中的關鍵代碼
在執行sql操作之前,會調用newExecutor,newParameterHandler,newResultSetHandler,newStatementHandler方法來得到相應的代理對象。
在這幾個方法中,調用第一步保存在InterceptorChain中的自定義interceptor的plugin方法,參數target爲Executor,ParameterHandle,ResultSetHandler,StatementHandler接口的實現類的對象。
我們在plugin中寫的代碼爲Plugin.warp(target, this),這個時候,會調用Plugin的warp方法。
在wrap方法中,getSignatureMap方法會解析Signature註解,getAllInterfaces會獲得Signature註解中需要被代理的接口,最後通過動態代理返回代理對象爲Plugin,
值得注意的是,Plugin是實現了InvocationHandler接口的,而且它在構造的時候保存了target,即Executor,ParameterHandle,ResultSetHandler,StatementHandler接口的實現類的對象。interceptor,作爲wrap方法的局部變量傳入的。
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
3、 獲得了代理對象,也就是Plugin的實例後,執行sql操作
其中紫色代表類,綠色代表方法,藍色代表方法中的關鍵代碼
第二步返回的代理對象爲Plugin的實例,Plugin實現了InvocationHandler接口,因此在執行sql操作的時候會執行Plugin的invoke方法。由於Plugin在實例化的時候保存了interceptor的引用,因此在invoke方法中調用interceptor的intercept方法。
-
仿造mybatis的interceptor
分析完mybatis的interceptor的實現後,我們完全可以仿造它的實現方式,實現一個針對於redis的interceptor。
1、被代理的接口
我們需要定義一個需要被代理的接口,類似於Mybatis的Executor,ParameterHandle,ResultSetHandler,StatementHandler接口
public interface RedisOperator {
boolean setBit(String key, int offset, boolean value);
boolean getBit(String key, int offset);
String set(String key, String data, int expiredSeconds);
String get(String key);
long del(String key);
}
2、實際處理操作的類
我們需要真正處理操作的類,類似於Mybatis的Executor,ParameterHandle,ResultSetHandler,StatementHandler接口的實現類
public class JedisRedisOperator implements RedisOperator {
private JedisUtil jedisUtil;
public JedisRedisOperator(String host, int port, String password) {
this.jedisUtil = new JedisUtil(host, port, password);
}
public JedisRedisOperator(String host, int port, String password, JedisPoolConfig jedisPoolConfig) {
this.jedisUtil = new JedisUtil(host, port, password, jedisPoolConfig);
}
@Override
public boolean setBit(String key, int offset, boolean value) {
return jedisUtil.setBit(key, offset, value);
}
@Override
public boolean getBit(String key, int offset) {
return jedisUtil.getBit(key, offset);
}
@Override
public String set(String key, String data, int expiredSeconds) {
return jedisUtil.set(key, data, expiredSeconds);
}
@Override
public String get(String key) {
return jedisUtil.get(key);
}
@Override
public long del(String key) {
return jedisUtil.del(key);
}
public String getHostPort() {
return this.jedisUtil.getHost()+":"+jedisUtil.getPort();
}
}
public class RedisTemplateRedisOperator implements RedisOperator {
private RedisTemplate<String, String> redisTemplate;
public RedisTemplateRedisOperator(RedisTemplate<String, String> redisTemplate) {
this.redisTemplate = redisTemplate;
}
@Override
public boolean setBit(String key, int offset, boolean value) {
return redisTemplate.opsForValue().setBit(key, offset, value);
}
@Override
public boolean getBit(String key, int offset) {
return redisTemplate.opsForValue().getBit(key, offset);
}
@Override
public String set(String key, String data, int expiredSeconds) {
redisTemplate.opsForValue().set(key, data, expiredSeconds, TimeUnit.SECONDS);
return null;
}
@Override
public String get(String key) {
return redisTemplate.opsForValue().get(key);
}
@Override
public long del(String key) {
redisTemplate.delete(key);
return 0;
}
public String getHostPort() {
return redisTemplate.getConnectionFactory().getConnection().getClientName();
}
}
3、Interceptor接口,供用戶自定義
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
}
4、實現了InvocationHandler接口的代理類,該類和第5步的類一致
5、提供生成代理對象功能的類,該類和第4步的類一致
public class Plugin implements InvocationHandler {
private final Object target;
private final Interceptor interceptor;
private Plugin(Object target, Interceptor interceptor) {
this.target = target;
this.interceptor = interceptor;
}
/* (non-Javadoc)
* <p>Title: invoke</p>
* <p>Description: </p>
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
* @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(method != null)
return interceptor.intercept(new Invocation(target, method, args));
return method.invoke(target, args);
}
/**
*
* <p>Title: wrap</p>
* <p>Description: 生成代理對象</p>
* @param target 需要被代理的接口
* @param interceptor 攔截器
* @return
*/
public static Object wrap(Object target, Interceptor interceptor) {
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor));
}
return target;
}
/**
*
* <p>Title: getAllInterfaces</p>
* <p>Description: 獲得被代理對象的所有可代理接口</p>
* @param type
* @return
*/
private static Class<?>[] getAllInterfaces(Class<?> type) {
Set<Class<?>> interfaces = new HashSet<Class<?>>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
// 必須實現了RedisOperator接口
if(c.getName().equals(RedisOperator.class.getName()))
interfaces.add(c);
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
}
6、代理類的生成時機
Mybatis是在執行sql操作的時候,我們可以在執行redis操作的時候,或者提供一個工具類,在工具類實例化的時候直接保存代理對象。
我這裏是定義了一個CacheProssor的工具類,在實例化的時候傳入的是RedisOperator的代理對象。同時也提供了一個方便獲得代理對象的工具類CacheConfiguration
public class CacheConfiguration {
public static Interceptor interceptor;
/**
*
* <p>Title: wrapRedisOperator</p>
* <p>Description: 獲得RedisOperator的代理類</p>
* @param redisOperator
* @return
*/
public static RedisOperator wrapRedisOperator(RedisOperator redisOperator) {
if(interceptor == null)
return redisOperator;
return (RedisOperator) Plugin.wrap(redisOperator, interceptor);
}
}
public class JedisRedisCacheProcessor extends RedisCacheProcessor{
public JedisRedisCacheProcessor(String key, String host, int port, String password) {
super(key, CacheConfiguration.wrapRedisOperator(new JedisRedisOperator(host, port, password)));
}
public JedisRedisCacheProcessor(String key, JedisPoolConfig jedisPoolConfig, String host, int port, String password) {
super(key, CacheConfiguration.wrapRedisOperator(new JedisRedisOperator(host, port, password, jedisPoolConfig)));
}
public JedisRedisCacheProcessor(String key, String host, int port, String password, ExpiredStrategy expiredStrategy) {
super(key, CacheConfiguration.wrapRedisOperator(new JedisRedisOperator(host, port, password)), expiredStrategy);
}
public JedisRedisCacheProcessor(String key, JedisPoolConfig jedisPoolConfig, String host, int port, String password, ExpiredStrategy expiredStrategy) {
super(key, CacheConfiguration.wrapRedisOperator(new JedisRedisOperator(host, port, password, jedisPoolConfig)), expiredStrategy);
}
}
7、通過配置文件實例化用戶自定義的interceptor
類似於Mybatis的XMLConfigBuilder,只不過我們是用spring通過yml文件進行配置
@Bean
@ConditionalOnMissingBean(RedisOperator.class)
public RedisOperator redisOperator() throws ClassNotFoundException, LinkageError, InstantiationException, IllegalAccessException {
String interceptorClassName = cacheProperties.getInterceptor();
if(StringUtils.isEmpty(interceptorClassName)) {
return new RedisTemplateRedisOperator(redisTemplate);
}
else {
CacheConfiguration.interceptor = (Interceptor) ClassUtils.forName(interceptorClassName, null).newInstance();
return CacheConfiguration.wrapRedisOperator(new RedisTemplateRedisOperator(redisTemplate));
}
}
-
類加載
說到這裏,還有一個細節沒有說, Mybatis的XMLConfigBuilder也好,Spring的ClassUtils工具也好。這時候我們一定要注意雙親委派模型
順便來看一下這兩個組件的實現方式吧
Mybatis
關鍵在於獲取classLoader
public static Class<?> classForName(String className) throws ClassNotFoundException {
return classLoaderWrapper.classForName(className);
}
public Class<?> classForName(String name) throws ClassNotFoundException {
return classForName(name, getClassLoaders(null));
}
ClassLoader[] getClassLoaders(ClassLoader classLoader) {
return new ClassLoader[]{
classLoader,
defaultClassLoader,
Thread.currentThread().getContextClassLoader(),
getClass().getClassLoader(),
systemClassLoader};
}
Class<?> classForName(String name, ClassLoader[] classLoader) throws ClassNotFoundException {
for (ClassLoader cl : classLoader) {
if (null != cl) {
try {
Class<?> c = Class.forName(name, true, cl);
if (null != c) {
return c;
}
} catch (ClassNotFoundException e) {
// we'll ignore this until all classloaders fail to locate the class
}
}
}
throw new ClassNotFoundException("Cannot find class: " + name);
}
Spring
關鍵在於獲取ClassLoader,另外,spring考慮的非常全面,考慮到了數組,內部類,Cglib動態代理類
@Nullable
public static ClassLoader getDefaultClassLoader() {
ClassLoader cl = null;
try {
cl = Thread.currentThread().getContextClassLoader();
}
catch (Throwable ex) {
// Cannot access thread context ClassLoader - falling back...
}
if (cl == null) {
// No thread context class loader -> use class loader of this class.
cl = ClassUtils.class.getClassLoader();
if (cl == null) {
// getClassLoader() returning null indicates the bootstrap ClassLoader
try {
cl = ClassLoader.getSystemClassLoader();
}
catch (Throwable ex) {
// Cannot access system ClassLoader - oh well, maybe the caller can live with null...
}
}
}
return cl;
}
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = commonClassCache.get(name);
}
if (clazz != null) {
return clazz;
}
// "java.lang.String[]" style arrays
if (name.endsWith(ARRAY_SUFFIX)) {
String elementClassName = name.substring(0, name.length() - ARRAY_SUFFIX.length());
Class<?> elementClass = forName(elementClassName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[Ljava.lang.String;" style arrays
if (name.startsWith(NON_PRIMITIVE_ARRAY_PREFIX) && name.endsWith(";")) {
String elementName = name.substring(NON_PRIMITIVE_ARRAY_PREFIX.length(), name.length() - 1);
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
// "[[I" or "[[Ljava.lang.String;" style arrays
if (name.startsWith(INTERNAL_ARRAY_PREFIX)) {
String elementName = name.substring(INTERNAL_ARRAY_PREFIX.length());
Class<?> elementClass = forName(elementName, classLoader);
return Array.newInstance(elementClass, 0).getClass();
}
ClassLoader clToUse = classLoader;
if (clToUse == null) {
clToUse = getDefaultClassLoader();
}
try {
return (clToUse != null ? clToUse.loadClass(name) : Class.forName(name));
}
catch (ClassNotFoundException ex) {
int lastDotIndex = name.lastIndexOf(PACKAGE_SEPARATOR);
if (lastDotIndex != -1) {
String innerClassName =
name.substring(0, lastDotIndex) + INNER_CLASS_SEPARATOR + name.substring(lastDotIndex + 1);
try {
return (clToUse != null ? clToUse.loadClass(innerClassName) : Class.forName(innerClassName));
}
catch (ClassNotFoundException ex2) {
// Swallow - let original exception get through
}
}
throw ex;
}
}
三、示例代碼
@Slf4j
public class RedisCacheTracker extends GenericTracker implements Interceptor {
public RedisCacheTracker() {
super();
}
public RedisCacheTracker(TraceClient traceClient) {
super(traceClient);
}
/* (non-Javadoc)
* <p>Title: intercept</p>
* <p>Description: </p>
* @param invocation
* @return
* @throws Throwable
* @see com.luminary.component.cache.plugin.Interceptor#intercept(com.luminary.component.cache.plugin.Invocation)
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("cache tracker");
TraceHolder traceHolder = new TraceHolder();
Object result = null;
try {
Gson gson = new Gson();
if(traceClient == null)
traceClient = getTraceClient();
String serverHost = "";
Map<String, Object> params = new HashMap<String, Object>();
Object target = invocation.getTarget();
if(target instanceof JedisRedisOperator) {
JedisRedisOperator jedisRedisOperator = (JedisRedisOperator) target;
serverHost = jedisRedisOperator.getHostPort();
}
else if(target instanceof RedisTemplateRedisOperator) {
serverHost = getServerHost();
}
Method method = invocation.getMethod();
Object[] args = invocation.getArgs();
if(method.getName().equals("setBit")) {
params.put("key", args[0]);
params.put("offset", args[1]);
params.put("value", args[2]);
}
else if(method.getName().equals("getBit")) {
params.put("key", args[0]);
params.put("offset", args[1]);
}
else if(method.getName().equals("set")) {
params.put("key", args[0]);
params.put("data", args[1]);
params.put("expiredSeconds", args[2]);
}
else if(method.getName().equals("get")) {
params.put("key", args[0]);
}
else if(method.getName().equals("del")) {
params.put("key", args[0]);
}
traceHolder.setProfile(getProfile());
traceHolder.setRpcType(RpcTypeEnum.CACHE.name());
traceHolder.setServiceCategory("redis");
traceHolder.setServiceName(invocation.getTarget().getClass().getName());
traceHolder.setServiceHost(serverHost);
traceHolder.setMethodName(method.getName());
traceHolder.setRequestParam(gson.toJson(params));
preHandle(traceHolder);
result = invocation.proceed();
traceHolder.getEntity().setResponseInfo(result == null ? "" : result.toString());
postHandle(traceHolder);
} catch(Exception e) {
log.error(e.getMessage(), e);
exceptionHandle(traceHolder, e);
}
return result;
}
/* (non-Javadoc)
* <p>Title: plugin</p>
* <p>Description: </p>
* @param target
* @return
* @see com.luminary.component.cache.plugin.Interceptor#plugin(java.lang.Object)
*/
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public String getProfile() {
log.warn("默認實現方法");
return null;
}
public TraceClient getTraceClient() {
log.warn("默認實現方法");
return null;
}
public String getServerHost() {
log.warn("默認實現方法");
return null;
}
}