springCloud微服务系列——链路跟踪第六篇——redis缓存链路跟踪器

目录

一、简介

二、思路

给redis操作提供定义拦截器的功能

静态代理

动态代理

mybatis的interceptor实现

仿造mybatis的interceptor

类加载

三、示例代码


一、简介

     这篇文章总结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;
	}

}

https://github.com/wulinfeng2/luminary-component

猜你喜欢

转载自blog.csdn.net/guduyishuai/article/details/81382022