【问题标题】:Lambda - ClassNotFoundExceptionLambda - ClassNotFoundException
【发布时间】:2019-12-24 03:15:23
【问题描述】:

这是我的代码的样子,目前尚不清楚executorService.submit(work::get) 如何/为什么会在有问题的匿名类上抛出ClassNotFoundException。它不会一直发生,但是一旦遇到此异常,它似乎就不会恢复 - 随后的请求会遇到相同的异常。有谁知道是什么原因导致这种情况发生?

编辑:我可以确认在 VM 会话中对该方法的所有调用都有效,或者没有一个有效 - 它不像某些成功,而另一些则由于上述异常而失败。

进一步编辑:https://bugs.openjdk.java.net/browse/JDK-8148560 正是我遇到的错误,但该错误已关闭,因为它不可重现和/或记者没有回应。不知何故,从 lambda 表达式产生的匿名类型在执行程序开始执行表达式之前被垃圾收集,但显然并非总是如此。使用的jdk是openjdk1.8.0_221。

package com.ab.cde.ct.service.impl;

@Service
public class IngestionService {
    @Autowired private TransactionTemplate transactionTemplate;
    @Autowired private AsyncTaskExecutor executorService;

    @Transactional
    public void ingest(Data data) {
        Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
            // actual work on the data object, enclosed in a try/catch/finally
        });
        executorService.submit(work::get); // this is where the exception gets thrown
    }
}

这是异常堆栈跟踪的样子(行号不对应,因为上面的代码只是一个原型):

2019-10-23 19:11:35,267|[http-apr-26001-exec-10]|[B6AC864143092042BBB4A0876BB51EB6.1]|[]|[ERROR] web.error.ErrorServlet  [line:142] org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
    at org.springframework.web.servlet.DispatcherServlet.triggerAfterCompletionWithError(DispatcherServlet.java:1275)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:951)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:867)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:951)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:853)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:661)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:827)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:742)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231)
Caused by: java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53
    at com.ab.cde.ct.service.impl.IngestionService$$Lambda$53/812375226.get$Lambda(Unknown Source)
    at com.ab.cde.ct.service.impl.IngestionService.ingest(IngestionService.java:264)
    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 org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:317)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
    at org.springframework.transaction.interceptor.TransactionInterceptor$1.proceedWithInvocation(TransactionInterceptor.java:96)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:260)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:94)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
    at com.sun.proxy.$Proxy252.ingest(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.ab.cde.ct.service.impl.IngestionService$$Lambda$53
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1364)
    at org.apache.catalina.loader.WebappClassLoaderBase.loadClass(WebappClassLoaderBase.java:1185)
    ... 115 more

【问题讨论】:

  • 这发生在本地工作区或 prod/pre-prod 环境中?
  • @SubirKumarSao 非 prod 环境(非本地),但这也很可能发生在 prod 中。
  • 是否有任何特殊原因需要使用@Transactional 注释方法以及使用transactionTemplate 内的@ ?

标签: java spring lambda


【解决方案1】:

这是由 lambda 生成的合成方法的情况,它无法找到所需的类(即TransactionCallback),因此出现以下错误

原因:java.lang.NoClassDefFoundError: com/ab/cde/ct/service/impl/IngestionService$$Lambda$53 在 com.ab.cde.ct.service.impl.IngestionService$$Lambda$53/812375226.get$Lambda(Unknown Source)

导致此问题的特定代码是

Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
        // actual work on the data object, enclosed in a try/catch/finally
});

要通过这个修改代码如下

TransactionCallback<Optional<String>> callback = transactionStatus -> {
      // your processing goes here  
      return Optional.of("some value"); 
};

Supplier<Optional<String>> work = () -> transactionTemplate.execute(callback);

如果上述方法仍然不起作用,请使用以下解决方法

Object callback = (TransactionCallback<Optional<String>>)transactionStatus -> {
     // your processing goes here     
     return Optional.of("some value");
};

Supplier<Optional<String>> work = () -> transactionTemplate.execute((TransactionCallback<Optional<String>>)callback);

如果需要更多信息,请在 cmets 中告知。

P.S.:如果transactionTemplate 被使用,则不需要@Transactional,因为两者的目的基本相同。

参考资料:

  1. Lambda 编译here 和here
  2. Synthetic methods 在java中

