"Compreensão aprofundada da máquina virtual Java" notas de leitura (oito) - carregamento de classe e caso de subsistema de execução (carregamento de classe Tomcat, OSGI, proxy dinâmico)

Um, a arquitetura do carregador de classes Tomcat

Como um servidor web, os seguintes problemas precisam ser resolvidos:

  • Bibliotecas de classes Java usadas por aplicativos da web implementados no mesmo servidor podem ser isoladas umas das outras.
  • As bibliotecas de classes Java usadas por dois aplicativos da web implementados no mesmo servidor podem ser compartilhadas entre si.
  • O servidor precisa garantir que sua própria segurança não seja afetada pelo aplicativo da web implementado tanto quanto possível.
  • JSP precisa oferecer suporte a atualizações importantes

Devido aos problemas acima, um único caminho de classe não pode atender à demanda, portanto, o servidor da web fornece vários caminhos de caminho de classe para os usuários armazenarem bibliotecas de classes de terceiros. Na estrutura de diretórios do Tomcat, existem 3 conjuntos de diretórios ("/ common / *", "/ server / *" e "/ shared / *"), além do próprio diretório do aplicativo da web "/ WEB-INF / *".

  • Diretório / common: A biblioteca de classes pode ser usada pelo Tomcat e todos os aplicativos da web.

  • Diretório / server: A biblioteca de classes pode ser usada pelo Tomcat e é invisível para todos os aplicativos da web.

  • / diretório compartilhado: a biblioteca de classes pode ser usada por todos os aplicativos da web, mas não é visível para o próprio Tomcat.

  • Diretório / webapp / WEB-INF: A biblioteca de classes só pode ser usada por este microaplicativo web e não é visível para o Tomcat e outros aplicativos web.

O Tomcat personalizou vários carregadores de classes, que são implementados de acordo com o modelo clássico de delegação dos pais. A relação é mostrada na figura:

Estrutura do carregador de classe Tomcat 5.x (diagrama de rede)

Entre eles, o carregador de classes de inicialização de nível superior, o carregador de classes estendido e o carregador de classes de aplicativos são todos os carregadores de classes fornecidos pelo JDK por padrão. Outro CommonClassLoader é responsável por carregar a biblioteca de classes Java em "/ common / *", CatalinaClassLoader é responsável por carregar a biblioteca de classes Java em "/ server / *", SharedClassLoader é responsável por carregar a biblioteca de classes Java em "/ shared / * ", e WebAppClassLoader é responsável por Carregar as respectivas bibliotecas de classes Java em" / WebApp / WEB-INF / * "e, finalmente, cada arquivo jsp corresponde a um carregador de classes.

Observação: após a versão 6.x do Tomcat, common, server e shared são mesclados no diretório lib por padrão. A biblioteca de classes neste diretório é equivalente à função desempenhada pela biblioteca de classes no diretório comum anterior. Se as configurações padrão não atenderem aos requisitos, o usuário pode reativar a estrutura do carregador 5.x modificando o arquivo de configuração para especificar server.loader e share.loader

Como pode ser visto na figura acima, as classes que CommonClassLoader pode carregar podem ser carregadas pelos carregadores de classes CatalinaClassLoader e SharedClassLoader, enquanto as classes que CatalinaClassLoader e SharedClassloader podem carregar são isoladas umas das outras. WebClassLoader pode usar as classes carregadas por SharedClassLoader (incluindo carregador de classe superior), mas cada instância de WebAppClassLoader é isolada uma da outra. O intervalo de carregamento de JasperLoader é apenas a classe compilada por este arquivo jsp. Quando o servidor detecta que o arquivo jsp foi modificado, ele substituirá a instância JasperLoader atual e criará uma nova classe jsp para carregar para alcançar a função de atualização Hot jsp ( sobre JSP pode referir - se a como JSP é compilado em servlet e fornece serviços ).

Em relação à violação da delegação dos pais

Como mencionado acima, para os contêineres da web, há um problema importante que precisa ser resolvido: para aplicativos da web diferentes, os pacotes jar em seus respectivos diretórios podem precisar ser isolados uns dos outros. Um exemplo simples é que diferentes aplicativos implantados no mesmo contêiner podem depender de diferentes versões do mesmo pacote jar, portanto, eles devem ser isolados uns dos outros. Para obter o isolamento também é muito simples.Cada aplicativo tem um carregador de classes correspondente a seu próprio diretório, e esses pacotes jar são armazenados em seu próprio diretório de aplicativo e carregados por seu respectivo carregador de classes para obter o isolamento mútuo. Mas a partir dessa descrição, na verdade não viola o modelo de delegação parental, depende principalmente de como alcançá-lo.

