Намерение:
Я использую java.lang .instrument для создания инструментов для Java-программ. Идея состоит в том, что я использую манипуляции с байт-кодом через эту систему, чтобы добавлять вызовы методов в начале и в конце каждого метода. Вообще говоря, модифицированный метод Java будет выглядеть так:
public void whateverMethod(){
MyFancyProfiler.methodEntered("whateverMethod");
//the rest of the method as usual...
MyFancyProfiler.methodExited("whateverMethod");
}
MyFancyProfiler
— это точка входа в относительно сложную систему, которая инициализируется во время метода premain
(который является частью java.lang.instrument
).
редактировать - MyFancyProfiler
содержит статический API, который получит ссылку на остальную часть системы с помощью механизма, подобного описанному в решении этого вопроса. Ссылка получается как Object
, а соответствующие вызовы выполняются через отражение, так что даже если текущий ClassLoader не знает о базовых классах, он все равно будет работать.
Трудности
Для простой Java-программы этот подход отлично работает. Для «настоящих» приложений (таких как оконные приложения и особенно приложения RCP/OSGi) я столкнулся с проблемами с ClassLoaders
. Некоторые ClassLoaders
не будут знать, как найти класс MyFancyProfiler
, поэтому будут генерировать исключения при попытке вызвать статические методы в MyFancyProfiler
.
Мое решение этого (и где происходит моя настоящая проблема) в настоящее время состоит в том, чтобы «внедрить» MyFancyProfiler
в каждый встреченный ClassLoader
путем рефлексивного вызова defineClass
. Суть в следующем:
public byte[] transform(ClassLoader loader, String className, /* etc... */) {
if(/* this is the first time I've seen loader */){
//try to look up `MyFancyProfiler` in `loader`.
if(/* loader can't find my class */){
// get the bytes for the MyFancyProfiler class
// reflective call to
// loader.defineClass(
// "com.foo.bar.MyFancyProfiler", classBytes, 0, classBytes.length);
}
}
// actually do class transformation via ASM bytecode manipulation
}
отредактируйте для получения дополнительной информации. Причина этой инъекции заключается в том, чтобы гарантировать, что каждый класс, независимо от того, какой ClassLoader его загрузил, сможет напрямую вызывать MyFancyProfiler.methodEntered
. Как только он сделает этот вызов, MyFancyProfiler
потребуется использовать отражение для взаимодействия с остальной системой, иначе я получу исключение InvocationTargetException или NoClassDef, когда он попытается обратиться напрямую. В настоящее время у меня это работает, так что единственными «прямыми» зависимостями MyFancyProfiler
являются системные классы JRE, так что все в порядке.
Проблема
Это даже работает! Большую часть времени! Но по крайней мере для двух отдельных загрузчиков классов, с которыми я столкнулся при попытке отследить Eclipse (запуск IDE из командной строки), я получаю NullPointerException
из метода ClassLoader.defineClass
:
java.lang.NullPointerException
at java.lang.ClassLoader.checkPackageAccess(ClassLoader.java:500)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:791)
at java.lang.ClassLoader.defineClass(ClassLoader.java:634)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:601)
// at my code that calls the reflection
Строка 500 файла ClassLoader.java — это вызов domains.add(pd)
, где domains
кажется набором, который инициализируется во время конструктора, а pd
— это ProtectionDomain
, который (насколько я могу судить) должен быть "по умолчанию" ProtectionDomain
. Поэтому я не вижу очевидного способа, чтобы эта строка вызывала ошибку NullPointerException
. В настоящее время я в тупике, и я надеюсь, что кто-то может дать некоторое представление об этом.
Что может привести к тому, что defineClass
выйдет из строя таким образом? И в случае отсутствия очевидного решения, можете ли вы предложить потенциальный альтернативный подход к моей общей проблеме?
MyFancyProfiler
в каждом загрузчике классов, который иначе не может его найти. Он использует код, аналогичный stackoverflow.com/questions/2063829/, чтобы он был единственной добавленной зависимостью. Неудачные загрузчики классов — этоDelegatingClassLoader
экземпляра. - person Dylan   schedule 29.03.2013MyFancyProfiler
и о том, почему я добавляю его в каждый ClassLoader. - person Dylan   schedule 29.03.2013