【讨论】:

  • 谢谢 - 我从这个link 中得到了很多,但如果是这样,它不应该总是失败吗?为什么它大部分时间都能正常工作,而只在某些 VM 会话中失败?
  • 对于transactionTemplate,此方法包含使用默认@Transactional 行为以及使用自定义模板对象的交互。为简洁起见,我省略了所有代码。
  • 也许值得一提 - 如果此方法恰好是在给定 VM 会话中使用它的第一个方法,则看起来 TransactionCallback 尚未加载。这能解释这种行为吗?
  • 正确,类加载顺序在这里很重要,为什么它有时会失败,而不是总是失败。提到的解决方案通过在执行之前显式强制加载类来消除这种情况。
  • 尽管有变通办法,但又发生了这种情况,因此撤消了对这个答案的接受。从错误堆栈跟踪来看,被抱怨的类似乎是从 lambda 本身生成的匿名类;不是功能接口方法中提到的任何类型,尽管我不确定。 bugs.openjdk.java.net/browse/JDK-8148560 基本上是我正在经历的。
【解决方案2】:

我之前遇到过 DI 问题和包解析中的歧义错误/配置问题。我从您的帖子中假设错误发生在成功启动后,并且恰好在方法中调用该行时发生,并且可以在调试器中命中。

第一个建议:

使用 Gradle/Maven,检查依赖包以确保所有内容都具有所需的版本,并且您不会全局覆盖可能影响需要更高或更低版本的依赖包的依赖包的版本。

先尝试一些低垂的果实(如果它很容易采摘的话):

  • 更新您的 JDK 版本或 Java 版本(或查看您团队中的其他开发人员是否有不同的版本,他们可以重现该问题)
  • 更新您的 spring 版本(即使是次要版本)
  • 更新您的 IDE
  • 添加日志并检查问题是否可以在发布环境中重现。

关于依赖注入,

我建议尝试以下类似的方法。这也是在 spring 中进行依赖注入的一个好习惯,因为它为 spring 提供了更明确的依赖映射,并提高了调试应用程序依赖项的能力。

@Service
public class IngestionService {

    private TransactionTemplate transactionTemplate;
    private AsyncTaskExecutor executorService;

    public IngestionService(TransactionTemplate transactionTemplate, AsyncTaskExecutor executorService) {
         this.transactionTemplate = transactionTemplate;
         this.executorService = executorService;
    }

    @Transactional
    public void ingest(Data data) {
        Supplier<Optional<String>> work = () -> transactionTemplate.execute(s -> {
            // actual work on the data object, enclosed in a try/catch/finally
        });
        executorService.submit(work::get); // this is where the exception gets thrown
    }
}

我推荐这个有几个原因:

  1. 在java中,当没有定义构造函数时,暗示有默认构造函数,编译器会为你生成一个构造函数。到春天,这可能会令人困惑,也会降低性能。
  2. 定义这个构造函数明确地告诉 Spring:我依赖这两个依赖项,我也将它们设置为 Bean,它们将是非空的并且在构造时完全解析。您必须先初始化这些依赖项并将它们传入,然后才能成为有效对象。
  3. 这有助于调试,您可以在构造函数中设置断点并验证即将到来的内容。
  4. 如果依赖项的 bean 设置存在问题,Spring 将爆炸。 spring 堆栈跟踪并不总是最有帮助的,但它可以帮助您调试任何您没有以正确方式完全隔离和声明依赖的 bean 的问题。
  5. 无论是从 Spring 框架的角度(很难说出幕后发生的情况)还是从应用程序/域逻辑的角度来看,它都允许您消除任何注入问题的可能性。如果它稍后仍然为空,您将能够调试在构造函数中传递的内容 - 这意味着它要么是空的,要么是稍后被取消分配的,要么是存在歧义问题,其中可能有两个定义,而 spring 将传递创建的第一个,即使最终可能创建了多个 executorServices。

只要类包含在配置的组件扫描中,这应该是一个有效的 bean 定义,因此您可能需要在配置类中显式定义 bean,特别是如果您有每种类型的多个 bean(也可以是你的问题)

例如:

@Configuration
class SomeConfiguration {

    @Bean
    public IngestionService myIngestionServiceDefaultBeanNameChangeMe(TransactionTemplate transactionTemplateParamSentBySpringAutomaticallyChangeMyName, AsyncTaskExecutor executorServiceSentBySpringAutomaticallyChangeMyName) {
         return new IngestionService(transactionTemplateParamSentBySpringAutomaticallyChangeMyName, executorServiceSentBySpringAutomaticallyChangeMyName);
    }
}

请注意,对于配置,一旦这些 bean 在此配置或其他配置中初始化,bean 方法的参数将由 spring 自动发送。很酷吧?

另外你的bean的名字和这里的方法名是对应的,如果你有多个相同类型的bean,spring可以作为参数传入,你可能需要告诉spring使用哪个bean的名字。为此,您将使用 @Qualifier 注释。

我真的希望这会有所帮助,或者至少验证实例化是否正确发生。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-03
    • 1970-01-01
    • 2020-11-02
    • 1970-01-01
    • 1970-01-01
    • 2023-03-16
    相关资源
    最近更新 更多