Transactional annotation to rollback for every checked exception - with AspectJ [duplicate] - java

One way to configure Spring to rollback on a non RuntimeExceptions is using #Transactional(rollbackFor=...) annotation on the service classes. The problem with this approach is that we need to define (rollbackFor=...) for almost all the service classes which seems really redundant.
My question: Is there any way to configure a default behaviour for Spring transaction manager to rollback on a non RuntimeException whenever it happens without declaring it on every #Transactional annotation. Something like using #ApplicationException(rollback=true) annotation on an exception class in EJB.

You can't do it for application level with #Transactional , but you can :
variant 1 : extend #Transactional annotation and put it as default value for rollbackfor. But set rollbackFor unchecked exceptions only that you need .With this you can control rollbacks only for case that you sure , and avoid copy past of #Transactional(rollbackFor =MyCheckedException.class)
Like:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Transactional(rollbackFor=MyCheckedException.class)
public #interface TransactionalWithRollback {
}
And use this annotation instead of standard #Transactional.
variant 2 : you can create extension from AnnotationTransactionAttributeSource and override method determineTransactionAttribute:
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement ae)
//Determine the transaction attribute for the given method or class.
TransactionAttribute see TransactionAttribute api , there is a method
boolean rollbackOn(Throwable ex) Should we roll back on the given exception?
protected TransactionAttribute determineTransactionAttribute(
AnnotatedElement ae) {
return new DelegatingTransactionAttribute(target) {
#Override
public boolean rollbackOn(Throwable ex) {
return (check is exception type as you need for rollback );
}
};
}
Second approach is not so good as first as you do it really global for transaction manager. Better use custom annotation as you can control it any apply only for methods/classes where you really need it. But if you need it in any case use second variant , it will be your default transnational behavior.

This config solves it:
#Configuration
public class MyProxyTransactionManagementConfiguration extends ProxyTransactionManagementConfiguration {
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource() {
#Nullable
protected TransactionAttribute determineTransactionAttribute(AnnotatedElement element) {
TransactionAttribute ta = super.determineTransactionAttribute(element);
if (ta == null) {
return null;
} else {
return new DelegatingTransactionAttribute(ta) {
#Override
public boolean rollbackOn(Throwable ex) {
return super.rollbackOn(ex) || ex instanceof Exception;
}
};
}
}
};
}
}

This is a similar approach as this answer, i.e. changing the default globally, but with as minimal change to Spring's config as possible, and still leaving the possibility to customize rollback rules per method as usual (with rollbackFor, noRollbackFor etc.).
This is achieved by simply adding a default RollbackRule for Exception.class. Since the rules have precedence according to the exception class hierarchy (the rule for the most specific exception class applicable wins), the new rule has basically lowest precendence, if no other rules are defined on the annotation.
#Configuration
public class MyTransactionManagementConfiguration {
/**
* Note: This custom config does NOT recognize {#code javax.transaction.Transactional} annotations in contrast to
* the original Spring behaviour. Check the original {#code AnnotationTransactionAttributeSource} source code for an idea how to add that.
*
* #see AnnotationTransactionAttributeSource#AnnotationTransactionAttributeSource(boolean)
*/
#Bean
#Primary
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public TransactionAttributeSource transactionAttributeSourceWithDefaultRollBackForAllExceptions() {
return new AnnotationTransactionAttributeSource(
new SpringTransactionAnnotationParser() {
#Override
protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = (RuleBasedTransactionAttribute) super.parseTransactionAnnotation(attributes);
List<RollbackRuleAttribute> rules = new ArrayList<>(rbta.getRollbackRules());
rules.add(new RollbackRuleAttribute(Exception.class));
rbta.setRollbackRules(rules);
return rbta;
}
}
);
}
}

Related

Is it possible to create a custom condition equivalate of #ConditionalOnMissingBean with Spring (no spring-boot)?

