我有一个相当标准的Spring webapp,我有一些自定义注释,我想用它来表示应用于给定web服务方法的要求和约束。例如,我可以将@RequiresLogin
注释应用于需要有效用户会话的任何方法,并对需要设置“name”和“email”的方法应用@RequiresParameters(paramNames = {"name", "email"})
,依此类推。
为了支持这一点,我实现了一个ad-hoc实用程序,用于在运行时验证方法的注释约束,基本上遵循以下模式:
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;
}
每次添加新的注释类型时,都不会再需要修改特殊代码。我只是将验证器作为注释的一部分来实现,我已经完成了。
这看起来像一个合理的模式吗?如果不是那么什么可能更好?
答案 0 :(得分:2)
您可能想要调查AOP。您可以建议公开某些注释的方法,并相应地执行前/后处理。
答案 1 :(得分:1)
我想补充一点,虽然AOP是一个很好的解决方案,但Spring框架已经通过@Secured注释提供了这个功能。
@Secured("ROLE_USER")
public void foo() {
}
Spring还支持使用@Valid注释进行JSR-303验证。所以至少对于这些用例,似乎你正在重新发明轮子。
答案 2 :(得分:0)
恕我直言可以考虑将访客模式与工厂结合起来。工厂将返回一个包装器对象,该对象知道确切的注释类型以及访问者将能够...
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);
}
}
}
由于我们无法扩展Annotation,因此我们需要工厂中的那些包装类。您还可以传递原始注释,然后将其包含在该包装类中。
你需要做的事情:为每个新的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);
}
访问通过间接访问工作,访问对象将自己作为参数调用访问者,因此将调用正确的访问方法。 希望有所帮助;-) 代码未经过测试,但是......