11 changed files with 455 additions and 68 deletions
@ -1,56 +1,63 @@ |
|||||||
/* |
/* |
||||||
* Copyright 2002-2011 the original author or authors. |
* Copyright 2002-2011 the original author or authors. |
||||||
* |
* |
||||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||||
* you may not use this file except in compliance with the License. |
* you may not use this file except in compliance with the License. |
||||||
* You may obtain a copy of the License at |
* You may obtain a copy of the License at |
||||||
* |
* |
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
* |
* |
||||||
* Unless required by applicable law or agreed to in writing, software |
* Unless required by applicable law or agreed to in writing, software |
||||||
* distributed under the License is distributed on an "AS IS" BASIS, |
* distributed under the License is distributed on an "AS IS" BASIS, |
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||||
* See the License for the specific language governing permissions and |
* See the License for the specific language governing permissions and |
||||||
* limitations under the License. |
* limitations under the License. |
||||||
*/ |
*/ |
||||||
|
|
||||||
package org.springframework.validation.annotation; |
package org.springframework.validation.annotation; |
||||||
|
|
||||||
import java.lang.annotation.Documented; |
import java.lang.annotation.Documented; |
||||||
import java.lang.annotation.ElementType; |
import java.lang.annotation.ElementType; |
||||||
import java.lang.annotation.Retention; |
import java.lang.annotation.Retention; |
||||||
import java.lang.annotation.RetentionPolicy; |
import java.lang.annotation.RetentionPolicy; |
||||||
import java.lang.annotation.Target; |
import java.lang.annotation.Target; |
||||||
|
|
||||||
/** |
/** |
||||||
* Extended variant of JSR-303's {@link javax.validation.Valid}, |
* Variant of JSR-303's {@link javax.validation.Valid}, supporting the |
||||||
* supporting the specification of validation groups. Designed for |
* specification of validation groups. Designed for convenient use with |
||||||
* convenient use with Spring's JSR-303 support but not JSR-303 specific. |
* Spring's JSR-303 support but not JSR-303 specific. |
||||||
* |
* |
||||||
* <p>Can be used e.g. with Spring MVC handler methods arguments. |
* <p>Can be used e.g. with Spring MVC handler methods arguments. |
||||||
* Supported through {@link org.springframework.validation.SmartValidator}'s |
* Supported through {@link org.springframework.validation.SmartValidator}'s |
||||||
* validation hint concept, with validation group classes acting as hint objects. |
* validation hint concept, with validation group classes acting as hint objects. |
||||||
* |
* |
||||||
* @author Juergen Hoeller |
* <p>Can also be used with method level validation, indicating that a specific |
||||||
* @since 3.1 |
* class is supposed to be validated at the method level (acting as a pointcut |
||||||
* @see javax.validation.Validator#validate(Object, Class[]) |
* for the corresponding validation interceptor), but also optionally specifying |
||||||
* @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...) |
* the validation groups for method-level validation in the annotated class. |
||||||
* @see org.springframework.validation.beanvalidation.SpringValidatorAdapter |
* Can also be used as a meta-annotation on a custom stereotype annotation. |
||||||
*/ |
* |
||||||
@Target(ElementType.PARAMETER) |
* @author Juergen Hoeller |
||||||
@Retention(RetentionPolicy.RUNTIME) |
* @since 3.1 |
||||||
@Documented |
* @see javax.validation.Validator#validate(Object, Class[]) |
||||||
public @interface Valid { |
* @see org.springframework.validation.SmartValidator#validate(Object, org.springframework.validation.Errors, Object...) |
||||||
|
* @see org.springframework.validation.beanvalidation.SpringValidatorAdapter |
||||||
/** |
* @see org.springframework.validation.beanvalidation.MethodValidationPostProcessor |
||||||
* Specify one or more validation groups to apply to the validation step |
*/ |
||||||
* kicked off by this annotation. |
@Target({ElementType.TYPE, ElementType.PARAMETER}) |
||||||
* <p>JSR-303 defines validation groups as custom annotations which an application declares |
@Retention(RetentionPolicy.RUNTIME) |
||||||
* for the sole purpose of using them as type-safe group arguments, as implemented in |
@Documented |
||||||
* {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}. |
public @interface Validated { |
||||||
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may |
|
||||||
* support class arguments in other ways as well. |
/** |
||||||
*/ |
* Specify one or more validation groups to apply to the validation step |
||||||
Class[] value() default {}; |
* kicked off by this annotation. |
||||||
|
* <p>JSR-303 defines validation groups as custom annotations which an application declares |
||||||
} |
* for the sole purpose of using them as type-safe group arguments, as implemented in |
||||||
|
* {@link org.springframework.validation.beanvalidation.SpringValidatorAdapter}. |
||||||
|
* <p>Other {@link org.springframework.validation.SmartValidator} implementations may |
||||||
|
* support class arguments in other ways as well. |
||||||
|
*/ |
||||||
|
Class[] value() default {}; |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,114 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2011 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 |
||||||
|
* |
||||||
|
* http://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.util.Set; |
||||||
|
import javax.validation.Validation; |
||||||
|
import javax.validation.Validator; |
||||||
|
import javax.validation.ValidatorFactory; |
||||||
|
|
||||||
|
import org.aopalliance.intercept.MethodInterceptor; |
||||||
|
import org.aopalliance.intercept.MethodInvocation; |
||||||
|
import org.hibernate.validator.HibernateValidator; |
||||||
|
import org.hibernate.validator.method.MethodConstraintViolation; |
||||||
|
import org.hibernate.validator.method.MethodConstraintViolationException; |
||||||
|
import org.hibernate.validator.method.MethodValidator; |
||||||
|
|
||||||
|
import org.springframework.core.annotation.AnnotationUtils; |
||||||
|
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 JSR-303 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)</code> |
||||||
|
* |
||||||
|
* <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 3.1, this functionality requires Hibernate Validator 4.2 or higher. |
||||||
|
* In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider |
||||||
|
* and automatically use the standard method validation support there (once available). |
||||||
|
* |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @since 3.1 |
||||||
|
* @see MethodValidationPostProcessor |
||||||
|
* @see org.hibernate.validator.method.MethodValidator |
||||||
|
*/ |
||||||
|
public class MethodValidationInterceptor implements MethodInterceptor { |
||||||
|
|
||||||
|
private final MethodValidator validator; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new MethodValidationInterceptor using a default JSR-303 validator underneath. |
||||||
|
*/ |
||||||
|
public MethodValidationInterceptor() { |
||||||
|
this(Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new MethodValidationInterceptor using the given JSR-303 ValidatorFactory. |
||||||
|
* @param validatorFactory the JSR-303 ValidatorFactory to use |
||||||
|
*/ |
||||||
|
public MethodValidationInterceptor(ValidatorFactory validatorFactory) { |
||||||
|
this(validatorFactory.getValidator()); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Create a new MethodValidationInterceptor using the given JSR-303 Validator. |
||||||
|
* @param validatorFactory the JSR-303 Validator to use |
||||||
|
*/ |
||||||
|
public MethodValidationInterceptor(Validator validator) { |
||||||
|
this.validator = validator.unwrap(MethodValidator.class); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Object invoke(MethodInvocation invocation) throws Throwable { |
||||||
|
Class[] groups = determineValidationGroups(invocation); |
||||||
|
Set<MethodConstraintViolation<Object>> result = this.validator.validateAllParameters( |
||||||
|
invocation.getThis(), invocation.getMethod(), invocation.getArguments(), groups); |
||||||
|
if (!result.isEmpty()) { |
||||||
|
throw new MethodConstraintViolationException(result); |
||||||
|
} |
||||||
|
Object returnValue = invocation.proceed(); |
||||||
|
result = this.validator.validateReturnValue( |
||||||
|
invocation.getThis(), invocation.getMethod(), returnValue, groups); |
||||||
|
if (!result.isEmpty()) { |
||||||
|
throw new MethodConstraintViolationException(result); |
||||||
|
} |
||||||
|
return returnValue; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 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 containing target class of the method. |
||||||
|
* @param invocation the current MethodInvocation |
||||||
|
* @return the applicable validation groups as a Class array |
||||||
|
*/ |
||||||
|
protected Class[] determineValidationGroups(MethodInvocation invocation) { |
||||||
|
Validated valid = AnnotationUtils.findAnnotation(invocation.getThis().getClass(), Validated.class); |
||||||
|
return (valid != null ? valid.value() : new Class[0]); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,158 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2011 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 |
||||||
|
* |
||||||
|
* http://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.annotation.Annotation; |
||||||
|
import javax.validation.Validator; |
||||||
|
import javax.validation.ValidatorFactory; |
||||||
|
|
||||||
|
import org.aopalliance.aop.Advice; |
||||||
|
|
||||||
|
import org.springframework.aop.Advisor; |
||||||
|
import org.springframework.aop.Pointcut; |
||||||
|
import org.springframework.aop.framework.Advised; |
||||||
|
import org.springframework.aop.framework.AopInfrastructureBean; |
||||||
|
import org.springframework.aop.framework.ProxyConfig; |
||||||
|
import org.springframework.aop.framework.ProxyFactory; |
||||||
|
import org.springframework.aop.support.AopUtils; |
||||||
|
import org.springframework.aop.support.DefaultPointcutAdvisor; |
||||||
|
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut; |
||||||
|
import org.springframework.beans.BeansException; |
||||||
|
import org.springframework.beans.factory.BeanClassLoaderAware; |
||||||
|
import org.springframework.beans.factory.InitializingBean; |
||||||
|
import org.springframework.beans.factory.config.BeanPostProcessor; |
||||||
|
import org.springframework.core.Ordered; |
||||||
|
import org.springframework.util.Assert; |
||||||
|
import org.springframework.util.ClassUtils; |
||||||
|
import org.springframework.validation.annotation.Validated; |
||||||
|
|
||||||
|
/** |
||||||
|
* A convenient {@link BeanPostProcessor} implementation that delegates to a |
||||||
|
* JSR-303 provider for performing method-level validation on annotated methods. |
||||||
|
* |
||||||
|
* <p>Applicable methods have JSR-303 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)</code> |
||||||
|
* |
||||||
|
* <p>Target classes with such annotated methods need to be annotated with Spring's |
||||||
|
* {@link Validated} annotation at the type level, for their methods to be searched for |
||||||
|
* inline constraint annotations. Validation groups can be specified through {@link Validated} |
||||||
|
* as well. By default, JSR-303 will validate against its default group only. |
||||||
|
* |
||||||
|
* <p>As of Spring 3.1, this functionality requires Hibernate Validator 4.2 or higher. |
||||||
|
* In Spring 3.2, this class will autodetect a Bean Validation 1.1 compliant provider |
||||||
|
* and automatically use the standard method validation support there (once available). |
||||||
|
* |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @since 3.1 |
||||||
|
* @see MethodValidationInterceptor |
||||||
|
* @see org.hibernate.validator.method.MethodValidator |
||||||
|
*/ |
||||||
|
public class MethodValidationPostProcessor extends ProxyConfig |
||||||
|
implements BeanPostProcessor, BeanClassLoaderAware, Ordered, InitializingBean { |
||||||
|
|
||||||
|
private Class<? extends Annotation> validatedAnnotationType = Validated.class; |
||||||
|
|
||||||
|
private Validator validator; |
||||||
|
|
||||||
|
private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader(); |
||||||
|
|
||||||
|
private Advisor advisor; |
||||||
|
|
||||||
|
|
||||||
|
/** |
||||||
|
* Set the 'validated' annotation type. |
||||||
|
* The default validated annotation type is the {@link Validated} annotation. |
||||||
|
* <p>This setter property exists so that developers can provide their own |
||||||
|
* (non-Spring-specific) annotation type to indicate that a class is supposed |
||||||
|
* to be validated in the sense of applying method validation. |
||||||
|
* @param validatedAnnotationType the desired annotation type |
||||||
|
*/ |
||||||
|
public void setValidatedAnnotationType(Class<? extends Annotation> validatedAnnotationType) { |
||||||
|
Assert.notNull(validatedAnnotationType, "'validatedAnnotationType' must not be null"); |
||||||
|
this.validatedAnnotationType = validatedAnnotationType; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the JSR-303 Validator to delegate to for validating methods. |
||||||
|
* <p>Default is the default ValidatorFactory's default Validator. |
||||||
|
*/ |
||||||
|
public void setValidator(Validator validator) { |
||||||
|
this.validator = validator; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Set the JSR-303 ValidatorFactory to delegate to for validating methods, |
||||||
|
* using its default Validator. |
||||||
|
* <p>Default is the default ValidatorFactory's default Validator. |
||||||
|
* @see javax.validation.ValidatorFactory#getValidator() |
||||||
|
*/ |
||||||
|
public void setValidatorFactory(ValidatorFactory validatorFactory) { |
||||||
|
this.validator = validatorFactory.getValidator(); |
||||||
|
} |
||||||
|
|
||||||
|
public void setBeanClassLoader(ClassLoader classLoader) { |
||||||
|
this.beanClassLoader = classLoader; |
||||||
|
} |
||||||
|
|
||||||
|
public int getOrder() { |
||||||
|
// This should run after all other post-processors, so that it can just add
|
||||||
|
// an advisor to existing proxies rather than double-proxy.
|
||||||
|
return LOWEST_PRECEDENCE; |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public void afterPropertiesSet() { |
||||||
|
Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true); |
||||||
|
Advice advice = (this.validator != null ? new MethodValidationInterceptor(this.validator) : |
||||||
|
new MethodValidationInterceptor()); |
||||||
|
this.advisor = new DefaultPointcutAdvisor(pointcut, advice); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { |
||||||
|
return bean; |
||||||
|
} |
||||||
|
|
||||||
|
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { |
||||||
|
if (bean instanceof AopInfrastructureBean) { |
||||||
|
// Ignore AOP infrastructure such as scoped proxies.
|
||||||
|
return bean; |
||||||
|
} |
||||||
|
Class<?> targetClass = AopUtils.getTargetClass(bean); |
||||||
|
if (AopUtils.canApply(this.advisor, targetClass)) { |
||||||
|
if (bean instanceof Advised) { |
||||||
|
((Advised) bean).addAdvisor(this.advisor); |
||||||
|
return bean; |
||||||
|
} |
||||||
|
else { |
||||||
|
ProxyFactory proxyFactory = new ProxyFactory(bean); |
||||||
|
// Copy our properties (proxyTargetClass etc) inherited from ProxyConfig.
|
||||||
|
proxyFactory.copyFrom(this); |
||||||
|
proxyFactory.addAdvisor(this.advisor); |
||||||
|
return proxyFactory.getProxy(this.beanClassLoader); |
||||||
|
} |
||||||
|
} |
||||||
|
else { |
||||||
|
// This is not a repository.
|
||||||
|
return bean; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
/* |
||||||
|
* Copyright 2002-2011 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 |
||||||
|
* |
||||||
|
* http://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.annotation.Retention; |
||||||
|
import java.lang.annotation.RetentionPolicy; |
||||||
|
import javax.validation.constraints.Max; |
||||||
|
import javax.validation.constraints.NotNull; |
||||||
|
import javax.validation.groups.Default; |
||||||
|
|
||||||
|
import org.hibernate.validator.method.MethodConstraintViolationException; |
||||||
|
import org.junit.Test; |
||||||
|
|
||||||
|
import org.springframework.aop.framework.ProxyFactory; |
||||||
|
import org.springframework.context.support.StaticApplicationContext; |
||||||
|
import org.springframework.validation.annotation.Validated; |
||||||
|
|
||||||
|
import static org.junit.Assert.*; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author Juergen Hoeller |
||||||
|
* @since 3.1 |
||||||
|
*/ |
||||||
|
public class MethodValidationTests { |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMethodValidationInterceptor() { |
||||||
|
MyValidBean bean = new MyValidBean(); |
||||||
|
ProxyFactory proxyFactory = new ProxyFactory(bean); |
||||||
|
proxyFactory.addAdvice(new MethodValidationInterceptor()); |
||||||
|
doTestProxyValidation((MyValidInterface) proxyFactory.getProxy()); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
public void testMethodValidationPostProcessor() { |
||||||
|
StaticApplicationContext ac = new StaticApplicationContext(); |
||||||
|
ac.registerSingleton("mvpp", MethodValidationPostProcessor.class); |
||||||
|
ac.registerSingleton("bean", MyValidBean.class); |
||||||
|
ac.refresh(); |
||||||
|
doTestProxyValidation(ac.getBean("bean", MyValidInterface.class)); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
private void doTestProxyValidation(MyValidInterface proxy) { |
||||||
|
assertNotNull(proxy.myValidMethod("value", 5)); |
||||||
|
try { |
||||||
|
assertNotNull(proxy.myValidMethod("value", 15)); |
||||||
|
fail("Should have thrown MethodConstraintViolationException"); |
||||||
|
} |
||||||
|
catch (MethodConstraintViolationException ex) { |
||||||
|
// expected
|
||||||
|
} |
||||||
|
try { |
||||||
|
assertNotNull(proxy.myValidMethod(null, 5)); |
||||||
|
fail("Should have thrown MethodConstraintViolationException"); |
||||||
|
} |
||||||
|
catch (MethodConstraintViolationException ex) { |
||||||
|
// expected
|
||||||
|
} |
||||||
|
try { |
||||||
|
assertNotNull(proxy.myValidMethod("value", 0)); |
||||||
|
fail("Should have thrown MethodConstraintViolationException"); |
||||||
|
} |
||||||
|
catch (MethodConstraintViolationException ex) { |
||||||
|
// expected
|
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@MyStereotype |
||||||
|
public static class MyValidBean implements MyValidInterface { |
||||||
|
|
||||||
|
public Object myValidMethod(String arg1, int arg2) { |
||||||
|
return (arg2 == 0 ? null : "value"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public interface MyValidInterface { |
||||||
|
|
||||||
|
@NotNull Object myValidMethod(@NotNull(groups = MyGroup.class) String arg1, @Max(10) int arg2); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
public interface MyGroup { |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
@Validated({MyGroup.class, Default.class}) |
||||||
|
@Retention(RetentionPolicy.RUNTIME) |
||||||
|
public @interface MyStereotype { |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue