天天看點

Javassist簡單應用小結

概述

  Javassist是一款位元組碼編輯工具,可以直接編輯和生成Java生成的位元組碼,以達到對.class檔案進行動态修改的效果。熟練使用這套工具,可以讓Java程式設計更接近與動态語言程式設計。   下面一個方法的目的是擷取一個類加載器(ClassLoader),以加載指定的.jar或.class檔案,在之後的代碼中會使用到。 [java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. private static ClassLoader getLocaleClassLoader() throws Exception {  
  2.     List<URL> classPathURLs = new ArrayList<>();  
  3.     // 加載.class檔案路徑  
  4.     classPathURLs.add(classesPath.toURI().toURL());  
  5.     // 擷取所有的jar檔案  
  6.     File[] jarFiles = libPath.listFiles(new FilenameFilter() {  
  7.         @Override  
  8.         public boolean accept(File dir, String name) {  
  9.             return name.endsWith(".jar");  
  10.         }  
  11.     });  
  12.     Assert.assertFalse(ObjectHelper.isArrayNullOrEmpty(jarFiles));  
  13.     // 将jar檔案路徑寫入集合  
  14.     for (File jarFile : jarFiles) {  
  15.         classPathURLs.add(jarFile.toURI().toURL());  
  16.     }  
  17.     // 執行個體化類加載器  
  18.     return new URLClassLoader(classPathURLs.toArray(new URL[classPathURLs.size()]));  
  19. }  

擷取類型資訊

[java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. @Test  
  2. public void test() throws NotFoundException {  
  3.     // 擷取預設類型池對象  
  4.     ClassPool classPool = ClassPool.getDefault();  
  5.     // 擷取指定的類型  
  6.     CtClass ctClass = classPool.get("java.lang.String");  
  7.     System.out.println(ctClass.getName());  // 擷取類名  
  8.     System.out.println("\tpackage " + ctClass.getPackageName());    // 擷取包名  
  9.     System.out.print("\t" + Modifier.toString(ctClass.getModifiers()) + " class " + ctClass.getSimpleName());   // 擷取限定符和簡要類名  
  10.     System.out.print(" extends " + ctClass.getSuperclass().getName());  // 擷取超類  
  11.     // 擷取接口  
  12.     if (ctClass.getInterfaces() != null) {  
  13.         System.out.print(" implements ");     
  14.         boolean first = true;  
  15.         for (CtClass c : ctClass.getInterfaces()) {  
  16.             if (first) {  
  17.                 first = false;  
  18.             } else {  
  19.                 System.out.print(", ");  
  20.             }  
  21.             System.out.print(c.getName());  
  22.         }  
  23.     }  
  24.     System.out.println();  
  25. }  

修改類方法

[java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. @Test  
  2. public void test() throws Exception {  
  3.     // 擷取本地類加載器  
  4.     ClassLoader classLoader = getLocaleClassLoader();  
  5.     // 擷取要修改的類  
  6.     Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");  
  7.     // 執行個體化類型池對象  
  8.     ClassPool classPool = ClassPool.getDefault();  
  9.     // 設定類搜尋路徑  
  10.     classPool.appendClassPath(new ClassClassPath(clazz));  
  11.     // 從類型池中讀取指定類型  
  12.     CtClass ctClass = classPool.get(clazz.getName());  
  13.     // 擷取String類型參數集合  
  14.     CtClass[] paramTypes = {classPool.get(String.class.getName())};  
  15.     // 擷取指定方法名稱  
  16.     CtMethod method = ctClass.getDeclaredMethod("show", paramTypes);  
  17.     // 指派方法到新方法中  
  18.     CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);  
  19.     // 修改源方法名稱  
  20.     String oldName = method.getName() + "$Impl";  
  21.     method.setName(oldName);  
  22.     // 修改原方法  
  23.     newMethod.setBody("{System.out.println(\"執行前\");" + oldName + "($$);System.out.println(\"執行後\");}");  
  24.     // 将新方法添加到類中  
  25.     ctClass.addMethod(newMethod);  
  26.     // 加載重新編譯的類  
  27.     clazz = ctClass.toClass();      // 注意,這一行會将類當機,無法在對位元組碼進行編輯  
  28.     // 執行方法  
  29.     clazz.getMethod("show", String.class).invoke(clazz.newInstance(), "hello");  
  30.     ctClass.defrost();  // 解凍一個類,對應freeze方法  
  31. }  

動态建立類

[java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. @Test  
  2. public void test() throws Exception {  
  3.     ClassPool classPool = ClassPool.getDefault();  
  4.     // 建立一個類  
  5.     CtClass ctClass = classPool.makeClass("edu.alvin.reflect.DynamiClass");  
  6.     // 為類型設定接口  
  7.     //ctClass.setInterfaces(new CtClass[] {classPool.get(Runnable.class.getName())});  
  8.     // 為類型設定字段  
  9.     CtField field = new CtField(classPool.get(String.class.getName()), "value", ctClass);  
  10.     field.setModifiers(Modifier.PRIVATE);  
  11.     // 添加getter和setter方法  
  12.     ctClass.addMethod(CtNewMethod.setter("setValue", field));  
  13.     ctClass.addMethod(CtNewMethod.getter("getValue", field));  
  14.     ctClass.addField(field);  
  15.     // 為類設定構造器  
  16.     // 無參構造器  
  17.     CtConstructor constructor = new CtConstructor(null, ctClass);  
  18.     constructor.setModifiers(Modifier.PUBLIC);  
  19.     constructor.setBody("{}");  
  20.     ctClass.addConstructor(constructor);  
  21.     // 參數構造器  
  22.     constructor = new CtConstructor(new CtClass[] {classPool.get(String.class.getName())}, ctClass);  
  23.     constructor.setModifiers(Modifier.PUBLIC);  
  24.     constructor.setBody("{this.value=$1;}");  
  25.     ctClass.addConstructor(constructor);  
  26.     // 為類設定方法  
  27.     CtMethod method = new CtMethod(CtClass.voidType, "run", null, ctClass);  
  28.     method.setModifiers(Modifier.PUBLIC);  
  29.     method.setBody("{System.out.println(\"執行結果\" + this.value);}");  
  30.     ctClass.addMethod(method);  
  31.     // 加載和執行生成的類  
  32.     Class<?> clazz = ctClass.toClass();  
  33.     Object obj = clazz.newInstance();  
  34.     clazz.getMethod("setValue", String.class).invoke(obj, "hello");  
  35.     clazz.getMethod("run").invoke(obj);  
  36.     obj = clazz.getConstructor(String.class).newInstance("OK");  
  37.     clazz.getMethod("run").invoke(obj);  
  38. }  

建立代理類

[java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. @Test  
  2. public void test() throws Exception {  
  3.     // 執行個體化代理類工廠  
  4.     ProxyFactory factory = new ProxyFactory();    
  5.     //設定父類,ProxyFactory将會動态生成一個類,繼承該父類    
  6.     factory.setSuperclass(TestProxy.class);  
  7.     //設定過濾器,判斷哪些方法調用需要被攔截  
  8.     factory.setFilter(new MethodFilter() {    
  9.         @Override    
  10.         public boolean isHandled(Method m) {    
  11.             return m.getName().startsWith("get");  
  12.         }    
  13.     });  
  14.     Class<?> clazz = factory.createClass();  
  15.     TestProxy proxy = (TestProxy) clazz.newInstance();  
  16.     ((ProxyObject)proxy).setHandler(new MethodHandler() {  
  17.         @Override  
  18.         public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {  
  19.             //攔截後前置處理,改寫name屬性的内容    
  20.             //實際情況可根據需求修改    
  21.             System.out.println(thisMethod.getName() + "被調用");  
  22.             try {  
  23.                 Object ret = proceed.invoke(self, args);    
  24.                 System.out.println("傳回值: " + ret);  
  25.                 return ret;  
  26.             } finally {  
  27.                 System.out.println(thisMethod.getName() + "調用完畢");  
  28.             }  
  29.         }  
  30.     });  
  31.     proxy.setName("Alvin");  
  32.     proxy.setValue("1000");  
  33.     proxy.getName();  
  34.     proxy.getValue();  
  35. }  

  其中,TestProxy類内容如下: [java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. public class TestProxy {  
  2.     private String name;  
  3.     private String value;  
  4.     public String getName() {  
  5.         return name;  
  6.     }  
  7.     public void setName(String name) {  
  8.         this.name = name;  
  9.     }  
  10.     public String getValue() {  
  11.         return value;  
  12.     }  
  13.     public void setValue(String value) {  
  14.         this.value = value;  
  15.     }  
  16. }  

擷取方法名稱

[java]  view plain copy

Javassist簡單應用小結
Javassist簡單應用小結
  1. @Test  
  2. public void test() throws Exception {  
  3.     // 擷取本地類加載器  
  4.     ClassLoader classLoader = getLocaleClassLoader();  
  5.     // 擷取要修改的類  
  6.     Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");  
  7.     // 執行個體化類型池  
  8.     ClassPool classPool = ClassPool.getDefault();  
  9.     classPool.appendClassPath(new ClassClassPath(clazz));  
  10.     CtClass ctClass = classPool.get(clazz.getName());  
  11.     // 擷取方法  
  12.     CtMethod method = ctClass.getDeclaredMethod("show", ObjectHelper.argumentsToArray(CtClass.class, classPool.get("java.lang.String")));  
  13.     // 判斷是否為靜态方法  
  14.     int staticIndex = Modifier.isStatic(method.getModifiers()) ? 0 : 1;   
  15.     // 擷取方法的參數  
  16.     MethodInfo methodInfo = method.getMethodInfo();  
  17.     CodeAttribute codeAttribute = methodInfo.getCodeAttribute();  
  18.     LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);  
  19.     for (int i = 0; i < method.getParameterTypes().length; i++) {  
  20.         System.out.println("第" + (i + 1) + "個參數名稱為: " + localVariableAttribute.variableName(staticIndex + i));  
  21.     }  
  22. }  

  關于“擷取方法名稱”,其主要作用是:當Java虛拟機加載.class檔案後,會将類方法“去名稱化”,即丢棄掉方法形參的參數名,而是用形參的序列号來傳遞參數。如果要通過Java反射擷取參數的參數名,則必須在編輯是指定“保留參數名稱”。Javassist則不存在這個問題,對于任意方法,都能正确的擷取其參數的參數名。   Spring MVC就是通過方法參數将請求參數進行注入的,這一點比struts2 MVC要友善很多,Spring也是借助了Javassist來實作這一點的。

附錄

  代碼中使用了一個ObjectHelper類,這是我自用的一個小工具類,該類的代碼可 點選檢視。