对于那些使用第三方工具来尝试扩展它们而又不完全了解它们的人来说,这是一个小技巧。 假定以下情况:
- 您想扩展一个公开分层数据模型的库(假设您要扩展Apache Jackrabbit )
- 该库在访问内容存储库的任何节点之前会内部检查访问权限
- 您想实现自己的访问控制算法
- 您的访问控制算法将访问内容存储库的其他节点
- …反过来又会触发访问控制
- …进而将再次访问内容存储库的其他节点
…无限递归,如果不递归广度优先,则可能会导致StackOverflowError 。
现在,您有两个选择:
- 花时间,坐下来,了解内部原理,并正确地做。 到达自己的扩展名后,您可能不应该递归到访问控制中。 在扩展Jackrabbit的情况下,这可以通过使用系统会话来进一步访问访问控制算法中的节点来完成。 系统会话通常绕过访问控制。
- 急躁,想快速获得结果,并通过技巧来防止递归
当然,您确实应该选择选项1。但是,谁有时间了解所有内容?
这是实现该技巧的方法。
/*** This thread local indicates whether you've* already started recursing with level 1*/
static final ThreadLocal<Boolean> RECURSION_CONTROL= new ThreadLocal<Boolean>();/*** This method executes a delegate in a "protected"* mode, preventing recursion. If a inadvertent* recursion occurred, return a default instead*/
public static <T> T protect(T resultOnRecursion,Protectable<T> delegate)
throws Exception {// Not recursing yet, allow a single level of// recursion and execute the delegate onceif (RECURSION_CONTROL.get() == null) {try {RECURSION_CONTROL.set(true);return delegate.call();}finally {RECURSION_CONTROL.remove();}}// Abort recursion and return earlyelse {return resultOnRecursion;}
}/*** An API to wrap your code with*/
public interface Protectable<T> {T call() throws Exception;
}
在此用法示例中可以很容易地看出这一点:
public static void main(String[] args)
throws Exception {protect(null, new Protectable<Void>() {@Overridepublic Void call() throws Exception {// Recurse infinitelySystem.out.println("Recursing?");main(null);System.out.println("No!");return null;}});
}
对main()
方法的递归调用将被保护方法终止,并提早返回,而不是执行call()
。 也可以通过使用ThreadLocals
Map
进一步详细说明此思想,从而允许指定各种键或上下文以防止递归。 然后,您还可以将Integer
放入ThreadLocal
,在递归时将其递增,最多允许N个递归级别。
static final ThreadLocal<Integer> RECURSION_CONTROL= new ThreadLocal<Integer>();public static <T> T protect(T resultOnRecursion,Protectable<T> delegate)
throws Exception {Integer level = RECURSION_CONTROL.get();level = (level == null) ? 0 : level;if (level < 5) {try {RECURSION_CONTROL.set(level + 1);return delegate.call();}finally {if (level > 0)RECURSION_CONTROL.set(level - 1);elseRECURSION_CONTROL.remove();}}else {return resultOnRecursion;}
}
但是再说一次。 也许您应该再花几分钟时间,了解主机库的内部机制是如何工作的,并从一开始就将事情做好……与往常一样,在使用技巧和黑客手段时!
参考:在Java,SQL和JOOQ博客上,我们的JCG合作伙伴 Lukas Eder 提供了Java ThreadLocals的意外递归保护 。
翻译自: https://www.javacodegeeks.com/2013/04/inadvertent-recursion-protection-with-java-threadlocals.html