【问题标题】:Spring Rest Controller inheritanceSpring Rest Controller 继承
【发布时间】:2019-07-16 08:26:49
【问题描述】:

我有一个类型化的抽象 RestController,其中包含一些用于处理该类型的所有对象的通用逻辑。处理服务是通过构造函数提供的。

在子类的bean实例化过程中,两个构造函数都被非空参数调用,超类非空断言成功通过。

调用 API 端点(URI 路径是子类和超类路径的组合)调用正确的方法,并带有正确标识的参数。 但是,端点方法会引发空指针异常,因为提供的服务(通过非空断言的服务)为空。在检查其方法被调用的 bean 的子类和超类的所有属性后,报告所有属性为空。

这是一个简化的例子:

型号:

public class Cookie {
    public long id;
}

public class ChocolateCookie extends Cookie {
    public long chipCount;
}

服务:

public interface CookieService<T extends Cookie> {
    T findCookie(long cookieId);
    void eatCookie(T cookie);
}

@Service
public class ChocolateCookieService implements CookieService<ChocolateCookie> {

    @Override
    public ChocolateCookie findCookie(long cookieId) {
        // TODO Load a stored cookie and return it.
        return new ChocolateCookie();
    }

    @Override
    public void eatCookie(ChocolateCookie cookie) {
        // TODO Eat cookie;
    }
}

休息控制器:

public abstract class CookieApi<T extends Cookie> {

    private final CookieService<T> cookieService;

    public CookieApi(CookieService<T> cookieService) {
        this.cookieService = cookieService;
        Assert.notNull(this.cookieService, "Cookie service must be set.");
    }

    @PostMapping("/{cookieId}")
    public ResponseEntity eatCookie(@PathVariable long cookieId) {
        final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
        cookieService.eatCookie(cookie);
        return ResponseEntity.ok();
    }
}

@RestController
@RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {

    @Autowired
    public ChocolateCookieApi(ChocolateCookieService cookieService) {
        super(cookieService);
    }

    @PostMapping
    public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) {
        // TODO Process DTO and store the cookie
        return ResponseEntity.ok(dto);
    }
}

请注意,如果我没有为超类提供服务对象,而是定义了一个用于按需获取服务的抽象方法并在子类中实现它,那么超类将按预期运行。

同样的原则适用于@RestController 和@RequestMapping 不包括在等式中的任何情况。

我的问题有两个:

  1. 为什么会这样?
  2. 有没有办法使用构造函数,或者至少不必为每个子类和超类所需的每个服务实现 getter 方法?

编辑 1:

我尝试重新创建问题,但提供的代码按照人们的建议运行良好。在篡改了简化的项目之后,我终于设法重现了这个问题。 重现问题的实际条件是子类必须无法访问超类中的端点方法(例如:类在不同的包中,并且该方法具有包可见性)。 这会导致 spring 创建一个带有零填充字段的 enhancerBySpringCGLIB 代理类。

修改超类方法以具有受保护/公共可见性解决了该问题。

【问题讨论】:

  • 非常有趣的问题,这件事暴露得很好。
  • @davidxxx 虽然重现问题的回购也很好,因为显然人们无法重现问题
  • @eis 我同意根据实际答案/评论。 Nikola Antic,您确实应该添加更多信息或提供包含重现该问题的最小项目的 repo。
  • 好吧,在阅读了您上次编辑的评论之后,这完全有道理,在您的问题中您错过了提及这些细节,并且由于您发布的所有课程方法都是公开的,我们没有抓住它。幸好你发现了发生了什么。编码愉快!
  • 您可以添加自己的答案并接受它,因此这个问题至少会被标记为已回答。要么接受,要么接受本质上说您发布的代码可以正常工作的答案之一。

标签: spring spring-boot spring-mvc


【解决方案1】:

尼古拉,

我不确定为什么你的代码不能在你的系统中运行,我在一个项目中创建了相同的类并且它运行良好,我什至添加了另一个 Cookie 类型、服务和 api 类。

