1.日志模块需求
- 1)Mybatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同。而Mybatis统一提供了trace/ debug/ warn/ error四个级别
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);
}
- 2)自动扫描日志实现,并且第三方日志插件加载优先级如下:
slf4j -> commonsLoging -> Log4J2 -> Log4J -> JdkLog
- 3)日志的使用要优雅地嵌入到主体功能中
2.相关设计模式——适配器模式
Adapter Pattern 是作为两个不兼容的接口之间的桥梁,将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。
- Target:目标角色,期待得到的接口
Adaptee:适配者角色,被适配的接口
Adapter:适配器角色,将源接口转换成目标接口
适配器满足:
1)单一职责原则:target/ adaptee/ adapter都负责单一职责
2)依赖倒转原则:只针对接口Log编程
3)开闭原则:日志模块更换时,只需增加一个针对新日志模块的Adapter接口
- 适用场景:当调用双方都不容易修改的时候,为了复用现有组件可以使用适配器模式,在系统中接入第三方组件的时候经常被用到。
- 注意:如果系统中存在过多的适配器,会增加系统的复杂性,设计人员应考虑对系统进行重构
3 前两个需求的解决
-
日志模块提供了针对不同第三方日志组件的Adapter,以实现统一接口
- LogFactory自动扫描日志实现,并且定义了第三方日志插件加载优先级
//自动扫描日志实现,并且第三方日志插件加载优先级如下
//slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
4.相关设计模式——代理模式
- 定义:给目标对象提供一个代理对象,并由代理对象控制对目标对象的引用
- 目的:
1)通过引入代理对象的方式来间接访问目标对象,防止直接访问目标对象给系统带来的不必要的复杂性
2)通过代理对象对原有业务增强 - 代理模式的有点
1)作为中介解耦客户端和真实对象,保护真实对象安全
2)防止直接访问目标对象给系统带来的不必要复杂性
3)对业务进行增强,增强点多样化:前入、后入、异常(AOP)
代理模式类图:
动态代理:
4.1 动态代理实例
真实对象:
public class WangMeiLi implements Girl {
@Override
public void date() {
System.out.println("王美丽说:跟你约会好开心啊");
//this.watchMovie();
}
@Override
public void watchMovie() {
System.out.println("王美丽说:这个电影我不喜欢看");
}
}
代理对象:
public class WangMeiLiProxy implements InvocationHandler {
private Girl girl;
public WangMeiLiProxy(Girl girl) {
super();
this.girl = girl;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
doSomeThingBefore();
Object ret = method.invoke(girl, args);
doSomeThingEnd();
return ret;
}
private void doSomeThingBefore(){
System.out.println("王美丽的父母说:我得先调查下这个男孩子的背景!");
}
private void doSomeThingEnd(){
System.out.println("王美丽的父母说:他有没有对你动手动脚啊?");
}
public Object getProxyInstance(){
return Proxy.newProxyInstance(girl.getClass().getClassLoader(), girl.getClass().getInterfaces(), this);
}
}
测试代码:
public class Lison {
public static void main(String[] args) {
//隔壁有个女孩,叫王美丽
Girl girl = new WangMeiLi();
//他有个庞大的家庭,想要跟她约会必须征得她家里人的同意
WangMeiLiProxy family = new WangMeiLiProxy(girl);
//有一次我去约王美丽,碰到了她的妈妈,我征得了她妈妈的同意
Girl mother = (Girl) family.getProxyInstance();
//通过她的妈妈这个代理才能与王美丽约会
mother.date();
//华丽分割线
System.out.println("-----------------------------------");
//通过她的妈妈这个代理才能与王美丽看电影
mother.watchMovie();
}
}
结果:
王美丽的父母说:我得先调查下这个男孩子的背景!
王美丽说:跟你约会好开心啊
王美丽的父母说:他有没有对你动手动脚啊?
-----------------------------------
王美丽的父母说:我得先调查下这个男孩子的背景!
王美丽说:这个电影我不喜欢看
王美丽的父母说:他有没有对你动手动脚啊?
5.用代理模式解决第三个需求
核心是jdbc包:
代理对象的类层次结构图:
创建代理对象的地方:
SimpleExecutor.doQuery
prepareStatement
Connection connection = getConnection(statementLog);//获取connection对象的动态代理,添加日志能力;
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
6.关于mybatis源码调试说明
mybatis源码有一个专门用于测试的目录:src/test,里面有针对各个模块进行测试代码。
参考
- 1)享学课堂Lison老师笔记
- 2)Java动态代理