Javaagentによるバイトコードの変更によるaopの実現
Javaagentによるバイトコードの変更によるaopの実現
起因:seata ATモードでグローバルトランザクションのコミット後にイベントをトリガーし、undo_を解析したいlogは、非同期書き込みesなどのいくつかの操作を実行します.seataはコードの切り込み点を提供していないため、プロジェクトにフレームコードと同じパッケージを構築するしかなく、フレームワークのクラスソースコードをコピーしてパッケージの下に置き、その中のソースコードを書き換え、classpath優先ロード原則は元のクラスを上書きする目的を達成する.このやり方では「優雅さ」が足りない.では、どのようにして無声でソースコードを強化することができますか?spring aopを思い浮かべますが、ソースコードはnewで出てきてspring工場の管理を受けていないので、この道は通じません.またClassLoaderを利用して、私たちが複写したコードをカスタマイズしてロードしたいと思っていますが、フレームワークは私たちがカスタマイズしたClassLoaderを使うことができません.この道も通じません.では、このclassをjvmにロードしてバイトコードに解析する過程で、こっそり元のバイトコードに置き換え、「盗天換日」計画が形成されるしかない.Java 5が提供するagentプロパティを使用して、この操作を完了します.この記事:Java 5プロパティInstrumentationの実践では、どのように使用するかを紹介しています.では始めましょう!準備作業、定義Adviceインタフェース、pom導入javassist依存 アプリケーションにpremainメソッドを追加し、agentにエントリを提供します.クラス名は勝手で、mainメソッドに似ていて、どのクラスに置くかは重要ではありません. Transformerクラスを追加し、transformメソッド を規定に従って書き換える.現在、どのクラス、どの方法、どのAdviceをエージェントとして使用する必要があるかがわかりました.バイトコードを変更してエージェントをTransformUtilでサポートする必要があります.javassistの使用方法は、このドキュメントを参照してください.http://www.javassist.org/tutorial/tutorial.html String類のsplit法をテストしたことがないことを許してください.ここでは栗を1つ挙げるだけです.この変換器は現在,構造方法,リロード方法,抽象方法の変換をサポートしておらず,検証もしていない点も不足しているのではないでしょうか.以降、以上の「ワンタッチ操作」を有効にする工程にadviceを加える.propertiesプロファイルは、 このように,工事開始後に用いられるTestクラスの例は修正されている.
ps:-javaagentが構成するjarパッケージは、premainメソッドを外部に含むjarである、MANIFESTを外部jarに組み込むことができる.MF構成.エンジニアリングが開始されると、このjarでpremainメソッドが検索され、後続の論理が実行され、得られたバイトコードが現在のjvmにロードされます.ここでは便宜上,本プロジェクト自体を外部のjar,自己agent自身とする.
起因:seata ATモードでグローバルトランザクションのコミット後にイベントをトリガーし、undo_を解析したいlogは、非同期書き込みesなどのいくつかの操作を実行します.seataはコードの切り込み点を提供していないため、プロジェクトにフレームコードと同じパッケージを構築するしかなく、フレームワークのクラスソースコードをコピーしてパッケージの下に置き、その中のソースコードを書き換え、classpath優先ロード原則は元のクラスを上書きする目的を達成する.このやり方では「優雅さ」が足りない.では、どのようにして無声でソースコードを強化することができますか?spring aopを思い浮かべますが、ソースコードはnewで出てきてspring工場の管理を受けていないので、この道は通じません.またClassLoaderを利用して、私たちが複写したコードをカスタマイズしてロードしたいと思っていますが、フレームワークは私たちがカスタマイズしたClassLoaderを使うことができません.この道も通じません.では、このclassをjvmにロードしてバイトコードに解析する過程で、こっそり元のバイトコードに置き換え、「盗天換日」計画が形成されるしかない.Java 5が提供するagentプロパティを使用して、この操作を完了します.この記事:Java 5プロパティInstrumentationの実践では、どのように使用するかを紹介しています.では始めましょう!
public interface Advice {
void before(Object[] params);
void after(Object[] params);
}
// pom.xml
org.javassist
javassist
3.27.0-GA
public class Premain {
public static void premain(String agentArgs, Instrumentation inst) {
// Transformer
inst.addTransformer(new Transformer());
}
}
public class Transformer implements ClassFileTransformer {
//
private volatile Map map = null;
/**
* :
* loader - ; , null
* className - The Java Virtual Machine Specification 。 ,"java/util/List"。
* classBeingRedefined - , ; , null
* protectionDomain -
* classfileBuffer - ( )
* :
* ( ), , null。
* :
* IllegalClassFormatException -
*/
public byte[] transform(ClassLoader l, String className, Class> c, ProtectionDomain pd, byte[] b) {
if (map == null) {
// ,
scanProps();
}
// aop
if (map.containsKey(className)) {
//
Pack p = map.get(className);
// , aop
return TransformUtil.getNewByteCode(p.targetClassName, p.targetMethodName);
}
// null ,
return null;
}
/**
* advice.properties ,
* :
* io.seata.rm.datasource.undo.AbstractUndoLogManager.batchDeleteUndoLog=com.a.b.c.AbstractUndoLogManagerAdvice
* key ,value Advice
*/
private synchronized void scanProps() {
if (map != null) {
// double check
return;
}
Map m = new HashMap<>();
Enumeration urls = null;
try {
// advice.properties
urls = Thread.currentThread().getContextClassLoader().getResources("advice.properties");
} catch (IOException e) {
e.printStackTrace();
}
if (urls != null) {
while (urls.hasMoreElements()) {
InputStream inStream = null;
Properties property = new Properties();
try {
inStream = urls.nextElement().openStream();
property.load(inStream);
property.stringPropertyNames().forEach(key -> {
// key
String targetClassName = key.substring(0, key.lastIndexOf("."));
// key
String targetMethodName = key.substring(key.lastIndexOf(".") + 1);
// advice
String adviceClassName = (String) property.get(key);
// key io/seata/rm.datasource/undo/AbstractUndoLogManager
String k = targetClassName.replaceAll("\\.", "/");
Pack p = m.get(k);
//
if (p == null) {
p = new Pack(targetClassName, targetMethodName, adviceClassName);
m.put(k, p);
} else {
p.add(targetMethodName, adviceClassName);
}
});
} catch (Exception e) {
e.printStackTrace();
} finally {
if (inStream != null) {
try {
inStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
// map volatile ,
map = m;
}
static class Pack {
String targetClassName;
// advice,
Map targetMethodName = new HashMap<>();
public Pack(String targetClassName, String targetMethodName, String adviceClassName) {
this.targetClassName = targetClassName;
this.targetMethodName.put(targetMethodName, adviceClassName);
}
public void add(String targetMethodName, String adviceClassName) {
this.targetMethodName.put(targetMethodName, adviceClassName);
}
}
}
public class TransformUtil {
/**
*
* 、static 。
* 、 、
*
* @param targetClassName Examples: java.lang.String
* @param method_advice
* @return
*/
public static byte[] getNewByteCode(String targetClassName, Map method_advice) {
try {
ClassPool pool = ClassPool.getDefault();
CtClass ctClass = pool.get(targetClassName);
//
for (Map.Entry entry : method_advice.entrySet()) {
String targetMethodName = entry.getKey();
String adviceClassName = entry.getValue();
CtMethod originalMethod = ctClass.getDeclaredMethod(targetMethodName);
CtClass originalReturnType = originalMethod.getReturnType();
// advice name
String adviceFieldName = targetMethodName + "$advice";
//
String newMethodName = "original$" + targetMethodName;
// Advice
String adviceField = "private" +
// static
((originalMethod.getMethodInfo().getAccessFlags() & AccessFlag.STATIC) == AccessFlag.STATIC ? " static " : " ")
// Advice
+ "final com.aop.javassist.Advice "
+ adviceFieldName + " = new " + adviceClassName + "(); ";
ctClass.addField(CtField.make(adviceField, ctClass));
// =================== ===================
//
StringBuilder methodBody = new StringBuilder();
methodBody.append("{ ");
methodBody.append("Object[] params = $args; ");
// before
methodBody.append("try { ");
methodBody.append(adviceFieldName)
.append(".before(params); ");
methodBody.append("} catch (Exception e) { ");
methodBody.append("e.printStackTrace(); ");
methodBody.append("} ");
// ,
if (!CtClass.voidType.equals(originalReturnType)) {
methodBody.append(originalReturnType.getName())
.append(" result = ")
.append(newMethodName)
.append("($$); ");
} else {
methodBody.append(newMethodName)
.append("($$); ");
}
// after
methodBody.append("try { ");
methodBody.append(adviceFieldName)
.append(".after(params); ");
methodBody.append("} catch (Exception e) { ");
methodBody.append("e.printStackTrace(); ");
methodBody.append("} ");
//
if (!CtClass.voidType.equals(originalReturnType)) {
methodBody.append("return result; ");
}
methodBody.append("} ");
// ================== =================
//
originalMethod.setName(newMethodName);
// ,
CtMethod copyMethod = CtNewMethod.copy(originalMethod, targetMethodName, ctClass, null);
copyMethod.setBody(methodBody.toString());
//
ctClass.addMethod(copyMethod);
}
byte[] b = ctClass.toBytecode();
// CtClass
ctClass.detach();
return b;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
ここで強化する構想は、元のclassメソッドを新しい名前に変更し、元のメソッド宣言と同じメソッドを追加し、新しいメソッドでAdviceのbefore、元のメソッド、Adviceのafterを順次呼び出し、元のメソッドに戻り値があればafterを呼び出した後、結果を返すことです.Stringクラスのsplitメソッドにaopを作成すると、Stringクラスはこのように変更されます: private final a.b.c.Advice split$advice = new a.b.c.AdviceImpl();
public String[] split(String regex) {
Object[] params = new Object[]{regex};
try {
split$advice.before(params);
} catch (Exception e) {
e.printStackTrace();
}
String[] result = original$split(regex);
try {
split$advice.after(params);
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
/**
* split ,
*/
public String[] original$split(String regex) {
return split(regex, 0);
}
. =AdviceImpl
Test operation :
a.b.c.d.Test.operation=d.c.b.a.AdviceImpl
プロジェクトにAdvice実装クラス: public class AdviceImpl implements Advice {
@Override
public void before(Object[] params) {
System.out.println("before");
}
@Override
public void after(Object[] params) {
System.out.println("after");
}
}
プロジェクトのMANIFESTを組み込むように構成されている.MFは以下の2つを配置する必要がある: Manifest-Version: 1.0
Premain-Class: com.aop.premain.Premain
mvnでパッケージ化する場合、pomにプラグインを追加してパッケージ化後に自動的に上述の配置に加えることができる:
maven-jar-plugin
3.0.2
com.aop.premain.Premain
パッケージ化工事は工事の起動パラメータに追加する: -javaagent:jar /test-aop.jar
ps:-javaagentが構成するjarパッケージは、premainメソッドを外部に含むjarである、MANIFESTを外部jarに組み込むことができる.MF構成.エンジニアリングが開始されると、このjarでpremainメソッドが検索され、後続の論理が実行され、得られたバイトコードが現在のjvmにロードされます.ここでは便宜上,本プロジェクト自体を外部のjar,自己agent自身とする.