Как создать родительский-последний/родительский класс 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, так как она уже загружалась и использовалась родительской системой, она не позволяет мне код для использования моей собственной банки из-за направления делегации. Надеюсь, этот вопрос станет более ясным, и я уверен, что его спросили
до. (Возможно, я не задаю правильный вопрос)
Ответы
Ответ 1
Сегодня ваш счастливый день, так как мне пришлось решить эту точную проблему. Тем не менее, я предупреждаю вас, что внутренняя загрузка классов - страшное место. Сделав это, я думаю, что разработчики 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);
}
}
}
РЕДАКТИРОВАТЬ: Серхио и Шоджи отметили, что если вы вызываете .loadClass
с тем же именем класса, вы получите LinkageError. Хотя это верно, нормальный прецедент для этого загрузчика классов заключается в том, чтобы установить его как загрузчик классов потоков Thread.currentThread().setContextClassLoader()
или через Class.forName()
, и это работает как есть.
Однако, если .loadClass()
был необходим напрямую, этот код можно было бы добавить в методе findClass ChildURLClassLoader вверху.
Class<?> loaded = super.findLoadedClass(name);
if( loaded != null )
return loaded;
Ответ 2
Следующий код - это то, что я использую. У этого есть преимущество перед другим ответом, что он не нарушает родительскую цепочку (вы можете следовать 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;
}
}
Ответ 3
Прочитав исходный код 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 между ними.
Ответ 4
(см. внизу обновление для найденного решения)
Кажется, что 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?)
Ответ 5
Вы можете переопределить 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;
}
}