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,大体原理就是这样子。
上述例子的类结构是这样子的

IConsumer o = (IConsumer) Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{IConsumer.class}, agent); 接口的实现就是Proxy#0 由Proxy生成的,这个内部类持有Agent的引用,依赖于Agent,o.buy()就是... h.invoke(...) ...
小结:jdk代理四要素:
- 编写接口
- 编写实现类
- 编写代理类
- 调用Proxy生成代理类
在Mybatis中代理的使用
- 由我们编写的Mapper(接口)
public interface RoleMapper {
public Role getRole(Long id);
}
- mybatis中的org.apache.ibatis.binding.MapperProxy(代理类)
- 调用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 实现类(), 然后把实现类加到代理类中去,在方法调用的时候还需要使用反射来执行实现类的方法。我们经常性的对照着所谓的例子,古板而无聊的进行模仿代码实现,就像我们经常讲设计模式怎么使用,只会去按照书上所说的来做,那肯定是不行的,本来设计模式是一种模式,只要你符合设计模式的规则,那就是对的。所以路还很长,源码路上共勉,加油~