坦白讲,动态代理在日常工作中真没怎么用过,也少见别人用过,网上见过不少示例,但总觉与装饰模式差别不大,都是对功能的增强,什么前置后置,其实也就那么回事,至于面试中经常被问的mybatis框架mapper接口这一块,少不了的要扯到动态代理。说起来高深莫测,其实只是在忽略自己,或者也包括面试官吧。不过,在仔细阅读了mybatis这一块的源代码之后,对动态代理的理解稍有加强。

请看下面这个jdk动态代理示例

public interface UserService {
Object sayHello(String name);
}
public class UserServiceImpl implements UserService {
@Override
public Object sayHello(String name) {
System.out.println("sayHello方法被调用。。。");
return "hello " + name;
}
}
public class UserProxy implements InvocationHandler {
private Object target; public UserProxy(Object target) {
this.target = target;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前置增强。。。");
Object result = method.invoke(target, args);
System.out.println("后置增强。。。");
return result;
}
}

测试代码

public class ProxyTest {
public static void main(String[] args) {
UserServiceImpl userServiceImpl = new UserServiceImpl();
UserProxy userProxy = new UserProxy(userServiceImpl);
UserService userService = (UserService) Proxy.newProxyInstance(userServiceImpl.getClass().getClassLoader(),
                       userServiceImpl.getClass().getInterfaces(), userProxy);
Object result = userService.sayHello("admin");
System.out.println("结果:" + result);
}
}

运行结果打印如下:

前置增强。。。
sayHello方法被调用。。。
后置增强。。。
结果:hello admin

类似于这样的示例,网上大把,单单就功能而言,此示例与装饰模式完全一样子,无非就是动态代理使用了invoke()方法,若是装饰模式,应该还会是sayHello()方法而已,

这有个鬼的区别。但是,我们再来看看mybatis框架中,是如何运用动态代理的,看了或许就会有不一样的感受了。。。

mybatis框架中Mapper接口动态代理相关的源码主要有下面两个类

import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap; import org.apache.ibatis.session.SqlSession; /**
* @author Lasse Voss
*/
public class MapperProxyFactory<T> { private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
} public Class<T> getMapperInterface() {
return mapperInterface;
} public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
} @SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
} public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
} }
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Map; import org.apache.ibatis.reflection.ExceptionUtil;
import org.apache.ibatis.session.SqlSession; /**
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class MapperProxy<T> implements InvocationHandler, Serializable { private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache; public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
} @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
} private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
} private Object invokeDefaultMethod(Object proxy, Method method, Object[] args)
throws Throwable {
final Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
.getDeclaredConstructor(Class.class, int.class);
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
final Class<?> declaringClass = method.getDeclaringClass();
return constructor
.newInstance(declaringClass,
MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
| MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC)
.unreflectSpecial(method, declaringClass).bindTo(proxy).invokeWithArguments(args);
}
}

mybatis框架的尿性,创建对象基本都是用工厂,MapperProxyFactory类就是创建MapperProxy的工厂类,而这个MapperProxy就是代替Mapper接口干活的,或许就是因为

这个原因,所以才叫代理模式吧!

Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

Proxy.newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)

接着说下我对上面这个方法的理解,:

该方法就是创建一个代理对象,方法中的有三个参数。

第1个参数: ClassLoader , 类加载器,对于理解代理模式而言,我觉得不重要。

第2个参数:interfaces, 接口类,我的理解就是,它是一个大老板,吩咐狗腿子干活,自个基本不干活。

第3个参数:InvocationHandler ,大老板的狗腿子,代替大老板的人,至于具体如何干活,就得看它的invoke()方法了。

而在mybatis框架中,Mapper接口就是大老板,狗腿子就是MapperProxy, 具体怎么干活就是下面这段代码

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}

看了这点代码,瞬间觉得与最前那个动态代理的例子大不相同,什么前置,后置,这里都没有使用到,或许动态代理的目的,根本就不是为了所谓的前置后置设计的吧(或许只是顺带的吧)。通过前面篇章的源码分析知道,操作数据库时,不会走if(){}else if(){}中的逻辑,直接走后面的逻辑,后面这段逻辑其实就是对Executor对象操作数据库的封装,这里就不会说了。

为什么不进入if逻辑?

 if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
}

通过最前面的示例知道 , invoke会调用method方法的真实现实,于Mapper接口而言,它根本没有实现,所以肯定不会走这段逻辑。 至于else if() ,完全没懂!

总结:

每次想到动态代理,我就想到装饰, 在工作中该如何取舍呢 ? 通过mybatis框架的启发,我觉得,如果接口有实现 ,使用装饰模式对原有功能进行增强便可,

而如果单单只是一个接口,并没有提供实现,这时为了完成接口的功能(接口就是个废人,啥事都干不成),就可以选择动态代理了,毕竟在使用动态代理时,真正干事的

是InvocationHandler#invoke, jdk提供的Proxy类就是将接口与真正干事InvocationHandler绑定关系。

上图县长相当于动态代理中的接口调用者; 大老板就是接口本身; 狗腿子就是InvocationHandler;   狗链子就是Proxy.

最后再用一下示例加深一下理解:

/**
* 大老板
*/
public interface IUserService {
Object sayHello(String name);
}
/**
* 狗腿子
*/
public class UserProxy implements InvocationHandler {
// 这里可以通过构造器传参,然后在invoke方法中执行业务逻辑 @Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是狗腿子。。。。。");
return "hello" + args[0];
} }/**
 * 县长
*/
public class UserProxyTest {
public static void main(String[] args) {
IUserService userService = (IUserService) Proxy.newProxyInstance(IUserService.class.getClassLoader(),
                                new Class[]{IUserService.class}, new UserProxy());
Object result = userService.sayHello("狗腿子");
System.out.println(result);
}
}

