概述
Javassist是一款位元組碼編輯工具,可以直接編輯和生成Java生成的位元組碼,以達到對.class檔案進行動态修改的效果。熟練使用這套工具,可以讓Java程式設計更接近與動态語言程式設計。 下面一個方法的目的是擷取一個類加載器(ClassLoader),以加載指定的.jar或.class檔案,在之後的代碼中會使用到。 [java] view plain copy
- private static ClassLoader getLocaleClassLoader() throws Exception {
- List<URL> classPathURLs = new ArrayList<>();
- // 加載.class檔案路徑
- classPathURLs.add(classesPath.toURI().toURL());
- // 擷取所有的jar檔案
- File[] jarFiles = libPath.listFiles(new FilenameFilter() {
- @Override
- public boolean accept(File dir, String name) {
- return name.endsWith(".jar");
- }
- });
- Assert.assertFalse(ObjectHelper.isArrayNullOrEmpty(jarFiles));
- // 将jar檔案路徑寫入集合
- for (File jarFile : jarFiles) {
- classPathURLs.add(jarFile.toURI().toURL());
- }
- // 執行個體化類加載器
- return new URLClassLoader(classPathURLs.toArray(new URL[classPathURLs.size()]));
- }
擷取類型資訊
[java] view plain copy
- @Test
- public void test() throws NotFoundException {
- // 擷取預設類型池對象
- ClassPool classPool = ClassPool.getDefault();
- // 擷取指定的類型
- CtClass ctClass = classPool.get("java.lang.String");
- System.out.println(ctClass.getName()); // 擷取類名
- System.out.println("\tpackage " + ctClass.getPackageName()); // 擷取包名
- System.out.print("\t" + Modifier.toString(ctClass.getModifiers()) + " class " + ctClass.getSimpleName()); // 擷取限定符和簡要類名
- System.out.print(" extends " + ctClass.getSuperclass().getName()); // 擷取超類
- // 擷取接口
- if (ctClass.getInterfaces() != null) {
- System.out.print(" implements ");
- boolean first = true;
- for (CtClass c : ctClass.getInterfaces()) {
- if (first) {
- first = false;
- } else {
- System.out.print(", ");
- }
- System.out.print(c.getName());
- }
- }
- System.out.println();
- }
修改類方法
[java] view plain copy
- @Test
- public void test() throws Exception {
- // 擷取本地類加載器
- ClassLoader classLoader = getLocaleClassLoader();
- // 擷取要修改的類
- Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");
- // 執行個體化類型池對象
- ClassPool classPool = ClassPool.getDefault();
- // 設定類搜尋路徑
- classPool.appendClassPath(new ClassClassPath(clazz));
- // 從類型池中讀取指定類型
- CtClass ctClass = classPool.get(clazz.getName());
- // 擷取String類型參數集合
- CtClass[] paramTypes = {classPool.get(String.class.getName())};
- // 擷取指定方法名稱
- CtMethod method = ctClass.getDeclaredMethod("show", paramTypes);
- // 指派方法到新方法中
- CtMethod newMethod = CtNewMethod.copy(method, ctClass, null);
- // 修改源方法名稱
- String oldName = method.getName() + "$Impl";
- method.setName(oldName);
- // 修改原方法
- newMethod.setBody("{System.out.println(\"執行前\");" + oldName + "($$);System.out.println(\"執行後\");}");
- // 将新方法添加到類中
- ctClass.addMethod(newMethod);
- // 加載重新編譯的類
- clazz = ctClass.toClass(); // 注意,這一行會将類當機,無法在對位元組碼進行編輯
- // 執行方法
- clazz.getMethod("show", String.class).invoke(clazz.newInstance(), "hello");
- ctClass.defrost(); // 解凍一個類,對應freeze方法
- }
動态建立類
[java] view plain copy
- @Test
- public void test() throws Exception {
- ClassPool classPool = ClassPool.getDefault();
- // 建立一個類
- CtClass ctClass = classPool.makeClass("edu.alvin.reflect.DynamiClass");
- // 為類型設定接口
- //ctClass.setInterfaces(new CtClass[] {classPool.get(Runnable.class.getName())});
- // 為類型設定字段
- CtField field = new CtField(classPool.get(String.class.getName()), "value", ctClass);
- field.setModifiers(Modifier.PRIVATE);
- // 添加getter和setter方法
- ctClass.addMethod(CtNewMethod.setter("setValue", field));
- ctClass.addMethod(CtNewMethod.getter("getValue", field));
- ctClass.addField(field);
- // 為類設定構造器
- // 無參構造器
- CtConstructor constructor = new CtConstructor(null, ctClass);
- constructor.setModifiers(Modifier.PUBLIC);
- constructor.setBody("{}");
- ctClass.addConstructor(constructor);
- // 參數構造器
- constructor = new CtConstructor(new CtClass[] {classPool.get(String.class.getName())}, ctClass);
- constructor.setModifiers(Modifier.PUBLIC);
- constructor.setBody("{this.value=$1;}");
- ctClass.addConstructor(constructor);
- // 為類設定方法
- CtMethod method = new CtMethod(CtClass.voidType, "run", null, ctClass);
- method.setModifiers(Modifier.PUBLIC);
- method.setBody("{System.out.println(\"執行結果\" + this.value);}");
- ctClass.addMethod(method);
- // 加載和執行生成的類
- Class<?> clazz = ctClass.toClass();
- Object obj = clazz.newInstance();
- clazz.getMethod("setValue", String.class).invoke(obj, "hello");
- clazz.getMethod("run").invoke(obj);
- obj = clazz.getConstructor(String.class).newInstance("OK");
- clazz.getMethod("run").invoke(obj);
- }
建立代理類
[java] view plain copy
- @Test
- public void test() throws Exception {
- // 執行個體化代理類工廠
- ProxyFactory factory = new ProxyFactory();
- //設定父類,ProxyFactory将會動态生成一個類,繼承該父類
- factory.setSuperclass(TestProxy.class);
- //設定過濾器,判斷哪些方法調用需要被攔截
- factory.setFilter(new MethodFilter() {
- @Override
- public boolean isHandled(Method m) {
- return m.getName().startsWith("get");
- }
- });
- Class<?> clazz = factory.createClass();
- TestProxy proxy = (TestProxy) clazz.newInstance();
- ((ProxyObject)proxy).setHandler(new MethodHandler() {
- @Override
- public Object invoke(Object self, Method thisMethod, Method proceed, Object[] args) throws Throwable {
- //攔截後前置處理,改寫name屬性的内容
- //實際情況可根據需求修改
- System.out.println(thisMethod.getName() + "被調用");
- try {
- Object ret = proceed.invoke(self, args);
- System.out.println("傳回值: " + ret);
- return ret;
- } finally {
- System.out.println(thisMethod.getName() + "調用完畢");
- }
- }
- });
- proxy.setName("Alvin");
- proxy.setValue("1000");
- proxy.getName();
- proxy.getValue();
- }
其中,TestProxy類内容如下: [java] view plain copy
- public class TestProxy {
- private String name;
- private String value;
- public String getName() {
- return name;
- }
- public void setName(String name) {
- this.name = name;
- }
- public String getValue() {
- return value;
- }
- public void setValue(String value) {
- this.value = value;
- }
- }
擷取方法名稱
[java] view plain copy
- @Test
- public void test() throws Exception {
- // 擷取本地類加載器
- ClassLoader classLoader = getLocaleClassLoader();
- // 擷取要修改的類
- Class<?> clazz = classLoader.loadClass("edu.alvin.reflect.TestLib");
- // 執行個體化類型池
- ClassPool classPool = ClassPool.getDefault();
- classPool.appendClassPath(new ClassClassPath(clazz));
- CtClass ctClass = classPool.get(clazz.getName());
- // 擷取方法
- CtMethod method = ctClass.getDeclaredMethod("show", ObjectHelper.argumentsToArray(CtClass.class, classPool.get("java.lang.String")));
- // 判斷是否為靜态方法
- int staticIndex = Modifier.isStatic(method.getModifiers()) ? 0 : 1;
- // 擷取方法的參數
- MethodInfo methodInfo = method.getMethodInfo();
- CodeAttribute codeAttribute = methodInfo.getCodeAttribute();
- LocalVariableAttribute localVariableAttribute = (LocalVariableAttribute)codeAttribute.getAttribute(LocalVariableAttribute.tag);
- for (int i = 0; i < method.getParameterTypes().length; i++) {
- System.out.println("第" + (i + 1) + "個參數名稱為: " + localVariableAttribute.variableName(staticIndex + i));
- }
- }
關于“擷取方法名稱”,其主要作用是:當Java虛拟機加載.class檔案後,會将類方法“去名稱化”,即丢棄掉方法形參的參數名,而是用形參的序列号來傳遞參數。如果要通過Java反射擷取參數的參數名,則必須在編輯是指定“保留參數名稱”。Javassist則不存在這個問題,對于任意方法,都能正确的擷取其參數的參數名。 Spring MVC就是通過方法參數将請求參數進行注入的,這一點比struts2 MVC要友善很多,Spring也是借助了Javassist來實作這一點的。
附錄
代碼中使用了一個ObjectHelper類,這是我自用的一個小工具類,該類的代碼可 點選檢視。