Mybatis之拦截器原理(jdk动态代理优化版本)
在介绍Mybatis拦截器代码之前,我们先研究下jdk自带的动态代理及优化
其实动态代理也是一种设计模式...优于静态代理,同时动态代理我知道的有两种,一种是面向接口的jdk的代理,第二种是基于第三方的非面向接口的cglib.
我们现在说的是jdk的动态代理,因为mybatis拦截器也是基于这个实现的。
简单介绍就是建立一个目标类的代理类。在执行目标类的方法前先执行代理类的方法,目标类的方法是在代理类中执行的,所以目标类方法执行前后,可以再代理类中进行其他操作。
简单版:
public interface Target {
void work();
}
public class TargetImpl implements Target {
@Override
public void work() {
System.out.println("我就只能做这么多了");
}
}
下面创建一个代理类
public class TargetProxy implements InvocationHandler { private Object target; public TargetProxy(Object target){
this.target = target;
} /**
* 缺点 代理需要做的事情不是很灵活。直接在这里面写死了。
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理在前面做事情了");
try {
return method.invoke(target, args);
} catch (InvocationTargetException e) {
throw e.getCause();
}
} public static Object getProxyObject(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new TargetProxy(target));
}
}
public class Client { public static void main(String[] args) {
Target target = new TargetImpl();
target.work();
System.out.println("-----------------------------");
Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl());
target1.work();
}
}
结果:
我就只能做这么多了
-----------------------------
代理在前面做事情了
我就只能做这么多了
———————————————————————————————————————————————————————————————————————————————
这样是最常见的代理了,但是有个缺点,代理类要做的事情在代理类写死了,要换就得多写一个代理类。那下面我们就把代理的事项单独拿出来。
增加拦截器接口和实现类
public interface Interceptor {
void doOtherThings();
}
public class InterceptorImpl implements Interceptor {
@Override
public void doOtherThings() {
System.out.println("还可以灵活地做其他事情");
}
}
代理类变一下:
public class TargetProxy implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxy(Object target, Interceptor interceptor) {
this.interceptor = interceptor;
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
interceptor.doOtherThings();
return method.invoke(target, args);
} /**
* 获取代理对象的时候顺便把拦截逻辑对象也传过来
*
* @param interceptor
* @return
* @paramarget
*/
public static Object getProxyObject(Object target, Interceptor interceptor) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
} }
public class Client { public static void main(String[] args) {
Target target = new TargetImpl();
target.work();
System.out.println("-----------------------------");
Interceptor interceptor = new InterceptorImpl();
Target target1 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor);
target1.work();
System.out.println("-----------------------------");
Interceptor interceptor1 = new Interceptor() {
@Override
public void doOtherThings() {
System.out.println("换个拦截方式?");
}
};
Target target2 = (Target) TargetProxy.getProxyObject(new TargetImpl(),interceptor1);
target2.work();
}
}
结果:
我就只能做这么多了
-----------------------------
还可以灵活地做其他事情
我就只能做这么多了
-----------------------------
换个拦截方式?
我就只能做这么多了
———————————————————————————————————————————————————————————————————————————————
这样写是不是感觉挺好了。但是你是不是要拦截所有方法的呢?正常场景是不是只需要拦截method中某个某几个方法呢?那这样就把拦截器加个参数,method好了。
public interface Interceptor {
void doOtherThings(Method method, Object[] args);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
interceptor.doOtherThings(method, args);
return method.invoke(target, args);
}
注意:java设计模式中有一个规则就迪米特法则,也叫最少知识法则,意思应该就是一个类知道的越少越好,对一个对象知道的越少越好。
那这样看反正拦截器都需要method还不如让代理类也不知道method好了,method执行需要代理类的target那就把target也传过去。
传了这么多参数,我们可以不可以把这些参数封装成一个对象传过去,暂时就叫Invocation
然后method.invoke执行就需要这三个参数,那么这三个操作就放在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 proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target,args);
} public Object getTarget() {
return target;
} public void setTarget(Object target) {
this.target = target;
} public Method getMethod() {
return method;
} public void setMethod(Method method) {
this.method = method;
} public Object[] getArgs() {
return args;
} public void setArgs(Object[] args) {
this.args = args;
}
}
下面看拦截器怎么实现
public interface Interceptor { public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
public class InterceptorImpl implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
if(invocation.getMethod().getName().equals("work")){
System.out.println("真的假的");
return invocation.proceed();
}else{
return null;
} }
public class TargetProxyTwo implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxyTwo(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(new Invocation(target,method,args));
} public static Object getProxyObj(Object target,Interceptor interceptor){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new TargetProxyTwo(target, interceptor));
}
}
public class Client { public static void main(String[] args) {
Target target = (Target) TargetProxyTwo.getProxyObj(new TargetImpl(),new InterceptorImpl());
target.work();
}
}
结果:
真的假的
我就只能做这么多了
———————————————————————————————————————————————————————————————————————————————
迪米特法则来看,客户端现在需要知道 拦截器,和代理类。 那么能不能把代理类的注册放到拦截器里面呢?可以的。来看下
public interface Interceptor { public Object register(Object target); public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException;
}
public class InterceptorImpl implements Interceptor { @Override
public Object register(Object target) {
return TargetProxyTwo.getProxyObj(target,this);
} @Override
public Object intercept(Invocation invocation) throws InvocationTargetException, IllegalAccessException {
if(invocation.getMethod().getName().equals("work")){
System.out.println("真的假的");
return invocation.proceed();
}else{
return invocation.procedd();
} }
}
public class Client { public static void main(String[] args) { Target target = (Target) new InterceptorImpl().register(new TargetImpl());
target.work();
}
}
这样是不是很完美?
这样写是有问题的
if(invocation.getMethod().getName().equals("work")){
System.out.println("真的假的");
return invocation.proceed();
}else{
return invocation.proceed();
}
把判断方法的逻辑方法拦截方法里面,那么假如十个方法,十个拦截逻辑呢?你是不是要写大一堆if else?这样是美观的。怎么解决呢?注解啊!!!
在拦截器上添加要拦截的方法注解不就好了嘛。
来看代码:
@Retention(RetentionPolicy.RUNTIME)
@java.lang.annotation.Target(ElementType.TYPE)
public @interface MethodName {
public String value();
}
@MethodName("work")
public class InterceptorImpl implements Interceptor
public class TargetProxy implements InvocationHandler { private Object target; private Interceptor interceptor; public TargetProxy(Object target, Interceptor interceptor) {
this.interceptor = interceptor;
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
MethodName methodName = this.interceptor.getClass().getAnnotation(MethodName.class);
if (methodName == null) {
throw new NullPointerException("拦截器注解方法名字为空");
}
String name = methodName.value();
if (name.equals(method.getName())) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} /**
* 获取代理对象的时候顺便把拦截逻辑对象也传过来
*
* @param interceptor
* @return
* @paramarget
*/
public static Object getProxyObject(Object target, Interceptor interceptor) {
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new TargetProxy(target, interceptor));
}
}
怎么样,这样是不是就很完美了。
先预告下: 上面的类对应Mybatis的类:
Invocation,Interceptor完全一样。TargetProxy对应Plugin类。regist方法对应wrap方法
好的下面来看Mybatis的拦截器了
MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
- Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
- ParameterHandler (getParameterObject, setParameters)
- ResultSetHandler (handleResultSets, handleOutputParameters)
- StatementHandler (prepare, parameterize, batch, update, query)
Mybatis的拦截器的使用方式是通过xml配置的
<plugins>
<plugin interceptor="org.format.mybatis.cache.interceptor.ExamplePlugin"></plugin>
</plugins>
通过这样的配置,就有了默认的拦截器:
那么这四种拦截器分别在什么时候添加的?
XMLConfigBuilder
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
String interceptor = child.getStringAttribute("interceptor");
Properties properties = child.getChildrenAsProperties();
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
interceptorInstance.setProperties(properties);
configuration.addInterceptor(interceptorInstance);
}
}
}
Mybatis中有一个拦截器链,典型的责任链模式
那么这四种拦截器分别在什么时候开始执行拦截呢?
先介绍下责任链获取恰当的拦截器的方法,Configuration类中
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
} public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
} public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
} public Executor newExecutor(Transaction transaction) {
return newExecutor(transaction, defaultExecutorType);
} public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) {
//遍历所有的拦截器看那个拦截器适用。plugin方法就是每个拦截器的适配方法
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);
} }
下面开始看那块开始拦截的
1.Executor接口的拦截器:
获取SqlSession的时候,就获取了代理拦截器:
类:DefaultSqlSessionFactory
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2 StatementHandler
处理SQL逻辑等方法
具体类:SimpleExecutor
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);
}
}
3.parameterHandler;resultSetHandler
一个参数的一个结果集的。
都在statementHandler确定后在类:BaseStatementHandler构造器中初始化的
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory(); if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
} this.boundSql = boundSql; 17 this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
18 this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
这下总体应该了解了,这四类接口都是在获取实现类之前,先通过代理获取对象。如果存在拦截器,则执行拦截器的方法,否则直接返回对象本身。
Mybatis好的地方在于他把四类拦截器用一个拦截器链管理了起来。 用责任链模式解决了要单独判断哪类拦截逻辑用什么拦截器的判断逻辑。
Mybatis之拦截器原理(jdk动态代理优化版本)的更多相关文章
- Mybatis Interceptor 拦截器原理 源码分析
Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的),由于插件会深入到Mybatis的核心,因此在编写自己的插件前最 ...
- MyBatis Mapper 接口如何通过JDK动态代理来包装SqlSession 源码分析
我们以往使用ibatis或者mybatis 都是以这种方式调用XML当中定义的CRUD标签来执行SQL 比如这样 <?xml version="1.0" encoding=& ...
- 利用JDK动态代理机制实现简单拦截器
利用JDK动态代理机制实现简单的多层拦截器 首先JDK动态代理是基于接口实现的,所以我们先定义一个接口 public interface Executer { public Object execut ...
- jdk动态代理使用及原理
jdk动态代理的使用 1.创建实现InvocationHandler接口的类,实现invoke(Object proxy, Method method, Object[] args)接口,其中invo ...
- Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理
Java之美[从菜鸟到高手演练]之JDK动态代理的实现及原理 JDK动态代理的实现及原理 作者:二青 邮箱:xtfggef@gmail.com 微博:http://weibo.com/xtfg ...
- JDK动态代理与CGLib动态代理相关问题
导读: 1.JDK动态代理原理是什么?为什么不支持类的代理? 2.JDK动态代理实例 3.CGLib代理原理是什么? 4.CGLib代理实例 5.JDK动态代理与CGLib代理的区别是什么? 6.总结 ...
- 浅谈Spring中JDK动态代理与CGLIB动态代理
前言Spring是Java程序员基本不可能绕开的一个框架,它的核心思想是IOC(控制反转)和AOP(面向切面编程).在Spring中这两个核心思想都是基于设计模式实现的,IOC思想的实现基于工厂模式, ...
- 静态代理、jdk动态代理、cglib动态代理
一.静态代理 Subject:抽象主题角色,抽象主题类可以是抽象类,也可以是接口,是一个最普通的业务类型定义,无特殊要求. RealSubject:具体主题角色,也叫被委托角色.被代理角色.是业务逻辑 ...
- JDK 动态代理与 CGLIB 动态代理,它俩真的不一样
摘要:一文带你搞懂JDK 动态代理与 CGLIB 动态代理 本文分享自华为云社区<一文带你搞懂JDK 动态代理与 CGLIB 动态代理>,作者: Code皮皮虾 . 两者有何区别 1.Jd ...
随机推荐
- 13.mysql基本查询
1. 给表起个别名:但是,前面的也是需要进行修改的,否则会报错的: select * from s.name from students as s; 2. 为字段起别名 select s,name a ...
- sqlserver主从复制
参考网站: http://www.178linux.com/9079 https://www.cnblogs.com/tatsuya/p/5025583.html windows系统环境进行主从复制操 ...
- 【Javascript Demo】遮罩层和百度地图弹出层简单实现
其实想做的就是显示百度地图的弹出层,现在已经简单实现了.示例和代码如下,点击按钮可以看到效果: 1.示例: 2.代码: <!DOCTYPE html PUBLIC "-//W3C/ ...
- DDoS攻防战 (一) : 概述
岁寒 然后知松柏之后凋也 ——论语·子罕 (此图摘自<Web脚本攻击与防御技术核心剖析>一书,作者:郝永清先生) DDoS,即 Distributed Denial of Servi ...
- VisualSVN:允许修改svn提交日志(pre-revpro-change hook)
有时候需要对之前版本提交的错误的日志信息进行修改或者进行补充描述: 1.在windows 7( 64位 )下使用TortoiseSVN客户端,选中代码目录,点击右键,选择<显示日志> 在出 ...
- RabbitMQ Window环境安装
转自:https://www.cnblogs.com/zzpblogs/p/8168763.html RabbitMQ环境的安装分别介绍在Window和Linux下两个环境的安装过程. Windo ...
- myBatis连接MySQL报异常:No operations allowed after connection closed.Connection was implicitly closed
网站运行一个晚上,早上来上班,发现报错: ### Error querying database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLNonTra ...
- Simple2D-18(音乐播放器)使用 bass 音频库
BASS 简介 BASS是一个在多个平台上用于软件的音频库.其目的是为开发人员提供功能强大且高效的示例流(MP3,MP2,MP1,OGG,WAV,AIFF),MOD 音乐(XM,IT,S3M,MOD ...
- 吴裕雄 数据挖掘与分析案例实战(8)——Logistic回归分类模型
import numpy as npimport pandas as pdimport matplotlib.pyplot as plt # 自定义绘制ks曲线的函数def plot_ks(y_tes ...
- 解决:Invalid character found in method name. HTTP method names must be tokens
阿里云上弄了一个tomcat,经常半夜发送崩溃,查看日志发现这个东西,查阅资料发现是Tomcat的header缓冲区大小不够,只需要在server.xml中增加maxHttpHeaderSize字 ...