[英]Java Annotation and Processor to mark a method as so it can be called once and only once?
我需要能够标记方法,以便在它们被多次调用时抛出 RuntimeException。
我试图强制执行一些单一的赋值语义,并且我的类的参数数量太大而无法放入单个构造函数中,并且我需要能够让这些类也知道JAXB
,因此对象需要是可变的,但我想要强制执行单一赋值语义。
我很确定我可以用 Aspects 做到这一点,但我真的希望能够使用我自己的 Annotations 处理器。
我知道如何使用 Python 中的装饰器来做到这一点。
如何编写一个注释处理器,它可以在运行时拦截对注释方法的调用,而不仅仅是在编译时?
我想我正在使用动态代理拦截方法调用,我只需要弄清楚如何将它们与我的注释处理器集成。
动态代理要求您使用接口,这很麻烦,我现在有一个CGLib MethodInterceptor工作,对拦截和装饰的要求要少得多,代价是添加依赖项。
不,没有什么可以立即使用的。 而 AspectJ 似乎是让它以更通用的方式工作的唯一方法。 正如 JB Nizet 所指出的 - 注释应该有一个解析器来解析它。
但是,我会建议一个更好、更简单的解决方案 - Builder 模式。 它是什么样子的:
FooBuilder
(它也可能是一个静态内部类),它是可变的,每个字段都有一个 setter 和 getterFooBuilder
有一个build()
方法,它返回一个Foo
的实例Foo
有一个只FooBuilder
的构造函数,你可以在那里分配每个字段。那样:
Foo
是不可变的,这是你的最终目标它很容易使用。 您只需设置您需要的字段。 就像是:
Foo foo = new Foo.FooBuilder().setBar(..).setBaz(..).build();
这样,构建器就可以识别 JAXB。 例如:
FooBuilder builder = (FooBuilder) unmarshaller.unmarshal(stream);
Foo foo = builder.build();
JAXB 对象需要是可变的,而您的要求是一个不可变的对象。 因此,构建器可以方便地解决这个问题。
这个问题与问题Applying CGLib Proxy from a Annotation Processor有一些相似之处。
如果您希望能够在注释处理器中更改原始源代码的行为,请查看http://projectlombok.org/如何实现这一点。 IMO 唯一的缺点是 lombok 依赖于 com.sun.* 类。
由于我自己需要这种东西,我想知道是否有人知道更好的方法来实现这一点,仍然使用注释处理器。
您可以使用@XmlAccessorType(XmlAccessType.FIELD)
将 JAXB 配置为使用字段(实例变量)访问。 这将允许您使用 set 方法执行所需的操作:
您还可以使用 JAXB 的XmlAdapter
机制来支持不可变对象:
我也有类似的需求。 长话短说,当你在 Spring 中注入组件时,像 A 依赖 B 和 B 依赖 A 这样的循环依赖情况非常好,但是你需要将这些组件作为字段或设置器注入。 构造函数注入导致堆栈溢出。 因此,我不得不为这些组件引入一个方法init()
,与构造函数不同,它可能会被错误地调用多次。 毋庸置疑,样板代码如下:
private volatile boolean wasInit = false;
public void init() {
if (wasInit) {
throw new IllegalStateException("Method has already been called");
}
wasInit = true;
logger.fine("ENTRY");
...
}
开始到处出现。 由于这远不是应用程序的关键点,我决定引入一个优雅的线程安全的单行解决方案,它更倾向于简洁而不是速度:
public class Guard {
private static final Map<String, Object> callersByMethods = new ConcurrentHashMap<String, Object>();
public static void requireCalledOnce(Object source) {
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
String fullClassName = stackTrace[1].getClassName();
String methodName = stackTrace[1].getMethodName();
int lineNumber = stackTrace[1].getLineNumber();
int hashCode = source.hashCode();
// Builds a key using full class name, method name and line number
String key = new StringBuilder().append(fullClassName).append(' ').append(methodName).append(' ').append(lineNumber).toString();
System.out.println(key);
if (callersByMethods.put(key, source) != null) {
throw new IllegalStateException(String.format("%s@%d.%s() was called the second time.", fullClassName, hashCode, methodName));
}
}
}
现在,因为我更喜欢在 DI 框架内构建应用程序,所以将Guard
声明为组件,然后注入它,并调用实例方法requireCalledOnce
听起来很自然。 但是由于它的通用性,静态引用产生了更多的意义。 现在我的代码如下所示:
private void init() {
Guard.requireCalledOnce(this);
...
}
这是第二次调用同一对象的init
时的异常:
Exception in thread "main" java.lang.IllegalStateException: my.package.MyComponent@4121506.init() was called the second time.
at my.package.Guard.requireCalledOnce(Guard.java:20)
at my.package.MyComponent.init(MyComponent.java:232)
at my.package.MyComponent.launch(MyComponent.java:238)
at my.package.MyComponent.main(MyComponent.java:48)
您可以使用而不是使用注释。
assert count++ != 0;
每个方法需要一个计数器。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.