Adicionando e ordenando loading nos outros classloaders do Tomcat5

Há algumas semanas publiquei um post que ensinava como ordenar os JARs das aplicações web (toda e qualquer aplicação dentro do diretório webapps do tomcat) através da manipulação do WebappClassloader, porém fiquei devendo falar sobre os outros classloaders presentes no tomcat, particularmente os que mais interessam são o common classloader e o shared classloader – não sabe o que são classloaders, ou não sabe o papel de cada um desses no tomcat? -> Understanding classloaders.

Achei que modificá-los seria uma tarefa mais fácil que enfrentei para manipular o loading das webapps. Definitivamente não! A começar que DocJar, muita reflexão e  java.lang.reflect.* foram precisos para hit-the-goal.

JAVA Reflection!!?? Pra quê??? Vou explicar…  A partir de gora leia (e talvez releia) com calma porque a priori pode parecer um pouco complicado por conta do vai-pra-lá-vem-pra-cá das classes das quais estarei falando a seguir.

Existe um tal de StandardClassLoader (org.apache.catalina.loader) que é a classe que irá implementar os três classloaders, do tomcat, pouco conhecidos mas essenciais para o seu funcionamento, a saber: common, shared e o server (também conhecido como o catalina classloader). E mais uma vez repito, se não tiver idéia do papel desses classloaders -> Understanding classloaders (é condição sine qua non para você entender este post). Enfim, voltando, essa classe extende o, largamente utilizado, URLClassLoader (java.net) o qual realiza suas tarefas lançando mão de um atributo do tipo URLClassPath (sun.misc) que realiza o trabalho pesado. Porém, se vocês tiverem a curiosidade, vejam que o atributo URLClassPath (o ucp) tem visibilidade default de forma que o mesmo não é acessível em uma subclasse de outro pacote. E infelizmente, como eu disse antes, o ucp realiza uma boa parte do trabalho atribuido ao URLClassLoader então como manipulá-lo? FOI TRISTE para nós!? Não!

Agora entra em cena as armas fornecidas pelo pacote java.lang.reflect. UM MOMENTO! Para o entendimento desse post é necessário que você saiba trabalhar bem com reflection, se não está seguro disso, consta aqui no publicclass{} um breve post que mostra como acessar métodos, atributos e inner classes inacessíveis (leia-se privados ou protected). Isso será suficiente para compreender os próximos parágrafos…

Mãos a obra! Objetivo: ordenar/adicionar, dinamicamente, os resources e .class da forma como desejarmos, dessa forma a versão da classe, ou um arquivo qualquer (resource), que desejamos esteja visível no escopo do classloader. Para isso precisaremos modificar o comportamento do URLClassPath. O principal método é o setRepositories, este realizará a reordenação da pilha mantida pelo URLClassPath, pois esta será a ordem a qual o URLClassLoader irá carregar os JARs (.class e resources se o programador achar necessário, no caso apresentado aqui só os jars serão carregados por esses classloaders). Abaixo consta a classe StandardClassLoader que, como dito antes, é responsável por implementar os três classloaders citados no início.

