问题描述
我需要更改方法(计算公式)而不重新编译应用程序.我知道可以在javassist的帮助下完成此操作.到目前为止,我正在尝试一个简单的示例.在post方法中,我调用createMethodHelper()方法,该方法必须更改Helper2方法.一切都好.但是在重复调用(重新加载页面)之后,错误javassist.CannotCompileException:通过java.lang.LinkageError:加载程序因此,包含我要更改的唯一方法的类
I need to change the method (calculation formula) without recompiling the application. I know that this can be done with the help of javassist. So far I'm trying on a simple example. In the post method, I call the createMethodHelper () method, which must change the Helper2 method. All OK. But after a repeated call (reloading the page), the error javassist.CannotCompileException: by java.lang.LinkageError: loaderSo, the class containing the only method I want to change
package ru.testScandJavaCafee.controller;
public class Helper2 {
public String createList()
{
System.out.println("++++");
return "1000";
}
}
以及我用来更改方法的类
And the class from which I change the method
public String createMethodHelper() throws NotFoundException, CannotCompileException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException, InstantiationException, ClassNotFoundException {
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath(this.getClass()));
CtClass cc = pool.get("ru.testScandJavaCafee.controller.Helper2");
cc.defrost();
CtMethod cm = cc.getMethod("createList","()Ljava/lang/String;" );
cc.defrost();
cm.setBody( "{ return \"300 \" ;}" );
cc.defrost();
Class c = cc.toClass();
cc.defrost();
ru.testScandJavaCafee.controller.Helper2 test = (ru.testScandJavaCafee.controller.Helper2) c.newInstance();
String sum = test.createList();
return sum;
}
第二次调用(重新加载页面)后,出现错误
After a second call (reloading the page), the error
javassist.CannotCompileException: by java.lang.LinkageError: loader (instance of org/apache/catalina/loader/WebappClassLoader): attempted duplicate class definition for name: "ru/testScandJavaCafee/controller/Helper2"
at javassist.ClassPool.toClass(ClassPool.java:1085)
at javassist.ClassPool.toClass(ClassPool.java:1028)
at javassist.ClassPool.toClass(ClassPool.java:986)
at javassist.CtClass.toClass(CtClass.java:1110)
at ru.testScandJavaCafee.controller.CoffeeTypeController.createMethodHelper(CoffeeTypeController.java:108)
at ru.testScandJavaCafee.controller.CoffeeTypeController.doPost(CoffeeTypeController.java:56)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:650)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:731)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:218)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:110)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:506)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:169)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:103)
at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:962)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:452)
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1087)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:637)
at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:318)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.LinkageError: loader (instance of org/apache/catalina/loader/WebappClassLoader): attempted duplicate class definition for name: "ru/testScandJavaCafee/controller/Helper2"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.lang.ClassLoader.defineClass(ClassLoader.java:642)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at javassist.ClassPool.toClass2(ClassPool.java:1098)
at javassist.ClassPool.toClass(ClassPool.java:1079)
... 27 more
帮助了解如何纠正错误
推荐答案
我从文档和此教程.而且,如果您想多次修改方法,则可以通过以下方式进行修改:
I did some research on this from the documentation and this tutorial. And if you want to modify the method multiple times, you can do it this way:
public static void main(String[] args) {
try {
// get a new instance of version 1
Object newClassInstance1 = m2("com.tima.Helper2", "public String createList() { System.out.println(\"++++\"); return \"200\";}");
Method method1 = newClassInstance1.getClass().getMethod("createList");
// this executes the createList in the new class com.tima.Helper2
String sum1 = (String) method1.invoke(newClassInstance1);
System.out.println("com.tima.Helper2: " + sum1);
// this shows that the original Helper method was not modified
Helper h = new Helper();
System.out.println("com.tima.Helper: " + h.createList());
// this shows that the com.tima.Helper2 overrides Helper and can be used as Helper with a modified method
Helper h2 = (Helper) newClassInstance1;
System.out.println("com.tima.Helper2 as Helper: " + h2.createList());
// below does the same thing a second time
Object newClassInstance2 = m2("com.tima.Helper3", "public String createList() { System.out.println(\"++++\"); return \"300\";}");
Method method2 = newClassInstance2.getClass().getMethod("createList");
String sum2 = (String) method2.invoke(newClassInstance2);
System.out.println("com.tima.Helper3: " + sum2);
Helper h3 = new Helper();
System.out.println("com.tima.Helper: " + h3.createList());
Helper h4 = (Helper) newClassInstance2;
System.out.println("com.tima.Helper3 as Helper: " + h4.createList());
} catch (Exception e) {
e.printStackTrace();
}
}
public static Object m2(String className, String methodBody)
throws CannotCompileException, InstantiationException, IllegalAccessException, NotFoundException {
// get the pool
ClassPool classPool = ClassPool.getDefault();
// this seems optional, but if it isn't Main.class (my test class) should be replaced with this.getClass()
classPool.insertClassPath(new ClassClassPath(Main.class));
// get the helper class
CtClass helperClass = classPool.get("com.tima.Helper");
// create a new class
CtClass newCtClass = classPool.makeClass(className);
// make it child of Helper
newCtClass.setSuperclass(helperClass);
// this overrides the method in Helper
newCtClass.addMethod(CtNewMethod.make(methodBody, newCtClass));
// get a new instance
Class<?> newClass = newCtClass.toClass();
Object newClassInstance = newClass.newInstance();
return newClassInstance;
}
输出
++++
com.tima.Helper2: 200
++++
com.tima.Helper: 1000
++++
com.tima.Helper2 as Helper: 200
++++
com.tima.Helper3: 300
++++
com.tima.Helper: 1000
++++
com.tima.Helper3 as Helper: 300
基本上,每次调用该方法时,它都会创建一个新类,使Helper
类成为其超类,并覆盖createList
方法.这里最明显的问题是取决于调用此方法的次数,最后可以使用许多生成的类.因此,您可能需要添加在创建之前检查这些类是否已加载的检查.
Basically, everytime you call that method it creates a new class, makes the Helper
class its superclass and overrides the createList
method. The obvious problem here is depending on how many times you call this method you can wind up with a lot of generated classes. So you may want to add checking of if these class are already loaded before creating.
您说此方法在刷新/加载页面时运行,因此,如果您只希望运行一次,建议您将此代码放在应用程序启动时运行的单例中.我不确定您要为Web应用程序使用什么,但是JSF和Spring都具有用于这种类型使用的Singleton bean.
You say that this method runs when you refresh / load the page so if you just want it to run once, I suggest you put this code in a singleton that runs when your application starts. I'm not sure what you are using for the web application but JSF and Spring both have singleton beans for this type of use.
这篇关于java javassist.CannotCompileException:通过java.lang.LinkageError:加载程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!