Это хороший шаблон для обработки аннотаций? - PullRequest
2 голосов
/ 21 июля 2011

У меня довольно стандартное веб-приложение 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;
}

, который не оставляет больше специального кода, который необходимо пересматривать каждый раз, когда я добавляю новый тип аннотации.Я просто внедрил валидатор как часть аннотации, и все готово.

Кажется ли это разумным для использования шаблоном?Если нет, то что может работать лучше?

Ответы [ 3 ]

2 голосов
/ 21 июля 2011

Вы можете исследовать АОП.Вы можете посоветовать методы, которые предоставляют определенные аннотации и соответственно выполняют предварительную / последующую обработку.

1 голос
/ 21 июля 2011

Я просто хотел бы добавить, что, хотя AOP будет хорошим решением, среда Spring уже предоставляет эту функциональность посредством аннотации @Secured.

@Secured("ROLE_USER")
public void foo() {

}

Spring также поддерживает проверку JSR-303 с аннотацией @Valid. Так что для этих случаев использования, по крайней мере, кажется, что вы заново изобретаете колесо.

0 голосов
/ 02 ноября 2011

ИМХО можно подумать о шаблоне посетителя в сочетании с фабрикой.Фабрика вернет объект-оболочку, который знает точный тип аннотации и который посетитель сможет ...

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);
        }
    }
}

Поскольку мы не можем расширять аннотацию, нам нужны эти классы-оболочки на фабрике.Вы также можете передать исходную аннотацию, которая затем содержится в этом классе оболочки.

Что нужно сделать: для каждого нового AnnotationType добавьте новый класс «оболочки» к фабрике, расширьте фабрику

getVisitable()

метод соответственно, а также добавить соответствующий метод для посетителя:

public void doSomething(VisitableAnnotationTypeXYZ at) {
    //something AnnotationTypeXYZ specific
}

теперь общий код проверки (или любой другой) выглядит следующим образом:

List<ValidatableAnnotation> annotations = mergeConstraintsFromClassAndMethod(serviceClass, serviceMethod);
MyVisitor visitor = new MyVisitor();
for (ValidatableAnnotation annotation : annotations) {
    VisitableFactory.getVisitable(annotation).accept(visitor);
}

Посещение работаетпо косвенной причине, что посещаемый объект вызывает посетителя с самим собой в качестве аргумента, и, таким образом, будет вызываться правильный метод посещения.Надеюсь, это поможет ;-) Код не проверен, хотя ...

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...