一、什么是适配器模式
适配器模式就是把一个正常工作但是不符合当前需求的接口转换成另外一个接口,使其符合当前的需求.
说白了就是针对老接口做一下兼容操作.
二、为什么要使用适配器模式
需求:
假设你接手了一个老项目,目前有一个接口A,接受a,b俩个参数,但是现在新增加了一个参数c,请问如何在不改动原有的代码逻辑下实现新的功能.
分析-解决:
1.首先因为是老项目,并且这个接口已经投入使用,所以不可能直接在原来的接口上新增一个参数.
2.那这个时候你可以新定义一个接口方法,传入abc三个参数,然后在新的方法中做逻辑判断,如果只有ab俩个参数的话可以去调用原来的接口方法,否则执行新的代码逻辑
总结:
1.这就是适配器模式的基本思想,可以理解为它起到了一个转换的作用(或者叫承上启下),承上为调用者提供了可以按新需求访问的接口,启下为原先不匹配的接口实现了整合与兼容.
三、适配器模式的分类
1.类适配器
主要是通过继承的方式去实现适配器模式(不推荐使用)
2.对象适配器
主要是通过组合的方式去实现适配器模式(推荐使用,各大框架的主流使用方式)
3.接口适配器
适配器模式的一种特殊情况
三、代码示例
四、在框架中的应用
1.适配器模式在Mybatis框架中的应用
问题:
已知Mybatis框架集成了多种日志框架(JDK-Logger,slf4j等等),那么Mybatis框架是如何去整合这些日志组件然后对外提供服务呢?
分析说明:
首先看一下Mybaits的Log接口,此接口为Log的顶层接口,最终使用的都是此接口的具体实现类
//org.apache.ibatis.logging.Log
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
然后看一下Log接口的实现类
这里的实现类就相当于是适配器了,可以从类的命名大致知道是适配的哪一种日志框架
这里使用Log4jImpl来举例说明:
/**
org.apache.ibatis.logging.log4j.Log4jImpl
此类就是适配的Log4j日志框架
**/
public class Log4jImpl implements Log {
private static final String FQCN = Log4jImpl.class.getName();
/**
源对象(就是被适配的对象)Log4j日志框架中的Logger对象
使用的是对象适配器模式
最终调用的是Log4j日志框架中的Logger对象的相关API
**/
private final Logger log;
//通过构造器获取到Logger对象
public Log4jImpl(String clazz) {
log = Logger.getLogger(clazz);
}
@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}
@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}
@Override
public void error(String s, Throwable e) {
//这里最终调用的是Logger对象的log()方法,其他方法都一样
log.log(FQCN, Level.ERROR, s, e);
}
@Override
public void error(String s) {
log.log(FQCN, Level.ERROR, s, null);
}
@Override
public void debug(String s) {
log.log(FQCN, Level.DEBUG, s, null);
}
@Override
public void trace(String s) {
log.log(FQCN, Level.TRACE, s, null);
}
@Override
public void warn(String s) {
log.log(FQCLogFactoryN, Level.WARN, s, null);
}
}
从这里可以看出假设Mybaits当前使用的是Log4j日志框架,那么对于调用方来说,他不需要知道最终使用的是哪种日志框架,他只需要获取到Mybatis中的Log对象,然后调用对应的API就可以了,Log4jImpl类就起到了一个转换的作用,这也是适配器模式的一种经典实现.
下面将结合代码的调用链来进一步理解Mybaits框架日志模块的设计
问题:
通过上诉可知,Mybatis框架是集成了多种日志框架的,那么他是怎么确定当前需要使用哪种日志框架呢?
分析说明:
首先看一下LogFactory类(Mybaits适配不同日志框架的核心类)
具体说明看代码注释:
/**
org.apache.ibatis.logging.LogFactory
代码执行逻辑:
1.先执行静态代码块中的代码去获取各个日志框架然后赋值给logConstructor变量保存(保存的是当前使用的日志框架)
2.如果配置文件中做了相关配置,则直接使用配置文件中配置的日志框架
(一般可能配的是:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl)
**/
public final class LogFactory {
public static final String MARKER = "MYBATIS";
//这个变量保存的是Mybatis当前使用的日志框架的实现类
private static Constructor<? extends Log> logConstructor;
/**
静态代码块.依次执行如下方法,获取到日志适配器对象保存至变量logConstructor上
**/
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
}
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}
public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}
public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}
public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}
public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}
public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}
public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}
public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}
//这是使用线程去执行方法,提高速度
private static void tryImplementation(Runnable runnable) {
//当logConstructor已经存在对应的日志适配器对象之后就不再执行了
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
/*
这里的catch是因为Mybatis框架中是集成了多种日志框架,但是项目中可能不会依赖这么多日志框架,
就会出现某些日志框架不存在,为了避免程序中断,这里不做任何处理,直接将异常吃掉
*/
}
}
}
//此方法就是获取日志适配器对象的方法
private static void setImplementation(Class<? extends Log> implClass) {
try {
/*
这里是通过class类的getConstructor()方法获取构造器
不明白的同学可以百度搜[JAVA Class类的getConstructor()方法的作用]
*/
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//通过构造器去实例化Log对象(具体实现类)
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
//将获取到的Log的适配器对象赋值给logConstructor变量保存
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
}
到这里大家应该也就明白了适配器模式在Mybatis框架中的应用了吧!
无论Mybatis集成多少第三方的日志框架,对于调用者来说都不用关心,也不需要考虑是否兼容,只需要正常调用Log对象的API就可以了!
下面说点题外话
1.当你在配置文件中配置了日志框架时,这里保存的Log适配器类将会被覆盖掉.
代码见:org.apache.ibatis.session.Configuration类和org.apache.ibatis.builder.xml.XMLConfigBuilder类,这里只粘贴核心代码
//这里就是去获取你自己配置的那个日志框架(例如:log-impl: org.apache.ibatis.logging.stdout.StdOutImpl)
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
//调用LogFactory的useCustomLogging()方法,去覆盖原来的Log适配器类
LogFactory.useCustomLogging(this.logImpl);
}
}
//读取配置文件,获取配置项 (XMLConfigBuilder类中的方法)
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
五、总结
至此,JAVA适配器模式就梳理完毕了!~