Para atingir esse objetivo, existem duas soluções em termos de realização:

  • Se um carregador de classes pai comum e um carregador de classes específico do aplicativo podem carregar esses pacotes jar, o carregador de classes respectivo do aplicativo de controle não será entregue ao carregador de classes pai para carregar primeiro de acordo com as recomendações da delegação dos pais, mas diretamente primeiro. Carregado por mim

  • Seguindo o modelo de delegação parental, o carregador de classe específico do aplicativo ainda confia no carregador de classe pai para carregar primeiro, mas é necessário garantir que todo o carregador de classe pai comum não possa carregar esses pacotes jar, de modo que de acordo com o retrocesso da delegação parental ainda será carregado sozinho

二 、 OSGI

OSGI (Open Service Gateway Initiative) é uma especificação modular dinâmica baseada na linguagem Java formulada pela OSGI Alliance. Cada módulo em OSGI (chamado Bundle) pode declarar o pacote Java do qual depende (descrito por Import-Package), ou pode declarar que permite exportar o Java-Package liberado (descrito por Export-Package), e cada módulo é iniciado na aparência, parece ser uma dependência entre pares. Uma das grandes vantagens do OSGI é que os programas baseados em OSGI são capazes de alcançar funções de conexão automática em nível de módulo, graças à sua arquitetura de carregador de classes flexível.

O carregador de classes Bundle do OSGI só tinha regras antes e não havia nenhum relacionamento de delegação fixo. Por exemplo, um Bundle declara um pacote do qual depende. Se outros Bundles declararem lançar este pacote, então todas as ações de carregamento de classe para este pacote serão delegadas ao carregador de classe Bundle que o lançou para ser concluído. Quando um pacote específico não está envolvido, cada carregador de pacote tem um relacionamento de nível.Somente quando um pacote e uma classe específicos são usados, a delegação e as dependências entre os pacotes são construídas de acordo com as definições de importação e exportação do pacote.

Suponha que existam três módulos, BundleA, BundleB e BundleC, e suas dependências definidas são as seguintes:

  • BundleA: Release packageA, dependente de java. *

  • BundleB: depende do packageA, packageC e java. *

  • BundleC: libere o pacote C, depende do pacote A

Em seguida, a relação de carregamento de classe entre esses três pacotes é mostrada na figura a seguir:

Arquitetura do carregador de classe OSGI

 

Pode-se ver que o relacionamento entre os carregadores em OSGI não é mais a estrutura de árvore do modelo de delegação pai, mas se desenvolveu em uma estrutura de rede mais complexa que só pode ser determinada em tempo de execução . Nos primeiros dias, esse tipo de arquitetura de carregador de classes não estruturada em árvore estava sujeito a um impasse devido à interdependência. Tome OSGI como um exemplo. Se BundleA depende do pacote B do BundleB, o BundleB depende do pacoteA do BundleA: Quando o BundleA carrega o PacoteB, primeiro bloqueie objeto de instância do carregador de classe atual (ClassLoader.loadClass () é um método sincronizado) e, em seguida, delegar a solicitação ao carregador de classes de BundleB, mas se BundleB também quiser apenas carregar a classe de pacoteA, você precisa primeiro bloquear seu próprio carregador de classes e, em seguida, solicite que o carregador de BundleA processe, de forma que ambos os carregadores aguardem um ao outro para processar seus próprios pedidos, mas cada um mantém seu próprio bloqueio, causando um estado de conflito.

No JDK1.7, uma atualização especial foi feita para a arquitetura do carregador de classe de herança sem árvore. Consulte: Bloqueio de controle de simultaneidade para carregamento de classe (ClassLoadingLock)

3. Tecnologia de geração de bytecode e proxy dinâmico

Comparado com o proxy dinâmico, a implementação do proxy estático é relativamente simples, basta deixar a classe proxy substituir diretamente a classe proxy para executar o método, é claro, a premissa é que a mesma interface é implementada ou a mesma classe pai é herdada. Aqui está um exemplo simples:

public class StaticProxyTest {
    interface Runnable {
        void run();
    }

    class Dog implements Runnable {
        @Override
        public void run() {
            System.out.println("dog run.");
        }
    }

    static class StaticProxy implements Runnable {
        Runnable originObj;

