Lambda - ClassNotFoundException

8

Hier ist, wie mein Code aussieht, und es ist unklar, wie / warum executorService.submit(work::get)eine ClassNotFoundExceptionin die betreffende anonyme Klasse geworfen werden soll . Es kommt nicht immer vor, aber sobald diese Ausnahme auftritt, scheint sie nicht wiederhergestellt zu werden. Nachfolgende Anforderungen werden dann mit denselben Ausnahmen erfüllt. Weiß jemand, was dies verursachen könnte?

BEARBEITEN: Ich kann bestätigen, dass entweder alle Aufrufe dieser Methode funktionieren oder keiner in einer VM-Sitzung - es ist nicht so, dass einige erfolgreich sind, während andere aufgrund dieser Ausnahme fehlschlagen.

Weitere Bearbeitung: https://bugs.openjdk.java.net/browse/JDK-8148560 ist genau der Fehler, den ich habe, aber dieser wurde geschlossen, da er nicht reproduzierbar war und / oder der Reporter nicht antwortete. Es sieht irgendwie so aus, als ob der anonyme Typ, der aus dem Lambda-Ausdruck resultiert, Müll ist, der gesammelt wird, bevor der Executor den Ausdruck ausführen kann, aber offensichtlich nicht immer. Das verwendete JDK ist 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
    }
}

So sieht die Ausnahme-Stacktrace aus (Zeilennummern stimmen nicht überein, da der obige Code nur ein Prototyp ist):

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
Mystarrocks
quelle
Dies geschieht in lokalen Arbeitsbereichen oder Prod / Pre-Prod-Umgebungen?
Subir Kumar Sao
@SubirKumarSao Nicht-Prod-Umgebungen (nicht lokal), aber dies könnte auch in Prod sehr gut passieren.
Mystarrocks
Gibt es einen bestimmten Grund, die Methode @Transactionalauch mit dem transactionTemplateInneren kommentieren zu lassen ?
Bond - Java Bond

Antworten:

5

Dies ist der Fall bei einer von Lambda generierten Synthesemethode, die die erforderliche Klasse (dh TransactionCallback ) und damit den folgenden Fehler nicht finden kann

Auslöser: java.lang.NoClassDefFoundError: com / ab / cde / ct / service / impl / IngestionService $$ Lambda $ 53 unter com.ab.cde.ct.service.impl.IngestionService $$ Lambda $ 53/812375226.get $ Lambda (Unbekannte Quelle)

Der bestimmte Code, der dieses Problem verursacht, ist

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

Um dies zu umgehen, ändern Sie den Code wie folgt

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

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

Wenn oben immer noch nicht funktioniert, verwenden Sie die folgende Problemumgehung

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

Lassen Sie in Kommentaren wissen, ob weitere Informationen erforderlich sind.

PS: Es besteht keine Notwendigkeit, @Transactionalwenn verwendet transactionTemplatewird, da beide im Wesentlichen dem gleichen Zweck dienen.

Verweise:

  1. Lambda-Zusammenstellung hier und hier
  2. Synthesemethoden in Java
Bond - Java Bond
quelle
Danke - das habe ich mir auch über diesen Link ausgedacht , aber sollte es nicht immer scheitern, wenn ja? Warum funktioniert es die meiste Zeit und schlägt nur in bestimmten VM-Sitzungen fehl?
Mystarrocks
Wie für die transactionTemplateenthält diese Methode Wechselwirkungen, die die Standard verwenden @TransactionalVerhaltensweisen sowie diejenigen, die das benutzerdefinierte Vorlagenobjekt verwenden. Ich habe den ganzen Code der Kürze halber weggelassen.
Mystarrocks
Vielleicht erwähnenswert - es sieht so aus, als ob die TransactionCallbacknoch nicht geladen ist, wenn diese Methode die erste ist, die sie in einer bestimmten VM-Sitzung verwendet. Erklärt das das Verhalten?
Mystarrocks
1
Richtig, die Klassenladesequenz ist hier von entscheidender Bedeutung, warum sie irgendwann und nicht immer fehlschlägt. Die erwähnte Lösung beseitigt dies, indem die Klasse vor dem Ausführen explizit geladen wird.
Bond - Java Bond
Dies geschah trotz der Problemumgehungen erneut, wodurch die Akzeptanz dieser Antwort rückgängig gemacht wurde. Dem Fehlerstapel-Trace nach zu urteilen, scheint es, dass die Klasse, deren Fehlen beanstandet wird, die anonyme Klasse ist, die aus dem Lambda selbst generiert wurde. Keiner der in der Funktionsschnittstellenmethode genannten Typen, obwohl ich mir nicht sicher bin. bugs.openjdk.java.net/browse/JDK-8148560 ist im Grunde das, was ich erlebe.
Mystarrocks
0

