我想将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时,不会引发异常。如果调用具有相同参数的相同方法,为什么不引发异常?
[除了其他答案以及您知道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
参考,而不是代理。这具有重要意义。这意味着自调用不会导致与方法调用相关的建议得到执行的机会。
在下一段中,提出了两个解决方案(或者实际上是三个,但是在这种情况下切换到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框架。
当托管bean调用另一个托管bean时,将调用Spring验证。
但是,Spring上下文不知道同一bean(即,内部bean而非内部bean)中的方法之间的调用,因此@Validation
没有影响。
一个简单的解决方案是将包装器方法从类中移到实用程序方法中,例如:
public static void methodA(MyService myService) {
myService.methodB("");
}
在Spring中没有注释@ Validation。我认为您的意思是@ Validated。
为了验证参数,Spring使用CGLIB创建了一种代理。这是类似于Spring用于事务处理的机制。仅当从another类调用您的类MyServiceImpl时,即两个类之间的控制流越过边界时,Spring才会添加此代码。当您从另一个类调用methodB时,Spring会添加验证代码。当您从同一类调用它时,Spring不会添加任何代码,因此不会触发任何验证。