【深入理解MyBatis】- 03Mybatis 從0開始實現Mybatis 插件(plugins)功能

Mybatis 插件(plugins)簡介

Mybatis 插件是個攔截器,本質上是JDK動態代理的封裝,返回了代理對象,起到了攔截器作用,接下來將按照從0開始實現Mybatis 插件(plugins)功能,再使用一下Mybatis插件應用一下,這裏實現並不是與Mybatis Plugin代碼一模一樣,有一些簡化和優化,更加方便理解

簡單版攔截器功能 v0.1

定義攔截器

public interface Interceptor {
	 Object intercept(Object target, Method method, Object[] args) throws Throwable;
}

通用動態代理實現

public class Plugin implements InvocationHandler {
	
	private final Object target;
	private final Interceptor interceptor;
	
	public Plugin(Object target, Interceptor interceptor) {
		this.target = target;
		this.interceptor = interceptor;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		return interceptor.intercept(target, method, args);
	}
}

封裝生成代理類工具類

public class PluginUtils {
	public static <T> T wrap(T target, Interceptor interceptor) {
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				new Plugin(target, interceptor));
	}
}

編寫業務測試類

public interface IPerson {
	String sayHello(String name);
	void test();
}

public class PersonImpl implements IPerson {
	@Override
	public String sayHello(String name) {
		String msg = "hello, " + name;
		System.out.println(msg);
		return msg;
	}
	
	@Override
	public void test() {
		System.out.println("test");
	}
}

測試類

實現自定義攔截器

public class MyInterceptor implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("before.");
		Object invoke = method.invoke(target, args);
		System.out.println("after.");
		return invoke;
	}
}

測試類

public class MyTest {
	public static void main(String[] args) {
		Interceptor interceptor = new MyInterceptor();
		IPerson person = new PersonImpl();
		IPerson personProxy = PluginUtils.wrap(person, interceptor);
		String message = personProxy.sayHello("zhangsan");
		System.out.println("return: " + message);
	}
}

測試結果,攔截成功

before.
hello, zhangsan
after.
return: hello, zhangsan

實現註解功能 v0.2

攔截器註解@Intercepts

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
	Signature[] value();
}

標記攔截哪些類註解@Signature

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
	Class<?> type();

	SignatureMethod[] method();
}

標記攔截哪些方法註解@SignatureMethod

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface SignatureMethod {
	String method();

	Class<?>[] args();
}

優化代理生成工具類,添加解析註解信息並傳入給Plugin類

public class PluginUtils {
	public static <T> T wrap(T target, Interceptor interceptor) {
		Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
		return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
				new Plugin(target, interceptor, signatureMap));
	}
	
	// 解析註解,獲取對應的類和方法
	private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
		Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
		if (interceptsAnnotation == null) {
			throw new RuntimeException(
					"No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
		}
		
		Signature[] sigs = interceptsAnnotation.value();
		Map<Class<?>, Set<Method>> signatureMap = new HashMap<Class<?>, Set<Method>>();
		for (Signature sig : sigs) {
			Set<Method> methods = signatureMap.get(sig.type());
			if (methods == null) {
				methods = new HashSet<Method>();
				signatureMap.put(sig.type(), methods);
			}

			SignatureMethod[] signatureMethod = sig.method();
			for (int i = 0; i < signatureMethod.length; i++) {
				try {
					Method method = sig.type().getMethod(signatureMethod[i].method(), signatureMethod[i].args());
					methods.add(method);
				} catch (NoSuchMethodException e) {
					throw new RuntimeException(
							"Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
				}
			}
		}
		return signatureMap;
	}
}

優化自定義攔截器,對方法進行控制,只攔截註解對應的方法

public class Plugin implements InvocationHandler {
	private final Object target;
	private final Interceptor interceptor;
	private final Map<Class<?>, Set<Method>> signatureMap;

	public Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
		this.target = target;
		this.interceptor = interceptor;
		this.signatureMap = signatureMap;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Set<Method> methods = signatureMap.get(method.getDeclaringClass());
		if (methods != null && methods.contains(method)) {
			return interceptor.intercept(target, method, args);
		}
		return method.invoke(target, args);
	}
}

測試類

實現自定義攔截器

@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }) }) })
public class MyInterceptor3 implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("3333.");
		Object invoke = method.invoke(target, args);
		return invoke;
	}
}

測試類

public class MyTest {
	public static void main(String[] args) {
		Interceptor interceptor = new MyInterceptor3();
		IPerson person = new PersonImpl();
		IPerson personProxy = PluginUtils.wrap(person, interceptor);
		String message = personProxy.sayHello("zhangsan");
		System.out.println("return: " + message);
	}
}

測試結果,攔截成功

3333.
hello, zhangsan
return: hello, zhangsan

修改攔截器自定義攔截器,改爲@SignatureMethod(method = "toString", args = {})

@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "test", args = {}) }) })
public class MyInterceptor3 implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("3333.");
		Object invoke = method.invoke(target, args);
		return invoke;
	}
}

