Статьи

Взгляд внутрь слоя загрузки JCoss микроконтейнера

Теперь, когда мы поняли концепции виртуальной файловой системы (VFS) , пришло время перейти к слою ClassLoading микроконтейнера (как было обещано в предыдущей статье ).

У JBoss всегда был уникальный способ работы с загрузкой классов , и новый уровень загрузки классов, который поставляется с Microcontainer, не является исключением (имейте в виду, что вы можете использовать Microcontainer без слоя загрузки классов — это дополнение, которое вы используете, когда вы хотите загрузка классов по умолчанию). Поскольку загрузка классов в стиле OSGi набирает все большую популярность, а на горизонте появляется множество новых Java-модулей / спецификаций загрузки классов, пришло время обновить наш уровень загрузки классов, чтобы можно было легко и приятно поддерживать новые требования.

Прочитайте другие части эксклюзивной серии микроконтейнеров JBoss от DZone :

 

Основываясь на наших предыдущих ошибках, мы хотели предоставить гибкую архитектуру, в то же время ограничивая раскрытие мельчайших деталей, тем самым сводя к минимуму потенциальное «хакерское» использование классов деталей реализации, которые не являются частью общедоступного API.

Как вы увидите, большинство, если не все детали скрыты за закрытыми и пакетными закрытыми методами, без ущерба для расширяемости и функциональности, доступных через открытые классы и методы, которые создают API. Это с точки зрения нового уровня загрузки классов означает, что вы кодируете против политики, а не против деталей загрузчика классов.

Проект ClassLoader разделен на 3 подпроекта.

  • Загрузчик классов
  • загрузки классов
  • VFS-загрузки классов

«Classloader» содержит наше специальное расширение java.lang.ClassLoader без какой-либо конкретной политики загрузки классов — знание того, откуда и как загружать.

«Classloading» — это расширение механизмов зависимости Microcontainer, основанных на конфигурации загрузки классов, и «classloading-vfs» в качестве его реализации с поддержкой VFS — VFS используется для загрузки классов.

ClassLoader

Целью подпроекта «загрузчик классов» является предоставление фактического механизма загрузки классов во время выполнения. Он предоставляет реализацию ClassLoader, которая поддерживает подключаемые политики и сама по себе является в конечном итоге классом, с которым не следует сталкиваться.

Вместо этого люди, которые хотят написать свои собственные реализации ClassLoader, напишут ClassLoaderPolicy, который предоставляет более простой API для поиска классов и ресурсов, а также для указания других правил, связанных с загрузчиком классов.

Пользователи, которые хотят возиться с загрузкой классов, создают экземпляр ClassLoaderPolicy и регистрируют его в ClassLoaderSystem для создания пользовательского ClassLoader. Они также могут создать ClassLoaderDomain для разделения ClassLoaderSystem.

Этот уровень также включает в себя реализацию таких вещей, как модель DelegateLoader, загрузка классов, фильтры ресурсов и политики делегирования родитель-потомок.

Среда выполнения JMX позволяет отображать политику, используемую для каждого загрузчика классов, а также статистику загрузки классов и методы отладки, помогающие определить, откуда что загружается.

 

 

 

Как большинство из нас знает, загрузка классов может быть настоящей PITA. К счастью, мы скрываем большинство неприятных деталей реализации. Давайте посмотрим на одну часть, которая заставляет загрузку ваших классов работать так, как вы этого хотите — ClassLoaderPolicy.

public abstract class ClassLoaderPolicy extends BaseClassLoaderPolicy
{
public DelegateLoader getExported()

public String[] getPackageNames()

protected List<? extends DelegateLoader> getDelegates()

protected boolean isImportAll()
protected boolean isCacheable()
protected boolean isBlackListable()

public abstract URL getResource(String path);

public InputStream getResourceAsStream(String path)

public abstract void getResources(String name, Set<URL> urls) throws IOException;

protected ProtectionDomain getProtectionDomain(String className, String path)
public PackageInformation getPackageInformation(String packageName)
public PackageInformation getClassPackageInformation(String className, String packageName)

protected ClassLoader isJDKRequest(String name)
}

 

