Spring Cloud 下使用Javassist 在类被加载之前修改字节码
Spring Cloud 项目中,很多功能都是用 aop去实现的,或者直接使用Java Agent。
在两者都不能使用的情况下,我们可以考虑使用Javassist 直接操作字节码来实现。
我们需要使用Spring 的一个扩展点 ApplicationContextInitializer,在类被加载之前修改字节码,注意在Spring Cloud 环境下,一般存在父子容器,此扩展点被执行两次,需要在子容器初始化时机操作。
示例代码如下:录制随机数函数的返回值
(为了复用,抽象出一个父类)
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.ApplicationContextInitializer;
import org.springframework.context.ConfigurableApplicationContext;
import java.io.IOException;
import java.util.*;
/**
* @author:
* date: 2088/15/19
*/
public abstract class BaseApplicationContextInitializer implements ApplicationContextInitializer {
protected Logger log = LoggerFactory.getLogger(getClass());
private static volatile int initialized = 0;
@Override
public void initialize(ConfigurableApplicationContext applicationContext) {
Optional<String> optional = Arrays.stream(applicationContext.getEnvironment().getActiveProfiles())
.filter(t -> "online".equals(t) || "unittest".equals(t))
.findFirst();
if (optional.isPresent()) {
return;
}
if (initialized == 1) {
log.info(getClass().getSimpleName() + " begin");
try {
doJavassist();
log.info(getClass().getSimpleName() + " end");
} catch (Throwable e) {
log.error(getClass().getSimpleName(), e);
throw new RuntimeException(e);
}
initialized += 1;
}
if (initialized == 0) {
initialized += 1;
}
}
protected abstract void doJavassist() throws Exception;
}
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;
import javassist.CtNewMethod;
import java.util.Random;
/**
* @author:
* date: 2088/15/19
*/
public class RandomStringUtilsAop extends BaseApplicationContextInitializer {
@Override
protected void doJavassist() throws Exception {
ClassPool classPool = ClassPool.getDefault();
classPool.importPackage("java.util.Random");
CtClass randomStringUtilsClass = classPool.get("org.apache.commons.lang3.RandomStringUtils");
CtClass[] paramsClasses = new CtClass[]{
classPool.get(int.class.getName()),
classPool.get(int.class.getName()),
classPool.get(int.class.getName()),
classPool.get(boolean.class.getName()),
classPool.get(boolean.class.getName()),
classPool.get(char[].class.getName()),
classPool.get(Random.class.getName())
};
CtMethod ctMethod = randomStringUtilsClass.getDeclaredMethod("random", paramsClasses);
CtMethod srcInvoke = CtNewMethod.copy(ctMethod, randomStringUtilsClass, null);
srcInvoke.setName("srcInvoke");
randomStringUtilsClass.addMethod(srcInvoke);
ctMethod.setBody("{\n" +
" String className = \"org.apache.commons.lang3.RandomStringUtils\";\n" +
" String methodName = \"random\";\n" +
" String result = null;\n" +
" Throwable ex = null;\n" +
"\n" +
" try {\n" +
" result = srcInvoke($$);\n" +
" } catch (RuntimeException e) {\n" +
" ex = e;\n" +
" }\n" +
"\n" +
" RecordUtils.record(className, methodName, result, ex);\n" +
" if (ex != null) {\n" +
" throw ex;\n" +
" }\n" +
" return result;\n" +
" }");
randomStringUtilsClass.toClass();//加载修改后的类,注意:必须保证调用前此类未加载
if (randomStringUtilsClass.isFrozen()) {
randomStringUtilsClass.defrost();
}
}
}