I am trying to implement a Spring condition that will load my bean only if there is no other beans for a certain class.
The desired behavior is similar to "#ConditionalOnMissingBean" but without using spring-boot.
I am using Spring versions 5.3.13.
Is that possible? thanks.
I found a solution by digging around spring-boot source code.
ConditionalOnBean will only work on beans defined inside a configuration (bean methods).
This is also recommended by spring-boot java doc for #ConditionalOnBean
The condition can only match the bean definitions that have been processed by the
application context so far and, as such, it is strongly recommended to use this
condition on auto-configuration classes only. If a candidate bean may be created by
another auto-configuration, make sure that the one using this condition runs after.
Here is the basics of the solution I came up with, this may be improved but the basics operate well.
The condition:
#Slf4j
class MissingBeanCondition implements ConfigurationCondition {
#Override
public ConfigurationPhase getConfigurationPhase() {
return ConfigurationPhase.REGISTER_BEAN;
}
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
var targetBeanType = metadata.getAnnotations()
.get(ConditionalOnMissingBean.class)
.getValue("value", Class.class)
// TODO throw a more informative error
.orElseThrow(() -> new RuntimeException("Failed to evaluate MissingBeanCondition"));
try {
context.getBeanFactory().getBean(targetBeanType);
} catch (NoSuchBeanDefinitionException e) {
return true;
}
return false;
}
}
The annotation:
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
#Conditional(MissingBeanCondition.class)
public #interface ConditionalOnMissingBean {
Class<?> value();
}
Usage example:
#Bean
#Singleton
#ConditionalOnMissingBean(Provider.class)
public Provider myClass() {
return new DefaultProvider();
}

Why can not get annotation from beanClass?

#Transactional
#Component
#EntranceLog
public class TransferServiceImpl implements TransferService {
xxxx
}
I hava a class with Transactional annotation and Component annotation. EntranceLog is my customize annotation to print log by aop.
public class LogProxyCreator extends AbstractAutoProxyCreator implements ApplicationContextAware {
private static final LogInterceptor LOG = new LogInterceptor();
private static Logger log = LoggerFactory.getLogger(LogProxyCreator.class);
#Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String s, TargetSource targetSource) throws BeansException {
Annotation anno = null;
for (Annotation annotationTemp : beanClass.getAnnotations()) {
Log temp = annotationTemp.annotationType().getAnnotation(EntranceLog.class);
if (temp != null) {
anno = temp;
break;
}
}
if (anno == null) {
return null;
}
Object[] additional = new Object[]{LOG};
log.error(beanClass.getName() + " has register the fc log.");
return additional;
}
#Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
LOG.setContext(applicationContext);
}
}
When my app is starting, the bean transferServiceImpl start, but beanClass.getAnnotations() can not get any annotation. Why?
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE})
#Log(logName = "entrance")
public #interface EntranceLog {
#AliasFor(
annotation = Log.class,
attribute = "subLogName"
)
String logName() default "";
#AliasFor(
annotation = Log.class,
attribute = "openInfoLog"
)
boolean openInfoLog() default false;
}
This is my annotation.
In Spring #Transactionalis already an AOP processed annotation, so adding your own will require some additional work. Let me explain how Spring AOP and #Transactional works.
Spring has two ways of doing AOP, if the class implements an interface it can use a standard JDK Proxy, if the class does not implement an interface it will create a new subclass by using CGLib to emit bytecode at runtime. Unless you are very careful you will almost always get a CGLib proxy with Spring AOP.
When Spring encounters a #Transactional (class or method level) it creates a new subclass using CGLib, you can think of this class as a decorator, which forwards all calls to your implementation class. Before and after (around Advice) it check the #Transactional annotation properties, and check Thread Local storage to see if a transaction already exist, if there is no transaction it creates one, and remembers it so it can commit it afterwards. If you set a breakoint inside a Transactional method and look at the callstack you will see the call to your implementation came from the decorater class, and that there is no source code for it.
In your case the bean that is added to the Application Context, is not your TransferServiceImplbean, but the CGLib proxy created by Spring when it found the #Transactional annotation on your class, it will be named something like TransferServiceImpl$$FastClassBySpringCGLIB$$<hexstring> - This class does not have the #EntranceLog annotation, which is why your own aspect is not working.
I have never encountered this problem myself, as I try to avoid AOP in general, or at always on classes that are already being CGLib proxied by Spring. Unless you want to dig deep into the Spring source, or find someone on the Spring dev team to help you with this, I suggest that you create another layer of indirection, so you don't need to handle two aspects in the same class.
For anyone who may be unwilling or unable to alter their code structure in order to avoid this issue, the following can probably help:
As Klaus mentioned, Spring creates a decorator class when it encounters a class tagged with #Transactional. However, because this new class is just that--a decorator--you should be able to call getSuperclass() on beanClass to give you the actual class Spring is decorating, like so:
beanClass.getSuperclass().getAnnotations()
If you're using your own Annotation, ensure it also persists through runtime by annotating the Annotation class with:
#Retention(RetentionPolicy.RUNTIME)