Давайте посмотрим на два примера ClassLoaderPolicy.

Первый знает, как извлечь ресурсы на основе регулярного выражения, а второй знает, как обращаться с зашифрованными ресурсами.

В обоих примерах используется код из демонстрационного модуля Microcontainer, который называется «policy». Основным классом является ClassLoaderMain из модуля демонстраций, который называется «classloader».

(Вы можете найти все демки в репозитории jboss svn )

Содержимое jboss-demos-policy.jar:

config/excluded.properties
config/properties.xml
config/settings.txt
META-INF/encrypted-beans.xml
META-INF/regexp-beans.xml
org/jboss/demos/policy/services/EncryptedService.class
org/jboss/demos/policy/services/PrintService.class
org/jboss/demos/policy/services/RegexpService.class

 

Здесь вы можете видеть, что у нас есть две службы (* -beans.xml в META-INF), которые обращаются к ресурсам (в каталоге config /) через загрузчик классов.

public class RegexpClassLoaderPolicy extends ClassLoaderPolicy
{
private VirtualFile[] roots;
private String[] packageNames;

public RegexpClassLoaderPolicy(VirtualFile[] roots)
{
this.roots = roots;
}

@Override
public String[] getPackageNames()
{
if (packageNames == null)
{
Set<String> exportedPackages = PackageVisitor.determineAllPackages(roots, null, ExportAll.NON_EMPTY, null, null, null);
packageNames = exportedPackages.toArray(new String[exportedPackages.size()]);
}
return packageNames;
}

protected Pattern createPattern(String regexp)
{
boolean outside = true;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < regexp.length(); i++)
{
char ch = regexp.charAt(i);
if ((ch == '[' || ch == ']' || ch == '.') && escaped(regexp, i) == false)
{
switch (ch)
{
case '[' : outside = false; break;
case ']' : outside = true; break;
case '.' : if (outside) builder.append("\\"); break;
}
}

builder.append(ch);
}
return Pattern.compile(builder.toString());
}

protected boolean escaped(String regexp, int i)
{
return i > 0 && regexp.charAt(i - 1) == '\\';
}

public URL getResource(String path)
{
Pattern pattern = createPattern(path);
for (VirtualFile root : roots)
{
URL url = findURL(root, root, pattern);
if (url != null)
return url;
}
return null;
}