Ich hatte dies zuvor sowohl mit DI-Problemen als auch mit Mehrdeutigkeitsfehlern / Konfigurationsproblemen bei der Paketauflösung. Ich gehe von Ihrem Beitrag aus, dass der Fehler nach erfolgreichem Start und genau beim Aufruf dieser Zeile in der Methode auftritt und im Debugger getroffen werden kann.

Erster Vorschlag:

Überprüfen Sie mit Gradle / Maven abhängige Pakete, um sicherzustellen, dass alles die benötigte Version hat, und Sie überschreiben keine Version global, die sich auf ein abhängiges Paket auswirken kann, für das eine höhere oder niedrigere Version dieser Abhängigkeit erforderlich ist.

Einige niedrig hängende Früchte, die Sie zuerst probieren sollten (wenn es leicht genug ist, sie zu pflücken):

  • Aktualisieren Sie Ihre JDK- oder Java-Version (oder prüfen Sie, ob ein anderer Entwickler in Ihrem Team eine andere Version hat und das Problem erneut testen kann).
  • Aktualisieren Sie Ihre Version des Frühlings (auch eine kleinere Version)
  • Aktualisieren Sie Ihre IDE
  • Fügen Sie die Protokollierung hinzu und prüfen Sie, ob das Problem in Release-Umgebungen reproduziert werden kann.

In Bezug auf die Abhängigkeitsinjektion,

Ich würde empfehlen, Folgendes zu versuchen. Dies ist auch eine gute Vorgehensweise für die Abhängigkeitsinjektion im Frühjahr, da Spring eine explizitere Abhängigkeitszuordnung erhält und Ihre Fähigkeit zum Debuggen der Anwendungsabhängigkeiten erhöht wird.

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

Es gibt einige Gründe, warum ich dies empfehle:

  1. Wenn in Java kein Konstruktor definiert ist, bedeutet dies, dass es einen Standardkonstruktor gibt, und der Compiler generiert einen Konstruktor für Sie. Für den Frühling kann dies verwirrend sein und auch die Leistung verringern.
  2. Das explizite Definieren dieses Konstruktors sagt Spring: Ich verlasse mich auf diese beiden Abhängigkeiten, die ich auch als Beans eingerichtet habe, die nicht null sind und bei der Konstruktion vollständig aufgelöst werden. Sie müssen diese Abhängigkeiten zuerst initialisieren und übergeben, bevor dies ein gültiges Objekt sein kann.
  3. Dies hilft beim Debuggen. Sie können einen Haltepunkt im Konstruktor festlegen und überprüfen, was hereinkommt.
  4. Wenn es ein Problem mit Ihrem Bean-Setup für die Abhängigkeiten gibt, explodiert Spring. Die Federstapelspuren sind nicht immer die hilfreichsten, aber sie können Ihnen beim Debuggen von Problemen helfen, bei denen Sie Bohnen, von denen Sie abhängig sind, nicht vollständig isolieren und deklarieren, auf die richtige Weise.
  5. Auf diese Weise können Sie die Möglichkeit von Problemen mit der Injektion ausschließen, sowohl aus Sicht von Spring Framework (schwer zu sagen, was hinter den Kulissen passiert) als auch aus Sicht Ihrer Anwendungs- / Domänenlogik. Wenn es später immer noch null ist, hätten Sie debuggen können, was im Konstruktor übergeben wurde - was bedeutet, dass es entweder null war, später aufgehoben wurde oder es ein Mehrdeutigkeitsproblem gibt, bei dem möglicherweise zwei definiert sind und Spring wird Übergeben Sie den ersten erstellten, obwohl möglicherweise mehrere executorServices erstellt wurden.

Da dies eine gültige Bean-Definition sein sollte, solange die Klasse im Komponentenscan Ihrer Konfiguration enthalten ist, müssen Sie die Bean möglicherweise explizit in einer Konfigurationsklasse definieren, insbesondere wenn Sie mehrere Beans jedes Typs haben (was auch Ihr Problem sein könnte )

Ex:

@Configuration
class SomeConfiguration {

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

Beachten Sie, dass für die Konfiguration die Parameter für die Bean-Methode bis zum Frühjahr automatisch gesendet werden, sobald diese Beans in dieser Konfiguration oder einer anderen Konfiguration initialisiert wurden. Ziemlich cool, oder?

Auch der Name Ihrer Bean entspricht hier dem Methodennamen. Wenn Sie mehrere Beans desselben Typs haben, die spring als Parameter übergeben könnte, müssen Sie spring möglicherweise mitteilen, welcher Bean-Name verwendet werden soll. Verwenden Sie dazu die Annotation @Qualifier.

Ich hoffe wirklich, dass dies hilft oder zumindest bestätigt, dass die Instanziierung korrekt erfolgt.

TheJeff
quelle