Mybatis那些事-攔截器(Plugin+Interceptor)
作者:yhjyumi的專欄
Mybatis的攔截器實現機制,使用的是JDK的InvocationHandler.當我們調用ParameterHandler,ResultSetHandler,StatementHandler,Executor的對象的時候,
實際上使用的是Plugin這個代理類的對象,這個類實現了InvocationHandler接口.
接下來我們就知道了,在調用上述被代理類的方法的時候,就會執行Plugin的invoke方法.
Plugin在invoke方法中根據@Intercepts的配置信息(方法名,參數等)動態判斷是否需要攔截該方法.
再然後使用需要攔截的方法Method封裝成Invocation,並調用Interceptor的proceed方法.
例如Executor的執行大概是這樣的流程:
攔截器代理類對象->攔截器->目標方法
Executor->Plugin->Interceptor->Invocation
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke
註解
@Intercepts 在實現Interceptor接口的類聲明,使該類註冊成為攔截器
Signature[] value//定義需要攔截哪些類,哪些方法
@Signature 定義哪些類(4種),方法,參數需要被攔截
String method()//
Class<?>[] args()//
接口
Interceptor 實現攔截器的接口
類
InterceptorChain 攔截器鏈,保存了Mybatis配置的所有攔截器,保存在Configuration
List<Interceptor> interceptors//攔截器
Invocation 類方法的一個封裝,在攔截器中就是被調用的目標方法
Object target//調用的對象
Object[] args//參數
Plugin 插件,其實就是ParameterHandler,ResultSetHandler,StatementHandler,Executor的代理類(Mybatis使用的JDK代理實現攔截器),實現了InvocationHandler接口
Object target//被代理的目標對象
Interceptor interceptor//攔截器
Map<Class<?>, Set<Method>> signatureMap//接口需要攔截的方法(一對多,每個接口對應多個方法)
//
wrap(Object target, Interceptor interceptor)//把攔截器對象封裝成Plugin代理對象.
invoke(Object proxy, Method method, Object[] args)//
使用例子
1.寫一個類,並且實現Interceptor接口
2.在上述類使用@Intercepts註解,配置攔截信息
3.在配置文件配置插件
@Intercepts({ @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class }) })
public class MyInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
// TODO Auto-generated method stub
}
}
配置文件加入
<plugins>
<plugin interceptor="weber.mybatis.plugin.MyInterceptor" />
</plugins>
加入測試代碼
[java] view plain copy
- public static void main(String[] args) throws IOException {
- String resource = "conf.xml";
- InputStream is = Test1.class.getClassLoader().getResourceAsStream(resource);
- SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(is);
- SqlSession session = sessionFactory.openSession();
- // User user = session.selectOne("weber.mybatis.mapper.getUser", 1);
- User userObj = new User();
- userObj.setId(123);
- session.selectList("weber.mybatis.mapper.searchByUser", userObj);
- // UserMapper mapper = session.getMapper(UserMapper.class);
- // mapper.selectAll();
- // System.out.println(user);
- }
開啟debug模式,我們將看到Plugin是如何實現代理的.
當代碼執行到下圖紅色部分的時候
將直接跳到下圖!所以,我們此時的executor不是CachingExecutor對象,而是Plugin代理對象.
此時的method,就是被調用的目標方法如下:
最後附上源碼註釋
[java] view plain copy
- /**
- * @author Clinton Begin
- */
- //Plugin是JDK動態代理類
- public class Plugin implements InvocationHandler {
- private Object target;//目標對象(ParameterHandler,ResultSetHandler,StatementHandler,Executor)
- private Interceptor interceptor;//被代理的攔截器
- //目標類需要攔截的方法緩存.因為一個攔截器可以攔截多個類,一個類可以攔截多個方法.
- //所以用Map + Set的數據結構存儲
- private Map<Class<?>, Set<Method>> signatureMap;//保存每個攔截器的@signature的配置信息
- private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
- this.target = target;
- this.interceptor = interceptor;
- this.signatureMap = signatureMap;
- }
- //把目標對象和攔截器封裝成Plugin代理類實例.
- public static Object wrap(Object target, Interceptor interceptor) {
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);//獲取攔截器的攔截信息(需要攔截的類和方法)
- Class<?> type = target.getClass();
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);//Proxy代理只能代理接口
- if (interfaces.length > 0) {
- return Proxy.newProxyInstance(
- type.getClassLoader(),
- interfaces,
- new Plugin(target, interceptor, signatureMap));//Plugin作為代理類,但是實際業務是由Interceptor攔截器完成的.
- }
- return target;
- }
- @Override
- //proxy,類代理的對象,例如CachingExecutor對象
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {//從這裏代碼看到,會攔截所有的Executor方法,動態的去判斷攔截器要不要去攔截.所以要小心使用攔截器,會影響性能.
- Set<Method> methods = signatureMap.get(method.getDeclaringClass());
- if (methods != null && methods.contains(method)) {
- //Invocation是目標對象,目標對象需要攔截的方法,我攔截方法的參數的封裝.
- return interceptor.intercept(new Invocation(target, method, args));//調用攔截器實現攔截
- }
- return method.invoke(target, args);//不需要攔截的方法直接放行
- } catch (Exception e) {
- throw ExceptionUtil.unwrapThrowable(e);
- }
- }
- private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
- Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);//獲取攔截器註解@Signature
- // issue #251
- if (interceptsAnnotation == null) {
- throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
- }
- Signature[] sigs = interceptsAnnotation.value();//一個Signature表示一個攔截類型
- //保存需要攔截類的信息,class作為key, 需要攔截類的方法作為value集合Set保存.一個攔截器可以攔截一個類中多個方法
- 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);
- }
- try {
- Method method = sig.type().getMethod(sig.method(), sig.args());//獲取需要攔截的方法
- methods.add(method);
- } catch (NoSuchMethodException e) {
- throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
- }
- }
- return signatureMap;
- }
- private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
- Set<Class<?>> interfaces = new HashSet<Class<?>>();
- while (type != null) {
- for (Class<?> c : type.getInterfaces()) {
- if (signatureMap.containsKey(c)) {
- interfaces.add(c);
- }
- }
- type = type.getSuperclass();
- }
- return interfaces.toArray(new Class<?>[interfaces.size()]);
- }
- }
[java] view plain copy
- /**
- * @author Clinton Begin
- */
- //InterceptorChain裏保存了所有的攔截器,它在mybatis初始化的時候創建。存在Configuration中
- public class InterceptorChain {
- private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
- //每一個攔截器對目標類都進行一次代理(也就是會出現代理的代理的代理.....有點拗口)
- public Object pluginAll(Object target) {
- for (Interceptor interceptor : interceptors) {
- target = interceptor.plugin(target);
- }
- return target;
- }
- public void addInterceptor(Interceptor interceptor) {
- interceptors.add(interceptor);
- }
- public List<Interceptor> getInterceptors() {
- return Collections.unmodifiableList(interceptors);
- }
- }
[java] view plain copy
- /**
- * @author Clinton Begin
- */
- public interface Interceptor {
- Object intercept(Invocation invocation) throws Throwable;//攔截方法,在這裏處理攔截器的業務邏輯
- Object plugin(Object target);//把目標對象封裝成Plugin對象
- void setProperties(Properties properties);
- }
Mybatis那些事-攔截器(Plugin+Interceptor)