private URL findURL(VirtualFile root, VirtualFile file, Pattern pattern)
{
try
{
String path = AbstractStructureDeployer.getRelativePath(root, file);
Matcher matcher = pattern.matcher(path);
if (matcher.matches())
return file.toURL();

List<VirtualFile> children = file.getChildren();
for (VirtualFile child : children)
{
URL url = findURL(root, child, pattern);
if (url != null)
return url;
}

return null;
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}

public void getResources(String name, Set<URL> urls) throws IOException
{
Pattern pattern = createPattern(name);
for (VirtualFile root : roots)
{
RegexpVisitor visitor = new RegexpVisitor(root, pattern);
root.visit(visitor);
urls.addAll(visitor.getUrls());
}
}

private static class RegexpVisitor implements VirtualFileVisitor
{
private VirtualFile root;
private Pattern pattern;
private Set<URL> urls = new HashSet<URL>();

private RegexpVisitor(VirtualFile root, Pattern pattern)
{
this.root = root;
this.pattern = pattern;
}

public VisitorAttributes getAttributes()
{
return VisitorAttributes.RECURSE_LEAVES_ONLY;
}

public void visit(VirtualFile file)
{
try
{
String path = AbstractStructureDeployer.getRelativePath(root, file);
Matcher matcher = pattern.matcher(path);
if (matcher.matches())
urls.add(file.toURL());
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}

public Set<URL> getUrls()
{
return urls;
}
}
}

 

RegexpClassLoaderPolicy использует довольно наивный механизм поиска подходящих ресурсов. Реальная реализация может лучше анализировать регулярные выражения и стараться избегать посещения всех ресурсов при поиске подходящего.

public class RegexpService extends PrintService
{
public void start() throws Exception
{
System.out.println();

ClassLoader cl = getClass().getClassLoader();
Enumeration<URL> urls = cl.getResources("config/[^.]+\\.[^.]{1,4}");
while (urls.hasMoreElements())
{
URL url = urls.nextElement();
print(url.openStream(), url.toExternalForm());
}
}
}

 

Служба регулярных выражений использует регулярное выражение «config / [^.] + \\. [^.] {1,4}» для перечисления ресурсов в каталоге config /. Мы ограничиваем длину суффикса, поэтому ожидаем, что исключенный файл не будет указан.

public class CrypterClassLoaderPolicy extends VFSClassLoaderPolicy
{
private Crypter crypter;

public CrypterClassLoaderPolicy(String name, VirtualFile[] roots, VirtualFile[] excludedRoots, Crypter crypter)
{
super(name, roots, excludedRoots);
this.crypter = crypter;
}

@Override
public URL getResource(String path)
{
try
{
URL resource = super.getResource(path);
return wrap(resource);
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}

@Override
public InputStream getResourceAsStream(String path)
{
InputStream stream = super.getResourceAsStream(path);
return crypter.crypt(stream);
}

@Override
public void getResources(String name, Set<URL> urls) throws IOException
{
super.getResources(name, urls);
Set<URL> temp = new HashSet<URL>(urls.size());
for (URL url : urls)
{
temp.add(wrap(url));
}
urls.clear();
urls.addAll(temp);
}

protected URL wrap(URL url) throws IOException
{
return new URL(url.getProtocol(), url.getHost(), url.getPort(), url.getFile(), new CrypterURLStreamHandler(crypter));
}
}

 

В этом примере мы предоставляем простой инструмент (класс Tools) для шифрования наших jar-файлов. Указав правильный фильтр, мы можем настроить, какие ресурсы мы хотим зашифровать. В нашем случае мы шифруем все, кроме содержимого META-INF. Для шифрования мы используем класс шифра JDK.

public class EncryptedService extends PrintService
{
public void start() throws Exception
{
ClassLoader cl = getClass().getClassLoader();

URL url = cl.getResource("config/settings.txt");
if (url == null)
throw new IllegalArgumentException("No such settings.txt.");

InputStream is = url.openStream();
print(is, "Printing settings:\n");

is = cl.getResourceAsStream("config/properties.xml");
if (is == null)
throw new IllegalArgumentException("No such properties.xml.");

print(is, "\nPrinting properties:\n");
}
}

 

Этот сервис на самом деле мало что делает. Он просто распечатывает содержимое двух файлов конфигурации. Суть в том, чтобы показать, что расшифровка любых зашифрованных ресурсов скрыта за уровнем загрузки классов.

Чтобы правильно проверить это, вы должны либо зашифровать модуль политики самостоятельно, либо использовать существующий зашифрованный: см. $ {Demos.home} / policy / src / etc / crypto — ([0-9] +). Jar file.

Чтобы реализовать это, нам нужно только правильно привязать EncryptedService к ClassLoaderSystem и к развертывателям. Чтобы увидеть, как это делается, проверьте демонстрационный код.

Мы поговорим подробнее о разбиении ClassLoaderSystem в следующем разделе.

загрузки классов

Подпроект ClassLoading представляет собой абстракцию более высокого уровня, основная роль которой заключается в управлении зависимостями загрузки классов.

Пользователи этого уровня вместо непосредственного использования абстракции ClassLoader будут создавать модули ClassLoading, которые содержат объявления зависимостей ClassLoader. Как только зависимости определены, ClassLoader (Policy) строятся и соединяются вместе соответственно.

Для облегчения определения ClassLoaders до того, как они реально существуют, абстракция включает модель ClassLoadingMetaData.

ClassLoadingMetaData может быть представлен как «Управляемый объект» в новой службе профиля JBoss5, позволяя системным администраторам иметь дело с деталями политики более высокого уровня, а не деталями реализации.

 

Простая диаграмма того, как выглядит механизм «загрузки классов».

public class ClassLoadingMetaData extends NameAndVersionSupport
{
/** The serialVersionUID */
private static final long serialVersionUID = -2782951093046585620L;

/** The classloading domain */
private String domain;

/** The parent domain */
private String parentDomain;

/** Whether to make a subdeployment classloader a top-level classloader */
private boolean topLevelClassLoader = false;

/** Whether to enforce j2se classloading compliance */
private boolean j2seClassLoadingCompliance = true;

/** Whether we are cacheable */
private boolean cacheable = true;

/** Whether we are blacklistable */
private boolean blackListable = true;

/** Whether to export all */
private ExportAll exportAll;

/** Whether to import all */
private boolean importAll;

/** The included packages */
private String includedPackages;

/** The excluded packages */
private String excludedPackages;

/** The excluded for export */
private String excludedExportPackages;

/** The included packages */
private ClassFilter included;

/** The excluded packages */
private ClassFilter excluded;

/** The excluded for export */
private ClassFilter excludedExport;

/** The requirements */
private RequirementsMetaData requirements = new RequirementsMetaData();

/** The capabilities */
private CapabilitiesMetaData capabilities = new CapabilitiesMetaData();

... setters & getters

ClassLoadingMetaData api:

<classloading xmlns="urn:jboss:classloading:1.0"
name="ptd-jsf-1.0.war"
domain="ptd-jsf-1.0.war"
parent-domain="ptd-ear-1.0.ear"
export-all="NON_EMPTY"
import-all="true"
parent-first="true"/>

 

Это пример того, как ClassLoadingMetaData выглядит через xml.

      ClassLoadingMetaData clmd = new ClassLoadingMetaData();
if (name != null)
clmd.setDomain(name + "_Domain");
clmd.setParentDomain(parentDomain);
clmd.setImportAll(true);
clmd.setExportAll(ExportAll.NON_EMPTY);
clmd.setVersion(Version.DEFAULT_VERSION);

 

Или как это выглядит, когда вы взламываете его в коде.

Вы можете добавить ClassLoadingMetaData в ваше развертывание либо программно, либо декларативно — через jboss-classloading.xml.

Мы будем использовать подход xml для определения различных примеров / конфигураций.

Давайте посмотрим на несколько примеров ClassLoadingMetaData, которые будут охватывать некоторые из наиболее распространенных вариантов использования. Эти примеры должны охватывать достаточно, чтобы вы могли легко справиться с вашими потребностями загрузки классов.

<classloading xmlns="urn:jboss:classloading:1.0"
domain="DefaultDomain"
top-level-classloader="true"
export-all="NON_EMPTY"
import-all="true">
</classloading>

 

Во-первых, давайте покажем пример унаследованной конфигурации, которую мы можем назвать «big-ball-o-mud» (ТМ Адриана Брока :-). Здесь мы помещаем загрузчик классов в «DefaultDomain», который является общим для всех приложений, которые не определяют свой собственный домен:

<classloading xmlns="urn:jboss:classloading:1.0"
domain="IsolatedDomain"
export-all="NON_EMPTY"
import-all="true">
</classloading>

 

Это ваша типичная корпоративная изоляция загрузки классов:

<classloading xmlns="urn:jboss:classloading:1.0"
domain="IsolatedWithParentDomain"
parent-domain="DefaultDomain"
export-all="NON_EMPTY"
import-all="true">
</classloading>

 

Больше изоляции, на этот раз с явным родителем:

<classloading xmlns="urn:jboss:classloading:1.0"
parent-first="false">
</classloading>

 

Это то, что мы называем non-j2seClassLoadingCompliance, то, что развертывания .war делают по умолчанию.
Вместо того, чтобы искать родительский поиск по умолчанию, вы сначала проверяете свои ресурсы

<classloading xmlns="urn:jboss:classloading:1.0">
<requirements>
<package name="org.jboss.dependency.spi"/>
</requirements>
<capabilities>
<package name="org.jboss.cache.api"/>
<package name="org.jboss.kernel.spi"/>
</capabilities>
</classloading>

 

Тип OSGi конфигурации:

<classloading xmlns="urn:jboss:classloading:1.0">
<requirements>
<module name="jboss-reflect.jar"/>
</requirements>
<capabilities>
<module name="jboss-cache.jar"/>
</capabilities>
</classloading> 

 

Вместо мелкозернистых пакетов мы можем импортировать / экспортировать целые модули / библиотеки. 

<classloading xmlns="urn:jboss:classloading:1.0">
<requirements>
<package name="si.acme.foobar"/>
<module name="jboss-reflect.jar"/>
</requirements>
<capabilities>
<package name="org.alesj.cl"/>
<module name="jboss-cache.jar"/>
</capabilities>
</classloading> 

 

Конечно, мы также можем смешивать требования и типы возможностей; используя пакеты и / или модули. 

Еще одна полезная функция в подпроекте «загрузка классов» — это его элегантная небольшая реализация шаблонов ресурсов-посетителей.

В проекте ClassLoader связь между развертыванием и загрузкой классов осуществляется через класс Module. Именно этот класс содержит всю необходимую информацию для правильного применения ограничений на шаблон посетителя (например, фильтрация):

public interface ResourceVisitor
{
ResourceFilter getFilter();

void visit(ResourceContext resource);
}

public interface ResourceContext
{
URL getUrl();

ClassLoader getClassLoader();

String getResourceName();

String getClassName();

boolean isClass();

Class<?> loadClass();

InputStream getInputStream() throws IOException;

byte[] getBytes() throws IOException;
}

Использование очень простое, просто создайте экземпляр вашего ResourceVisitor и передайте его в метод Module :: visit.

В нашей среде развертывания мы используем эту функцию для индексации использования аннотаций в развертываниях.

ClassLoading VFS

«Classloading-vfs» предоставляет реализацию ClassLoaderPolicy, которая использует проект виртуальной файловой системы JBoss для загрузки классов и ресурсов. Это можно использовать напрямую или в сочетании с «загрузкой классов».

При желании вы можете настроить свои модули в конфигурации микроконтейнера.

<deployment xmlns="urn:jboss:bean-deployer:2.0">

<classloader name="anys-classloader" xmlns="urn:jboss:classloader:1.0" import-all="true" domain="Anys" parent-domain="DefaultDomain">
<capabilities>
<package name="org.jboss.test.deployers.vfs.reflect.support.web"/>
</capabilities>
<root>${jboss.tests.url}</root>
</classloader>

<bean name="AnyServlet" class="org.jboss.test.deployers.vfs.reflect.support.web.AnyServlet">
<classloader><inject bean="anys-classloader:0.0.0"/></classloader>
</bean>

</deployment>

 

Этот XML-код преобразуется (через VFSClassLoaderFactory) в VFSClassLoaderPolicyModule, который затем создает фактический экземпляр ClassLoader.

Затем вы можете напрямую использовать этот новый экземпляр ClassLoader с вашими компонентами.

Примечание: VFSClassLoaderFactory расширяет ClassLoadingMetaData, поэтому все предыдущие примеры конфигурации применимы и в этом случае.

Мы достигли конца этого длинного представления о JBoss ClassLoader. Я надеюсь, что это пролило некоторый свет на эту сложную тему, которая немного облегчит вашу жизнь, когда вы столкнетесь с другой проблемой ClassNotFoundException в вашей среде JBoss.

Хотя мы уже активно использовали Deployers в статьях JBoss Microcontainer, мы еще не объяснили их использование и концепции должным образом. Это будет темой нашей следующей статьи. Оставайтесь в курсе!

PS: Спасибо Адриану за то, что он позволил мне использовать часть его документации для этой статьи. И Марко за редактирование этой статьи.

 

Об авторе

Семь лет назад он влюбился в Java и большую часть времени занимался разработкой информационных систем — от обслуживания клиентов до управления энергопотреблением. Он присоединился к JBoss в 2006 году, чтобы полностью посвятить себя работе над проектом Microcontainer, который в настоящее время является его руководителем. Он также участвует в JBoss AS и является специалистом по интеграции Seam и Spring. Он представляет JBoss в группах экспертов «Поддержка динамических компонентов JSR-291 для Java SE» и «OSGi».