SpringBoot 日志(可以看到初始化了 4 个端点):

2019-02-26 14:39:07.612  INFO 86060 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.ChocolateCookie> cookie.ChocolateCookieApi.create(cookie.ChocolateCookie)
2019-02-26 14:39:07.613  INFO 86060 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/chocolateCookies/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long)
2019-02-26 14:39:07.615  INFO 86060 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie],methods=[POST]}" onto public org.springframework.http.ResponseEntity<cookie.OatmealRaisinCookie> cookie.OatmealRaisingCookieApi.create(cookie.OatmealRaisinCookie)
2019-02-26 14:39:07.615  INFO 86060 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/oatmeal-raisin-cookie/{cookieId}],methods=[POST]}" onto public org.springframework.http.ResponseEntity<?> cookie.CookieApi.eatCookie(long)

在邮递员中测试控制器

正如@Domingo 所提到的,您的应用程序中可能存在一些配置问题,因为从 OOP 和 Spring IoC 的角度来看,您的代码看起来很好并且可以正常运行。

注意:我正在使用 SpringBoot 2.0.5、Java 8、Eclipse 运行这些控制器

我在 GitHub 上发布了我的项目供您参考。 https://github.com/karl-codes/cookie-monster

干杯!

【讨论】:

  • 我复制了同一个项目,但没有使用 sprign boot,它可以工作;我同意其他人的看法。好像是配置问题
  • 提供的代码按照人们的建议运行良好。重现问题的实际条件是子类必须无法访问超类中的端点方法(例如:类在不同的包中,并且该方法具有包可见性)。这会导致 spring 创建一个带有零填充字段的 enhancerBySpringCGLIB 代理类。修改超类方法以具有受保护/公共可见性解决了该问题。接受这个答案,因为它在技术上是正确的,努力最终让我找到了最终的解决方案。
【解决方案2】:

您可以在抽象类中定义一个抽象方法,并在每个实现上自动装配正确的服务:

public abstract class CookieApi<T extends Cookie> {

    protected abstract CookieService<T> getCookieService();

    @RequestMapping("/cookieId")
    public void eatCookie(@PathVariable long cookieId) {
        final T cookie = cookieService.findCookie(cookieId); // Cookie service is null
        this.getCookieService().eatCookie(cookie);
    }
}

@RestController
@RequestMapping("/chocolateCookies")
public class ChocolateCookieApi extends CookieApi<ChocolateCookie> {

    @Autowired
    private ChocolateCookie chocolateCookie;

    @Override
    protected CookieService<T> getCookieService() {
        return this.chocolateCookie;
    }

    @PostMapping
    public ResponseEntity<ChocolateCookie> create(@RequestBody ChocolateCookie dto) {
        // TODO Process DTO and store the cookie
        return ResponseEntity.ok(dto);
    }
}

【讨论】:

  • 我之前确实尝试过这种方法并且它有效。我的目标是避免在每个子类中实现 getXXXService()。大约有 10 个子类继承了有问题的超类,并且超类有 3-4 个它需要的服务。这相当于很多不必要的方法实现。
【解决方案3】:

您的示例总体上看起来不错,并且依赖注入应该可以与 spring 一起使用。

如果您想访问引用在父抽象类中的服务,则该引用不应是私有的,而是受保护的。

--

Spring 将所有 RestControllers 初始化为 Singleton Beans 到应用程序上下文中注入现有的 Services Beans,如果没有 bean 注入应用程序启动将失败。如果调用 rest 端点访问其中没有服务引用的控制器,则它与在其中使用服务初始化的控制器不同(我不知道怎么会发生)或者您的配置有问题。

把它放在 git hub 上。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2014-10-16
    • 1970-01-01
    • 2016-11-05
    • 2020-01-15
    • 2011-11-05
    • 1970-01-01
    • 1970-01-01
    • 2016-06-30
    相关资源
    最近更新 更多