java javassist.CannotCompileException: от java.lang.LinkageError: загрузчик

Мне нужно изменить метод (формулу расчета) без перекомпиляции приложения. Я знаю, что это можно сделать с помощью javassist. Пока пробую на простом примере. В методе поста я вызываю метод createMethodHelper(), который должен заменить метод Helper2. Все в порядке. Но после повторного вызова (перезагрузки страницы) ошибка javassist.CannotCompileException: by java.lang.LinkageError: loader Итак, класс, содержащий единственный метод, который я хочу изменить

package ru.testScandJavaCafee.controller;

public class Helper2 {
public String createList()
    {
        System.out.println("++++");
        return "1000";
    }
}

И класс, из которого я меняю метод

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;
    }

После второго звонка (перезагрузка страницы) ошибка

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

помогите понять как исправить ошибку


person Sergey Solomevich    schedule 08.05.2017    source источник
comment
Вы уже видели эту ошибку о дублированном определении класса attempted duplicate class definition for name: "ru/testScandJavaCafee/controller/Helper2"?   -  person SubOptimal    schedule 08.05.2017
comment
Есть похожая тема, но я не знаю, как применить к моему проекту stackoverflow.com/questions/23336172/   -  person Sergey Solomevich    schedule 08.05.2017


Ответы (1)


Я изучил это из документации и этого руководство. И если вы хотите изменить метод несколько раз, вы можете сделать это следующим образом:

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. Очевидная проблема здесь заключается в том, что в зависимости от того, сколько раз вы вызываете этот метод, вы можете получить множество сгенерированных классов. Поэтому вы можете добавить проверку того, загружены ли эти классы перед созданием.

Вы говорите, что этот метод запускается, когда вы обновляете/загружаете страницу, поэтому, если вы просто хотите, чтобы он запускался один раз, я предлагаю вам поместить этот код в синглтон, который запускается при запуске вашего приложения. Я не уверен, что вы используете для веб-приложения, но JSF и Spring имеют одноэлементные компоненты для такого типа использования.

person tima    schedule 09.05.2017