Note that there are some explanatory texts on larger screens.

plurals
  1. POIs this a good pattern for annotation processing?
    text
    copied!<p>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 <code>@RequiresLogin</code> annotation to any method that requires a valid user session, and <code>@RequiresParameters(paramNames = {"name", "email"})</code> on a method that requires that "name" and "email" be set, and so on. </p> <p>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:</p> <pre><code>Map&lt;Class&lt;? extends Annotation&gt;, Annotation&gt; 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' } //... </code></pre> <p>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:</p> <pre><code>List&lt;ValidatableAnnotation&gt; annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod); for (ValidatableAnnotation annotation : annotations) { annotation.validate(request); } </code></pre> <p>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 <code>java.lang.annotation.Annotation</code> (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). </p> <p>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:</p> <pre><code>public interface AnnotationProcessor { public boolean processRequest(Annotation theAnnotation, HttpServletRequest request); } </code></pre> <p>And then the annotations can be implemented like:</p> <pre><code>@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; } } } </code></pre> <p>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:</p> <pre><code>List&lt;Annotation&gt; annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod); for (Annotation annotation : annotations) { processAnnotation(annotation, request); } private static boolean processAnnotation(Annotation annotation, HttpServletRequest request) { AnnotationProcessor processor = null; for (Class&lt;?&gt; 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; } </code></pre> <p>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.</p> <p>Does this seem like a reasonable pattern to use? If not then what might work better?</p>
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload