使用Java 8流API时出现Aspectj BootstrapMethodError

3

所以我在这里 - 运行着一个使用Spring Roo的Spring应用程序。

我习惯将我的控制器切割成方面,因此我的主控制器看起来像这样:

@Controller
@RequestMapping("/apples")
@SessionAttributes(types = {Apple.class})
public class AppleController {
}

还有其他方面可以扩展其功能,例如:

privileged aspect AppleController_Basics {

    @RequestMapping(value = "/allApples", produces = "text/html", method=RequestMethod.GET)
    public String AppleController.allApples(Model model) {
       ...
       return "apples/list";
    }
}

现在,当我尝试在方面内使用Java 8 stream API时:
apples.stream().filter(a -> a.isSweet()).collect(Collectors.toList());

我遇到了以下异常:
org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: com.apple.web.AppleController.lambda$0(Lcom/apple/model/Apple;)Z

当我使用流API处理除了苹果本身之外的其他实体时,我会得到一个略微不同的异常:

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access method com.apple.web.AppleController.lambda$0(Lcom/apple/security/AppleEater;)Z from class com.apple.web.aspects.AppleController_Basics

使用forEach时,我遇到了OutOfMemoryError::

apples.forEach(System.out::println);

org.springframework.web.util.NestedServletException: Handler processing failed; nested exception is java.lang.OutOfMemoryError: Java heap space

当我在主类中使用这些表达式时,一切都正常。

插件看起来像这样:

<plugin>
   <groupId>org.codehaus.mojo</groupId>
   <artifactId>aspectj-maven-plugin</artifactId>
   <version>1.9</version>
   <dependencies>
      <dependency>
         <groupId>org.aspectj</groupId>
         <artifactId>aspectjrt</artifactId>
         <version>1.8.10</version>
      </dependency>
      <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjtools</artifactId>
          <version>1.8.10</version>
      </dependency>
   </dependencies>
   <executions>
      <execution>
         <phase>process-sources</phase>
         <goals>
            <goal>compile</goal>
         </goals>
      </execution>
   </executions>
   <configuration>
      <complianceLevel>1.8</complianceLevel>
      <outxml>true</outxml>
      <aspectLibraries>
         <aspectLibrary>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
         </aspectLibrary>
      </aspectLibraries>
      <source>1.8</source>
      <target>1.8</target>
      <showWeaveInfo>true</showWeaveInfo>
      <weaveWithAspectsInMainSourceFolder>false</weaveWithAspectsInMainSourceFolder>
   </configuration>
</plugin>

我尝试了不同的方法来更改我的AspectJ插件配置以使其正常工作,但都没有成功。我非常困惑,希望能得到任何提示或帮助,请不要讨厌我 <3

javap -c -p AppleController.class

public java.lang.String allApples(org.springframework.ui.Model);
Code:
  0: aload_0
  1: aload_1
  2: invokestatic  #528                // Method com/apple/web/aspects/AppleController_Basics.ajc$interMethod$com_apple_web_aspects_AppleController_Basics$com_apple_web_AppleController$allApples:(Lcom/apple/web/AppleController;Lorg/springframework/ui/Model;)Ljava/lang/String;
  5: areturn

2
在我们做任何其他事情之前并进行更深入的分析之前,您能否使用AspectJ Maven 1.9和AspectJ 1.8.10?AspectJ的小版本通常修复与Java8相关的问题。 - kriegaex
谢谢提示,我已更新到1.9和1.8.10版本(编辑问题),但是没有成功。 - user1386375
2个回答

3
这显然是AspectJ编译器的一个错误或不足之处。我已为此创建了一个bug票 (链接)
这里是我从你的代码中提取出来的(非Spring)测试案例:
package de.scrum_master.app;

public class Apple {
  private String type;
  private boolean sweet;

  public Apple(String type, boolean sweet) {
    this.type = type;
    this.sweet = sweet;
  }

  public String getType() {
    return type;
  }

  public boolean isSweet() {
    return sweet;
  }
}

package de.scrum_master.app;

import java.util.Arrays;
import java.util.List;

public class AppleController {
  private static final List<Apple> APPLES =
    Arrays.asList(new Apple("Granny Smith", false), new Apple("Golden Delicious", true));

  public static void main(String[] args) {
    AppleController appleController = new AppleController();
    System.out.println("Named: " + appleController.namedApples(APPLES, "Smith"));
    System.out.println("Sweet: " + appleController.sweetApples(APPLES));
    System.out.println("Sour:  " + appleController.sourApples(APPLES));
  }
}

