slf4j 日志门面模式应用(双亲委派特例)

Posted 双斜杠少年

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了slf4j 日志门面模式应用(双亲委派特例)相关的知识,希望对你有一定的参考价值。

SLF4J

SLF4J是一个日志抽象库 (Simple Logging Facede For Java 简单日志门面 ),它相当于一个服务的接口,具体的日志功能是有其中的日志框架去实现的,slf4j 可以在部署的时候接上想要使用的日志框架。

SLF4J 提供一个高层次的接口(门面),由不同的日志框架去实现。开发者不需要关心日志实现框架是Log4J2还是其它框架,开发者只需要使用相同的方法进行日志的打印就可以了,即使底层的日志实现更换掉了,开发者也不需要改动任何代码,使系统更易于使用、维护。

通俗来说

SLF4J是一个日志抽象库,必须和其他日志库配合才能正常运行。一般来说,需要将抽象层(例如slf4j-api-xx.jar)+ 中间层(例如slf4j-log4j12)+ 实现层(例如log4j)这三层都配置好才能保证SLF4J正常运行。另外,有的日志库可以去掉中间层,例如slf4j-api和slf4j-simple就可以直接配合

可以理解为 slf4j 提供一个 SPI (Service Provider Interface,服务提供商,按照一定规范实现服务,然后被解析加载。
一般是分为服务实现+配置信息。首先扫描配置信息,然后加载服务实现,然后被使用),其他的日志框架通过适配实现了 slf4j 的SPI接口。当需要日志对象时,slf4j 会通过 通过动态查找的机制,在程序运行时自动找出真正使用的日志库。它使用了线程上下文件类加载器(Thread Context ClassLoader,)寻找和载入底层的日志库,通过底层日志框架对象进行日志打印。

违反双亲委派

使用这个线程上下文类加载器去加载所需要的SPI代码,也就是父类加载器请求子类加载器去完成类加载动作,这种行为实际上就是打通了双亲委派模型的层次结构来逆向使用类加载器,已经违背了双亲委派模型

双亲委派机制,看这篇文章的1.3,点我你不后悔!

以log4j 为例 看 SLF4J

SLF4J + Log4j 就是抽象层+中间层+实现层的组合,需要三个jar包:

slf4j-api-1.8.0-beta0.jar
slf4j-log4j12-1.8.0-beta0.jar
log4j-1.2.17.jar
  • slf4j-api 定义了SPI : org.slf4j.spi.SLF4JServiceProvider
  • slf4j-log4j12 适配包中的 org.slf4j.log4j12.Log4j12ServiceProvider 实现了 slf4j 的 SLF4JServiceProvider接口。
  • 在 org.slf4j.log4j12.Log4jLoggerFactory 中 通过 getLogger(String name) 方法,获取 log4j 中的 logger对象,通过该对象进行日志打印。

源码追踪如下:

