Чтобы уменьшить зависимость от внешних библиотек (и в основном как учебное упражнение), я решил добавить ServletContextListener в педагогический webapp, который я разрабатываю. Он будет создавать реестр имен классов (хранится как строки), которые имеют определенную аннотацию, сканируя каталог «WEB-INF/classes».Реализация пользовательского класса ClassLoader для сканирования/WEB-INF/classes directory
В качестве части этого я написал пользовательский класс ClassLoader, который я могу выбросить так часто, чтобы предотвратить заполнение пермгена при загрузке контекста из-за злоупотребления WebappClassLoader, с которого я начинаю работать.
К сожалению, я получаю неприятное исключение java.lang.NoClassDefFoundError: javax/websocket/server/ServerEndpointConfig$Configurator
, когда он пытается загрузить один из моих ServerEndpointConfigurators.
public class MetascanClassLoader extends ClassLoader
{
private final String myBaseDir;
public MetascanClassLoader(final String baseDir)
{
if(!baseDir.endsWith(File.separator))
{
myBaseDir = baseDir + File.separator;
}
else
{
myBaseDir = baseDir;
}
}
@Override
protected Class<?> loadClass(final String name, final boolean resolve)
throws ClassNotFoundException
{
synchronized(getClassLoadingLock(name))
{
Class<?> clazz = findLoadedClass(name);
if (clazz == null)
{
try
{
final byte[] classBytes =
getClassBytesByName(name);
clazz = defineClass(
name, classBytes, 0, classBytes.length);
}
catch (final ClassNotFoundException e)
{
if (getParent() != null)
{
clazz = getParent().loadClass(name);
}
else
{
throw new ClassNotFoundException(
"Could not load class from MetascanClassloader's " +
"parent classloader",
e);
}
}
}
if(resolve)
{
resolveClass(clazz);
}
return clazz;
}
}
private byte[] getClassBytesByName(final String name)
throws ClassNotFoundException
{
final String pathToClass =
myBaseDir + name.replace(
'.', File.separatorChar) + ".class";
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
try(final InputStream stream = new FileInputStream(pathToClass))
{
int b;
while((b = stream.read()) != -1)
{
baos.write(b);
}
}
catch(final FileNotFoundException e)
{
throw new ClassNotFoundException(
"Could not load class in MetascanClassloader.", e);
}
catch(final IOException e)
{
throw new RuntimeException(e);
}
return baos.toByteArray();
}
}
Некоторая часть кода была подвержена влиянию реализации ClassLoader по умолчанию.
Вот общий поток, что я пытаюсь сделать:
- захватить замок для имени класса я пытаюсь загрузить, чтобы убедиться, что другие потоки (если я пытаюсь ударить этого ClassLoader в параллельное выполнение в какой-то момент в будущем) не пытайтесь загрузить один и тот же класс дважды.
- Проверьте, не загружен ли я этот класс с помощью метода findLoadedclass();
- Если он еще не загружен, попытайтесь извлечь класс из моего каталога «WEB-INF/classes».
- Если это не удается, передайте родительский загрузчик классов.
- Если это все еще не удается, взорвите (выбросьте).
Я могу гарантировать, что я передаю в именах классов корректно после сканирования для файлов класса - это отлично работает, если я заменю MetascanClassLoader.load (имя класса) звонки с Class.forName (Classname), но, как упоминалось ранее, Я не хочу забивать перген.
Похоже, что он падает только при попытке загрузить классы, содержащие ссылку на классы, которые могут быть найдены только в комплекте с Tomcat. У него вообще нет проблем с Java SE.
Сообщите мне, если вы также заметите что-либо особенно коварное/неприятное, что я делаю, кроме как заставить его не работать.
UPDATE: Похоже, что использование конструктора по умолчанию для суперкласса устанавливает родительский загрузчик классов в системный загрузчик классов, что объясняет недостающие классы, найденные в Tomcat.
я добавил следующую строку в качестве первой строки в моем конструкторе:
super(Thread.currentThread().getContextClassLoader());
К сожалению, я до сих пор работаю с проблемами, так как все объекты класса, возвращаемые моим ClassLoader не пустые, то с не информацию, за исключением имени класса. (Я проверил внутренние поля с помощью Eclipse, чтобы обнаружить это.)
Дополнительная информация: Классы Java SE по-прежнему загружаются правильно, путем осмотра объектов.Когда я проверяю один из классов, который живет в классах WEB-INF \, это то поведение, которое я получаю, когда пытаюсь проверить объект класса в любой момент после вызова loadClass() (Я скрыл имена пакетов для предотвращения совместного использования ненужной информации о проекте). .
Я также попытался обеспечить, что resolveClass() вызывается решением hardcoding loadClass (name, resolve) для true, но это не имеет значения.
ОБНОВЛЕНО СНОВА: Благодаря Хольгер отличный «пощечина-с-форелью» момент ниже, я был чрезвычайно глуп, чтобы думать, что я мог бы сделать вывод, смысл частного переменным в Class
объектах возвращаются.
Я бросил несколько System.out.print()
S внутри моей ClassLoader
(synchronized
конечно - первый раз, когда я попытался его без синхронизации был очень грязный!) Я воткнул следующую строку непосредственно перед обратным заявлением loadClass()
System.out.print(clazz.getName() + " annotations:");
for(final Annotation a : clazz.getAnnotations())
{
System.out.print(" " + a.annotationType().getName() + ";");
}
System.out.println();
Это дало мне результаты, которые я ожидал, напечатав что-то по строкам:
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;
Но подождите минутку - это работает?
System.out.print(clazz.getName() + " annotations:");
for(final Annotation a : clazz.getAnnotations())
{
System.out.print(" " + a.annotationType().getName() + ";");
}
System.out.print("HAS_ANNOTATION:");
if(clazz.getAnnotation(MyAnnotationOne.class) != null)
{
System.out.print("true");
}
else
{
System.out.print("false");
}
System.out.println();
Результат:
org.fun.MyClass annotations: org.fun.MyAnnotationOne; org.fun.MyAnnotationsTwo;HAS_ANNOTATION:false
Хлоп! Но потом это ударило меня. Проверьте мой ответ ниже.
«(Я проверил внутренние поля, используя Eclipse, чтобы обнаружить это.)« Это ничего не значит. Существует причина для инкапсуляции в ООП. Просто просмотр частных полей объекта ничего не говорит о семантике. Держу пари, если вы позвоните, например. 'getDeclaredMethods()', в экземпляре класса вместо поиска с отладчиком все будет выглядеть совершенно иначе. – Holger
Отличный комментарий. Заглядывание появилось, как и для других классов, эти поля были установлены, и я ошибочно предположил, что это связано с проблемой. Я обновляю вопрос своими новыми выводами. – studro
Для всех, что я мог знать, эти поля могли просто хранить ранее выкопанные значения. – studro