【问题标题】:Change Return type of Overridden Methods?更改覆盖方法的返回类型?
【发布时间】:2021-11-16 19:41:24
【问题描述】:

下面是“ControllerAdvice”,它充当多个微服务的全局异常处理程序。

// Custom Exception Handler
@ExceptionHandler(value = CustomException.class)
public ResponseEntity<ErrorResponse> customException(CustomException ex) {
    ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
    return new ResponseEntity<>(error, ex.getStatus());
}

@ExceptionHandler(value = Exception.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
public ErrorResponse globalException(Exception ex) {
        
    return new ErrorMessage("INTERNAL_SERVER_ERROR", "500");    
}

现在对于我的一个微服务,我需要自定义来自上述异常处理程序的错误响应,即我需要将来自全局异常处理程序的“ErrorResponse”包装在另一个名为“ErrorResponseWrapper”的对象下

class ErrorResponseWrapper extends ErrorResponse {

   String type;

   // getter & setters
}

现在在我需要自定义错误响应的我的微服务中,我只是重写了 GlobalExceptionHandler 方法

@RestControllerAdvice
public class ControllerException extends GlobalExceptionHandler {

    // This Works FINE...
    @Override
    public ErrorResponseWrapper globalException(Exception ex) {
        // 
        ErrorResponse error = super.globalException(ex);

        // New Response Wrapper
        ErrorResponseWrapper wrapper = new ResponseWrapper();
        wrapper.setType("SOMETHING");
        
        return wrapper;
    }

//Issue is here:- 
//   I get Compilation Error: ReturnType is Not Compatible with the GlobalException return type.

    @Override
    public ResponseEntity<ErrorResponseWrapper> customException(CustomException ex) {
        
        ErrorResponse error = super.customException(ex).getBody();

        // New Response Wrapper
        ErrorResponseWrapper wrapper = new ResponseWrapper();
        wrapper.setType("SOMETHING");
    
        return new ResponseEntity<>(wrapper,ex.getStatus());
}

}

它与 First Overridden 方法一起工作正常,为什么它与 ResponseEntity 不同?有什么我做错了,或者我该如何做到这一点?

【问题讨论】:

    标签: java spring spring-boot microservices


    【解决方案1】:

    您的问题是泛型,并且默认情况下它们是不变的,这会绊倒很多人,并且可能不会立即被认为是合乎逻辑或正确的(但是,它是 - 继续阅读)。

    有这 3 个概念,称为invariant / covariant / contravariant

    协变

    协变意味着事物的子类型与事物一样好。

    Java 基本类型是协变的。这里:

    Integer x = 5;
    Number n = x; // This compiles and runs just great.
    

    请注意x 的类型是整数,它是Number子类型,但这里没问题。那是因为这里应用了协方差。

    逆变

    逆变是相反的:超类型是一个公平的替身。这实际上是 java 的一部分:当覆盖方法时,您可以使用超类型作为参数,这很好。这样编译:

    public class Parent {
        public void foo(String x) {}
    }
    
    public class Child extends Parent {
        @Override public void foo(Object x) {}
    }
    

    这没问题的原因应该很明显:因为常识:所有字符串也是对象,因此 Child 定义的 foo 方法可以处理所有可能的调用 foo 的情况,只要 Child 的实例被视为如果它是Parent。它可以处理所有这些(当xString 时),以及更多的启动。

    泛型和变异

    泛型默认为不变。那是头陀螺!让我们来看看:

    List<String> list1 = new ArrayList<String>();
    List<Object> list2 = list1; // DOES NOT COMPILE!!
    

    这很奇怪,不是吗?但这是有道理的!毕竟,如果上面的代码有效,list1list2 都指向同一个实际列表;因此,如果您通过list1 参考对这一列表进行更改,您将能够在使用list2 参考时看到更改。但是,问题就在这里:我可以运行list2.add(new Object()),但这意味着list1.get() 返回一个Object 而不是String

    这就是泛型不变的原因:因为宇宙就是这样运作的。

    这适用于任何地方。因此,我们在这里解决了您问题的症结:

    ResponseEntity&lt;ErrorResponseWrapper&gt; 不是ResponseEntity&lt;ErrorResponse&gt; 的子类型!

    幸运的是,泛型非常灵活。如果需要,您可以拥有协变甚至逆变;只是,要求它。在这里,现在可以正常工作了:

    public class Parent {
        public ResponseEntity<? extends ErrorResponse> foo() {}
    }
    
    public class Child extends Parent {
        @Override public ResponseEntity<? extends ErrorResponseWrapper> foo() {}
    }
    

    确实很好用。 ? extends 是 java-ese 用于:我想要泛型,但要协变。然后编译器将帮助您并防止您破坏世界。让我们试试吧:

    List<String> list1 = new ArrayList<>();
    List<? extends Object> list2 = list1; // now it compiles!
    list2.add(new Object()); // but this does not!
    

    在任何List&lt;? extends Y&gt; 上调用.add(),其中Y 可以是任何你喜欢的东西不会编译,除非你传递一个文字null 并且只是因为null 是所有类型。这是故意的 - 它允许您将该字符串列表分配给参考。

    修复

    返回原始类型并更新返回类型。如果你不能做到这一点,那么你在这里无能为力。

    【讨论】:

      【解决方案2】:

      在定义你需要使用的CustomHandler时

      @ExceptionHandler(value = CustomException.class)
      public ResponseEntity<? extends ErrorResponse> customException(CustomException ex) {
          ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
          return new ResponseEntity<>(error, ex.getStatus());
      } 
      

      【讨论】:

        【解决方案3】:

        在您原来的 customException 方法中尝试以下操作:

        @ExceptionHandler(value = CustomException.class)
        public ResponseEntity<T extends ErrorResponse> customException(CustomException ex) {
            ErrorResponse error = new ErrorResponse(ex.getErrorDescription(), ex.getErrorMessage());
            return new ResponseEntity<>(error, ex.getStatus());
        }
        

        【讨论】:

        • 泛型用于链接事物,有利于编译器分析。 这就是它们的全部好处,因为泛型在运行时会被删除。它让编译器知道出现在 2 个或更多位置的表达式/类型链接在一起。这也意味着如果你声明一个类型 var 并且使用它的次数少于 2 次,那就是 hack,或者没用。因此,这段代码是一种非常糟糕的风格失礼,任何风格指南都应该将其标记为已损坏。很可能您的意思是ResponseEntity&lt;? extends ErrorResponse&gt;,但没有解释为什么这个答案仍然不合适。
        • 嗨@rzwitserloot,感谢您指出这一点!我已经编辑了我的答案。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-12-21
        相关资源
        最近更新 更多