Для такого преобразования вы можете использовать обычную цепочку ClassReader
→ ClassVisitor
(трансформатор) → ClassWriter
. Существует три основных шага:
Переопределить visitField
для отслеживания всех полей с постоянным значением и вызова метода супер-посещения без константы, то есть с null
, чтобы сохранить поле объявление, но удалите постоянное значение.
Переопределите visitMethod
, чтобы заметить, существует ли уже существующий инициализатор класса (метод <clinit>
). Если это так, верните специальный MethodVisitor
, который будет вводить инициализацию поля в начале кода, и очистите карту, чтобы третий шаг стал недоступным.
Override visitEnd
создать инициализатор класса, если были постоянные поля и не существовал инициализатор класса. Вновь созданный инициализатор класса должен выполнять те же назначения полей, поэтому стоит иметь общий код в методе injectFieldInit
. Затем нам нужно только добавить обязательную инструкцию RETURN
, которую нам не нужно добавлять для уже существующего инициализатора.
Этот код использует массив в качестве ключа карты, который здесь нет проблем, поскольку каждое поле отличается, поэтому тот факт, что массивы не имеют основанного на контенте метода equals
, не имеет значения. Вместо этого мы могли бы использовать List<Map.Entry<…>>
или список выделенных типов элементов для хранения всех необходимых значений, с тем же результатом, что и код не выполняет поиск, а только выполняет итерацию по обнаруженным полям один раз.
public static byte[] transform(byte[] classFile) {
ClassReader cr = new ClassReader(classFile);
ClassWriter cw = new ClassWriter(cr, ClassWriter.COMPUTE_MAXS);
ClassVisitor trans = new ClassVisitor(Opcodes.ASM5, cw) {
private String currClassName;
private Map<String[],Object> constants = new HashMap<>();
@Override public void visit(int version, int acc, String name,
String sig, String superName, String[] ifs) {
currClassName = name;
super.visit(version, acc, name, sig, superName, ifs);
}
@Override public FieldVisitor visitField(int acc, String name, String desc,
String sig, Object value) {
if(value != null && (acc & Opcodes.ACC_STATIC) != 0)
constants.put(new String[]{currClassName, name, desc}, value);
return super.visitField(acc, name, desc, sig, null);
}
@Override public MethodVisitor visitMethod(int acc, String name, String desc,
String sig, String[] ex) {
MethodVisitor mv = super.visitMethod(acc, name, desc, sig, ex);
if(name.equals("<clinit>")) {
mv = new MethodVisitor(Opcodes.ASM5, mv) {
@Override public void visitCode() {
super.visitCode();
injectFieldInit(this, constants);
constants.clear();
}
};
}
return mv;
}
@Override public void visitEnd() {
if(!constants.isEmpty()) {
MethodVisitor mv = super.visitMethod(
Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
injectFieldInit(mv, constants);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(-1, -1);
mv.visitEnd();
}
super.visitEnd();
}
};
cr.accept(trans, 0);
return cw.toByteArray();
}
static void injectFieldInit(MethodVisitor target, Map<String[], Object> constants) {
for(Map.Entry<String[],Object> e: constants.entrySet()) {
target.visitLdcInsn(e.getValue());
String[] field = e.getKey();
target.visitFieldInsn(Opcodes.PUTSTATIC, field[0], field[1], field[2]);
}
}