ASM, CGlib, Java Proxy, Javassist都是可以操作字節(jié)碼坛吁,但是這些操作字節(jié)碼都需要等到類加載到JVM中之后再對(duì)字節(jié)碼進(jìn)行重寫谋作。
JavaAgent則是一個(gè)可以在加載前就進(jìn)行重寫,然后再加載的方式教馆。
JavaAgent
JavaAgent 是JDK 1.5 以后引入的馋缅,也可以叫做Java代理扒腕。是運(yùn)行在 main方法之前的攔截器,它內(nèi)定的方法名叫 premain 股囊,也就是說先執(zhí)行 premain 方法然后再執(zhí)行 main 方法袜匿。
那么如何實(shí)現(xiàn)一個(gè) JavaAgent 呢更啄?很簡(jiǎn)單稚疹,只需要增加 premain 方法即可。
public class TestPreMain {
public static void premain(String agentOps, Instrumentation inst) {
System.out.println("=========premain方法執(zhí)行========");
System.out.println(agentOps);
inst.addTransformer(new FirstAgent());
}
}
說明一下premain方法祭务。該方法的原理與 main 應(yīng)用程序入口點(diǎn)類似内狗。在 Java 虛擬機(jī) (JVM) 初始化后,每個(gè) premain 方法將按照指定代理的順序調(diào)用义锥,然后將調(diào)用實(shí)際的應(yīng)用程序 main 方法柳沙。
JVMTI(Java Virtual Machine Tool Interface)是一套本地編程接口集合,它提供了一套”代理”程序機(jī)制拌倍,可以支持第三方工具程序以代理的方式連接和訪問 JVM赂鲤,并利用 JVMTI 提供的豐富的編程接口,完成很多跟 JVM 相關(guān)的功能柱恤。關(guān)于 JVMTI 的詳細(xì)信息数初,請(qǐng)參考 Java SE 6 文檔(請(qǐng)參見 參考資源)當(dāng)中的介紹。
java.lang.instrument 包的實(shí)現(xiàn)梗顺,也就是基于這種機(jī)制的:在 Instrumentation 的實(shí)現(xiàn)當(dāng)中泡孩,存在一個(gè) JVMTI 的代理程序,通過調(diào)用 JVMTI 當(dāng)中 Java 類相關(guān)的函數(shù)來完成 Java 類的動(dòng)態(tài)操作寺谤。
Instrumentation 的最大作用仑鸥,就是類定義動(dòng)態(tài)改變和操作。在 Java SE 5 及其后續(xù)版本當(dāng)中变屁,開發(fā)者可以在一個(gè)普通 Java 程序(帶有 main 函數(shù)的 Java 類)運(yùn)行時(shí)眼俊,通過
– javaagent
參數(shù)指定一個(gè)特定的 jar 文件(包含 Instrumentation 代理)來啟動(dòng) Instrumentation 的代理程序。
public class FirstAgent implements ClassFileTransformer {
public final String injectedClassName = "test.agent.wxl";
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
className = className.replace("/", ".");
// System.out.println(className);
if (className.startsWith(injectedClassName)) {
CtClass ctclass = null;
try {
ctclass = ClassPool.getDefault().get(className);// 使用全稱,用于取得字節(jié)碼類<使用javassist>
CtMethod[] ctmethods = ctclass.getMethods();
for (CtMethod ctMethod : ctmethods) {
CodeAttribute ca = ctMethod.getMethodInfo2().getCodeAttribute();
if (ca == null) {
continue;
}
if (!ctMethod.isEmpty()) {
// System.out.println(ctMethod.getName());
ctMethod.insertBefore("System.out.println(\"hello Im agent : " + ctMethod.getName() + "\");");
}
}
return ctclass.toBytecode();
} catch (Exception e) {
System.out.println(e.getMessage());
e.printStackTrace();
}
}
return null;
}
}
實(shí)現(xiàn) ClassFileTransformer 這個(gè)接口的目的就是在class被裝載到JVM之前將class字節(jié)碼轉(zhuǎn)換掉粟关,從而達(dá)到動(dòng)態(tài)注入代碼的目的泵琳。其中代碼的修改使用到了javassist。
上段代碼實(shí)現(xiàn)了把特定包下面的class的所有類的所有有body的非native的方法前面添加一句
System.out.println("hello im agent :" + ctMethod.getName());
在pom.xml文件中添加插件:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.0.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<manifestEntries>
<Premain-Class>agent.TestPreMain</Premain-Class>
</manifestEntries>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
構(gòu)建后會(huì)發(fā)現(xiàn)jar包里面的MANIFEST.MF文件如下
Manifest-Version: 1.0
Premain-Class: agent.TestPreMain
Archiver-Version: Plexus Archiver
Built-By: xiaolong.wei
Created-By: Apache Maven 3.3.9
Build-Jdk: 1.8.0_121
最后創(chuàng)建一個(gè)被代理的工程類
public class TestAgentDemo {
public static void main(String... args) {
testAgent();
testAgent1("1", "2");
}
public static void testAgent() {
System.out.println("test agent say hello");
}
public static void testAgent1(String one, String two) {
System.out.println("test agent say hello" + one + two);
}
}
在啟動(dòng)的時(shí)候使用-javaagent:來指定jar包
運(yùn)行:
可以看到方法main, testAgent testAgent1 的前面都被加入了代碼.
JavaAgent 實(shí)際應(yīng)用中
1. 可以在加載java文件之前做攔截把字節(jié)碼做修改
2. 獲取所有已經(jīng)被加載過的類
3. 獲取某個(gè)對(duì)象的大小
4. 將某個(gè)jar加入到bootstrapclasspath里作為高優(yōu)先級(jí)被bootstrapClassloader加載
5. 將某個(gè)jar加入到classpath里供AppClassload去加載
6. 設(shè)置某些native方法的前綴,主要在查找native方法的時(shí)候做規(guī)則匹配
動(dòng)態(tài)代理
動(dòng)態(tài)代理的實(shí)現(xiàn):
- cglib (Code Generation Library) 實(shí)現(xiàn)
- InvocationHandler
代理模式
代理模式就是給某一個(gè)對(duì)象提供一個(gè)代理對(duì)象获列,并由代理對(duì)象控制對(duì)原對(duì)象的引用谷市。通俗的來講代理模式就是我們生活中常見的中介。
如下圖
實(shí)際使用中击孩∑扔疲客戶端獲取到的是代理對(duì)象,而代理對(duì)象ProxyObject把請(qǐng)求轉(zhuǎn)發(fā)給RealObject巩梢,ProxyObject是不做業(yè)務(wù)的创泄,實(shí)際工作的其實(shí)只有RealObject 。
如果編寫一個(gè)類的同時(shí)就要一個(gè)代理類括蝠,那這樣系統(tǒng)就會(huì)變得臃腫且難以維護(hù)鞠抑,所以出現(xiàn)了動(dòng)態(tài)代理。在運(yùn)行狀態(tài)中忌警,需要代理的地方搁拙,根據(jù)AbstractObject和RealObject,動(dòng)態(tài)地創(chuàng)建一個(gè)Proxy法绵,用完之后箕速,就會(huì)銷毀,這樣就可以避免了Proxy 角色的class在系統(tǒng)中冗雜的問題了朋譬。
InvocationHandler
在考慮代理類的時(shí)候盐茎,我們無非是想在代理類調(diào)用實(shí)際對(duì)象的前后進(jìn)行一些業(yè)務(wù)處理,如下圖徙赢。
這就造成了代理類的通用性字柠,只需要在invoke的前后加入特定的代碼就可以了。所以就出現(xiàn)了將所有通用的調(diào)用真實(shí)方法的管理器狡赐,讓這個(gè)觸發(fā)管理器統(tǒng)一去管理就叫做Invocation Handler窑业。
JDK通過 java.lang.reflect包下面的Proxy與InvocationHandler來支持動(dòng)態(tài)代理。
RealObject這個(gè)類創(chuàng)建一個(gè)動(dòng)態(tài)代理對(duì)象阴汇,JDK主要會(huì)做以下工作:
1. 獲取 RealObject上的所有接口列表数冬;
2. 確定要生成的代理類的類名,默認(rèn)為:com.sun.proxy.$ProxyXXXX 搀庶;
3. 根據(jù)需要實(shí)現(xiàn)的接口信息拐纱,在代碼中動(dòng)態(tài)創(chuàng)建 該P(yáng)roxy類的字節(jié)碼;
4 . 將對(duì)應(yīng)的字節(jié)碼轉(zhuǎn)換為對(duì)應(yīng)的class 對(duì)象哥倔;
5. 創(chuàng)建InvocationHandler實(shí)例handler秸架,用來處理Proxy所有方法調(diào)用;
6. Proxy 的class對(duì)象 以創(chuàng)建的handler對(duì)象為參數(shù)咆蒿,實(shí)例化一個(gè)proxy對(duì)象
InvocationHandler东抹,我們需要實(shí)現(xiàn)下列的invoke方法:
在調(diào)用代理對(duì)象中的每一個(gè)方法時(shí)蚂子,在代碼內(nèi)部,都是直接調(diào)用了InvocationHandler 的invoke方法缭黔,而invoke方法根據(jù)代理類傳遞給自己的method參數(shù)來區(qū)分是什么方法食茎。
public interface Subject {
int getInt(Integer i);
}
public interface Subject2 {
String getString(String source);
}
public class RealSubject implements Subject, Subject2 {
@Override
public int getInt(Integer i) {
System.out.println("this is method of getInt " + i++);
return i;
}
@Override
public String getString(String source) {
System.out.println("source string is :" + source);
source = source + " suffix";
return source;
}
}
代理類
public class SubjectProxy implements InvocationHandler {
private RealSubject subject;
public SubjectProxy(RealSubject subject) {
this.subject = subject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("method " + method.getName() + "invoke by proxy, start");
Object o = method.invoke(subject, args);
System.out.println("method " + method.getName() + "invoke by proxy, end");
return o;
}
}
測(cè)試代碼
public class ProxyMain {
public static void main(String... args) {
RealSubject subject = new RealSubject();
//創(chuàng)建一個(gè)Invoke Handler
SubjectProxy proxy = new SubjectProxy(subject);
// 通過Proxy newProxyInstance方法創(chuàng)建代理類
Object o = Proxy.newProxyInstance(ProxyMain.class.getClassLoader(), subject.getClass().getInterfaces(), proxy);
Subject realSubject = (Subject) o;
int result = realSubject.getInt(12);
System.out.println("result:" + result);
Subject2 s2 = (Subject2) o;
s2.getString("hello");
}
}
看一下代碼運(yùn)行結(jié)果
當(dāng)然JDK的InvokeHandler也是生成了一個(gè)代理類。
JDK提供了sun.misc.ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 底層方法來產(chǎn)生動(dòng)態(tài)代理類的字節(jié)碼:
cglib —— 通過類繼承
JDK提供的生成動(dòng)態(tài)代理的方式有個(gè)明顯的特點(diǎn)就是被代理的類它必須要有實(shí)現(xiàn)的接口才行馏谨。既RealObject必須實(shí)現(xiàn)了某個(gè)接口或者某幾個(gè)接口别渔,才能代理接口這些接口的方法。
還好有CGLib惧互。CGLIB(Code Generation Library)哎媚,是一個(gè)強(qiáng)大的,高性能喊儡,高質(zhì)量的Code生成類庫拨与,它可以在運(yùn)行期擴(kuò)展Java類與實(shí)現(xiàn)Java接口。而CGLIB底層使用的是ASM庫艾猜。
cglib 創(chuàng)建某個(gè)類A的動(dòng)態(tài)代理類的模式是:
1. 查找A上的所有非final 的public類型的方法定義买喧;
2. 將這些方法的定義轉(zhuǎn)換成字節(jié)碼;
3. 將組成的字節(jié)碼轉(zhuǎn)換成相應(yīng)的代理的class對(duì)象箩朴;
4. 實(shí)現(xiàn) MethodInterceptor接口岗喉,用來處理 對(duì)代理類上所有方法的請(qǐng)求(這個(gè)接口和JDK動(dòng)態(tài)代理InvocationHandler的功能和角色是一樣的)
被代理類
public class ElectricCar {
public boolean charging(Double fee) {
System.out.println("charging ~~ Fee:" + fee);
return true;
}
public boolean driver(String van, Integer color) {
System.out.println(van + " is runing ");
return true;
}
}
代理方法
public class CarInterceptor implements MethodInterceptor {
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("intercept in car before");
Object result = proxy.invokeSuper(obj, args);
System.out.println("intercept in car after");
return result;
}
}
測(cè)試方法
public class CgLibMain {
public static void main(String... args) {
//生成class文件 保存
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\work\\agentDemo\\target");
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(ElectricCar.class);
CarInterceptor carInterceptor = new CarInterceptor();
//設(shè)置代理前后的Interceptor
enhancer.setCallback(carInterceptor);
ElectricCar electricCar = (ElectricCar) enhancer.create();
electricCar.driver("川A12131", 1);
electricCar.charging(12d);
}
}
代碼運(yùn)行結(jié)果:
參考:
https://blog.csdn.net/luanlouis/article/details/24589193
https://www.ibm.com/developerworks/cn/java/j-lo-jse61/