private void setRepositories() {
try {
InputStream is = loadOrderedRepositoriesFile();
try {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document doc = db.parse(is);
doc.getDocumentElement().normalize();
NodeList nodeList = doc.getElementsByTagName(“repository”);
URL[] urls = super.getURLs();
Arrays.sort(urls, comparator);
ArrayList<URL> urlList = new ArrayList<URL>(nodeList.getLength() + urls.length);
/**
* find last lib/X.jar entry
*/
int index = 0;
int notFoundIndex = 0;
String classloaderLibStringPath = getClassloaderLibPath().toURI().toURL().toExternalForm();
for (int i = 0; i < urls.length; i++){
String urlTemp = urls[i].toExternalForm();
if (urlTemp.startsWith(classloaderLibStringPath)){
String fromLib = urlTemp.substring(classloaderLibStringPath.length());
if(fromLib.indexOf(‘/’) == -1 && fromLib.endsWith(“.jar”))
index = i;
// first entry in lib (ONLY)
if(notFoundIndex == 0)
notFoundIndex = i-1;
}
}
/**
* in case of lib not contains any .jar the last jar will be the lib path itself
*/
if(index == 0)
index = notFoundIndex;
index++;
/** found! */
for (int i = 0; i < urls.length; i++)
urlList.add(urls[i]);
if (!classloaderLibPath.exists())
new FileNotFoundException(name + ” directory does not exists”);
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
String path = node.getTextContent();
if (log.isDebugEnabled())
log.debug(”  Including directory glob ” + classloaderLibPath.getAbsolutePath() + File.separatorChar + path);
File actualDirectory = new File(classloaderLibPath, path);
if (!actualDirectory.exists() || !actualDirectory.isDirectory())
continue;
File[] files = actualDirectory.listFiles(new FileFilter(){
public boolean accept(File file) {
return file.getName().toLowerCase().endsWith(“.jar”);
}
});
for (int j = 0; j < files.length; j++) {
if (log.isDebugEnabled())
log.debug(”    Including glob jar file ” + files[j].getAbsolutePath());
// adicionar repositorio a partir de /lib e nao antes de incluir endorsed, i18n ou classes
urlList.add(index++, files[j].toURI().toURL());
}
}
urls = urlList.toArray(new URL[urlList.size()]);
final URLClassPath ucp = (URLClassPath) getUcpField().get(this);
Field urlsField = URLClassPath.class.getDeclaredField(“urls”);
urlsField.setAccessible(true);
Stack urlStack = (Stack) urlsField.get(ucp);
// limpo a pilha de url do URLClassPath para preencher com a
// ordem que eu preciso
urlStack.removeAllElements();
// como uma pilha é FIFO tenho que por os primeiros da lista por
// ultimo
for (int i = urls.length – 1; i >= 0; i–)
urlStack.add(urls[i]);
// simula um ucp.setURLs(stack)
urlsField.set(ucp, urlStack);
if(log.isDebugEnabled()){
StringBuilder sb = new StringBuilder(”   !!!! URLs ordered AFTER ADD: \n”);
for (int i = 0; i < urls.length; i++)
sb.append(urls[i].toExternalForm()).append(“\n”);
log.debug(sb);
}
} catch (ParserConfigurationException e) {
log.error(e);
} catch (SAXException e) {
log.error(e);
} catch (IOException e) {
log.error(e);
} catch (Exception e) {
log.error(e);
}
} catch (FileNotFoundException e) {
log.warn(“No ordered-repositories.xml not found for ” + name
+ ” Classloader. Any extra repositories where configured.”);
URL[] urls = super.getURLs();
Arrays.sort(urls, comparator);
try {
URLClassPath ucp = (URLClassPath) getUcpField().get(this);
Field urlsField = URLClassPath.class.getDeclaredField(“urls”);
urlsField.setAccessible(true);
Stack urlStack = (Stack) urlsField.get(ucp);
// limpo a pilha de url do URLClassPath para preencher com a
// ordem que eu preciso
urlStack.removeAllElements();
// como uma pilha é FIFO tenho que por os primeiros da lista por
// ultimo
for (int i = urls.length – 1; i >= 0; i–)
urlStack.add(urls[i]);
// simula um ucp.setURLs(stack)
urlsField.set(ucp, urlStack);
} catch (Exception e2) {
log.error(e2);
}
}
}
classe: org.apache.catalina.loader.StandardClassLoader
jar: bin/bootstrap.jar

private void setRepositories() {

        URL[] urls = getURLs();

        Arrays.sort(urls, comparator);
        try {

            InputStream is = loadOrderedRepositoriesFile();

            try {

                DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
                DocumentBuilder db = dbf.newDocumentBuilder();

                Document doc = db.parse(is);
                doc.getDocumentElement().normalize();

                NodeList nodeList = doc.getElementsByTagName("repository");

                ArrayList<URL> urlList = new ArrayList<URL>(nodeList.getLength() + urls.length);

                int lastJarEntryFromLibIndex = findLastJarEntryFromLib(urls);

                for (int i = 0; i < urls.length; i++)
                    urlList.add(urls[i]);

                if (!classloaderLibPath.exists())
                    new FileNotFoundException(name + " directory does not exists");

                for (int i = 0; i < nodeList.getLength(); i++) {

                    String path = nodeList.item(i).getTextContent();

                    if (log.isDebugEnabled())
                        log.debug("  Including directory glob " + classloaderLibPath.getAbsolutePath()
                            + File.separatorChar + path);

                    File actualDirectory = new File(classloaderLibPath, path);

                    if (!actualDirectory.exists() || !actualDirectory.isDirectory())
                        continue;

                    File[] files = actualDirectory.listFiles(fileFilter);

                    for (int j = 0; j < files.length; j++) {

                        if (log.isDebugEnabled())
                            log.debug("    Including glob jar file " + files[j].getAbsolutePath());

                        // adicionar repositorio a partir de /lib e nao antes de incluir endorsed, i18n ou classes
                        urlList.add(lastJarEntryFromLibIndex++, files[j].toURI().toURL());
                    }
                }

                urls = urlList.toArray(new URL[urlList.size()]);

                putURLStackOrderedByMyWay(urls);

                log.info("Urls added to " + name + " correctly");

            } catch (Exception e1) {
                log.error(e1);
            }

        } catch (FileNotFoundException e2) {

            log.warn("No ordered-repositories.xml not found for " + name
                + " Classloader. Any extra repositories where configured.");

            try {

                putURLStackOrderedByMyWay(urls);

                log.info("Urls added to " + name + " correctly");

            } catch (Exception e3) {
                log.error(e3);

            }
        }
    }