package de.scrum_master.aspect;

import java.util.List;
import java.util.stream.Collectors;

import java.util.function.Predicate;
import de.scrum_master.app.Apple;
import de.scrum_master.app.AppleController;

public privileged aspect AppleControllerITDAspect {
  public List<Apple> AppleController.namedApples(List<Apple> apples, String subString) {
    // Anonymous subclass works
    return apples.stream().filter(new Predicate<Apple>() {
      @Override
      public boolean test(Apple a) {
        return a.getType().contains(subString);
      }
    }).collect(Collectors.toList());
  }

  public List<Apple> AppleController.sweetApples(List<Apple> apples) {
    // Method reference works
    return apples.stream().filter(Apple::isSweet).collect(Collectors.toList());
  }

  public List<Apple> AppleController.sourApples(List<Apple> apples) {
    // Lambda causes IllegalAccessError
    return apples.stream().filter(a -> !a.isSweet()).collect(Collectors.toList());
  }
}

控制台日志如下:

Named: [de.scrum_master.app.Apple@6f496d9f]
Sweet: [de.scrum_master.app.Apple@4e50df2e]
Exception in thread "main" java.lang.BootstrapMethodError: java.lang.IllegalAccessError: tried to access method de.scrum_master.app.AppleController.lambda$0(Lde/scrum_master/app/Apple;)Z from class de.scrum_master.aspect.AppleControllerITDAspect
    at de.scrum_master.aspect.AppleControllerITDAspect.ajc$interMethod$de_scrum_master_aspect_AppleControllerITDAspect$de_scrum_master_app_AppleController$sourApples(AppleControllerITDAspect.aj:28)
    at de.scrum_master.app.AppleController.sourApples(AppleController.java:1)
    at de.scrum_master.aspect.AppleControllerITDAspect.ajc$interMethodDispatch1$de_scrum_master_aspect_AppleControllerITDAspect$de_scrum_master_app_AppleController$sourApples(AppleControllerITDAspect.aj)
    at de.scrum_master.app.AppleController.main(AppleController.java:14)
Caused by: java.lang.IllegalAccessError: tried to access method de.scrum_master.app.AppleController.lambda$0(Lde/scrum_master/app/Apple;)Z from class de.scrum_master.aspect.AppleControllerITDAspect
    at java.lang.invoke.MethodHandleNatives.resolve(Native Method)
    at java.lang.invoke.MemberName$Factory.resolve(Unknown Source)
    at java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source)
    at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source)
    at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source)
    at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source)
    ... 4 more

在上面的方面中,您还可以看到一个临时解决方法:使用方法引用或经典匿名子类代替lambda表达式。

背景信息:AspectJ编译器AJC是Eclipse Java编译器ECJ的定期更新分支(顺便说一句,AspectJ也是一个官方的Eclipse项目)。因此,也许bug存在于ECJ中,但更可能存在于AJC中。


谢谢您让这一点变得清晰明了。我会接受您的答案并进行测试和确认,感谢您花费时间、关注并开启工单。赞! - user1386375
1
更新:添加了错误票据链接。 - kriegaex

2
现在有这个方法:
lambda$0(Lcom/apple/model/Apple;)Z

实际上,这是你lambda表达式 a -> a.isSweet() 的去糖语法,它看起来像这样:

private static boolean lambda$0(Apple s){
    return s.isSweet(); 
}

这个方法是由编译器生成的。除非你使用了一些奇怪的编译器,否则这必须是aspectj中的一个bug。

您可以通过调用反编译.class文件的命令来检查该方法是否存在于AppleController中:

 javap -c -p AppleController.class

输出应该类似于这样:
 private static boolean lambda$0(com.model.apple.Apple);
Code:
   0: aload_0
   1: invokevirtual #9 // Method isSweet:()Z
   4: ireturn

如果这个方法确实存在(javac正确执行了它的工作),理论上你不会得到java.lang.NoSuchMethodError,这意味着aspectj在你所使用的版本中做了一些非常有趣的事情。
我非常怀疑最后一段话,但为了以防万一...另一方面,如果你反编译(使用javap命令)而没有看到lambda$0方法,而是看到了例如lambda$main$0之类的方法,那么这意味着你正在使用jdk-9或某些非明显的Eclipse编译器。

我运行了javap -c -p AppleController.class,看起来不错 - 我将结果添加到问题中。 - user1386375
哦,不,那里没有 lambda。 - user1386375
@user1386375 我不太确定方面是如何工作的,但你是否有一个名为 AppleController_Basics.class 的文件? - Eugene

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接