Lambda - ClassNotFoundException


8

Đây là mã của tôi trông như thế nào và không rõ bằng cách nào / tại sao executorService.submit(work::get)lại ném một ClassNotFoundExceptionlớp ẩn danh vào câu hỏi. Nó không xảy ra mọi lúc, nhưng một khi ngoại lệ này gặp phải, nó dường như không phục hồi - các yêu cầu tiếp theo được đáp ứng với cùng một ngoại lệ. Bất cứ ai cũng biết những gì có thể gây ra điều này xảy ra?

EDIT: Tôi có thể xác nhận rằng tất cả các cuộc gọi đến phương thức này đều hoạt động, hoặc không có cuộc gọi nào trong phiên VM - nó không giống như một số thành công trong khi các cuộc gọi khác thất bại do ngoại lệ đã nói.

Chỉnh sửa thêm: https://bugs.openjdk.java.net/browse/JDK-8148560 là chính xác lỗi tôi gặp phải, nhưng lỗi đó đã bị đóng do không thể tái tạo và / hoặc phóng viên không phản hồi. Nó bằng cách nào đó trông giống như kiểu ẩn danh do biểu thức lambda là rác được thu thập trước khi người thực thi thực hiện biểu thức, nhưng rõ ràng không phải lúc nào cũng vậy. Các jdk đang sử dụng là 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
    }
}

Đây là giao diện của stacktrace ngoại lệ (dòng nos. Sẽ không tương ứng vì đoạn mã trên chỉ là một nguyên mẫu):

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

Điều này xảy ra trong không gian làm việc cục bộ hoặc môi trường prod / pre-prod?
Subir Kumar Sao

@SubirKumarSao môi trường không prod (không phải cục bộ), nhưng điều này rất có thể xảy ra trong prod là tốt.
Mystarrocks

Bất kỳ lý do cụ thể để có phương pháp chú thích @Transactionalcũng như sử dụng transactionTemplatebên trong?
Trái phiếu

Câu trả lời:


5

Đây là trường hợp của phương thức tổng hợp được tạo bởi lambda mà không thể tìm thấy lớp được yêu cầu (ví dụ: TransactionCallback ) và do đó lỗi dưới đây

Nguyên nhân bởi: java.lang.NoClassDefFoundError: com / ab / cde / ct / service / impl / IngestionService $$ Lambda $ 53 tại com.ab.cde.ct.service.impl.IngestionService $$ Lambda $ 53/812375226.get (Nguồn không rõ)

Mã cụ thể gây ra vấn đề này là

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

Để vượt qua điều này, sửa đổi mã như dưới đây

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

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

Nếu ở trên vẫn không làm việc sử dụng dưới cách giải quyết

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

Hãy cho biết trong ý kiến ​​nếu cần thêm thông tin.

PS: Không cần @Transactionalnếu transactionTemplateđang được sử dụng vì cả hai về cơ bản đều phục vụ cùng một mục đích.

Người giới thiệu:

  1. Biên soạn Lambda ở đâyđây
  2. Phương pháp tổng hợp trong java

Cảm ơn - Tôi đã tìm ra nhiều từ liên kết này , nhưng không phải lúc nào nó cũng thất bại? Tại sao nó hoạt động hầu hết thời gian và chỉ thất bại trong một số phiên VM nhất định?
Mystarrocks

Đối với transactionTemplate, phương thức này chứa các tương tác sử dụng các @Transactionalhành vi mặc định cũng như các tương tác sử dụng đối tượng mẫu tùy chỉnh. Tôi đã bỏ qua tất cả các mã cho ngắn gọn.
Mystarrocks

Có lẽ đáng nói - có vẻ như TransactionCallbackchưa được tải nếu phương thức này là phương thức đầu tiên sử dụng nó trong một phiên VM nhất định. Điều đó giải thích hành vi?
Mystarrocks

1
Chính xác, trình tự tải lớp là điều cốt yếu ở đây tại sao đôi khi nó thất bại và không phải lúc nào cũng vậy. Giải pháp được đề cập loại bỏ điều này bằng cách buộc rõ ràng lớp được tải trước khi thực hiện.
Trái phiếu

Điều này xảy ra một lần nữa mặc dù có cách giải quyết, vì vậy không thể chấp nhận câu trả lời này. Đánh giá theo dấu vết ngăn xếp lỗi, có vẻ như lớp bị phàn nàn là mất tích là lớp ẩn danh được tạo từ chính lambda; không phải bất kỳ loại nào được đề cập trong phương thức giao diện chức năng, mặc dù tôi không chắc chắn. bug.openjdk.java.net/browse/JDK-8148560 về cơ bản là những gì tôi đang trải nghiệm.
Mystarrocks

0

