结构型-门面模式


一、介绍

门面模式(也叫外观模式,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 不能同时整合多个日志框架。


  目录