打印结果如下:

over! IUserService接口没有实现类,sayHello的具体实现是在UserProxy#invoke()方法中完成,当然,这只是个简单示例,在真正的业务代码中,完全可以通过UserProxy构造器传参数(包括对象), 然后调用这些参数,完成复杂的业务逻辑!

通过此篇文章,应该是可以将装饰与代理的使用场景区分开了!

mybatis框架之动态代理的更多相关文章

  1. JavaWeb_(Mybatis框架)Mapper动态代理开发_三

    系列博文: JavaWeb_(Mybatis框架)JDBC操作数据库和Mybatis框架操作数据库区别_一 传送门 JavaWeb_(Mybatis框架)使用Mybatis对表进行增.删.改.查操作_ ...

  2. mybatis框架中 动态代理的问题

    在配置文件时候 id唯一性 所以不允许重载 <select id=" querydemo"   resultType="pojo"> sql 语句  ...

  3. mybatis 05: mybatis中的动态代理

    mybatis中动态代理存在的意义 图示 图示分析 分层说明:界面层为第一层,业务逻辑层(接口层 + 实现层)为第二层,数据访问层(接口层 + 实现层)为第三层 业务逻辑层和数据访问层:分别分两层来开 ...

  4. JAVA框架 Mybaits 动态代理

    一.动态代理: mybaits给咱们提供一套动态代理,我们只需要按他的要求写接口即可,mybatis帮做动态代理,相当于咱们写的接口的实现类.底层通过反射实例化代理对象,通过代理对象调用相应的方法, ...

  5. java框架之MyBatis(1)-入门&动态代理开发

    前言 学MyBatis的原因 1.目前最主流的持久层框架为 Hibernate 与 MyBatis,而且国内公司目前使用 Mybatis 的要比 Hibernate 要多. 2.Hibernate 学 ...

  6. MyBatis笔记——Mapper动态代理

    前提概要 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同上边Dao接口实现类方法. Mappe ...

  7. 【Mybatis】-- Mapper动态代理开发注意事项

    1.1. Mapper动态代理方式 1.1.1. 开发规范 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对 ...

  8. Mybatis之Mapper动态代理

    一.什么是Mapper的动态代理 采用Mapper动态代理方法只需要编写相应的Mapper接口(相当于Dao接口),那么Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同Dao接 ...

  9. MyBatis使用Mapper动态代理开发Dao层

    开发规范 Mapper接口开发方法只需要程序员编写Mapper接口(相当于Dao接口),由Mybatis框架根据接口定义创建接口的动态代理对象,代理对象的方法体同原始Dao接口实现类方法. Mappe ...

随机推荐

  1. iOS 创建.xcworkspace文件

    首先创建一个HelloWorld工程,步骤如下:iOS开发环境搭建 及 编写1个hello world 然后: 1. 终端run $ cd到.xcodeproj同级文件夹 2. 终端run $ pod ...

  2. Entity Framework Code First数据库连接 转载 https://www.cnblogs.com/libingql/p/3351275.html

    Entity Framework Code First数据库连接   1. 安装Entity Framework 使用NuGet安装Entity Framework程序包:工具->库程序包管理器 ...

  3. javascript获取select 的id与值

    javascript获取select 的id与值 <script type="text/javascript"> function showOptionId () { ...

  4. Altium Designer chapter5总结

    PCB设计环境中需要注意的如下: (1)PCB设计步骤:绘制原理图和生成网表—规划电路板—载入网表—元件布局—制定设计规则—布线—后期处理—DRC检查—信号完整性分析—gerbera文件输出 (2)P ...

  5. c++内存布局与c程序的内存布局

    c/c++的内存布局:堆,栈,自由存储区(与堆的区别),全局/静态存储区,常量存储区(字符串常量,const常量) http://www.cnblogs.com/QG-whz/p/5060894.ht ...

  6. virtualenv-windows下排坑

    1. 安装 pip install virtualenv pip install virtualenvwrapper-win    (win下一定要有这个 -win,不然后续 workon,mkvir ...

  7. Convolutional Neural Networks(2):Sparse Interactions, Receptive Field and Parameter Sharing

    Sparse Interactions, Receptive Field and Parameter Sharing是整个CNN深度网络的核心部分,我们用本文来具体分析其原理. 首先我们考虑Feedf ...

  8. linux + eclipse + cdt 报错undefined reference......好麻烦的,这位大牛给出的方法可行,特此MARK!!!!

    http://bbs.csdn.net/topics/390239632 kerosun kerosun 等级: 结帖率:96.92% 楼主 发表于: 2012-10-11 12:00:51   比如 ...

  9. Java thread(4)

    这一块主要是讨论关于进程同步的相关问题,主要是考虑一下的关键字:锁对象.条件对象 -> synchronized wait() notify(). 1.关于锁对象与条件对象: 所对象的定义在ja ...

  10. [洛谷P3261] [JLOI2015]城池攻占(左偏树)

    不得不说,这道题目是真的难,真不愧它的“省选/NOI-”的紫色大火题!!! 花了我晚自习前半节课看题解,写代码,又花了我半节晚自习调代码,真的心态爆炸.基本上改得和题解完全一样了我才过了这道题!真的烦 ...