Tôi đã có điều này trước đây với cả hai vấn đề DI và với các vấn đề mơ hồ / lỗi cấu hình trong độ phân giải gói. Tôi giả sử từ bài đăng của bạn rằng lỗi xảy ra sau khi khởi động thành công và chính xác là do việc gọi dòng đó trong phương thức và có thể bị tấn công trong trình gỡ lỗi.

Gợi ý đầu tiên:

Với Gradle / Maven, hãy kiểm tra các gói phụ thuộc để đảm bảo mọi thứ đều có phiên bản cần thiết và bạn không ghi đè phiên bản trên toàn cầu có thể ảnh hưởng đến gói phụ thuộc yêu cầu phiên bản phụ thuộc cao hơn hoặc thấp hơn.

Một số trái cây treo thấp để thử đầu tiên (nếu nó đủ dễ để chọn):

  • Cập nhật phiên bản JDK hoặc phiên bản Java của bạn (hoặc xem nếu một nhà phát triển khác trong nhóm của bạn có phiên bản khác và họ có thể khắc phục sự cố không)
  • Cập nhật phiên bản mùa xuân của bạn (ngay cả phiên bản nhỏ)
  • Cập nhật IDE của bạn
  • Thêm ghi nhật ký và kiểm tra xem sự cố có thể được sao chép trong môi trường phát hành hay không.

Về tiêm phụ thuộc,

Tôi khuyên bạn nên thử một cái gì đó như sau .. và cũng là một cách tốt để tiêm phụ thuộc vào mùa xuân, vì nó mang lại cho mùa xuân một bản đồ phụ thuộc rõ ràng hơn và tăng khả năng gỡ lỗi các phụ thuộc ứng dụng.

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

Có một vài lý do tôi khuyên bạn nên điều này:

  1. Trong java, khi không có hàm tạo nào được định nghĩa, có nghĩa là có một hàm tạo mặc định và trình biên dịch sẽ tạo ra một hàm tạo cho bạn. Để mùa xuân, điều này có thể gây nhầm lẫn và cũng làm giảm hiệu suất.
  2. Xác định rõ ràng hàm tạo này cho Spring: Tôi đang dựa vào hai phụ thuộc này mà tôi cũng đã thiết lập là Đậu sẽ không có giá trị và được giải quyết hoàn toàn khi xây dựng. Bạn phải khởi tạo các phụ thuộc đó trước và chuyển chúng vào trước khi đây có thể là một đối tượng hợp lệ.
  3. Điều này giúp gỡ lỗi, bạn có thể đặt điểm dừng trong hàm tạo và xác thực những gì đang đến.
  4. Nếu có vấn đề với thiết lập bean của bạn cho các phụ thuộc, Spring sẽ phát nổ. Dấu vết ngăn xếp mùa xuân không phải lúc nào cũng hữu ích nhất, nhưng nó có thể giúp bạn gỡ lỗi mọi vấn đề khi bạn không cách ly hoàn toàn và khai báo các loại đậu mà bạn phụ thuộc theo cách chính xác.
  5. Nó cho phép bạn loại bỏ khả năng xảy ra bất kỳ vấn đề nào khi tiêm, cả từ quan điểm Spring Framework (khó có thể nói điều gì xảy ra đằng sau hậu trường) và từ quan điểm logic ứng dụng / tên miền của bạn. Nếu sau đó nó vẫn là null, bạn đã có thể gỡ lỗi những gì đã được truyền trong hàm tạo - có nghĩa là nó bị vô hiệu hóa, được phân bổ lại sau đó hoặc có một vấn đề mơ hồ trong đó có thể có hai định nghĩa và mùa xuân sẽ vượt qua trong cái đầu tiên được tạo mặc dù cuối cùng có thể có nhiều dịch vụ thực thi được tạo.

Vì đây phải là một định nghĩa bean hợp lệ miễn là lớp được bao gồm trong quét thành phần cấu hình của bạn, bạn có thể cần xác định rõ ràng bean trong một lớp cấu hình, đặc biệt là nếu bạn có nhiều bean của từng loại (cũng có thể là vấn đề của bạn )

Ví dụ:

@Configuration
class SomeConfiguration {

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

Lưu ý rằng đối với cấu hình, các thông số cho phương thức bean sẽ được gửi tự động vào mùa xuân sau khi các bean đó được khởi tạo trong cấu hình này hoặc cấu hình khác. Khá tuyệt nhỉ?

Ngoài ra tên của bean của bạn tương ứng với tên phương thức ở đây và nếu bạn có nhiều bean cùng loại có thể truyền vào làm tham số, bạn có thể cần nói cho mùa xuân biết nên sử dụng tên bean nào. Để làm điều này, bạn sử dụng chú thích @Qualifier.

Tôi thực sự hy vọng điều này sẽ giúp, hoặc ít nhất là xác nhận việc khởi tạo đang diễn ra chính xác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.