Как создать родительский-последний/родительский класс ClassLoader в Java или Как переопределить старую версию Xerces, которая уже была загружена в родительский CL?

Я хотел бы создать загрузчик класса parent-last/child-first, например. загрузчик классов, который сначала будет искать классы в классе класса loder, а затем делегирует ему родительский ClassLoader для поиска классов.

Разъяснение:

Теперь я знаю, что для того, чтобы получить полное разделение ClassLoading, мне нужно использовать что-то вроде URLClassLoader, передавая null, поскольку он является родителем, благодаря этому ответу в предыдущем вопросе

Однако текущий вопрос приходит мне на помощь, чтобы решить эту проблему:

  • Мои кодовые + зависимые банки загружаются в существующую систему, используя ClassLoader, который устанавливает этот System ClassLoader как родительский (URLClassLoader)

  • Эта система использует некоторые библиотеки версии, не совместимые с той, которая мне нужна (например, более старая версия Xerces, которая не позволяет мне запускать мой код)

  • Мой код работает отлично, если работает автономно, но он терпит неудачу, если выполняется из этого класса ClassLoader

  • Howerver Мне нужен доступ ко многим другим классам в родительском ClassLoader

  • Поэтому я хочу разрешить мне переопределять родительский класс "jars" с моим собственным: если класс, который я вызываю, найден в загрузчике дочерних классов (например, я предоставил более новую версию Xerces с моими собственными банками, а не один пользователь класса ClassLoader, который загрузил мой код и банки.

Вот системный код, загружающий мой код + Jars (я не могу изменить этот)

File addOnFolder = new File("/addOns"); 
URL url = addOnFolder.toURL(); 
URL[] urls = new URL[]{url};
ClassLoader parent = getClass().getClassLoader();
cl = URLClassLoader.newInstance(urls, parent);

Вот "мой" код (полностью взятый из демоверсии "Hello World" Flying Sauser):

package flyingsaucerpdf;
import java.io.*;
import com.lowagie.text.DocumentException;
import org.xhtmlrenderer.pdf.ITextRenderer;
public class FirstDoc {
 public static void main(String[] args) 
 throws IOException, DocumentException {
 String f = new File("sample.xhtml").getAbsolutePath();
 System.out.println(f);
 //if(true) return;
 String inputFile = "sample.html";
 String url = new File(inputFile).toURI().toURL().toString();
 String outputFile = "firstdoc.pdf";
 OutputStream os = new FileOutputStream(outputFile);
 ITextRenderer renderer = new ITextRenderer();
 renderer.setDocument(url);
 renderer.layout();
 renderer.createPDF(os);
 os.close();
 }
}

Это работает автономно (работает main), но с ошибкой при загрузке через родительский CL:

org.w3c.dom.DOMException: NAMESPACE_ERR: Попытка сделать создавать или изменять объект таким образом что неверно в отношении Пространства имен.

возможно потому, что родительская система использует Xerces более старой версии, и даже если я предоставляю правильную jerer Xerces в папке /addOns, так как она уже загружалась и использовалась родительской системой, она не позволяет мне код для использования моей собственной банки из-за направления делегации. Надеюсь, этот вопрос станет более ясным, и я уверен, что его спросили до. (Возможно, я не задаю правильный вопрос)

5 ответов

Сегодня ваш счастливый день, так как мне пришлось решить эту точную проблему. Тем не менее, я предупреждаю вас, что внутренняя загрузка классов - страшное место. Сделав это, я думаю, что разработчики Java никогда не думали, что вам может понадобиться родительский загрузчик классов.

Чтобы просто указать список URL-адресов, содержащих классы или банки, которые будут доступны в дочернем загрузчике классов.

/**
 * A parent-last classloader that will try the child classloader first and then the parent.
 * This takes a fair bit of doing because java really prefers parent-first.
 * 
 * For those not familiar with class loading trickery, be wary
 */
private static class ParentLastURLClassLoader extends ClassLoader 
{
 private ChildURLClassLoader childClassLoader;
 /**
 * This class allows me to call findClass on a classloader
 */
 private static class FindClassClassLoader extends ClassLoader
 {
 public FindClassClassLoader(ClassLoader parent)
 {
 super(parent);
 }
 @Override
 public Class<!--?--> findClass(String name) throws ClassNotFoundException
 {
 return super.findClass(name);
 }
 }
 /**
 * This class delegates (child then parent) for the findClass method for a URLClassLoader.
 * We need this because findClass is protected in URLClassLoader
 */
 private static class ChildURLClassLoader extends URLClassLoader
 {
 private FindClassClassLoader realParent;
 public ChildURLClassLoader( URL[] urls, FindClassClassLoader realParent )
 {
 super(urls, null);
 this.realParent = realParent;
 }
 @Override
 public Class<!--?--> findClass(String name) throws ClassNotFoundException
 {
 try
 {
 // first try to use the URLClassLoader findClass
 return super.findClass(name);
 }
 catch( ClassNotFoundException e )
 {
 // if that fails, we ask our real parent classloader to load the class (we give up)
 return realParent.loadClass(name);
 }
 }
 }
 public ParentLastURLClassLoader(List<url> classpath)
 {
 super(Thread.currentThread().getContextClassLoader());
 URL[] urls = classpath.toArray(new URL[classpath.size()]);
 childClassLoader = new ChildURLClassLoader( urls, new FindClassClassLoader(this.getParent()) );
 }
 @Override
 protected synchronized Class<!--?--> loadClass(String name, boolean resolve) throws ClassNotFoundException
 {
 try
 {
 // first we try to find a class inside the child classloader
 return childClassLoader.findClass(name);
 }
 catch( ClassNotFoundException e )
 {
 // didn't find it, try the parent
 return super.loadClass(name, resolve);
 }
 }
}
</url>

РЕДАКТИРОВАТЬ: Серхио и Шоджи отметили, что если вы вызываете .loadClass с тем же именем класса, вы получите LinkageError. Хотя это верно, нормальный прецедент для этого загрузчика классов заключается в том, чтобы установить его как загрузчик классов потоков Thread.currentThread().setContextClassLoader() или через Class.forName(), и это работает как есть.

Однако, если .loadClass() был необходим напрямую, этот код можно было бы добавить в методе findClass ChildURLClassLoader вверху.

Class<!--?--> loaded = super.findLoadedClass(name);
 if( loaded != null )
 return loaded;


Следующий код - это то, что я использую. У этого есть преимущество перед другим ответом, что он не нарушает родительскую цепочку (вы можете следовать getClassLoader().getParent()).

Он также имеет преимущество перед tomcat WebappClassLoader, не изобретая колесо и не завися от других объектов. Он как можно больше использует код из URLClassLoader.

(он еще не почитает загрузчик системного класса, но когда я получу это исправление, я обновлю ответ)

Он отличает загрузчик системного класса (для классов java. *, одобренный каталог и т.д.). Он также работает, когда включена защита, и у загрузчика классов нет доступа к его родительскому элементу (да, эта ситуация странная, но возможна).

public class ChildFirstURLClassLoader extends URLClassLoader {
 private ClassLoader system;
 public ChildFirstURLClassLoader(URL[] classpath, ClassLoader parent) {
 super(classpath, parent);
 system = getSystemClassLoader();
 }
 @Override
 protected synchronized Class<!--?--> loadClass(String name, boolean resolve)
 throws ClassNotFoundException {
 // First, check if the class has already been loaded
 Class<!--?--> c = findLoadedClass(name);
 if (c == null) {
 if (system != null) {
 try {
 // checking system: jvm classes, endorsed, cmd classpath, etc.
 c = system.loadClass(name);
 }
 catch (ClassNotFoundException ignored) {
 }
 }
 if (c == null) {
 try {
 // checking local
 c = findClass(name);
 } catch (ClassNotFoundException e) {
 // checking parent
 // This call to loadClass may eventually call findClass again, in case the parent doesn't find anything.
 c = super.loadClass(name, resolve);
 }
 }
 }
 if (resolve) {
 resolveClass(c);
 }
 return c;
 }
 @Override
 public URL getResource(String name) {
 URL url = null;
 if (system != null) {
 url = system.getResource(name); 
 }
 if (url == null) {
 url = findResource(name);
 if (url == null) {
 // This call to getResource may eventually call findResource again, in case the parent doesn't find anything.
 url = super.getResource(name);
 }
 }
 return url;
 }
 @Override
 public Enumeration<url> getResources(String name) throws IOException {
 /**
 * Similar to super, but local resources are enumerated before parent resources
 */
 Enumeration<url> systemUrls = null;
 if (system != null) {
 systemUrls = system.getResources(name);
 }
 Enumeration<url> localUrls = findResources(name);
 Enumeration<url> parentUrls = null;
 if (getParent() != null) {
 parentUrls = getParent().getResources(name);
 }
 final List<url> urls = new ArrayList<url>();
 if (systemUrls != null) {
 while(systemUrls.hasMoreElements()) {
 urls.add(systemUrls.nextElement());
 }
 }
 if (localUrls != null) {
 while (localUrls.hasMoreElements()) {
 urls.add(localUrls.nextElement());
 }
 }
 if (parentUrls != null) {
 while (parentUrls.hasMoreElements()) {
 urls.add(parentUrls.nextElement());
 }
 }
 return new Enumeration<url>() {
 Iterator<url> iter = urls.iterator();
 public boolean hasMoreElements() {
 return iter.hasNext(); 
 }
 public URL nextElement() {
 return iter.next();
 }
 };
 }
 @Override
 public InputStream getResourceAsStream(String name) {
 URL url = getResource(name);
 try {
 return url != null ? url.openStream() : null;
 } catch (IOException e) {
 }
 return null;
 }
}
</url></url></url></url></url></url></url></url>


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

http://svn.apache.org/repos/asf/tomcat/tc7.0.x/tags/TOMCAT_7_0_0/java/org/apache/catalina/loader/WebappClassLoader.java

То есть, переопределяя метод findClass в вашем классе ClassLoader. Но зачем изобретать колесо, когда вы можете его украсть?

Читая различные обновления, я вижу, что вы столкнулись с некоторыми классическими проблемами с системой XML SPI.

Общая проблема заключается в следующем: если вы создаете полностью изолированный загрузчик классов, то трудно использовать возвращаемые им объекты. Если вы разрешаете общий доступ, у вас могут быть проблемы, когда родитель содержит неправильные версии вещей.

Чтобы справиться со всем этим безумием, OSGi был изобретен, но это большая таблетка, чтобы усвоить.

Даже в webapps загрузчики классов освобождают некоторые пакеты от обработки "local-first" в предположении, что контейнер и webapp должны согласовать API между ними.


(см. внизу обновление для найденного решения)

Кажется, что AntClassLoader имеет поддержку родительского первого/последнего (еще не проверял его)

http://svn.apache.org/repos/asf/ant/core/trunk/src/main/org/apache/tools/ant/AntClassLoader.java

Вот фрагмент

/**
 * Creates a classloader for the given project using the classpath given.
 *
 * @param parent The parent classloader to which unsatisfied loading
 * attempts are delegated. May be <code>null</code>,
 * in which case the classloader which loaded this
 * class is used as the parent.
 * @param project The project to which this classloader is to belong.
 * Must not be <code>null</code>.
 * @param classpath the classpath to use to load the classes.
 * May be <code>null</code>, in which case no path
 * elements are set up to start with.
 * @param parentFirst If <code>true</code>, indicates that the parent
 * classloader should be consulted before trying to
 * load the a class through this loader.
 */
public AntClassLoader(
 ClassLoader parent, Project project, Path classpath, boolean parentFirst) {
 this(project, classpath);
 if (parent != null) {
 setParent(parent);
 }
 setParentFirst(parentFirst);
 addJavaLibraries();
}

Update:

Нашел этот, а когда, в крайнем случае, я начал угадывать имена классов в google (это то, что производил ChildFirstURLClassLoader) - но кажется быть неверным

Обновление 2:

Первый вариант (AntClassLoader) очень привязан к Ant (требуется контекст Project и не легко передать ему URL[]

Второй вариант (из проекта OSGI в коде google) не совсем то, что мне нужно, поскольку он искал родительский загрузчик классов перед загрузкой class classloader (Ant загрузчик классов делает это правильно, кстати). Проблема, как я ее вижу, думаю, что ваш родительский загрузчик классов включает в себя банку (которой она не должна быть) функциональности, которая не была на JDK 1.4, но была добавлена ​​в 1.5, это не имеет вреда как родительский последний загрузчик класса ( регулярная модель делегирования, например URLClassLoader) всегда будет загружать сначала классы JDK, но здесь первая наивная реализация ребенка, кажется, раскрывает старый избыточный баннер в загрузчике родительского класса, затеняя собственную реализацию JDK/JRE.

Мне еще предстоит найти сертифицированную, полностью проверенную, зрелую родительскую последнюю/детскую первую правильную реализацию, которая не связана с конкретным решением (Ant, Catalina/Tomcat)

Обновление 3 - я нашел его! Я смотрел не в том месте,

Все, что я сделал, это добавить META-INF/services/javax.xml.transform.TransformerFactory и восстановить JDK com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl вместо старого Xalan org.apache.xalan.processor.TransformerFactoryImpl

Единственная причина, по которой я не принимаю свой собственный ответ, заключается в том, что я не знаю, имеет ли подход META-INF/services такое же делегирование загрузчика классов, что и обычные классы (например, это parent-first/child-last или parent-last/child-first?)


Вы можете переопределить findClass() и loadClass() для реализации дочернего загрузчика первого класса:

/**
 * Always throws {@link ClassNotFoundException}. Is called if parent class loader
 * did not find class.
 */
@Override
protected final Class findClass(String name)
 throws ClassNotFoundException
{
 throw new ClassNotFoundException();
}
@Override
protected Class loadClass(String name, boolean resolve)
 throws ClassNotFoundException
{
 synchronized (getClassLoadingLock(name)){
 /*
 * Check if we have already loaded this class.
 */
 Class c = findLoadedClass(name);
 if (c == null){
 try {
 /*
 * We haven't previously loaded this class, try load it now
 * from SUPER.findClass()
 */
 c = super.findClass(name);
 }catch (ClassNotFoundException ignore){
 /*
 * Child did not find class, try parent.
 */
 return super.loadClass(name, resolve);
 }
 }
 if (resolve){
 resolveClass(c);
 }
 return c;
 }
}

licensed under cc by-sa 3.0 with attribution.