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 ...
随机推荐
- 24.OGNL与ValueStack(VS)-集合对象初步
转自:https://wenku.baidu.com/view/84fa86ae360cba1aa911da02.html 首先在LoginAction中增加如下字段并提供相应的get/set方法: ...
- 什么是kafka以及如何搭建kafka集群?
一.Kafka是一种高吞吐量的分布式发布订阅消息系统,它可以处理消费者规模的网站中的所有动作流数据. Kafka场景比喻 接下来我大概比喻下Kafka的使用场景 消息中间件:生产者和消费者 妈妈:生产 ...
- table边框和td的width失效
table元素有一个属性border,可设置table的边框.这个边框对内部元素有效. 不同于style:border,这个仅仅是外边框. table{ width:60%; border-colla ...
- 前端-javascript-BOM-浏览器对象模型
BOM的介绍---浏览器对象模型. 操作浏览器部分功能的API.比如让浏览器自动滚动. -------------------------------------------------------- ...
- Kafka Manager 监控
1.安装: 依赖java环境,须首先安装java运行环境,并正确设置路径. 确保kafka已经安装,且版本合适. 修改配置文件: kafka-manager.zkhosts="你的zoo ...
- memcache命令
Command Description Example get 读取键值 get mykey set 设置新键值 set mykey 0 60 5 add 新增键值 add newkey 0 60 5 ...
- 关于eval()函数处理后台返回的json数据
对于服务器返回的JSON字符串,如果jquery异步请求没做类型说明,或者以字符串方式接受,那么需要做一次对象化处理,方式不是太麻烦,就是将该字符串放于eval()中执行一次.这种方式也适合以普通ja ...
- Haskell语言学习笔记(28)Data.Map
Map Prelude> import Data.Map as Map Prelude Map> :set -XOverloadedLists Prelude Map> Overlo ...
- one by one 项目 part 4
出现异常”The last packet sent successfully to the server was 0 milliseconds ago.“的大部分原因是由于数据库回收了连接,而系统的缓 ...
- mysql 新增数据