1、概述
日志模块除了实现常规第三方日志的集成外,还实现了通过JDK动态代理实现了对JDBC操作进行了日志增强功能。
2、动态代理
Subject是程序中的业务逻辑接口, RealSubject 是实现了Subject接口的真正业务类,Proxy是实现了Subject接口的代理类,其中封装了RealSubject 对象。在程序中不会直接调动RealSubject对象的方法,而是使用Proxy对象实现相关功能。Proxy.operation()方法的实现会调用RealSubject 对象的operation ()方法执行真正的业务逻辑,但是处理完业务逻辑,Proxy.operation ()会在RealSubj 巳ct.operation()方法调用前后进行预处理和相关的后置处理。这就是所谓的“代理模式” 。
3、动态增强类(Proxy)
日志模块中,使用的动态增强类有这么多。其中,BaseJdbcLogger是基类,然后分布实现了Connection、PreparedStatement、ResultSet、Statement等类。如下图所示:
- BaseJdbcLogger 基类
/**
* Base class for proxies to do logging
* jdbc日志基类,代理类,实现日志功能
* @author Clinton Begin
* @author Eduardo Macarron
*/
public abstract class BaseJdbcLogger {
protected static final Set<String> SET_METHODS = new HashSet<String>();
protected static final Set<String> EXECUTE_METHODS = new HashSet<String>();
private final Map<Object, Object> columnMap = new HashMap<Object, Object>();
private final List<Object> columnNames = new ArrayList<Object>();
private final List<Object> columnValues = new ArrayList<Object>();
protected Log statementLog;
protected int queryStack;
/*
* Default constructor
*/
public BaseJdbcLogger(Log log, int queryStack) {
this.statementLog = log;
if (queryStack == 0) {
this.queryStack = 1;
} else {
this.queryStack = queryStack;
}
}
static {
SET_METHODS.add("setString");
SET_METHODS.add("setNString");
SET_METHODS.add("setInt");
SET_METHODS.add("setByte");
SET_METHODS.add("setShort");
SET_METHODS.add("setLong");
SET_METHODS.add("setDouble");
SET_METHODS.add("setFloat");
SET_METHODS.add("setTimestamp");
SET_METHODS.add("setDate");
SET_METHODS.add("setTime");
SET_METHODS.add("setArray");
SET_METHODS.add("setBigDecimal");
SET_METHODS.add("setAsciiStream");
SET_METHODS.add("setBinaryStream");
SET_METHODS.add("setBlob");
SET_METHODS.add("setBoolean");
SET_METHODS.add("setBytes");
SET_METHODS.add("setCharacterStream");
SET_METHODS.add("setNCharacterStream");
SET_METHODS.add("setClob");
SET_METHODS.add("setNClob");
SET_METHODS.add("setObject");
SET_METHODS.add("setNull");
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
protected Object getColumn(Object key) {
return columnMap.get(key);
}
/**
* 把columnValues集合转换成String字符串,并删除字符串前后的中括号“[”,“]”
* @return
*/
protected String getParameterValueString() {
List<Object> typeList = new ArrayList<Object>(columnValues.size());
for (Object value : columnValues) {
if (value == null) {
typeList.add("null");
} else {
typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
}
}
final String parameters = typeList.toString();
//去除开始和结束的中括号“[”,“]”
return parameters.substring(1, parameters.length() - 1);
}
/**
* 对象转换
* 如果value属于Array类,则通过Arrays.toString()实现
* 否则,直接返回对象的toString()值。
* @param value
* @return
*/
protected String objectValueString(Object value) {
if (value instanceof Array) {
try {
return ArrayUtil.toString(((Array) value).getArray());
} catch (SQLException e) {
return value.toString();
}
}
return value.toString();
}
protected String getColumnString() {
return columnNames.toString();
}
protected void clearColumnInfo() {
columnMap.clear();
columnNames.clear();
columnValues.clear();
}
/**
* 移除字符串特殊字符
* @param original
* @return
*/
protected String removeBreakingWhitespace(String original) {
StringTokenizer whitespaceStripper = new StringTokenizer(original);
StringBuilder builder = new StringBuilder();
while (whitespaceStripper.hasMoreTokens()) {
builder.append(whitespaceStripper.nextToken());
builder.append(" ");
}
return builder.toString();
}
protected boolean isDebugEnabled() {
return statementLog.isDebugEnabled();
}
protected boolean isTraceEnabled() {
return statementLog.isTraceEnabled();
}
protected void debug(String text, boolean input) {
if (statementLog.isDebugEnabled()) {
statementLog.debug(prefix(input) + text);
}
}
protected void trace(String text, boolean input) {
if (statementLog.isTraceEnabled()) {
statementLog.trace(prefix(input) + text);
}
}
/**
* 生成"<== "或"==> "字符串,等号“=”的数量为queryStack * 2 + 1
* isInput=true时,生成"==> "类型字符串,否则生成"<== "类型字符串
* @param isInput
* @return
*/
private String prefix(boolean isInput) {
char[] buffer = new char[queryStack * 2 + 2];
Arrays.fill(buffer, '=');
buffer[queryStack * 2 + 1] = ' ';
if (isInput) {
buffer[queryStack * 2] = '>';
} else {
buffer[0] = '<';
}
return new String(buffer);
}
}
- ConnectionLogger类
下面以ConnectionLogger类为例,学习如何实现了对对象功能进增强。其中,属性字段是Connection,该实例对象就是Mybatis的ConnectionLogger对Connection进行的一次增强。
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
private final Connection connection;
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn;
}
/**
* 增强Connection功能,添加日志功能
*/
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
//method.getDeclaringClass();方法定义所在的类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
* Creates a logging version of a connection
* 创建一个带有日志功能的connection实例对象,通过java的代理(Proxy)实现。
* @param conn - the original connection
* @return - the connection with logging
*/
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
/**
* return the wrapped connection
*
* @return the connection
*/
public Connection getConnection() {
return connection;
}
}
4、JDBC动态代理日志在Mybatis中的应用
下面以SimpleExecutor.doQuery()方法为例,分析JDBC动态代理日志的使用情况。
- SimpleExecutor.doQuery()方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
该方法是Mybatis实现列表数据查询的底层方法,其中prepareStatement()方法是对jdbc调用数据库进行的封装。
- prepareStatement()方法
其中getConnection()返回的Connection对象,就是被增强后具有日志功能的对象。该方法位于SimpleExecutor的基类BaseExecutor类中。
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
- getConnection()方法
在基类BaseExecutor的getConnection()方法中,实现了对Connection对象的日志增强功能。首先获取真正的Connection 对象,然后判断是否开启了日志功能,如果开启了就通过ConnectionLogger.newInstance()方法创建增强类,否则原样返回。
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
3.ConnectionLogger.newInstance()方法
该方法,通过JDK的动态代理类Proxy创建了一个增强后的对象,并返回。 前面在查询的时候,再调用的Connection对象已经是增强后的对象了。这样就在原有的Connection对象上实现了日志功能。
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/68913.html