Exploração do código-fonte da estrutura de log de fachada SLF4J | Equipe técnica do JD Cloud

1 Introdução ao SLF4J

SLF4J é Simple Logging Facade for Java, que fornece uma aparência simples ou abstração de todas as estruturas de registro em Java. Assim, permite que os usuários trabalhem com qualquer framework de registro como: Log4j, Logback e JUL (java.util.logging) com uma única dependência. A estrutura de criação de log necessária pode ser inserida no momento da implementação, inserindo os arquivos jar apropriados (ligações) no caminho de classe. Se você quiser alterar a estrutura de registro, apenas substitua as ligações slf4j dependentes. Por exemplo, para substituir java.util.logging por log4j, basta substituir slf4j-jdk14-1.7.28.jar por slf4j-log4j12-1.7.28.jar.

2 SLF4J análise de código-fonte

Começamos com o código, adicionamos código camada por camada, sentimos intuitivamente o log de impressão SLF4J e rastreamos o código de volta à fonte. Entenda principalmente como o SLF4J é desacoplado como uma fachada e outras estruturas de registro.

2.1 O pom refere-se apenas à dependência do slf4j-api, a versão é 1.7.30

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.30</version>
        </dependency>

2.1.1 Executar uma Demonstração

public class HelloSlf4j {

    public static void main(String[] args) {
        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");
    }
}

2.1.2 Informações de prompt de registro

Falha ao vincular org.slf4j.impl.StaticLoggerBinder. O SLF4J assumirá como padrão uma implementação não operacional se nenhuma ligação for encontrada no caminho de classe

2.1.3 Rastreamento do código-fonte

Clique no método getLogger(), e você poderá ver intuitivamente que LoggerFactory utiliza uma fábrica estática para criar um Logger. Através dos métodos a seguir, clique passo a passo e é fácil encontrar o relatório de erro.Você pode ver as informações impressas do log de exceção no método bind().

org.slf4j.LoggerFactory#getLogger(java.lang.Class<?>)
org.slf4j.LoggerFactory#getLogger(java.lang.String)
org.slf4j.LoggerFactory#getILoggerFactory
org.slf4j.LoggerFactory#performInitialization
org.slf4j.LoggerFactory #vincular

private final static void bind() {
        try {
            Set<URL> staticLoggerBinderPathSet = null;
            // skip check under android, see also
            // http://jira.qos.ch/browse/SLF4J-328
            if (!isAndroid()) {
                staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
            }
            // the next line does the binding
            StaticLoggerBinder.getSingleton();
            INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
            reportActualBinding(staticLoggerBinderPathSet);
        } catch (NoClassDefFoundError ncde) {
            String msg = ncde.getMessage();
            if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("Failed to load class "org.slf4j.impl.StaticLoggerBinder".");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
            } else {
                failedBinding(ncde);
                throw ncde;
            }
        } catch (java.lang.NoSuchMethodError nsme) {
            String msg = nsme.getMessage();
            if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
                INITIALIZATION_STATE = FAILED_INITIALIZATION;
                Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
                Util.report("Your binding is version 1.5.5 or earlier.");
                Util.report("Upgrade your binding to version 1.6.x.");
            }
            throw nsme;
        } catch (Exception e) {
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        } finally {
            postBindCleanUp();
        }
    }

Com uma análise mais aprofundada do método de ligação findPossibleStaticLoggerBinderPathSet(), podemos descobrir que todos os recursos "org/slf4j/impl/StaticLoggerBinder.class" deste caminho foram consultados no ClassPath atual. Pode não haver nenhum arquivo carregado aqui ou vários vinculações podem ser possíveis. Existem lembretes amigáveis ​​para cenas sem vinculação e várias vinculações. O objetivo de carregar recursos por meio do caminho aqui é principalmente solicitar vários cenários anormais de carregamento.

O código abaixo de StaticLoggerBinder.getSingleton() é a ligação real e obtém a instância de StaticLoggerBinder. Se você descompilar aqui, descobrirá que não existe essa classe StaticLoggerBinder.

Se não for carregado no arquivo, como resultado da execução da demonstração acima, uma exceção NoSuchMethodError será atingida e uma mensagem de prompt informando que não há cena vinculada será impressa.

O código-fonte do método findPossibleStaticLoggerBinderPathSet() é o seguinte: Pode-se descobrir que o carregador de classes obtém recursos de URL por meio do caminho.

            ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
            Enumeration<URL> paths;
            if (loggerFactoryClassLoader == null) {
                paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
            } else {
                paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
            }

2.2 referência pom depende de logback-classic

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>

2.2.1 Execute a demonstração

Você pode ver as informações normais do log de impressão e não há anormalidade

