【问题标题】:How to prevent false positive null pointer warnings, when using CGLIB / Spring AOP?使用CGLIB / Spring AOP时如何防止误报空指针警告?
【发布时间】:2016-02-09 10:24:03
【问题描述】:

我在我的 Spring MVC 控制器中使用 Spring AOP,因此间接使用 CGLIB。由于 CGLIB 需要一个默认构造函数,所以我包含了一个,我的控制器现在看起来像这样:

@Controller
public class ExampleController {

    private final ExampleService exampleService;

    public ExampleController(){
        this.exampleService = null;
    }

    @Autowired
    public ExampleController(ExampleService exampleService){
        this.exampleService = exampleService;
    }

    @Transactional
    @ResponseBody
    @RequestMapping(value = "/example/foo")
    public ExampleResponse profilePicture(){
        return this.exampleService.foo(); // IntelliJ reports potential NPE here
    }
}

现在的问题是,IntelliJ IDEA 的静态代码分析报告了一个潜在的 NullPointerException,因为this.exampleService 可能为空。

我的问题是:

如何防止这些误报空指针警告?一种解决方案是添加assert this.exampleService != null 或者使用Guava 的Preconditions.checkNotNull(this.exampleService)

但是,对于此方法中使用的每个字段,必须将其添加到每个方法中。我更喜欢可以在一个地方添加的解决方案。可能是默认构造函数上的注释或其他什么?

编辑:

似乎已使用 Spring 4 修复,但我目前使用的是 Spring 3: http://blog.codeleak.pl/2014/07/spring-4-cglib-based-proxy-classes-with-no-default-ctor.html

【问题讨论】:

  • 该构造函数实际上必须是可调用的,还是必须存在?你能从中抛出一个异常吗?
  • 一种选择是在编译时或运行时使用 AspectJ weaver,而不是 cglib。 Spring 文档解释了如何做到这一点。这种方法的一个好处是它允许依赖注入和检测通常是“贫血”的域对象。另一个是支持将使用接口检测的类,因此使用动态代理而不是 cglib - 取决于类的性质,这可能是一个好习惯。
  • 当存在setterexampleService 时问题仍然存在吗?

标签: java intellij-idea spring-aop static-analysis cglib


【解决方案1】:

您可以使用以下方式注释您的字段(如果您确定它不会为空):

//import org.jetbrains.annotations.NotNull;
@NotNull
private final ExampleService exampleService;

这将指示 Idea 在所有情况下都假定该字段不为空。在这种情况下,您的真实构造函数也将由 Idea 自动注释:

public ExampleController(@NotNull ExampleService exampleService){
    this.exampleService = exampleService;
}

【讨论】:

  • 我认为这是最好的。我希望像 @IgnoreNotUsed 注释这样的东西可以添加到无参数的构造函数中,但这毕竟不是那么糟糕......
【解决方案2】:

您可以创建 ExampleService 的默认新实例并在默认构造函数中分配它,而不是将其分配给 null:

public ExampleController(){
    this.exampleService = new ExampleService();
}

public ExampleController(){
    this.exampleService = ExampleServiceFactory.Create();
}

由于这个对象永远不应该在正常操作中使用,它不会产生任何影响,但是如果该对象被框架使用,或者由于后来代码更改而意外直接使用,这将为您提供更多信息堆栈跟踪比空指针异常,这也解决了this.exampleService可以为空的错误。

这可能需要对 ExampleService 类进行一些更改,以允许使用默认参数创建新实例,或者允许创建本质上是一个什么都不做的 shell 的新实例。如果它继承自基接口类型,则非功能类可以继承自相同的基类型,特别是作为占位符。如果应用程序尝试使用默认的非功能实例,此模式还允许您注入错误处理代码以提供明确的警告。

我发现,在 Java 和 C# 等几乎所有内容都是指针的语言中,即使在不应该使用空指针的区域也依赖空指针,这会使维护变得比应有的更加困难,因为它们经常会被意外使用。每当代码尝试使用空指针时,底层虚拟机的设计就相当于恐慌攻击——我怀疑这是因为 C 的遗留问题,空指针真的会弄乱整个正在运行的程序。由于这种虚拟的恐慌发作,它们没有提供任何有助于诊断问题的有用信息,特别是因为值 (null) 对于识别发生的事情完全没有用处。通过避免空指针,而是专门设计类层次结构来确定实例化对象是否应该做任何实际工作,您可以避免空指针的潜在问题,并使您的代码更容易和更安全地维护。

【讨论】:

    【解决方案3】:

    IntelliJ IDEA 的静态代码分析报告潜在的 NullPointerException

    您可以使用@SuppressWarnings({"unchecked", "UnusedDeclaration"}) 或注释来关闭特定字段、变量、方法等的这些报告。实际上,IDEA 本身可以向您推荐这个解决方案。见https://www.jetbrains.com/idea/help/suppressing-inspections.html

    您可以为单行代码切换警告:

    void foo(java.util.Set set) {
        @SuppressWarnings("unchecked")
        java.util.Set<String> strings = set;
        System.out.println(strings);
    }
    

    【讨论】:

    • 潜在的 NullPointerExceptions-warnings 非常有用,我不想完全停用或禁止它
    • 您可以为代码的特定行禁用它,而不是整个异常类
    • 正如我在问题中所写,我可以使用assert this.exampleService != null,它比@SuppressWarnings 更具表现力。但是,我不想到处添加断言。
    • BTW 断言是相当糟糕的做法,因为您无法确定它们是否已为您的应用程序打开
    • 我不同意,在这种情况下断言是完全正确的。你告诉编译器,你确定某事。在开发时它会被检查,而在运行时它不会被检查,并且您可以避免性能损失。
    猜你喜欢
    • 1970-01-01
    • 2012-10-27
    • 2016-04-06
    • 2020-10-15
    • 2015-01-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多