У меня довольно стандартное веб-приложение Spring, и у меня есть несколько пользовательских аннотаций, которые я хотел бы использовать для обозначения требований и ограничений, применяемых к данному методу веб-службы.Например, я мог бы применить аннотацию @RequiresLogin
к любому методу, который требует допустимого сеанса пользователя, и @RequiresParameters(paramNames = {"name", "email"})
к методу, который требует, чтобы были установлены «имя» и «электронная почта», и так далее.
В поддержку этого я реализовал специальную утилиту для проверки аннотированных ограничений метода во время выполнения, которая в основном следовала шаблону:
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'
}
//...
Это прекрасно работает, но сталонемного громоздко, так как я добавил дополнительные аннотации.Я хотел бы заменить это чем-то более легким в обслуживании.В идеале я хотел бы иметь возможность:
List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
for (ValidatableAnnotation annotation : annotations) {
annotation.validate(request);
}
Но я уверен, что это невозможно, поскольку сами аннотации не могут содержать исполняемый код и компилятор не позволяет мне расширять java.lang.annotation.Annotation
(не то чтобы я знал, как разрешить содержать исполняемый код в аннотации, даже если компилятор позволил мне попробовать).
Однако аннотации могут содержать вложенный внутренний класс, и этот внутренний класс может делать все, что может делать обычный класс Java.Итак, на основании этого я пришел к выводу, что мой код проверки должен быть максимально тесно связан с проверяемой аннотацией:
public interface AnnotationProcessor {
public boolean processRequest(Annotation theAnnotation, HttpServletRequest request);
}
А затем аннотации могут быть реализованы следующим образом:
@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;
}
}
}
, который сохраняет логику проверки правильной и тесно связанной с проверяемой аннотацией.Тогда весь мой специальный код проверки может быть заменен на:
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;
}
, который не оставляет больше специального кода, который необходимо пересматривать каждый раз, когда я добавляю новый тип аннотации.Я просто внедрил валидатор как часть аннотации, и все готово.
Кажется ли это разумным для использования шаблоном?Если нет, то что может работать лучше?