執行測試類

public class MyTest3 {

	public static void main(String[] args) {
		Interceptor interceptor = new MyInterceptor3();
		IPerson person = new PersonImpl();
		IPerson personProxy = PluginUtils.wrap(person, interceptor);
		String message = personProxy.sayHello("zhangsan");
		System.out.println("return: " + message);
		personProxy.test();
	}
}

測試結果,可以看出,只代理了test方法,沒有代理sayHello

hello, zhangsan
return: hello, zhangsan
3333.
test

若想代理,需要修改註解,再次執行測試類即可

@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }),
				@SignatureMethod(method = "test", args = { }) }) })

攔截器鏈實現 v0.3

鏈式封裝

public class InterceptorChain {

	private final List<Interceptor> interceptors = new ArrayList<>();

	public Object wrapAll(Object target) {
		for (Interceptor interceptor : interceptors) {
			target = PluginUtils.wrap(target, interceptor);
		}
		return target;
	}

	public void addInterceptor(Interceptor interceptor) {
		interceptors.add(interceptor);
	}
}

添加實現自定義攔截器

@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }) }) })
public class MyInterceptor3 implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("3333.");
		Object invoke = method.invoke(target, args);
		return invoke;
	}
}
@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }) }) })
public class MyInterceptor4 implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("4444.");
		Object invoke = method.invoke(target, args);
		return invoke;
	}
}
@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }) }) })
public class MyInterceptor5 implements Interceptor {
	@Override
	public Object intercept(Object target, Method method, Object[] args) throws Throwable {
		System.out.println("5555.");
		Object invoke = method.invoke(target, args);
		return invoke;
	}
}

測試類

public class MyTest2 {
	public static void main(String[] args) {
		InterceptorChain chain = new InterceptorChain();
		chain.addInterceptor(new MyInterceptor3());
		chain.addInterceptor(new MyInterceptor4());
		chain.addInterceptor(new MyInterceptor5());
		IPerson person = new PersonImpl();
		IPerson personProxy = (IPerson) chain.wrapAll(person);
		String message = personProxy.sayHello("zhangsan");
		System.out.println("return: " + message);
	}
}

測試結果,多攔截器效果

5555.
4444.
3333.
hello, zhangsan
return: hello, zhangsan

可以思考一下爲什麼倒序輸出5555. 4444. 3333. ?

攔截器鏈實現 v0.4

封裝一下攔截器Interceptor的參數

public class Invocation {

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
  }

}

修改攔截器Interceptor參數

public interface Interceptor {
	 Object intercept(Invocation invocation) throws Throwable;
}

插件Plugin封裝修改return interceptor.intercept(new Invocation(target, method, args));

public class Plugin implements InvocationHandler {
	private final Object target;
	private final Interceptor interceptor;
	private final Map<Class<?>, Set<Method>> signatureMap;

	public Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
		this.target = target;
		this.interceptor = interceptor;
		this.signatureMap = signatureMap;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		Set<Method> methods = signatureMap.get(method.getDeclaringClass());
		if (methods != null && methods.contains(method)) {
			return interceptor.intercept(new Invocation(target, method, args));
		}
		return method.invoke(target, args);
	}
}

修改自定義攔截器實現Object invoke = invocation.proceed();

@Intercepts({
		@Signature(type = IPerson.class, method = { 
				@SignatureMethod(method = "sayHello", args = { String.class }) }) })
public class MyInterceptor3 implements Interceptor {
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		System.out.println("3333.");
		Object invoke = invocation.proceed();
		return invoke;
	}
}

測試類

	public static void main(String[] args) {
		Interceptor interceptor = new MyInterceptor3();
		IPerson person = new PersonImpl();
		IPerson personProxy = PluginUtils.wrap(person, interceptor);
		String message = personProxy.sayHello("zhangsan");
		System.out.println("return: " + message);
	}
}

測試類

3333.
hello, zhangsan
return: hello, zhangsan

接下來分析一下官網的使用

Mybatis官網插件(plugins)分析

MyBatis 允許你在映射語句執行過程中的某一點進行攔截調用。默認情況下,MyBatis 允許使用插件來攔截的方法調用包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

所有我們註冊的類只能是上面的四個接口,ExamplePlugin.java攔截器例子

@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  private Properties properties = new Properties();
  public Object intercept(Invocation invocation) throws Throwable {
    // implement pre processing if need, 攔截前
    Object returnObject = invocation.proceed();
    // implement post processing if need, 攔截後
    return returnObject;
  }
  public void setProperties(Properties properties) {
    this.properties = properties;
  }
}

從例子可以看出使用,enjoy…

完整代碼例子

  1. 執行命令從github上面拉取代碼:git clone [email protected]:dengjili/mybatis-3-mybatis-3.4.6.git
  2. 相關代碼目錄
    src/test/java priv.mybatis.example03
    src/test/java priv.mybatis.example04
    src/test/java priv.mybatis.example05
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章