How can I ignore spring #Transactional annotation for a specific method when #ActiveProfiles("test")

I need to ignore the following #Transactional annotation during my integration tests.
#Service
public class MyClass {
#Transactional(propagation = Propagation.NEVER)
public void doSomething() {
// do something that once in production can not be inside a transaction (reasons are omitted)
}
}
The problem is that all my tests are executed inside a transaction that is rolled back by default. How could I ignore the #Transactional(propagation = Propagation.NEVER) annotation for this method when it is running in the scope of a test (#ActiveProfiles("test")) allowing it to be executed inside a transaction?
First of all, you need to exclude your current #EnableTransactionManagement annotation to be active in your test profile. You can do this by isolating the #EnableTransactionManagement annotation to a separate configuration class which excludes the profile test so it only gets activated whenever the test profile is not active.
#EnableTransactionManagement(mode=AdviceMode.PROXY)
#Profile("!test")
public class TransactionManagementConfig {}
With that in place, we can start building up the custom transaction management configuration for your test profile. First we define an annotation that will be used to activate custom transaction management (javadoc comments stripped for compactness of the example, see EnableTransactionManagement javadoc for details).
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Import(CustomTransactionManagementConfigurationSelector.class)
public #interface EnableCustomTransactionManagement {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
Then we need an import selector. Note, that since you're using AdviceMode.PROXY, i skipped implementing the ASPECTJ part, but that should be done analogously in order to use AspectJ based transaction management.
public class CustomTransactionManagementConfigurationSelector extends
AdviceModeImportSelector<EnableCustomTransactionManagement> {
#Override
protected String[] selectImports(AdviceMode adviceMode) {
switch (adviceMode) {
case PROXY:
return new String[] {
AutoProxyRegistrar.class.getName(),
CustomTransactionAttributeSourceConfig.class.getName()
};
case ASPECTJ:
default:
return null;
}
}
}
And finally the part where you will be able to override the transaction attributes. This one subclasses ProxyTransactionManagementConfiguration for AdviceMode.PROXY, you would need an analogous implementation based on AspectJTransactionManagementConfiguration for AdviceMode.ASPECTJ. Feel free to implement your own logic, whether be it a constant override of whatever attributes the original AnnotationTransactionAttributeSource would determine as in my example, or going to greater lengths by introducing and handling your own custom annotation for this purpose.
#Configuration
public class CustomTransactionAttributeSourceConfig
extends ProxyTransactionManagementConfiguration {
#Override
public void setImportMetadata(AnnotationMetadata importMetadata) {
this.enableTx = AnnotationAttributes
.fromMap(importMetadata.getAnnotationAttributes(
EnableCustomTransactionManagement.class.getName(),
false));
Assert.notNull(this.enableTx,
"#EnableCustomTransactionManagement is not present on importing class "
+ importMetadata.getClassName());
}
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
#Override
public TransactionAttributeSource transactionAttributeSource() {
return new AnnotationTransactionAttributeSource() {
private static final long serialVersionUID = 1L;
#Override
protected TransactionAttribute findTransactionAttribute(
Class<?> clazz) {
TransactionAttribute transactionAttribute =
super.findTransactionAttribute(clazz);
if (transactionAttribute != null) {
// implement whatever logic to override transaction attributes
// extracted from #Transactional annotation
transactionAttribute = new DefaultTransactionAttribute(
TransactionAttribute.PROPAGATION_REQUIRED);
}
return transactionAttribute;
}
#Override
protected TransactionAttribute findTransactionAttribute(
Method method) {
TransactionAttribute transactionAttribute =
super.findTransactionAttribute(method);
if (transactionAttribute != null) {
// implement whatever logic to override transaction attributes
// extracted from #Transactional annotation
transactionAttribute = new DefaultTransactionAttribute(
TransactionAttribute.PROPAGATION_REQUIRED);
}
return transactionAttribute;
}
};
}
}
Finally, you'll need to enable the custom transaction management configuration with a configuration class tied to the test profile.
#EnableCustomTransactionManagement(mode=AdviceMode.PROXY)
#Profile("test")
public class TransactionManagementTestConfig {}
I hope this helps.

