java动态代理_从 Spring 集成 MyBatis 到浅析 Java 动态代理

原文作者: 码匠笔记

b944906c28cd93240d691e61a4f64c25.png

前言

因为 MyBatis 的易上手性和可控性,使得它成为了 ORM框架中的首选。近日新起了一个项目,所以重新搭建了一下 Spring-mybatis, 下面是搭建笔记和从 Spring-mybatis源码分析其如何使用 Java动态代理,希望对大家有帮助。

Spring 集成 Mybatis

Spring 集成 Mybatis的方式有很多种,大家耳熟能详的 xml配置方式或者本文的采用的方式:

首先需要添加 MyBatis的和 MyBatis-Spring的依赖,本文使用的 Spring-mybatis版本是1.3.1。在 mvnrepository里面我们可以找到当前 Spring-mybatis依赖的 spring和 mybatis版本,最好是选择匹配的版本以避免处理不必要的兼容性问题。因为 MyBatis-Spring中对 mybatis的依赖选择了 provided模式,所以我们不得不额外添加 mybatis依赖,依赖配置如下。

org.mybatis mybatis-spring 1.3.1org.mybatis mybatis 3.4.1

接下来会我们要创建工厂bean,放置下面的代码在 Spring 的 XML 配置文件中:

这个工厂需要一个 DataSource,就是我们熟知的数据源了。这里我们选择了阿里的 Druid,同样我们需要引入两个配置

mysql mysql-connector-java 5.1.41com.alibaba druid 1.1.2

添加 Spring配置如下

接下来我们要编写数据库访问对象,大多数人会把它叫做 DAO或者 Repository,在这里其被称为

Mapper,也是因为它的实现方式所决定。要注意的是所指定的映射器类必须是一个接口,而不是具体的实现类。这便因为 Mybatis的内部实现使用的是 Java动态代理,而 Java动态代理只支持接口,关于 动态代理我们下文有更详细的描述。

public interface UserMapper { @Select("SELECT * FROM users WHERE id = #{userId}") User getUser(@Param("userId") String userId);} 

接下来可以使用 MapperFactoryBean,像下面这样来把接口加入到 Spring 中,这样就把 UserMapper 和 SessionFactory关联到一起了,原来使用 xml配置的时候还需要Dao继承 SqlSessionDaoSupport才能注入 SessionFactory,这种方式直接通过 Java动态代理把 SqlSessionFactory代理给了 UserMapper,使得我们直接使用 UserMapper即可。配置如下。

这样我们已经完成了90%,就差调用了,前提是你 Spring环境是OK的。调用 MyBatis数据方法现在只需一行代码:

public class FooServiceImpl implements FooService {private UserMapper userMapper;public void setUserMapper(UserMapper userMapper) { this.userMapper = userMapper;}public User doSomeBusinessStuff(String userId) { return this.userMapper.getUser(userId);}

那么问题又来了,每次写一个DAO都需要为其写一个 Bean配置,那不是累死?于是我们又寻找另一种方案,代替手动声明 *Mapper。 MapperScannerConfigurer的出现解决了这个问题, 它会根据你配置的包路径自动的扫描类文件并自动将它们创建成 MapperFactoryBean,可以在 Spring 的配置中添加如下代码:

basePackage属性是让你为映射器接口文件设置基本的包路径。你可以使用分号或逗号作为分隔符设置多于一个的包路径。这个时候如果想自定义 sqlSessionFactory可以添加如下配置:

这样以后还有一点点小瑕疵,如果我们数据的 column名字是 _连接的,那么它不会那么聪明自动转换为驼峰的变量,所以我们需要对 SqlSessionFactoryBean做如下配置,但是在1.3.0以后才可以通过xml配置,如果用早起版本的需要注意了。

至此关于 SpringMyBatis的配置已经全部结束,后面我们会简单说下 SpringMyBatis中的动态代理。

浅析 Java 动态代理

JDK自带的动态代理需要了解InvocationHandler接口和Proxy类,他们都是在java.lang.reflect包下。

InvocationHandler是代理实例的调用处理程序实现的接口。每个代理实例都具有一个关联的 InvocationHandler。对代理实例调用方法时,这个方法会调用 InvocationHandler的 invoke方法。 Proxy提供静态方法用于创建动态代理类和实例,同时后面自动生成的代理类都是 Proxy对象。下面我们直接通过代码来分析 Java动态代理: InvocationInterceptor实现 InvocationHandler接口,用于处理具体的代理逻辑。

/** * Created by codedrinker on 12/10/2017. */public class InvocationInterceptor implements InvocationHandler { private Object target; public InvocationInterceptor(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before user create"); method.invoke(target, args); System.out.println("end user create"); return null; }}

User和 UserImpl是被代理对象的接口和类

/** * Created by codedrinker on 12/10/2017. */public interface User { void create();}
/** * Created by codedrinker on 12/10/2017. */public class UserImpl implements User { @Override public void create() { System.out.println("create user"); }}

DynamicProxyTest是测试类,用于创建 InvocationInterceptor和 Proxy类以便测试。

/** * Created by codedrinker on 12/10/2017. */public class DynamicProxyTest { public static void main(String[] args) { User target = new UserImpl(); InvocationInterceptor invocationInterceptor = new InvocationInterceptor(target); User proxyInstance = (User) Proxy.newProxyInstance(UserImpl.class.getClassLoader(), UserImpl.class.getInterfaces(), invocationInterceptor); proxyInstance.create(); }}

输入结果如下:

before user createcreate userend user create

很明显,我们通过proxyInstance这个代理类进行方法调用的时候,会在方法调用前后进行输出打印,这样就简单的实现了一个 Java动态代理例子。动态代理不仅仅是打印输出这么简单,我们可以通过它打印日志,打开关闭事务, 权限检查了等等。当然它更是许多框架的钟爱,就如下文我们要说的 MyBatis中 Java动态代理的实现。再多说一句 Spring的 AOP也是使用动态代理实现的,当然它同时使用了 Java动态代理和 CGLib两种方式。不过 CGLIB不是本文要讨论的范围。

注意观察的同学看到上面代码的时候可能发现 invoke方法的 proxy参数并没有被使用,笔者查阅了一些相关文档也没有找到合理的说法,只能在源码中看看究竟喽,笔者当前的JDK版本是1.8。我们从入口开始, Proxy.newProxyInstance:

/* * Look up or generate the designated proxy class. */@CallerSensitivepublic static Object newProxyInstance(ClassLoader loader, Class>[] interfaces, InvocationHandler h) throws IllegalArgumentException{ Class> cl = getProxyClass0(loader, intfs);}

如上代码由此可见,它调用了 getProxyClass0来获取 ProxyClass,那我们继续往下看。

private static Class> getProxyClass0(ClassLoader loader, Class>... interfaces) { if (interfaces.length > 65535) { throw new IllegalArgumentException("interface limit exceeded"); } //If the proxy class defined by the given loader implementing //the given interfaces exists, this will simply return the cached copy; //otherwise, it will create the proxy class via the ProxyClassFactory return proxyClassCache.get(loader, interfaces);}

其实上面写的已经很简单了,如果存在就在 proxyClassCache里面获取到,如果不存在就使用 ProxyClassFactory创建一个。当然我们如果看一下 proxyClassCache变量的话其也是 ProxyClassFactory对象。

 private static final WeakCache[], Class>> proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

那么我们直接就去查看 ProxyClassFactory的实现问题不就解决了吗?

 private static final class ProxyClassFactory implements BiFunction[], Class>> { // prefix for all proxy class names private static final String proxyClassNamePrefix = "$Proxy"; //next number to use for generation of unique proxy class names private static final AtomicLong nextUniqueNumber = new AtomicLong(); @Override public Class> apply(ClassLoader loader, Class>[] interfaces) { String proxyName = proxyPkg + proxyClassNamePrefix + num; /* * Generate the specified proxy class. */ byte[] proxyClassFile = ProxyGenerator.generateProxyClass( proxyName, interfaces, accessFlags); } }

由上代码便一目了然了,为什么我们 Debug的时候 Proxy对象是 $Proxy0,是因为他通过 $Proxy和 AtomicLong拼起来的类名,其实这不是重点。重点是 ProxyGenerator.generateProxyClass(proxyName,interfaces,accessFlags)。这就是生成 class的地方,它把所有的条件组合好,生成 class文件,然后再加载到内存里面以供使用。有兴趣的同学可以继续往深处查看。而我们需要做的是获取到他生成的字节码,看一下里面到底是什么?当 saveGeneratedFiles为 true的时候会保存 class文件,所以我们在 DynamicProxyTest的 main函数添加一行即可:

System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值