sun.reflect.MethodAccessorImpl 在重新部署时使用 Janino + Logback + Tomcat 的 ClassNotFoundException

ClassNotFoundException for sun.reflect.MethodAccessorImpl using Janino + Logback + Tomcat on redeploy

我在 tomcat 8.5.32(最新的 java 8、1.8.0_181)下部署了一个应用程序,使用最新的 logback 1.2.3、janino 3.0.8 和slf4f 1.7.25.

我的 logback xml 有一个这样的过滤器:

<filter class="ch.qos.logback.core.filter.EvaluatorFilter">   
<evaluator> 
        <expression>return message != null &amp;&amp; message.contains("127.0.0.1");
</expression>   
</evaluator>   
<OnMismatch>NEUTRAL</OnMismatch>   
<OnMatch>DENY</OnMatch>
</filter>

在我重新部署之前一切正常。然后我得到这样的异常:

Jul 30, 2018 8:00:18 AM org.apache.catalina.startup.HostConfig reload
WARNING: Error during context [] restart
org.apache.catalina.LifecycleException: Failed to stop component [StandardEngine[Catalina-b].StandardHost[localhost].StandardContext[]]
        at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:238)
        at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:142)
        at org.apache.catalina.startup.HostConfig.reload(HostConfig.java:1386)
        at org.apache.catalina.startup.HostConfig.checkResources(HostConfig.java:1287)
        at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1586)
        at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:280)
        at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:94)
        at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:1136)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1372)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1376)
        at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1344)
        at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.NoClassDefFoundError: sun/reflect/MethodAccessorImpl
        at sun.misc.Unsafe.defineClass(Native Method)
        at sun.reflect.ClassDefiner.defineClass(ClassDefiner.java:63)
        at sun.reflect.MethodAccessorGenerator.run(MethodAccessorGenerator.java:399)
        at sun.reflect.MethodAccessorGenerator.run(MethodAccessorGenerator.java:394)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.reflect.MethodAccessorGenerator.generate(MethodAccessorGenerator.java:393)
        at sun.reflect.MethodAccessorGenerator.generateMethod(MethodAccessorGenerator.java:75)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:53)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.codehaus.janino.ScriptEvaluator.evaluate(ScriptEvaluator.java:756)
        at org.codehaus.janino.ScriptEvaluator.evaluate(ScriptEvaluator.java:748)
        at ch.qos.logback.core.boolex.JaninoEventEvaluatorBase.evaluate(JaninoEventEvaluatorBase.java:72)
        at ch.qos.logback.core.filter.EvaluatorFilter.decide(EvaluatorFilter.java:65)
        at ch.qos.logback.core.spi.FilterAttachableImpl.getFilterChainDecision(FilterAttachableImpl.java:52)
        at ch.qos.logback.core.AppenderBase.getFilterChainDecision(AppenderBase.java:134)
        at ch.qos.logback.core.AppenderBase.doAppend(AppenderBase.java:80)
        at ch.qos.logback.core.spi.AppenderAttachableImpl.appendLoopOnAppenders(AppenderAttachableImpl.java:48)
        at ch.qos.logback.classic.Logger.appendLoopOnAppenders(Logger.java:272)
        at ch.qos.logback.classic.Logger.callAppenders(Logger.java:259)
        at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:441)
        at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:395)
        at ch.qos.logback.classic.Logger.log(Logger.java:787)
        at org.slf4j.bridge.SLF4JBridgeHandler.callLocationAwareLogger(SLF4JBridgeHandler.java:224)
        at org.slf4j.bridge.SLF4JBridgeHandler.publish(SLF4JBridgeHandler.java:301)
        at java.util.logging.Logger.log(Logger.java:738)
        at java.util.logging.Logger.doLog(Logger.java:765)
        at java.util.logging.Logger.logp(Logger.java:1042)
        at org.apache.juli.logging.DirectJDKLog.log(DirectJDKLog.java:181)
        at org.apache.juli.logging.DirectJDKLog.error(DirectJDKLog.java:147)
        at org.apache.catalina.core.StandardContext.listenerStop(StandardContext.java:4780)
        at org.apache.catalina.core.StandardContext.stopInternal(StandardContext.java:5411)
        at org.apache.catalina.util.LifecycleBase.stop(LifecycleBase.java:226)
        ... 11 more
Caused by: java.lang.ClassNotFoundException: sun.reflect.MethodAccessorImpl
        at org.codehaus.janino.ByteArrayClassLoader.findClass(ByteArrayClassLoader.java:68)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
        at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
        ... 44 more

我的理论是 juli->SLF4J->logback->janino 最终使用容器 class 加载器,最终停止,但日志框架没有意识到这一点。

知道如何解决这个问题吗?

原来是logback监听器在我的web.xml

中不是第一个导致的问题
<listener>
    <listener-class>ch.qos.logback.classic.selector.servlet.ContextDetachingSCL</listener-class>
</listener>

https://logback.qos.ch/manual/loggingSeparation.html 说的很清楚:

Most containers invoke the contextInitialized() method of listeners in the order in which they are declared but invoke their contextDestroyed() method in reverse order. It follows that if you have multiple ServletContextListener declarations in your web.xml, then ContextDetachingSCL should be declared first so that its contextDestroyed() method is invoked last during application shutdown.