【问题标题】:Java constraint validation doesn't work for parameters [duplicate]Java约束验证不适用于参数[重复]
【发布时间】:2020-01-25 22:34:30
【问题描述】:

我想对我的 spring 服务的参数使用 java bean 验证注释。考虑以下服务:

public interface MyService {

    void methodA();


    void methodB(@NotBlank String param)
}

实施:

@Validated
public class MyServiceImpl implements MyService {

    @Override
    public void methodA() {
        String param = "";
        methodB(param)
    }

    @Override
    public void methodB(@NotBlank String param) {
        // some logic
    }
}

你能告诉我当传递的字符串为空时如何触发验证并抛出约束异常吗?当我以这种方式调用服务时:

@Autowired
MyService myService;

myService.methodB("");

当从另一个类调用methodB时,会按预期抛出约束异常。

但是当同样的methodB 被调用表单MethodA 时,不会抛出异常。为什么调用同一个参数的同一个方法不抛出异常?

【问题讨论】:

  • 我不确定我是否遵循。您说约束是按预期抛出的,那么为什么需要“触发验证并抛出约束异常”? Spring 正在为你做这件事。
  • 我投票决定将此问题作为离题结束,因为 OP 描述的代码的当前行为符合预期。
  • @mentallurg 我同意戴夫的观点:OP 说抛出异常,这是意料之中的。 OP 确实需要做任何事情——它已经在工作了。
  • @mentallurg 问题在我关闭后被编辑。 现在是有道理的:)

标签: java spring-boot bean-validation spring-validator


【解决方案1】:

除了其他答案以及您知道 AOP 代理存在这一事实之外,让我指出您的 the relevant chapter in Spring documentation,其中提到了您遇到的 AOP 代理的自调用问题:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

这里要理解的关键是Main 类的main(..) 方法内的客户端代码有对代理的引用。这意味着对该对象引用的方法调用是对代理的调用。因此,代理可以委托给与该特定方法调用相关的所有拦截器(建议)。 但是,一旦调用最终到达目标对象(SimplePojo,在本例中为引用),它可能对自身进行的任何方法调用,例如 this.bar()this.foo(),都将被针对this 引用而不是代理调用。这具有重要意义。这意味着自调用不会导致与方法调用相关的建议有机会执行。

-- https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#aop-understanding-aop-proxies

在下一段中,提出了两个解决方案(或者实际上是三个,但在这种特殊情况下切换到 AspectJ 可能会变得很麻烦):

好的,那该怎么办呢?最好的方法(术语“最好”在这里被广泛使用)是重构你的代码,这样自调用就不会发生。这确实需要您做一些工作,但它是最好的、侵入性最小的方法。下一种方法绝对可怕,我们不愿指出,正是因为它太可怕了。您可以(对我们来说很痛苦)完全将您的类中的逻辑与 Spring AOP 联系起来,如下例所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

class SimplePojo : Pojo {

    fun foo() {
        // this works, but... gah!
        (AopContext.currentProxy() as Pojo).bar()
    }

    fun bar() {
        // some logic...
    }
}

这完全将您的代码与 Spring AOP 耦合在一起,并且它使类本身意识到它正在 AOP 上下文中使用,这与 AOP 相悖。在创建代理时还需要一些额外的配置,如下例所示:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

fun main() {
    val factory = ProxyFactory(SimplePojo())
    factory.addInterface(Pojo::class.java)
    factory.addAdvice(RetryAdvice())
    factory.isExposeProxy = true

    val pojo = factory.proxy as Pojo
    // this is a method call on the proxy!
    pojo.foo()
}

最后需要说明的是,AspectJ 不存在这个自调用问题,因为它不是基于代理的 AOP 框架。

-- https://docs.spring.io/spring/docs/5.2.3.RELEASE/spring-framework-reference/core.html#aop-understanding-aop-proxies

【讨论】:

    【解决方案2】:

    当一个托管 bean 调用另一个托管 bean 时调用 Spring 验证。

    但是,spring context 不知道同一个bean 内的方法之间的调用,即intrabean 而不是interbean,因此@Validation 没有影响。

    一个简单的解决方案是将包装方法从类中移出到实用方法中,例如:

    public static void methodA(MyService myService) {
        myService.methodB("");
    }
    

    【讨论】:

    • 我知道这与代理概念有关,所以我正在寻找解决方案,这就是我发布这个问题的原因。
    【解决方案3】:

    Spring 中没有注解@Validation。我想你的意思是 @Validated

    为了验证参数,Spring 使用 CGLIB 创建了一种代理。这是类似于 Spring 用于事务的机制。 Spring 仅当您的类 MyServiceImpl另一个 类调用时才添加此代码,即控制流跨越两个类之间的边界。当您从另一个类调用 methodB 时,Spring 会添加验证代码。当您从同一个类中调用它时,Spring 不会添加任何代码,因此不会触发验证。

    【讨论】:

    • 谢谢,我的意思是@Validated。我编辑问题,我的错。我知道 Spring 的代理,所以我正在寻找解决方案。
    猜你喜欢
    • 1970-01-01
    • 2018-07-05
    • 2018-07-17
    • 1970-01-01
    • 1970-01-01
    • 2014-06-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多