Java 约束验证不适用于参数

Java constraint validation doesn't work for parameters

我想对 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("");

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

但是当相同的methodB ias 调用窗体MethodA 时,不会抛出异常。为什么同一个参数调用同一个方法不抛出异常?

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

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

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

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

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

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

除了其他答案和您知道 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()
}

The key thing to understand here is that the client code inside the main(..) method of the Main class has a reference to the proxy. This means that method calls on that object reference are calls on the proxy. As a result, the proxy can delegate to all of the interceptors (advice) that are relevant to that particular method call. However, once the call has finally reached the target object (the SimplePojo, reference in this case), any method calls that it may make on itself, such as this.bar() or this.foo(), are going to be invoked against the this reference, and not the proxy. This has important implications. It means that self-invocation is not going to result in the advice associated with a method invocation getting a chance to execute.

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

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

Okay, so what is to be done about this? The best approach (the term, “best,” is used loosely here) is to refactor your code such that the self-invocation does not happen. This does entail some work on your part, but it is the best, least-invasive approach. The next approach is absolutely horrendous, and we hesitate to point it out, precisely because it is so horrendous. You can (painful as it is to us) totally tie the logic within your class to Spring AOP, as the following example shows:

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...
    }
}

This totally couples your code to Spring AOP, and it makes the class itself aware of the fact that it is being used in an AOP context, which flies in the face of AOP. It also requires some additional configuration when the proxy is being created, as the following example shows:

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()
}

Finally, it must be noted that AspectJ does not have this self-invocation issue because it is not a proxy-based AOP framework.

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