相关设计模式
适配器模式
@startuml
namespace mybatis {
interface Log
class LogFactory
LogFactory .down.> Log
Jdk14LoggingImpl .up.|> Log
Slf4jImpl .up.|> Log
StdOutImpl .up.|> Log
NoLoggingImpl .up.|> Log
JakartaCommonsLoggingImpl .up.|> Log
Log4jImpl .up.|> Log
Log4j2Impl .up.|> Log
}
namespace slf4j_api {
interface Logger
}
namespace log4j_api_2 {
interface Logger
}
namespace java.lang {
class System
}
namespace java.util.logging {
class Logger
}
namespace jcl_over_slf4j {
interface Log
}
namespace log4j {
interface Logger
}
mybatis.Slf4jImpl o-down- slf4j_api.Logger
mybatis.StdOutImpl o-down- java.lang.System
mybatis.Jdk14LoggingImpl o-down- java.util.logging.Logger
mybatis.JakartaCommonsLoggingImpl o-down- jcl_over_slf4j.Log
mybatis.Log4jImpl o-down- log4j.Logger
mybatis.Log4j2Impl o-down- log4j_api_2.Logger
@enduml
目标接口为 Log 接口,需要适配的类为实际提供日志支持的类,比如 log4j_api_2 .Logger
,
适配器实现了 Log
接口,并由 mybatis.Log4j2Impl
组成,两者之间是聚合关系。
门面模式
@startuml
namespace slf4j_api {
interface Logger
interface LocationAwareLogger
LocationAwareLogger -up-|> Logger
}
namespace log4j_slf4j_impl {
class Log4jLogger
}
log4j_slf4j_impl.Log4jLogger .up.|> slf4j_api.LocationAwareLogger
namespace Client {
}
Client -down-> slf4j_api.Logger
@enduml
slf4j_api.Logger
充当门面,Client 调用 slf4j_api.Logger
获得服务。
具体的服务支持由 og4j_slf4j_impl.Log4jLogger 或jul_over_slf4j.Log4jLogger等
等提供支持
代理模式
@startuml
interface Connection
ConnectionImpl .up.|> Connection
note left of ConnectionImpl:不需要创建此类
ConnectionProxy .up.|> Connection
ConnectionProxy o-up- "1" ConnectionImpl
@enduml
ConnectionProxy
作为 Connection
接口实现类的代理类。
日志选择
org.apache.ibatis.logging.LogFactory 静态初始化块中
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
一共调用了 6 次 tryImplementation,每次调用传入不同的门面,按顺序找到第 1 个实现了门面逻辑的子系统,写入 logConstructor,不再往下找。
这里虽然创建了 Runnable,但并没有启动线程,因为 tryImplementation 调用的是 run 而不是 start
以 log4j2 为例
只贴出流程中需要的代码,其他的用 ...
表示省略
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
...
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
...
}
iLoggerFactory 打上条件断点,条件是 name.contains("LogFactory")
。
在 expression 中输出 class org.apache.logging.slf4j.Log4jLoggerFactory
,可看到
name 为 org.apache.logging.slf4j.Log4jLoggerFactory
,在 log4j-slf4j-impl
中。
选择了 slf4j
门面,log4j-slf4j-impl
为子系统
日志打印逻辑
@startuml
abstract class BaseJdbcLogger
interface InvocationHandler
StatementLogger -up-|> BaseJdbcLogger
ConnectionLogger -up-|> BaseJdbcLogger
ResultSetLogger -up-|> BaseJdbcLogger
PreparedStatementLogger -up-|> BaseJdbcLogger
StatementLogger .down.|> InvocationHandler
ConnectionLogger .down.|> InvocationHandler
ResultSetLogger .down.|> InvocationHandler
PreparedStatementLogger .down.|> InvocationHandler
@enduml
在代理类的 invoke 方法中打印 log
打印完整日志
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.CollectionUtils;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.type.TypeHandlerRegistry;
import java.text.DateFormat;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.regex.Matcher;
import org.springframework.stereotype.Component;
@Component
@Log4j2
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {
MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {
MappedStatement.class, Object.class, RowBounds.class,
ResultHandler.class})})
public class MyBatisLogInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
MappedStatement mappedStatement = (MappedStatement) invocation
.getArgs()[0];
Object parameter = null;
if (invocation.getArgs().length > 1) {
parameter = invocation.getArgs()[1];
}
String sqlId = mappedStatement.getId();
BoundSql boundSql = mappedStatement
.getBoundSql(parameter);
Configuration configuration = mappedStatement.getConfiguration();
String sql = getSql(configuration, boundSql, sqlId);
log.info(sql);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return invocation.proceed();
}
private static String getSql(Configuration configuration, BoundSql boundSql, String sqlId) {
String sql = showSql(configuration, boundSql);
return sqlId.concat(":").concat(sql);
}
/**
* 如果参数是String,则添加单引号
* 如果是日期,则转换为时间格式器并加单引号
* 对参数是null和不是null的情况作了处理
*/
private static String getParameterValue(Object obj) {
String value = null;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
DateFormat formatter = DateFormat
.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
value = "'" + formatter.format(new Date()) + "'";
} else {
if (obj != null) {
value = obj.toString();
} else {
value = "";
}
}
return value;
}
// 进行?的替换
private static String showSql(Configuration configuration, BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
List parameterMappings = boundSql
.getParameterMappings();
String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
if (CollectionUtils.isNotEmpty(parameterMappings) && parameterObject != null) {
TypeHandlerRegistry typeHandlerRegistry = configuration
.getTypeHandlerRegistry();
if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
sql = sql.replaceFirst("\\?",
Matcher.quoteReplacement(getParameterValue(parameterObject)));
} else {
MetaObject metaObject = configuration.newMetaObject(
parameterObject);
for (ParameterMapping parameterMapping : parameterMappings) {
String propertyName = parameterMapping.getProperty();
if (metaObject.hasGetter(propertyName)) {
Object obj = metaObject.getValue(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
Object obj = boundSql.getAdditionalParameter(propertyName);
sql = sql
.replaceFirst("\\?", Matcher.quoteReplacement(getParameterValue(obj)));
} else {
sql = sql.replaceFirst("\\?", "missing");
}
}
}
}
return sql;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
关于拦截器、JDK 动态代理、CGLIB,有时间再开一篇。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于