Se vocês repararem no StandardClassLoader original verão que o mesmo não implementa nenhuma funcionalidade, simplesmente extendeURLClassLoader e esta é a verdadeira responsável por carregar as classes solicitadas a ele. Houve a necessidade de implementar vários métodos mas nenhum foi sobrescrito, pois as mudanças foram de fora para dentro. Vou explicar melhor. Para atingir nosso objetivo, sobrescrever um método de URLClassLoader não era suficiente, pois era necessário modificar o comportamento da URLClassPath e isso só poderia ser feito via reflection ou recompilando a mesma e “inputando-o” num jar. A primeira opção pareceu ser mais interessante pelo fato de que só seria necessário alterar uma classe ao invés de outras e alterar somente um jar o bootstrap.jar.

Vamos ao que interessa. O método loadOrderedRepositoriesFile() basicamente carrega o XML ordered-repositories.xml que conterá as informações necessárias para carregar os diretórios (os jar e arquivos contidos no mesmo). O findLastJarEntryFromLib é importante, ele é responsável por retornar a posição (num array de URLs) que está o último jar contido no diretório lib do classloader (common/lib, server/lib ou shared/lib). Isso é importante pois todos os repositórios serão ordenados com prevalência sobre tudo que houver dentro de classloader-name/lib (classloader-name = common|shared|server). Ora, nada mais justo! A não ser que o programador queira sobrescrever uma das classes essenciais do tomcat (o que pode realmente acontecer, mas não é recomendado que esta seja carregada dentro do diretório lib e sim no seu diretório padrão), por exemplo: servlet-api.jar, o jsp-api.jar para que irá usar JSP (a maioria). Com a informação do índice do último jar do constante no lib podemos enfim adicionar nossos jars a diretórios dentro de lib.

urlList.add(lastJarEntryFromLibIndex++, files[j].toURI().toURL());

Isso é feito nessa linha acima. Após adicionar todos os jars configurádos para isso no ordered-repositories.xml o método putURLStackOrderedByMyWay é necessário para “inputar” a Stack<URL> no atributo urls específico do URLClassPath. O ponto principal da rotina é aí, tal array de URLs lógico estará ordenado como queiramos, logo, quando a aplicação solicitar uma classe, a busca será realizada nesse array o qual estará estruturado conforme a vontade do programador. Abaixo se pode ver o método responsável por isso.

private void putURLStackOrderedByMyWay(URL[] urls) throws SecurityException, NoSuchFieldException,
	IllegalArgumentException, IllegalAccessException {

	URLClassPath ucp = (URLClassPath) getUcpField().get(this);

	Field urlsField = URLClassPath.class.getDeclaredField("urls");
	urlsField.setAccessible(true);

	Stack urlStack = (Stack) urlsField.get(ucp);

	// limpo a pilha de url do URLClassPath para preencher com a ordem que eu preciso
	urlStack.removeAllElements();

	// como uma pilha é FIFO tenho que por os primeiros da lista por ultimo
	for (int i = urls.length - 1; i >= 0; i--)
		urlStack.add(urls[i]);

	// simula um ucp.setURLs(stack)
	urlsField.set(ucp, urlStack);
}

Enfim pessoal, por enquanto é isso. Creio que cumpri a missão de passar uma boa idéia de como realizar essa tarefa que ao meu ver é de muita importência. O código fonte pode ser baixado para os que quiserem analisá-lo para melhor compreensão, afinal um post detalhado custaria nó mínimo mais umas 200 linhas.

Source: ordenar-classloaders-src.rar

Advertisement

Tags: , , , , , , , , , , , , ,

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s


Follow

Get every new post delivered to your Inbox.