一、介绍
门面模式(也叫外观模式,Facade Pattern)在许多源码中有使用,比如 slf4j 就可以理解为是门面模式的应用。
有一个接口,我们可以在不改变接口原有实现类的情况下,提供一个门面类,通过调用门面类的方法,让门面类帮我们决定调用哪个实现类的具体方法。
二、代码案例
首先,我们定义一个接口:
public interface Shape {
void draw();
}
定义几个实现类:
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("Circle::draw()");
}
}
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}
客户端调用:
public static void main(String[] args) {
// 画一个圆形
Shape circle = new Circle();
circle.draw();
// 画一个长方形
Shape rectangle = new Rectangle();
rectangle.draw();
}
以上是我们常写的代码,我们需要画圆就要先实例化圆,画长方形就需要先实例化一个长方形,然后再调用相应的 draw() 方法。
下面,我们看看怎么用门面模式来让客户端调用更加友好一些。
我们先定义一个门面:
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;
public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}
/**
* 下面定义一堆方法,具体应该调用什么方法,由这个门面来决定
*/
public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}
看看现在客户端怎么调用:
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();
// 客户端调用现在更加清晰了
shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
门面模式的优点显而易见,客户端不再需要关注实例化时应该使用哪个实现类,直接调用门面提供的方法就可以了,因为门面类提供的方法的方法名对于客户端来说已经很友好了。
三、slf4j源码
slf4j 提供了统一的 logger api
,每个日志框架都会实现 logger api
。在调用 logger
的时候 ,slf4j 会根据 ClassLoader
加载各框架的 StaticLoggerBinder
,然后根据 StaticLoggerBinder
获取对应的loggerFactory
对象,在根据 LoggerFactory
创建 Logger
对象,所以调用 logger
的方法时,实际是调用各个框架 logger
方法,这实际上就是门面模式的一种实现。
protected Logger logger = LoggerFactory.getLogger(getClass());
LoggerFactory.getLogger
是用来初始化Logger对象的所以这个代码就是入口,所以我们就从这开始分析
public static Logger getLogger(Class<?> clazz) {
// 用来初始化logger对象的
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;
}
重点是怎么获取logger对象,所以我们点击第二行代码
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
这里面是通过 getILoggerFactory()
初始化一个 loggerFactory ,然后根据loggerFactory获取 logger对象,这里使用了工厂设计模式,当开始阅读源码时会发现设计模式无处不在,我们继续往下走getILoggerFactory
public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}
}
}
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
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_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
第2行~第9行: 判断 loggerFactory
是否已经初始化,如果没有初始化就开始执行初始化performInitialization()
,这里使用单例模式,保证 loggerFactory 只被初始化一次。
第10行 ~ 第20:判断 loggerFactory 初始化状态,成功、失败等,从字面意思上就可以理解,从这里看出一般都会静态变量来做为状态码,比如 INITIALIZATION_STATE ,这还使用volatile修饰符保证变量线程间的一致性
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
然后我们继续往下走
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
重点来了!bind()
方法是绑定 loggerFactory
的实现,往下走
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();
// 检查是否两个logger日志绑定器,这就是为什么 slf4j只能有且仅有一种日志框架的binding
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
//初始化StaticLoggerBinder绑定器对象,这个对象是用来创建loggerFatory,在前面getILoggerFactory() 方法中有使用 StaticLoggerBinder.getSingleton().getLoggerFactory()
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();
}
}
然后到这里我们已经知道 loggerFactory工厂是怎么创建的,但是StaticLoggerBinder 是怎么加载的,我们不知道,所以我们到 findPossibleStaticLoggerBinderPathSet()
里看
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order
// during iteration
Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
try {
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);
}
while (paths.hasMoreElements()) {
URL path = paths.nextElement();
staticLoggerBinderPathSet.add(path);
}
} catch (IOException ioe) {
Util.report("Error getting resources from path", ioe);
}
return staticLoggerBinderPathSet;
}
通过ClassLoader.getResources 去项目的资源目录下寻找org/slf4j/impl/StaticLoggerBinder.class 文件,如果找到多个的StaticLoggerBinderclass文件,就会在reportMultipleBindingAmbiguity中判断,并将每个StaticLoggerBinder文件路径打印出来。这里就解释了为什么slf4j 不能同时整合多个日志框架。