-
Notifications
You must be signed in to change notification settings - Fork 37.7k
/
MethodValidationInterceptor.java
159 lines (137 loc) · 5.88 KB
/
MethodValidationInterceptor.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.validation.beanvalidation;
import java.lang.reflect.Method;
import java.util.function.Supplier;
import jakarta.validation.Validator;
import jakarta.validation.ValidatorFactory;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.ProxyMethodInvocation;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.SmartFactoryBean;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.validation.annotation.Validated;
/**
* An AOP Alliance {@link MethodInterceptor} implementation that delegates to a
* JSR-303 provider for performing method-level validation on annotated methods.
*
* <p>Applicable methods have {@link jakarta.validation.Constraint} annotations on
* their parameters and/or on their return value (in the latter case specified at
* the method level, typically as inline annotation).
*
* <p>E.g.: {@code public @NotNull Object myValidMethod(@NotNull String arg1, @Max(10) int arg2)}
*
* <p>Validation groups can be specified through Spring's {@link Validated} annotation
* at the type level of the containing target class, applying to all public service methods
* of that class. By default, JSR-303 will validate against its default group only.
*
* <p>As of Spring 5.0, this functionality requires a Bean Validation 1.1+ provider.
*
* @author Juergen Hoeller
* @since 3.1
* @see MethodValidationPostProcessor
* @see jakarta.validation.executable.ExecutableValidator
*/
public class MethodValidationInterceptor implements MethodInterceptor {
private final MethodValidationAdapter delegate;
/**
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath.
*/
public MethodValidationInterceptor() {
this.delegate = new MethodValidationAdapter();
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory.
* @param validatorFactory the JSR-303 ValidatorFactory to use
*/
public MethodValidationInterceptor(ValidatorFactory validatorFactory) {
this.delegate = new MethodValidationAdapter(validatorFactory);
}
/**
* Create a new MethodValidationInterceptor using the given JSR-303 Validator.
* @param validator the JSR-303 Validator to use
*/
public MethodValidationInterceptor(Validator validator) {
this.delegate = new MethodValidationAdapter(validator);
}
/**
* Create a new MethodValidationInterceptor for the supplied
* (potentially lazily initialized) Validator.
* @param validator a Supplier for the Validator to use
* @since 6.0
*/
public MethodValidationInterceptor(Supplier<Validator> validator) {
this.delegate = new MethodValidationAdapter(validator);
}
@Override
@Nullable
public Object invoke(MethodInvocation invocation) throws Throwable {
// Avoid Validator invocation on FactoryBean.getObjectType/isSingleton
if (isFactoryBeanMetadataMethod(invocation.getMethod())) {
return invocation.proceed();
}
Object target = getTarget(invocation);
Method method = invocation.getMethod();
Class<?>[] groups = determineValidationGroups(invocation);
this.delegate.validateMethodArguments(target, method, invocation.getArguments(), groups);
Object returnValue = invocation.proceed();
this.delegate.validateMethodReturnValue(target, method, returnValue, groups);
return returnValue;
}
private static Object getTarget(MethodInvocation invocation) {
Object target = invocation.getThis();
if (target == null && invocation instanceof ProxyMethodInvocation methodInvocation) {
// Allow validation for AOP proxy without a target
target = methodInvocation.getProxy();
}
Assert.state(target != null, "Target must not be null");
return target;
}
private boolean isFactoryBeanMetadataMethod(Method method) {
Class<?> clazz = method.getDeclaringClass();
// Call from interface-based proxy handle, allowing for an efficient check?
if (clazz.isInterface()) {
return ((clazz == FactoryBean.class || clazz == SmartFactoryBean.class) &&
!method.getName().equals("getObject"));
}
// Call from CGLIB proxy handle, potentially implementing a FactoryBean method?
Class<?> factoryBeanType = null;
if (SmartFactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = SmartFactoryBean.class;
}
else if (FactoryBean.class.isAssignableFrom(clazz)) {
factoryBeanType = FactoryBean.class;
}
return (factoryBeanType != null && !method.getName().equals("getObject") &&
ClassUtils.hasMethod(factoryBeanType, method));
}
/**
* Determine the validation groups to validate against for the given method invocation.
* <p>Default are the validation groups as specified in the {@link Validated} annotation
* on the method, or on the containing target class of the method, or for an AOP proxy
* without a target (with all behavior in advisors), also check on proxied interfaces.
* @param invocation the current MethodInvocation
* @return the applicable validation groups as a Class array
*/
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
Object target = getTarget(invocation);
Method method = invocation.getMethod();
return MethodValidationAdapter.determineValidationGroups(target, method);
}
}