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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章