0

I want use java bean validation annotations for parameters of my spring services. Consider following service:

public interface MyService {

    void methodA();


    void methodB(@NotBlank String param)
}

with implementation:

@Validated
public class MyServiceImpl implements MyService {

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

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

Can you tell me how to fire validation and throw constraint exception when passed string is blank? When I call service this way:

@Autowired
MyService myService;

myService.methodB("");

When methodB is called from another class, a constraint exception is thrown as expected.

But when the same methodB ias called form MethodA, no exception is thrown. Why no exception is thrown, if the same method with the same parameter is called?

Denis Stephanov
  • 4,563
  • 24
  • 78
  • 174
  • 2
    I'm not sure I follow. You say that the constraint is thrown as you expect, so why do you need to "fire validation and throw constraint exception"? Spring is doing this for you. – dave Jan 25 '20 at 22:49
  • I'm voting to close this question as off-topic because current behaviour of code described by OP is as expected. – Bohemian Jan 25 '20 at 23:27
  • @Bohemian: You didn't understand the question as well as "dave". – mentallurg Jan 25 '20 at 23:29
  • @Denis Stepanov: Write a question once again, and will answer you and explain this behaviour. – mentallurg Jan 25 '20 at 23:34
  • 1
    @mentallurg I agree with Dave: OP says an exception is thrown, which is expected. OP does need to do anything - it’s already working. – Bohemian Jan 25 '20 at 23:38
  • @Bohemian: You **didn't read the question**. Please read the last paragraph: *But when the same methodB ias called form MethodA, no exception is thrown*. – mentallurg Jan 25 '20 at 23:49
  • 1
    @mentallurg question was edited after I closed it. *Now* is makes sense :) – Bohemian Jan 25 '20 at 23:50
  • @Bohemian: To me it was **obvious**. But I edited the question so that *you* understand :) – mentallurg Jan 25 '20 at 23:52
  • 1
    Does this answer your question? [Same class invoke NOT effective in Spring AOP cglib](https://stackoverflow.com/questions/42921388/same-class-invoke-not-effective-in-spring-aop-cglib) – jannis Jan 25 '20 at 23:59
  • I am sorry if my question is not so clear. When I call MyService methods through interface then java bean validations works as expected. But they don't works when I call one method inside another of current service. And my question how to trigger bean validations in that case. – Denis Stephanov Jan 26 '20 at 00:44
  • @jannis with that solution I have cycle dependencies – Denis Stephanov Jan 26 '20 at 00:45

3 Answers3

2

In addition to the other answers and the fact you are aware of the AOP proxies existance let me just point you to the relevant chapter in Spring documentation which mentiones self-invocation problem with AOP proxies that you've come across:

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

In the next paragraph two solutions are proposed (or in fact three, but switching to AspectJ in this particular case might turn out cumbersome):

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

jannis
  • 4,843
  • 1
  • 23
  • 53
1

Spring validation is invoked when a managed bean calls another managed bean.

However, spring context is unaware of calls between methods within the same bean, ie intrabean rather than interbean, so @Validation has no influence.

One simple solution is to move the wrapper method out of the class into a utility method, something like:

public static void methodA(MyService myService) {
    myService.methodB("");
}
Bohemian
  • 412,405
  • 93
  • 575
  • 722
0

There is no annotation @Validation in Spring. I think you meant @Validated.

To validate parameters, Spring creates kind of proxy using CGLIB. This is mechanism similar to what Spring uses for transactions. Spring adds this code only if your class MyServiceImpl is called from another class, i.e. where a control flow crosses the border between two classes. When you call your methodB from another class, Spring adds validation code. When you call it from the same class, Spring adds no code and thus no validation is triggered.

mentallurg
  • 4,967
  • 5
  • 28
  • 36