一、Mybatis插件的使用
1、我们先来看一下mybatis中怎么声明一个插件
下面代码的是一个对用户信息加解密的插件
@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 CommonInterceptor implements Interceptor {
private static final Logger LOG = LoggerFactory.getLogger(CommonInterceptor.class);
public Object intercept(Invocation invocation) throws Throwable {
String methodName = invocation.getMethod().getName();
Object parameter = invocation.getArgs()[1];//Object.class
MappedStatement statement = (MappedStatement) invocation.getArgs()[0];//MappedStatement.class
try {
if ("query".equals(methodName) && (parameter instanceof BaseEntity)) {
Field[] fields = parameter.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(EncryptAnnotation.class)) {
EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
switch (en.value().getCode()) {
case 0:
String name = f.getName();
String set_method = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
String get_method = "get" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
Method g_method = parameter.getClass().getMethod(get_method, null);
Object value = g_method.invoke(parameter, null);
if (value != null && !StringUtil.isEmpty(value.toString())) {
String encrypt = DesUtil.getUtil().encrypt(value.toString());
Method s_method = parameter.getClass().getMethod(set_method, String.class);
s_method.invoke(parameter, encrypt);
}
break;
case 1:
name = f.getName();
set_method = "set" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
get_method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
g_method = parameter.getClass().getMethod(get_method, null);
value = g_method.invoke(parameter, null);
if (value != null && !StringUtil.isEmpty(value.toString())) {
String encrypt = Des4MysqlUtil.aes_encrypt(value.toString());
Method s_method = parameter.getClass().getMethod(set_method, String.class);
s_method.invoke(parameter, encrypt);
}
break;
default:
break;
}
}
}
}
if ("update".equals(methodName) && (parameter instanceof BaseEntity)) {
Field[] fields = parameter.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(EncryptAnnotation.class)) {
EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
switch (en.value().getCode()) {
case 0:
String name = f.getName();
String set_method = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
String get_method = "get" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
Method g_method = parameter.getClass().getMethod(get_method, null);
Object value = g_method.invoke(parameter, null);
if (value != null) {
String encrypt = DesUtil.getUtil().encrypt(value.toString());
Method s_method = parameter.getClass().getMethod(set_method, String.class);
s_method.invoke(parameter, encrypt);
}
break;
case 1:
name = f.getName();
set_method = "set" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
get_method = "get" + name.substring(0, 1).toUpperCase() + name.substring(1, name.length());
g_method = parameter.getClass().getMethod(get_method, null);
value = g_method.invoke(parameter, null);
if (value != null) {
String encrypt = Des4MysqlUtil.aes_encrypt(value.toString());
Method s_method = parameter.getClass().getMethod(set_method, String.class);
s_method.invoke(parameter, encrypt);
}
break;
default:
break;
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("数据拦截解析失败!");
}
Object returnValue = invocation.proceed();
try {
if (returnValue instanceof ArrayList<?>) {
List<?> list = (ArrayList<?>) returnValue;
for (Object val : list) {
if (val == null) {
continue;
}
Field[] fields = val.getClass().getDeclaredFields();
for (Field f : fields) {
if (f.isAnnotationPresent(EncryptAnnotation.class)) {
EncryptAnnotation en = f.getAnnotation(EncryptAnnotation.class);
switch (en.value().getCode()) {
case 0:
String name = f.getName();
String set_method = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
String get_method = "get" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
Method g_method = val.getClass().getMethod(get_method, null);
Object value = g_method.invoke(val, null);
if (value != null) {
String encrypt = DesUtil.getUtil().decrypt(value.toString());
Method s_method = val.getClass().getMethod(set_method, String.class);
s_method.invoke(val, encrypt);
}
break;
case 1:
name = f.getName();
set_method = "set" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
get_method = "get" + name.substring(0, 1).toUpperCase()
+ name.substring(1, name.length());
g_method = val.getClass().getMethod(get_method, null);
value = g_method.invoke(val, null);
if (value != null) {
String encrypt = Des4MysqlUtil.aes_decrypt(value.toString());
Method s_method = val.getClass().getMethod(set_method, String.class);
s_method.invoke(val, encrypt);
}
break;
default:
break;
}
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
LOG.error("数据拦截解析失败!");
}
return returnValue;
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties arg0) {
}
}
看了一下mybatis插件的简单使用,在类头上我们可以看见有签名注解,里面又有mybatis的四大对象信息;mybatis其实拦截的就是四大对象;什么是mybatis的四大对象:传动门
2、知道了四大对象,那么来看一下四大对象都有哪些方法可以被拦截
其中,
- Executor:是执行SQL的全过程,包括组装参数,组装结果返回和执行SQL过程;
- StatementHandler:最常使用,分页插件的实现就是拦截它里面的prepare;
3、我们在编写插件时候,需要对四大对象中的属性进行读取、修改,但是四大对象提供的public方法很少
这里mybatis提供了一些工具类,供我们使用如:SystemMetaObject、MetaObject
public final class SystemMetaObject {
。。。。。。。。。。。
//这个方法主要用于包装对象;
public static MetaObject forObject(Object object) {
return MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory());
}
。。。。。。。
}
public class MetaObject {
/**
* 该方法主要用户获取对象属性值
* @param name
* @return
*/
public Object getValue(String name) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());
return metaValue == SystemMetaObject.NULL_META_OBJECT ? null : metaValue.getValue(prop.getChildren());
} else {
return this.objectWrapper.get(prop);
}
}
//该方法主要用于改变属性值
public void setValue(String name, Object value) {
PropertyTokenizer prop = new PropertyTokenizer(name);
if (prop.hasNext()) {
MetaObject metaValue = this.metaObjectForProperty(prop.getIndexedName());
if (metaValue == SystemMetaObject.NULL_META_OBJECT) {
if (value == null && prop.getChildren() != null) {
return;
}
metaValue = this.objectWrapper.instantiatePropertyValue(name, prop, this.objectFactory);
}
metaValue.setValue(prop.getChildren(), value);
} else {
this.objectWrapper.set(prop, value);
}
}
}
二、插件的实现原理:
1、在mybatis中要使用插件,必须实现Interceptor接口,它是插件的骨架,也可以说是模板,这里使用的就是模板模式;
看一下Interceptor为我们提供的方法:
public interface Interceptor {
/**
* 它会直接覆盖原有对象的方法,它是插件的核心方法;里面有一个invocation对象,通过它可以反射调用原对象中的方法;
* @param var1
* @return
* @throws Throwable
*/
Object intercept(Invocation var1) throws Throwable;
/**
* var这里是被拦截对象,本方法的作用是给被拦截对象生产一个代理对象,并返回它;
* mybatis提供了一个Plugin类,里面有个静态wrap方法可以生成代理对象;
* @param var1
* @return
*/
Object plugin(Object var1);
/**
* 本方法会在插件初始化时候被调用,然后存入到配置中;
* @param var1
*/
void setProperties(Properties var1);
}
2、插件初始化是在mybatis初始化的时候进行的,由于插件可能会有很多个,这里各个插件被组织成一个责任链;
org.apache.ibatis.builder.xml.XMLConfigBuilder#pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
Iterator var2 = parent.getChildren().iterator();
while(var2.hasNext()) {
XNode child = (XNode)var2.next();
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
this.configuration.addInterceptor(interceptorInstance);
}
}
}
public void addInterceptor(Interceptor interceptor) {
this.interceptorChain.addInterceptor(interceptor);
}
到这里我们可以看到插件被放到了interceptorChain,它主要负责组织责任链;
下面的方法就是责任链的具体实现;
org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
Interceptor interceptor;
for(Iterator var2 = this.interceptors.iterator(); var2.hasNext(); target = interceptor.plugin(target)) {
interceptor = (Interceptor)var2.next();
}
return target;
}
3、下面我们来看一下Plugin到底是何方神圣
public class Plugin implements InvocationHandler {
。。。。。
/**
* 该方法主要用来封装代理对象;
* @param target
* @param interceptor
* @return
*/
public static Object wrap(Object target, Interceptor interceptor) {
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target;
}
/**
* 如果代理对象被调用,就会进入到这里的invoke
* 如果存在签名的拦截方法,插件中的Intercept方法就会在这里被调用,然后可以使用Invocation进行插件逻辑处理;最后通过Invocation中的proceed方法,调用真实对象;
* 否则,直接反射调用执行的方法;
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass());
return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args);
} catch (Exception var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
}
。。。。。。。。。。
}
4、再来简单的看一下Invocation
public class Invocation {
private Object target;
private Method method;
private Object[] args;
public Invocation(Object target, Method method, Object[] args) {
this.target = target;
this.method = method;
this.args = args;
}
public Object getTarget() {
return this.target;
}
public Method getMethod() {
return this.method;
}
public Object[] getArgs() {
return this.args;
}
//在责任链最底层的时候,这里调用的才是真实对象的目标方法;
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return this.method.invoke(this.target, this.args);
}
}
最后再通过一张时序图来看一下调用链路
总结:
1、在mybatis初始化的时候,使用Plugin包装生成代理对象,
2、当发起SQL执行的时候,会先执行Plugin中的invoke,
3、如果存在签名的拦截方法,会执行Interceptor中的interceptor,这里主要进行插件逻辑处理,如果符合预期逻辑,这里可以通过invocation中的proceed调用真实对象中的目标方法(当然这里如果有多个插件,会先调用责任链上层的代理方法);
图片来源:https://www.cnblogs.com/wuzhenzhao/p/11120848.html