1. why
为什么要拦截传递给 mapper 文件的参数对象呢?因为要对指定属性设置默认值。如何拦截传递给 mapper 文件的参数对象可以参考《使用(org.apache.ibatis.plugin.Interceptor)拦截器实现全局参数注入》这里我们只贴出处理拦截对象的核心方法。
2. code
既然是核心方法,无关的@Override
方法不再贴出, 参数对象是Map类型的不是重点,这里主要看一下封装对象借助反射机制通过 getter 和 setter 方法获取和设置指定属性值的操作。
public class SchemaInterceptor implements Interceptor {
/**
* mapper.xml 使用SCHEMA时的参数名称
*/
private static final String SCHEMA = "schemaName";
/**
* 设置默认的schema
*/
private String schema = "public";
/**
* 拦截到的动态SQL
*/
private Set<Integer> sourceStorage = new HashSet<>();
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement) args[0];
SqlSource sqlSource = mappedStatement.getSqlSource();
// 只拦截动态SQL
if (sqlSource instanceof DynamicSqlSource) {
// 获取到sqlNode对象
Field field = DynamicSqlSource.class.getDeclaredField("rootSqlNode");
field.setAccessible(true);
SqlNode sqlNode = (SqlNode) field.get(sqlSource);
Object argParameter = args[1];
// 获取传递给 mapper 的参数对象
if (!sourceStorage.contains(sqlSource.hashCode()) && argParameter != null) {
if (argParameter instanceof HashMap) {
// 处理 map 类型的参数
Map<String, Object> argMap = (Map<String, Object>) argParameter;
// 判断是否传递 schemaName或schema 如果已经传递则使用用户传递的值 否则使用默认值
String schemaNameStr = "schemaName", schemaStr = "schema";
if (StringUtils.isEmpty(MapUtils.getString(argMap, schemaNameStr)) && StringUtils.isEmpty(MapUtils.getString(argMap, schemaStr))) {
SqlNode proxyNode = proxyNode(sqlNode);
field.set(sqlSource, proxyNode);
}
} else {
// 处理封装成请求对象类型的参数
Class<?> clz = argParameter.getClass();
// 获取实体类的所有属性
Field[] fields = clz.getDeclaredFields();
for (Field f : fields) {
String fieldName = f.getName();
Class<?> fieldType = f.getType();
// 处理 schemaName 属性
if (SCHEMA.equals(fieldName)) {
// 获取 schemaName 属性的 getter 方法
String methodName = getMethodName(fieldName);
String getName = "get" + methodName;
Method getFieldValue = clz.getMethod(getName);
String val = (String) getFieldValue.invoke(argParameter);
// schemaName 属性值为空则使用默认值
if (StringUtils.isBlank(val)) {
String setName = "set" + methodName;
Method setFieldValue = clz.getMethod(setName, fieldType);
setFieldValue.invoke(argParameter, schema);
}
}
}
}
sourceStorage.add(sqlSource.hashCode());
}
}
return invocation.proceed();
}
/**
* 通过动态代理对象 添加schema参数
*
* @param sqlNode SQL节点
* @return SqlNode
*/
private SqlNode proxyNode(SqlNode sqlNode) {
return (SqlNode) Proxy.newProxyInstance(sqlNode.getClass().getClassLoader(),
new Class[]{SqlNode.class}, new SqlNodeInvocationHandler(sqlNode));
}
private class SqlNodeInvocationHandler implements InvocationHandler {
private SqlNode target;
SqlNodeInvocationHandler(SqlNode target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DynamicContext context = (DynamicContext) args[0];
setSchema(schema);
context.getBindings().put(SCHEMA, schema);
return method.invoke(target, args);
}
}
/**
* 给schema 添加.
*
* @param schema schemaName
*/
private void setSchema(String schema) {
String pointStr = ".";
if (StringUtils.isNotBlank(schema)) {
if (!schema.endsWith(pointStr)) {
schema += pointStr;
}
}
this.schema = schema;
}
/**
* 将首字母变成大写
*
* @param fieldName 字段名称
* @return 首字母大写的字段名称
*/
private static String getMethodName(String fieldName) {
byte[] items = fieldName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}
方法解析
拦截到的参数对象是 Object 类型,可以通过方法名获取其 getter 和 setter 方法,使用获取到的方法执行即可获取或设置指定的属性值:
Object argParameter = args[1];
// 处理封装成请求对象类型的参数
Class<?> clz = argParameter.getClass();
// 获取实体类的所有属性
Field[] fields = clz.getDeclaredFields();
for (Field f : fields) {
// 属性的名称
String fieldName = f.getName();
// 属性的类型
Class<?> fieldType = f.getType();
// 处理 schemaName 属性
if (SCHEMA.equals(fieldName)) {
// 获取 schemaName 属性的 getter 方法
String methodName = getMethodName(fieldName);
// 方法名
String getName = "get" + methodName;
// 获取到的 getter 方法
Method getFieldValue = clz.getMethod("get" + methodName);
// 执行 getter 方法
String val = (String) getFieldValue.invoke(argParameter);
// schemaName 属性值为空则使用默认值
if (StringUtils.isBlank(val)) {
// 方法名
String setName = "set" + methodName;
// 获取 setter 方法 需要传递当前属性的类型
Method setFieldValue = clz.getMethod(setName, fieldType);
// 执行 setter 方法
setFieldValue.invoke(argParameter, schema);
}
}
}
将首字母大写:
/**
* 将首字母变成大写
*
* @param fieldName 字段名称【字段的第一位必须是小写的字母】
* @return 首字母大写的字段名称
*/
private static String getMethodName(String fieldName) {
byte[] items = fieldName.getBytes();
items[0] = (byte) ((char) items[0] - 'a' + 'A');
return new String(items);
}