目录
一、简介
这篇文章总结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;
}
}