从Mybatis中看Jdk代理的使用

409 阅读3分钟

jdk动态代理

这个例子是消费者海购,并没有亲去到海外,跟代理商购买即可。 代码很简单


public interface IConsumer {
    void buy(String productName);
}

public class IConsumerImpl implements IConsumer {
    @Override
    public void buy(String productName) {
        System.out.println(productName);
    }
}

// 代理商
public class Agent implements InvocationHandler {

    Object target;

    public Agent(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理商购买前");
        Object invoke = method.invoke(target, args);
        System.out.println("代理商购买后");

        return invoke;
    }
}
// 测试类
public static void main(String[] args) {
        IConsumer consumer = new IConsumerImpl();
        Agent agent = new Agent(consumer);
        System.err.println(agent);
        IConsumer o = (IConsumer) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{IConsumer.class}, agent);
        o.buy("牙刷");

    }

执行运行输出:

mytest.learn.proxy.Agent@7a81197d
代理商购买前
牙刷
代理商购买后

假如在修改测试类代码

		IConsumer consumer = new IConsumerImpl();
        Agent agent = new Agent(consumer);
        System.err.println(agent);
        IConsumer o = (IConsumer) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{IConsumer.class}, agent);
//        o.buy("牙刷");
        o.hashCode();

执行输出:

mytest.learn.proxy.Agent@7a81197d
代理商购买前
代理商购买后

从而得到拦截所有方法了,这个就是jdk代理,用来实现AOP的 Proxy.newProxyInstance会生成一个类Proxy#0,这个类会实现new Class[]数组的所有接口。并且每个调用都类似于

@Override
  public int hashCode() {
    h.invoke(this, m1,(Object)null);
  }

其实就是调用这个代理商(Agent)的方法InvocationHandler#invoke,大体原理就是这样子。

上述例子的类结构是这样子的

UML继承关系
IConsumer o = (IConsumer) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{IConsumer.class}, agent); 接口的实现就是Proxy#0 由Proxy生成的,这个内部类持有Agent的引用,依赖于Agent,o.buy()就是... h.invoke(...) ...

小结:jdk代理四要素:

  1. 编写接口
  2. 编写实现类
  3. 编写代理类
  4. 调用Proxy生成代理类

在Mybatis中代理的使用

  1. 由我们编写的Mapper(接口)
public interface RoleMapper {
    public Role getRole(Long id);
}
  1. mybatis中的org.apache.ibatis.binding.MapperProxy(代理类)
  2. 调用Proxy生成代理类 Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);

只有三个步骤。 但是如果只有这三个步骤,肯定是没办法完成代码功能的。所以还需要我们编写xml文件,也就是sql来完成对应的功能,我们编写好xml之后,mybatis把他们解析成MapperMethod。

  • xml 文件
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mytest.learn.RoleMapper">
    <resultMap type="role" id="roleMap">
        <id column="id" property="id" javaType="long" jdbcType="BIGINT" />
        <result column="role_name" property="roleName" javaType="string"
                jdbcType="VARCHAR" />
        <result column="note" property="note"
                typeHandler="mytest.learn.MyStringHandler" />
    </resultMap>
    <select id="getRole" parameterType="long" resultMap="roleMap">
        select
        id,role_name as roleName,note from role where id=#{id}
    </select>
</mapper>

接下来我们只需要重点关注InvocationHandler是怎么实现的

public class MapperProxy<T> implements InvocationHandler, Serializable {
 @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    ...省略,不重要的无关代码
    //这里优化了,去缓存中找MapperMethod
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    //执行 并没有用传统的反射而是直接调用
    return mapperMethod.execute(sqlSession, args);
  }
}

由此可见,没有RoleMapper接口的实现,只是由MapperProxyFactory

protected T newInstance(MapperProxy<T> mapperProxy) {
    //用JDK自带的动态代理生成映射器
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

生成的Proxy#0类。而且这个类也没有实现具体功能。都需要依赖MapperMethod,MapperMethod才是功能的实现。

总结

通过阅读mybatis源码,可以发现到jdk的代理变通之处,并不是一味的实现接口,通过new 实现类(), 然后把实现类加到代理类中去,在方法调用的时候还需要使用反射来执行实现类的方法。我们经常性的对照着所谓的例子,古板而无聊的进行模仿代码实现,就像我们经常讲设计模式怎么使用,只会去按照书上所说的来做,那肯定是不行的,本来设计模式是一种模式,只要你符合设计模式的规则,那就是对的。所以路还很长,源码路上共勉,加油~