Lambda-ClassNotFoundException

时间:2019-10-23 20:46:19

标签: java spring lambda

这是我的代码的样子,目前尚不清楚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

2 个答案:

答案 0 :(得分:5)

这是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(来源不明)

导致此问题的特定代码是

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

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

P.S。:如果使用@TransactionaltransactionTemplate是不必要的,因为两者本质上是出于相同的目的。

参考文献:

  1. Lambda编译herehere
  2. Synthetic methods在Java中

答案 1 :(得分:0)

我以前遇到过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,这些Bean将为非null并在构造时完全解析。您必须先初始化这些依赖项,然后将其传递,然后才能成为有效对象。
  3. 这有助于调试,您可以在构造函数中设置断点并验证即将出现的内容。
  4. 如果您的bean设置依赖项存在问题,Spring将会爆炸。弹簧堆栈跟踪并不总是最有用的方法,但是它可以帮助您调试任何未完全隔离和声明以正确方式声明依赖的bean的问题。
  5. 从Spring框架的角度(很难说出幕后发生的事情)以及从应用程序/域逻辑的角度,它都可以消除注入问题的可能性。如果以后仍然为null,则可以调试在构造函数中传递的内容-意味着它要么为null,以后被取消分配,要么存在模棱两可的问题,其中可能定义了两个,并且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批注。

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