Spring #Validated in service layer

Hej,
I want to use the #Validated(group=Foo.class) annotation to validate an argument before executing a method like following:
public void doFoo(Foo #Validated(groups=Foo.class) foo){}
When i put this method in the Controller of my Spring application, the #Validated is executed and throws an error when the Foo object is not valid. However if I put the same thing in a method in the Service layer of my application, the validation is not executed and the method just runs even when the Foo object isn't valid.
Can't you use the #Validated annotation in the service layer ? Or do I have to do configure something extra to make it work ?
Update:
I have added the following two beans to my service.xml:
<bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>
<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>
and replaced the #Validate with #Null like so:
public void doFoo(Foo #Null(groups=Foo.class) foo){}
I know it is a pretty silly annotation to do but I wanted to check that if I call the method now and passing null it would throw an violation exception which it does. So why does it execute the #Null annotation and not the #Validate annotation ? I know one is from javax.validation and the other is from Spring but I do not think that has anything to do with it ?
In the eyes of a Spring MVC stack, there is no such thing as a service layer. The reason it works for #Controller class handler methods is that Spring uses a special HandlerMethodArgumentResolver called ModelAttributeMethodProcessor which performs validation before resolving the argument to use in your handler method.
The service layer, as we call it, is just a plain bean with no additional behavior added to it from the MVC (DispatcherServlet) stack. As such you cannot expect any validation from Spring. You need to roll your own, probably with AOP.
With MethodValidationPostProcessor, take a look at the javadoc
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).
Validation groups can be specified through Spring's 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.
The #Validated annotation is only used to specify a validation group, it doesn't itself force any validation. You need to use one of the javax.validation annotations like #Null or #Valid. Remember that you can use as many annotations as you would like on a method parameter.
As a side note on Spring Validation for methods:
Since Spring uses interceptors in its approach, the validation itself is only performed when you're talking to a Bean's method:
When talking to an instance of this bean through the Spring or JSR-303 Validator interfaces, you'll be talking to the default Validator of the underlying ValidatorFactory. This is very convenient in that you don't have to perform yet another call on the factory, assuming that you will almost always use the default Validator anyway.
This is important because if you're trying to implement a validation in such a way for method calls within the class, it won't work. E.g.:
#Autowired
WannaValidate service;
//...
service.callMeOutside(new Form);
#Service
public class WannaValidate {
/* Spring Validation will work fine when executed from outside, as above */
#Validated
public void callMeOutside(#Valid Form form) {
AnotherForm anotherForm = new AnotherForm(form);
callMeInside(anotherForm);
}
/* Spring Validation won't work for AnotherForm if executed from inner method */
#Validated
public void callMeInside(#Valid AnotherForm form) {
// stuff
}
}
Hope someone finds this helpful. Tested with Spring 4.3, so things might be different for other versions.
#pgiecek You don't need to create a new Annotation. You can use:
#Validated
public class MyClass {
#Validated({Group1.class})
public myMethod1(#Valid Foo foo) { ... }
#Validated({Group2.class})
public myMethod2(#Valid Foo foo) { ... }
...
}
Be careful with rubensa's approach.
This only works when you declare #Valid as the only annotation. When you combine it with other annotations like #NotNull everything except the #Valid will be ignored.
The following will not work and the #NotNull will be ignored:
#Validated
public class MyClass {
#Validated(Group1.class)
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated(Group2.class)
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
In combination with other annotations you need to declare the javax.validation.groups.Default Group as well, like this:
#Validated
public class MyClass {
#Validated({ Default.class, Group1.class })
public void myMethod1(#NotNull #Valid Foo foo) { ... }
#Validated({ Default.class, Group2.class })
public void myMethod2(#NotNull #Valid Foo foo) { ... }
}
As stated above to specify validation groups is possible only through #Validated annotation at class level. However, it is not very convenient since sometimes you have a class containing several methods with the same entity as a parameter but each of which requiring different subset of properties to validate. It was also my case and below you can find several steps to take to solve it.
1) Implement custom annotation that enables to specify validation groups at method level in addition to groups specified through #Validated at class level.
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface ValidatedGroups {
Class<?>[] value() default {};
}
2) Extend MethodValidationInterceptor and override determineValidationGroups method as follows.
#Override
protected Class<?>[] determineValidationGroups(MethodInvocation invocation) {
final Class<?>[] classLevelGroups = super.determineValidationGroups(invocation);
final ValidatedGroups validatedGroups = AnnotationUtils.findAnnotation(
invocation.getMethod(), ValidatedGroups.class);
final Class<?>[] methodLevelGroups = validatedGroups != null ? validatedGroups.value() : new Class<?>[0];
if (methodLevelGroups.length == 0) {
return classLevelGroups;
}
final int newLength = classLevelGroups.length + methodLevelGroups.length;
final Class<?>[] mergedGroups = Arrays.copyOf(classLevelGroups, newLength);
System.arraycopy(methodLevelGroups, 0, mergedGroups, classLevelGroups.length, methodLevelGroups.length);
return mergedGroups;
}
3) Implement your own MethodValidationPostProcessor (just copy the Spring one) and in the method afterPropertiesSet use validation interceptor implemented in step 2.
#Override
public void afterPropertiesSet() throws Exception {
Pointcut pointcut = new AnnotationMatchingPointcut(Validated.class, true);
Advice advice = (this.validator != null ? new ValidatedGroupsAwareMethodValidationInterceptor(this.validator) :
new ValidatedGroupsAwareMethodValidationInterceptor());
this.advisor = new DefaultPointcutAdvisor(pointcut, advice);
}
4) Register your validation post processor instead of Spring one.
<bean class="my.package.ValidatedGroupsAwareMethodValidationPostProcessor"/>
That's it. Now you can use it as follows.
#Validated(groups = Group1.class)
public class MyClass {
#ValidatedGroups(Group2.class)
public myMethod1(Foo foo) { ... }
public myMethod2(Foo foo) { ... }
...
}

Is this a good pattern for annotation processing?

I've got a fairly standard Spring webapp, and I have a number of custom annotations that I would like to use to denote the requirements and constraints applied to a given web-service method. For instance, I might apply an #RequiresLogin annotation to any method that requires a valid user session, and #RequiresParameters(paramNames = {"name", "email"}) on a method that requires that "name" and "email" be set, and so on.
In support of this I implemented an ad-hoc utility for validating a method's annotated constraints at runtime, which basically followed a pattern of:
Map<Class<? extends Annotation>, Annotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
if (annotations.containsKey(AnnotationType1.class)) {
AnnotationType1 annotation = (AnnotationType1)annotations.get(AnnotationType1.class);
//do validation appropriate to 'AnnotationType1'
}
if (annotations.containsKey(AnnotationType2.class)) {
AnnotationType2 annotation = (AnnotationType2)annotations.get(AnnotationType2.class);
//do validation appropriate to 'AnnotationType2'
}
//...
This works fine, but has become a bit unwieldy as I have added additional annotations. I'd like to replace it with something a bit more maintainable. Ideally I'd like to be able to do:
List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
for (ValidatableAnnotation annotation : annotations) {
annotation.validate(request);
}
But I'm pretty sure that is not possible since annotations themselves cannot contain executable code and since the compiler will not let me extend java.lang.annotation.Annotation (not that I'd know how to go about allowing executable code to be contained in an annotation even if the compiler let me try).
What annotations can contain, however, is a nested inner class, and that inner class can do anything that a normal Java class can do. So what I've come up with based upon that and in the interest of keeping my validation code as closely associated with the annotation being validated as possible is:
public interface AnnotationProcessor {
public boolean processRequest(Annotation theAnnotation, HttpServletRequest request);
}
And then the annotations can be implemented like:
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.METHOD, ElementType.TYPE})
public #interface RequiresLogin {
public static class Processor implements AnnotationProcessor {
#Override
public boolean processRequest(Annotation theAnnotation, HttpServletRequest request) {
if (! (theAnnotation instanceof RequiresLogin)) {
//someone made an invalid call, just return true
return true;
}
return request.getSession().getAttribute(Constants.SESSION_USER_KEY) != null;
}
}
}
Which keeps the validation logic nice and tightly coupled with the annotation that is being validated. Then all my ad-hoc validation code can be replaced with:
List<Annotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
for (Annotation annotation : annotations) {
processAnnotation(annotation, request);
}
private static boolean processAnnotation(Annotation annotation, HttpServletRequest request) {
AnnotationProcessor processor = null;
for (Class<?> processorClass : annotation.annotationType().getDeclaredClasses()) {
if (AnnotationProcessor.class.isAssignableFrom(processorClass)) {
try {
processor = (AnnotationProcessor)processorClass.newInstance();
break;
}
catch (Exception ignored) {
//couldn't create it, but maybe there is another inner
//class that also implements the required interface that
//we can construct, so keep going
}
}
}
if (processor != null) {
return processor.processRequest(annotation, request);
}
//couldn't get a a processor and thus can't process the
//annotation, perhaps this annotation does not support
//validation, return true
return true;
}
Which leaves no more ad-hoc code that needs to be revised every time I add a new annotation type. I just implement the validator as part of the annotation, and I'm done.
Does this seem like a reasonable pattern to use? If not then what might work better?
You may want to investigate AOP. You can advise methods that expose certain annotations and perform pre/post processing accordingly.
I would just like to add that while AOP would be a good solution, the Spring framework already provides this functionality by way of the #Secured annotation.
#Secured("ROLE_USER")
public void foo() {
}
Spring also supports JSR-303 validation with the #Valid annotation. So for these use cases at least, it seems you are re-inventing the wheel.
IMHO one could think about the Visitor pattern in combination with a factory. The factory will return a wrapper object that knows the exact annotation type and which the visitor will be able...
class MyVisitor {
public void visit(VisitableAnnotationType1 at) {
//something AnnotationType1 specific
}
public void visit(VisitableAnnotationType2 at) {
//something AnnotationType2 specific
}
... // put methods for further annotation types here
}
class VisitableFactory {
public abstract class VisitableAnnotation {
public abstract void accept(MyVisitor visitor);
}
class VisitableAnnotationType1 implements VisitableAnnotation {
public void accept(MyVisitor visitor) {
visitor.visit(this);
}
}
public static VisitableAnnotation getVisitable(Annotation a) {
if(AnnotationType1.class.isAssignableFrom(a.getClass()) {
//explicitely cast to the respective AnnotationType
return new VisitableAnnotationType1((AnnotationType1)a);
} else if (AnnotationType2.class.isAssignableFrom(a.getClass()) {
//explicitely cast to the respective AnnotationType
return new VisitableAnnotationType1((AnnotationType1)a);
}
}
}
As we cannot extend Annotation, we need those wrapper classes in the factory. You could also pass the original annotation which is then contained in that wrapper class.
What you have to do: For each new AnnotationType add a new "wrapper" class to the factory, extend the factory's
getVisitable()
method accordingly and also add an according method to the Visitor:
public void doSomething(VisitableAnnotationTypeXYZ at) {
//something AnnotationTypeXYZ specific
}
now the generic validation (or whatever) code looks like:
List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
MyVisitor visitor = new MyVisitor();
for (ValidatableAnnotation annotation : annotations) {
VisitableFactory.getVisitable(annotation).accept(visitor);
}
The visiting works by the indirection that the visited object calls the visitor with itself as the argument and thus the correct visit method will be invoked.
Hope that helps ;-)
Code is not tested, though...

Categories

Resources