Теперь вот небольшая хитрость для тех из вас, кто взламывает сторонние инструменты, пытается расширить их, не понимая их полностью (пока!). Предположим следующую ситуацию:
- Вы хотите расширить библиотеку, которая предоставляет иерархическую модель данных (предположим, вы хотите расширить Apache Jackrabbit )
- Эта библиотека внутренне проверяет права доступа перед доступом к любым узлам хранилища содержимого
- Вы хотите реализовать свой собственный алгоритм контроля доступа
- Ваш алгоритм контроля доступа будет обращаться к другим узлам хранилища контента
- … Что, в свою очередь, снова вызовет контроль доступа
- … Который, в свою очередь, снова получит доступ к другим узлам хранилища контента
… Бесконечная рекурсия, возможно, приводящая к ошибке StackOverflowError , если вы не используете рекурсивную ширину.
Теперь у вас есть два варианта:
- Потратьте время, сядьте, разберитесь с внутренностями и сделайте это правильно. Вы, вероятно, не должны возвращаться в свой контроль доступа, как только вы достигли своего собственного добавочного номера. В случае расширения Jackrabbit это будет сделано с помощью системного сеанса для дальнейшего доступа к узлам в вашем алгоритме управления доступом. Системный сеанс обычно обходит контроль доступа.
- Будьте нетерпеливы, желая быстро получить результаты, и с помощью хитрости предотвратите рекурсию
Конечно, вы действительно должны выбрать вариант 1. Но у кого есть время, чтобы все понять?
Вот как реализовать этот трюк.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
/** * 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 once if (RECURSION_CONTROL.get() == null) { try { RECURSION_CONTROL.set(true); return delegate.call(); } finally { RECURSION_CONTROL.remove(); } } // Abort recursion and return early else { return resultOnRecursion; }} /** * An API to wrap your code with */public interface Protectable<T> { T call() throws Exception;} |
Это работает легко, как видно из этого примера использования:
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
public static void main(String[] args)throws Exception { protect(null, new Protectable<Void>() { @Override public Void call() throws Exception { // Recurse infinitely System.out.println("Recursing?"); main(null); System.out.println("No!"); return null; } });} |
Рекурсивный вызов метода main() будет прерван методом protect и возвратится раньше, чем при выполнении call() . Эту идею также можно развить, используя вместо этого Map of ThreadLocals , позволяющую указать различные ключи или контексты, для которых необходимо предотвратить рекурсию. Затем вы также можете поместить Integer в ThreadLocal , увеличивая его при рекурсии, допуская максимум N уровней рекурсии.
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
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); else RECURSION_CONTROL.remove(); } } else { return resultOnRecursion; }} |
Но опять. Может быть, вам стоит потратить пару минут и узнать, как на самом деле работают внутренние компоненты вашей хост-библиотеки, и понять все правильно с самого начала … Как всегда, применяя трюки и хаки!