前言:本篇主要采用JavaAgent实现 字节码 层面的代码修改;
目标实现:DRDS, mongodb ,KVS,MQ以及ES数据路由(数据路由:请求线程添加标记,判断标记是否存在,以此为依据切换数据源)。
一:JavaAgent实现方法
请参考:
总结的比较清楚,基本使用没有问题
本人初学,简单实现如下:
以DRDS数据路由为例,当前实现Druid数据源的数据路由。需要编写的一共两个部分:
1,agent premain 方法实现入口
public class DruidAgent {
private static Instrumentation inst = null;
public static void premain(String agentArgs, Instrumentation _inst) throws ClassNotFoundException, UnmodifiableClassException
{
System.out.println("===进入Druid-Agent方法===");
inst = _inst;
inst.addTransformer(new DruidTransformer());
}
}
2,实现ClassFileTransformer的类转换器
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
/**判断加载的class的包路径是不是需要监控的类*/
if (methodMap.containsKey(className)) {
System.out.println("当前处理类名:" + className);
CtClass ctclass = null;
System.out.println("获取pool,开始,classLoader:"+loader.getClass()+","+loader);
ClassPool pool = PoolUtil.getClassPool(loader);
try {
/**使用javassist取得字节码类*/
ctclass = pool.get(className);
// 解冻CtClass对象
ctclass.defrost();
ctclass.addField(CtField.make("private static com.alibaba.druid.pool.DruidDataSource shadowDS = null ;", ctclass));
ctclass.addField(CtField.make("private static final java.lang.String CONFIG_PATH_ARG = \"propertyFile\";", ctclass));
//植入shadowInit方法,其中shadow库配置文件以VM参数-DpropertyFile指定,文件中需要有配置参数。略
/**自行补充*/
for (String methodName : methodMap.get(className)) {
System.out.println("当前处理方法名:" + methodName);
String newMethod =
"if(StressTestFlagHolder.isStressTest() && null == druid.DruidFlagHolder.getFlag()){\n" +
"\t druid.DruidFlagHolder.setFlag(\"DRUID\");\n" +
"\ttry{\n" +
"\t\t System.out.println(\"DruidDataSource的CL:\"+com.alibaba.druid.pool.DruidDataSource.class.getClassLoader().getClass()); \n"+
"\t\t shadowInit();\n" +
"\t\t return shadowDS.getConnection();\n" +
"\t\t}finally{\n" +
"\t\t druid.DruidFlagHolder.clearFlag();\n" +
"\t } \n" +
" }";
/**获取方法实例*/
CtMethod ctmethod = ctclass.getDeclaredMethod(methodName);
/**构建方法体*/
StringBuilder bodyStr = new StringBuilder();
bodyStr.append(newMethod);
System.out.println("新方法内容:" + bodyStr. toString ());
ctmethod.insertBefore(bodyStr.toString());// 替换新方法
}
return ctclass.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}
.
二,数据路由切面,需要具体阅读源码,分析代码植入点,以下为本人浅显实现
- DRDS
切入******.druid方法,判断是否存在压测标识,提换数据源
- ES(本方法切入构造方法之后,启动报错,后封装工具层切入实现,原生切入待研究)
index加前缀,拦截CreateIndexRequestBuilder 构造器
IndicesExistsRequest构造器
ReplicationRequest的index方法,在index加上es的前缀,前缀在agent的配置文件配置
- Mongo
org.springframework.data.mongodb.core.MongoTemplate.getDb切换数据源
- MQ
植入代码,在com.aliyun.openservices.ons.api.impl.rocketmq.ProducerImpl的send方法中植入: msg.putUserProperties("StressTestFlag",StressTestFlagHolder.getFlag()); 植入代码,在MqMessageListener的consume方法中植 入: StressTestFlagHolder.setFlag(msg.getUserProperties("StressTestFlag")); StressTestFlagHolder.clearString();
- Redis
切入redis.clients.jedis.BinaryJedis.set 和redis.clients.jedis.BinaryJedis.get方法,key添加前缀
标记类参考:
public class FlagHolder { private static final ThreadLocal<String> redisFlagHolder = new ThreadLocal<String>(); /**添加标记*/ public static void setFlag(String flag) { redisFlagHolder.set(flag); } /**获取标记*/ public static String getFlag() { String flag = (String)redisFlagHolder.get(); return flag; } /**清除标记*/ public static void clearFlag() { redisFlagHolder.remove(); } /**标记判断*/ public static boolean isStressTest() { return "DRUID".equals(getFlag()); } }
三,打包启动
本次实现中不再赘述打包过程,有兴趣参考
打包结束启动之前:
你需要有:待切入工程jar包,agent代码jar包,javassist依赖包,如果你采用外部配置文件,还需要一份外部配置文件,这样你可以在把agent的一些配置信息放在这里。
指令参考:java -javaagent:E:/Agent/agent.jar -jar -Xmx1g -Xms1g -Xmn512m -XX:SurvivorRatio=8 -DpropertyFile=E:/Agent/外部配置文件.properties 你的服务打包.jar