Проверка ограничений Java не работает для параметров

Я хочу использовать аннотации проверки java bean для параметров моих сервисов Spring. Рассмотрим следующую услугу:

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, исключение не выдается. Почему не генерируется исключение, если вызывается тот же метод с тем же параметром?


person Denis Stephanov    schedule 25.01.2020    source источник
comment
Я не уверен, что следую. Вы говорите, что ограничение генерируется так, как вы ожидаете, так зачем вам запускать проверку и выдавать исключение ограничения? Весна делает это за вас.   -  person dave    schedule 26.01.2020
comment
Я голосую за то, чтобы закрыть этот вопрос как не по теме, потому что текущее поведение кода, описанного OP, соответствует ожиданиям.   -  person Bohemian♦    schedule 26.01.2020
comment
@Bohemian: Ты не так хорошо понял вопрос, как Дэйв.   -  person mentallurg    schedule 26.01.2020
comment
@Денис Степанов: Напишите вопрос еще раз, и вам ответят и объяснят такое поведение.   -  person mentallurg    schedule 26.01.2020
comment
@mentallurg Я согласен с Дэйвом: ОП говорит, что выброшено исключение, чего и следовало ожидать. ОП нужно что-то делать - он уже работает.   -  person Bohemian♦    schedule 26.01.2020
comment
@Bohemian: Вы не прочитали вопрос. Прочтите, пожалуйста, последний абзац: Но когда тот же метод B вызывается из метода A, исключение не генерируется.   -  person mentallurg    schedule 26.01.2020
comment
Вопрос @mentallurg был отредактирован после того, как я его закрыл. Теперь имеет смысл :)   -  person Bohemian♦    schedule 26.01.2020
comment
@Bohemian: Для меня это было очевидно. Но я отредактировал вопрос так, чтобы вам было понятно :)   -  person mentallurg    schedule 26.01.2020
comment
Отвечает ли это на ваш вопрос? Вызов одного и того же класса НЕ эффективен в Spring AOP cglib   -  person jannis    schedule 26.01.2020
comment
Извините, если мой вопрос не так ясен. Когда я вызываю методы MyService через интерфейс, проверки java bean работают должным образом. Но они не работают, когда я вызываю один метод внутри другого текущего сервиса. И мой вопрос, как в этом случае запускать проверки bean-компонентов.   -  person Denis Stephanov    schedule 26.01.2020
comment
@jannis с этим решением у меня есть циклические зависимости   -  person Denis Stephanov    schedule 26.01.2020


Ответы (3)


В дополнение к другим ответам и тому факту, что вы знаете о существовании прокси-серверов AOP, позвольте мне указать вам на соответствующая глава в документации Spring, в которой упоминается проблема самовызова с прокси-серверами 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-proxy

В следующем абзаце предлагаются два решения (а на самом деле три, но переход на 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 и позволяет самому классу осознавать тот факт, что он используется в контексте АОП, что бросает вызов АОП. Также требуется дополнительная настройка при создании прокси, как показано в следующем примере:

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-proxy

person jannis    schedule 26.01.2020

Проверка Spring вызывается, когда управляемый компонент вызывает другой управляемый компонент.

Однако контекст Spring не знает о вызовах между методами внутри одного и того же bean-компонента, т.е. внутриbean, а не между bean-компонентами, поэтому @Validation не имеет никакого влияния.

Одним из простых решений является перемещение метода-оболочки из класса в служебный метод, например:

public static void methodA(MyService myService) {
    myService.methodB("");
}
person Bohemian♦    schedule 25.01.2020
comment
Я знаю, что это связано с концепцией прокси, поэтому я ищу решение, поэтому я публикую этот вопрос. - person Denis Stephanov; 26.01.2020

В Spring нет аннотации @Validation. Я думаю, вы имели в виду @Validated.

Для проверки параметров Spring создает своего рода прокси, используя CGLIB. Это механизм, аналогичный тому, что Spring использует для транзакций. Spring добавляет этот код, только если ваш класс MyServiceImpl вызывается из другого класса, т. е. когда поток управления пересекает границу между двумя классами. Когда вы вызываете свой methodB из другого класса, Spring добавляет код проверки. Когда вы вызываете его из того же класса, Spring не добавляет кода, и поэтому проверка не запускается.

person mentallurg    schedule 25.01.2020
comment
спасибо, я имел в виду @Validated. Я редактирую вопрос, мой плохой. Я знаю о весеннем прокси-сервере, поэтому я ищу решение для этого. - person Denis Stephanov; 26.01.2020