slf4j-api 定义SPI 及加载


	//获取日志对象
	private static Logger log = LoggerFactory.getLogger(LogTest.class);

	// getLogger
	public static Logger getLogger(Class<?> clazz) 
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) 
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) 
                Util.report(String.format("Detected logger name mismatch. Given name: \\"%s\\"; computed name: \\"%s\\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            
        
        return logger;
    
    
    //7行跳转 重载getLogger
    public static Logger getLogger(String name) 
        ILoggerFactory iLoggerFactory = getILoggerFactory();
        return iLoggerFactory.getLogger(name);
    
    
    // 21行跳转 getILoggerFactory
    public static ILoggerFactory getILoggerFactory() 
        return getProvider().getLoggerFactory();
    
    
	//27行跳转 getProvider 获取日志框架的provider 实现类
    static SLF4JServiceProvider getProvider() 
        if (INITIALIZATION_STATE == UNINITIALIZED) 
            synchronized (LoggerFactory.class) 
                if (INITIALIZATION_STATE == UNINITIALIZED) 
                    INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                    performInitialization();
                
            
        
        switch (INITIALIZATION_STATE) 
        case SUCCESSFUL_INITIALIZATION:
            return PROVIDER;
        case NOP_FALLBACK_INITIALIZATION:
            return NOP_FALLBACK_FACTORY;
        case FAILED_INITIALIZATION:
            throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
        case ONGOING_INITIALIZATION:
            // support re-entrant behavior.
            // See also http://jira.qos.ch/browse/SLF4J-97
            return SUBST_PROVIDER;
        
        throw new IllegalStateException("Unreachable code");
    
    
    
    //36行跳转 performInitialization
    private final static void performInitialization() 
        bind();
        if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) 
            versionSanityCheck();
        
    
    
 	//58 行跳转 bind
    private final static void bind() 
        try 
            List<SLF4JServiceProvider> providersList = findServiceProviders();
            reportMultipleBindingAmbiguity(providersList);
            if (providersList != null && !providersList.isEmpty()) 
            	PROVIDER = providersList.get(0);
            	// SLF4JServiceProvider.initialize() is intended to be called here and nowhere else.
            	PROVIDER.initialize();
            	INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
                reportActualBinding(providersList);
                fixSubstituteLoggers();
                replayEvents();
                // release all resources in SUBST_FACTORY
                SUBST_PROVIDER.getSubstituteLoggerFactory().clear();
             else 
                INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
                Util.report("No SLF4J providers were found.");
                Util.report("Defaulting to no-operation (NOP) logger implementation");
                Util.report("See " + NO_PROVIDERS_URL + " for further details.");

                Set<URL> staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
                reportIgnoredStaticLoggerBinders(staticLoggerBinderPathSet);
            
         catch (Exception e) 
            failedBinding(e);
            throw new IllegalStateException("Unexpected initialization failure", e);
        
    
    
    //67 行跳转 findServiceProviders 通过ServiceLoader.load去加载需要的 SPI,也就是日志框架的实现
    private static List<SLF4JServiceProvider> findServiceProviders() 
        ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class);
        List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>();
        for (SLF4JServiceProvider provider : serviceLoader) 
            //此处 ServiceLoader 是懒加载,所以在进for循环后才会加载provider
            providerList.add(provider);
        
        return providerList;
    

	// 96行跳转,通过 线程上下文件类加载器(Thread Context ClassLoader,如果在应用程序的全局范围内都没有设置过,
	// 那么这个类加载器默认就是应用程序类加载器)加载指定的 接口实现
    public static <S> ServiceLoader<S> load(Class<S> service) 
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
    

slf4j-log4j12 中间层

// 实现 SPI
public class Log4j12ServiceProvider implements SLF4JServiceProvider

 @Override
    public void initialize() 
        loggerFactory = new Log4jLoggerFactory();
        markerFactory = new BasicMarkerFactory();
        mdcAdapter = new Log4jMDCAdapter();
    


// 获取log4j对象
    public Logger getLogger(String name) 
        Logger slf4jLogger = loggerMap.get(name);
        if (slf4jLogger != null) 
            return slf4jLogger;
         else 
            org.apache.log4j.Logger log4jLogger;
            if (name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME))
                log4jLogger = LogManager.getRootLogger();
            else
                log4jLogger = LogManager.getLogger(name);

            Logger newInstance = new Log4jLoggerAdapter(log4jLogger);
            Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
            return oldInstance == null ? newInstance : oldInstance;
        
    

使用配置

代码中需要SLF4J和Log4j的配合使用只需要三步

导入依赖


 	<dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>2.0.0-alpha1</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.17</version>
        </dependency>
 

代码使用

//注解方式
@Slf4j
public class LogTest 

    public static void main(String[] args) 
        log.info("test");
    


// 获取对象方式
public class LogTest 
    private static Logger log = LoggerFactory.getLogger(LogTest.class);

    public static void main(String[] args) 
        log.info("test");
    

增加log4j配置文件

将配置文件放到classpath中

# Set root logger level to DEBUG and its only appender to A1.
log4j.rootLogger=DEBUG, A1
 
# A1 is set to be a ConsoleAppender.
log4j.appender.A1=org.apache.log4j.ConsoleAppender
 
# A1 uses PatternLayout.
log4j.appender.A1.layout=org.apache.log4j.PatternLayout
log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n

以上是关于slf4j 日志门面模式应用(双亲委派特例)的主要内容,如果未能解决你的问题,请参考以下文章

slf4j 日志门面模式应用(双亲委派特例)

slf4j 日志门面模式应用(双亲委派特例)

slf4j 日志门面模式应用(双亲委派特例)

slf4j 日志门面模式应用(双亲委派特例)

slf4j 日志门面模式应用(双亲委派特例)

Java日志框架:slf4j作用及其实现原理