        public Runnable bind(Runnable obj) {
            this.originObj = obj;
            return this;
        }

        @Override
        public void run() {
            System.out.println("before run.");
            originObj.run();
        }
    }

    public static void main(String[] args) {
        new StaticProxy().bind(new StaticProxyTest().new Dog()).run();
    }
}

Pode-se ver que a implementação do proxy estático é muito simples e não há intrusão no objeto proxy, mas é limitada a este cenário simples. O proxy estático requer que a classe e a interface originais sejam conhecidas no momento da compilação e não seja dinâmica e, se a classe do proxy implementar várias interfaces, não será tão fácil de manter. A implementação de proxy dinâmico em Java pode determinar o comportamento do proxy da classe de proxy quando a classe e a interface originais ainda não são conhecidas.A classe de proxy é separada da classe original diretamente e pode ser reutilizada de forma flexível em diferentes cenários de aplicativos. A implementação correspondente também é relativamente simples, dependendo principalmente de duas classes: Proxy e InvocationHandler, o seguinte é um exemplo simples:

public class DynamicProxyTest {
    interface Speakable {
        void speak();
    }

    interface Runnable {
        void run();
    }

    class Dog implements Runnable, Speakable {

        @Override
        public void run() {
            System.out.println("dog run.");
        }

        @Override
        public void speak() {
            System.out.println("dog speak.");
        }
    }

    static class DynamicProxy implements InvocationHandler {
        //原始对象
        Object originObj;

        public Object bind(Object obj) {
            this.originObj = obj;
            //生成代理对象
            return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before method:" + method.getName());
            return method.invoke(originObj);
        }
    }

    public static void main(String[] args) {
        System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
        Object proxy = (new DynamicProxy().bind(new DynamicProxyTest().new Dog()));
        ((Runnable) proxy).run();
        ((Speakable) proxy).speak();
    }
}

A lógica do proxy dinâmico acima é muito clara. A única coisa que não está clara é o método Proxy.newProxyInstance. Como esse método retorna o objeto de proxy? Todos nós sabemos que o princípio do proxy dinâmico é criar uma nova classe de proxy (bytecode) com base na interface de proxy e na instância InvocationHandler que fornecemos. Como a análise do código-fonte não é o foco desta série de artigos, apenas parte da lógica principal é postada aqui:

O método Proxy.newProxyInstance eventualmente gerará bytecode de classe de proxy por meio de um método de classe interna estática ProxyClassFactory.apply (classLoader, interfaces) da classe java.lang.reflect.Proxy e análise de bytecode completa (parte do código):

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names
        private static final AtomicLong nextUniqueNumber = new AtomicLong();

        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
            .......
           /*
             * Generate the specified proxy class.
             * 这里生成代理类字节码内容
             */
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                proxyName, interfaces, accessFlags);
            try {
                return defineClass0(loader, proxyName,
                                    proxyClassFile, 0, proxyClassFile.length);
            } catch (ClassFormatError e) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
            ......
        }
    }

Enquanto isso, ProxyGenerator.generateProxyClass será chamado para gerar uma classe de proxy. Adicione uma declaração no método principal:

System.getProperties (). Put ("sun.misc.ProxyGenerator.saveGeneratedFiles", "true") é a saída da classe proxy gerada automaticamente. Após a execução, verifica-se que existe uma classe $ Proxy0 no diretório:

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.demo.aop;

import com.demo.aop.DynamicProxyTest.Runnable;
import com.demo.aop.DynamicProxyTest.Speakable;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

final class $Proxy0 extends Proxy implements Runnable, Speakable {
    private static Method m1;
    private static Method m3;
    private static Method m2;
    private static Method m4;
    private static Method m0;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final void run() throws  {
        try {
            super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void speak() throws  {
        try {
            super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m3 = Class.forName("com.demo.aop.DynamicProxyTest$Runnable").getMethod("run");
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.demo.aop.DynamicProxyTest$Speakable").getMethod("speak");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

Como você pode ver, a classe de proxy gerada automaticamente herda java.lang.reflect.Proxy por padrão, implementa a interface implementada pela classe de proxy e chama o método invoke para finalmente chamar o método invoke que implementamos. Como Java é uma herança única, o proxy dinâmico fornecido por Java requer que a classe de proxy implemente a interface, portanto, no início do ano, InvocationHandler e cglib são fornecidos em dois modos de proxy.

Acho que você gosta

Origin blog.csdn.net/huangzhilin2015/article/details/114554579
Recomendado
Clasificación