2.2.2 Rastreamento do código-fonte

Neste momento, se você clicar para entrar no método StaticLoggerBinder.getSingleton(), você descobrirá que a classe StaticLoggerBinder é fornecida pelo pacote logback-classic e implementa a interface LoggerFactoryBinder no SLF4J. A criação de StaticLoggerBinder utiliza um padrão singleton, e o objetivo principal desta classe é retornar uma fábrica que cria um Logger. Aqui, uma instância de ch.qos.logback.classic.LoggerContext é realmente retornada e, em seguida, ch.qos.logback.classic.Logger é criado por essa instância.

O diagrama de classes UML é o seguinte:

2.3 Pom reintroduz log4j-slf4j-impl

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.9.1</version>
        </dependency>

2.3.1 Execute a demonstração

O log impresso é o seguinte, o que indica que dois StaticLoggerBinder.classes estão vinculados, mas, no final, ch.qos.logback.classic.util.ContextSelectorStaticBinder está realmente vinculado. Também é verificado aqui que uma vez que uma classe é carregada, classes com o mesmo nome globalmente qualificado não podem ser carregadas. A ordem na qual os pacotes Jar são carregados aqui determina diretamente a ordem na qual as classes são carregadas.

SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]
18:19:43.521 [main] INFO com.cj.HelloSlf4j - Hello World info

2.4 Transformação de posição de importação de log4j-slf4j-impl e logback-classic

Se o arquivo Pom primeiro importar log4j-slf4j-impl, então importa logback-classic

2.4.1 Execute a demonstração

De acordo com os resultados da impressão do log, pode-se ver que a ligação real é org.apache.logging.slf4j.Log4jLoggerFactory; mas o log não é impresso normalmente e a configuração do log de log4j2 é necessária. Ele mostra que a ligação real é o arquivo org/slf4j/impl/StaticLoggerBinder.class no pacote og4j-slf4j-impl; aqui também é verificado que se vários pacotes de ponte forem introduzidos, a ligação real é o arquivo carregado primeiro;

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/D:/.m2/repository/org/apache/logging/log4j/log4j-slf4j-impl/2.9.1/log4j-slf4j-impl-2.9.1.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/D:/.m2/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [org.apache.logging.slf4j.Log4jLoggerFactory]
ERROR StatusLogger No log4j2 configuration file found. Using default configuration: logging only errors to the console. Set system property 'log4j2.debug' to show Log4j2 internal initialization logging.

2.5 Mudanças nos métodos de carregamento de classes

2.5.1 Habilidades de empacotamento da versão slf4j-api-1.7.30

Veja slf4j-api-1.7.30-sources.jar para descompilação e descubra que não existe tal classe org.slf4j.impl.StaticLoggerBinder. Como ele pode compilar com sucesso? Adivinha se essa classe foi excluída na embalagem? Baixe o código-fonte através do git e descubra que o código-fonte slf4j realmente possui este arquivo, org/slf4j/impl/StaticLoggerBinder.class; um pequeno truque é usado aqui para excluir a classe de implementação ao empacotar. Embora não seja elegante, a ideia é muito inteligente.

A versão 2.5.2 slf4j-api-2.0.0 apresenta SPI (Service Provider Interface)

Esta versão usa o método SPI para carregar a classe de implementação, que parece muito mais elegante do que o método de implementação anterior. O pacote de ponte só precisa estar neste local: META-INF/services/, definir um arquivo org.slf4j.spi.SLF4JServiceProvider (nomeado como o nome da interface fornecido por SLFJ4) e especificar a classe de implementação no arquivo. Desde que este pacote de ponte seja introduzido, ele pode ser adaptado à estrutura de log da implementação correspondente.

A seguir está o código-fonte carregado pelo SPI

private static List<SLF4JServiceProvider> findServiceProviders() {
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList();
        Iterator var2 = serviceLoader.iterator();

        while(var2.hasNext()) {
            SLF4JServiceProvider provider = (SLF4JServiceProvider)var2.next();
            providerList.add(provider);
        }

        return providerList;
     }

2.5.3 Comparação de Métodos de Carregamento de Classe

2.6 SLF4J implementou oficialmente a estrutura de log de ligação

O slf4j fornece pacotes de ponte para estruturas de log comumente usadas, bem como descrições de documentação detalhadas, que são muito fáceis de usar.
A figura a seguir é fornecida no site oficial do SLF4J, mostrando a relação entre várias estruturas de implementação de log e o SLF4J:

2.7 Resumo

  • A API SLF4J foi projetada para vincular-se a uma e apenas uma estrutura de log subjacente por vez. Além disso, após a introdução do SLF4J, não importa se ele pode ser carregado no StaticLoggerBinder ou carregado em vários StaticLoggerBinders, prompts amigáveis ​​serão fornecidos e a experiência do usuário será considerada muito cuidadosa. Se houver várias ligações no caminho de classe, o SLF4J emitirá uma listagem de aviso onde essas ligações estão localizadas. Quando várias ligações estão disponíveis no caminho de classe, você deve escolher aquela que deseja usar e excluir as outras.
  • Olhando apenas para o código-fonte do SLF4J, na verdade, o design geral e a implementação são muito simples e claros, e o posicionamento é muito claro, apenas para dar uma boa aparência.
  • Dada a simplicidade da interface SLF4J e seu modelo de implantação, os desenvolvedores de novas estruturas de log devem achar fácil escrever ligações SLF4J.
  • Para as atuais estruturas de registro convencionais, elas são compatíveis e suportadas pela implementação da adaptação. Desde que o usuário escolha SLF4J, a liberdade de alterar a estrutura de log no futuro é garantida.

3 Uso dos Padrões de Projeto SLF4J

Alguns padrões de projeto clássicos são usados ​​no slf4j, como padrão de fachada, padrão singleton, padrão de fábrica estático, etc. Vamos analisar os seguintes padrões de projeto.

3.1 Padrão de Fachada

1) explicar

O padrão de fachada, também chamado de padrão de aparência, exige que a comunicação entre o exterior de um subsistema e seu interior seja realizada por meio de um objeto unificado. O padrão Facade fornece uma interface de alto nível que torna o subsistema mais fácil de usar. O padrão de fachada é usado para facilitar as chamadas do cliente.

Slf4j formulou o padrão de uso de log e forneceu uma interface de alto nível. Nosso processo de codificação só precisa contar com a interface Logger e a classe de fábrica LoggerFactory para realizar a impressão do log. Não precisamos nos preocupar com a implementação interna detalhes do log.A implementação de log4j.

2) Diagrama

        Logger logger = LoggerFactory.getLogger(HelloSlf4j.class);
        logger.info("Hello World info");

3) Vantagens

O desacoplamento reduz a interdependência do sistema. Todas as dependências são dependentes de objetos de fachada e não têm nada a ver com subsistemas. O desenvolvimento da camada de negócios não precisa se preocupar com a implementação e os detalhes da estrutura de log subjacente e não há necessidade de considerar o custo de substituição da estrutura no futuro ao codificar.
A interface e a implementação são separadas, protegendo os detalhes de implementação subjacentes e a programação orientada à interface.

3.2 Padrão Singleton

1) explicar

O padrão singleton garante que uma classe tenha apenas uma instância e forneça um ponto de acesso global a ela.
No pacote de adaptação SLF4J, a classe StaticLoggerBinder precisa ser implementada, e a implementação da classe StaticLoggerBinder usa o modo singleton, e é o método de implementação mais simples. No inicializador estático, diretamente new StaticLoggerBinder() fornece um método de acesso global para obter a instância.

2) Diagrama UML

3) Vantagens

No padrão singleton, há apenas uma instância do singleton ativo e todas as instanciações da classe singleton obtêm a mesma instância. Isso evita que outros objetos se instanciem e garante que todos os objetos acessem uma instância. O
modo singleton tem certa escalabilidade. A própria classe controla o processo de instanciação e a classe tem escalabilidade correspondente ao alterar o processo de instanciação.

Fornece acesso controlado a instâncias exclusivas.

Há apenas uma instância na memória, o que reduz a sobrecarga de memória e melhora o desempenho do sistema.

4 Apocalipse

  • Embora o código geral do SLF4J seja curto, mas muito refinado, pode-se ver que o modo fachada é bem utilizado. O modo de fachada também nos fornece uma referência de como definir interfaces uniformemente e fornecer compatibilidade para implementações de várias versões.
  • A definição e a implementação do SLF4J são muito amigáveis ​​para os usuários e, ao mesmo tempo, vários pacotes de ponte são fornecidos para orientar o uso de documentos completos. Em suma, a experiência do usuário é ótima, o que pode ser um dos motivos mais populares para o SLF4J.
  • Precisamos pensar mais sobre a programação orientada à interface, reduzir o acoplamento de código e melhorar a escalabilidade do código.
  • Use o método SPI para carregar elegantemente a implementação da extensão.
  • Um bom produto é projetado, otimizado e iterado.

5 Referências

Autor: JD Logística Cao Jun

Fonte: JD Cloud Developer Community

{{o.name}}
{{m.name}}

Acho que você gosta

Origin my.oschina.net/u/4090830/blog/10084131
Recomendado
Clasificación