Java Dynamic Proxy

*前言：Java标准库提供的动态代理（Dynamic Proxy）功能，允许在运行期动态创建一个接口的实例。动态代理实际上是通过JDK在运行期动态创建class字节码并加载的过程。*

# 代理模式
## 关于代理模式
**代理模式**（Proxy pattern）是经典的23种设计模式之一，Java的动态代理就是代理模式。首先来看看什么是代理模式？下面是代理模式的UML图：
![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/9dcf0e0a-6508-154a-2e15-862942f4989d.png)


该模式的参与者：

- Proxy
    - 保存一个引用使得代理可以访问实体。Proxy引用Subject。
    - 控制实体的存取，负责创建和删除它。
    - 提供一个与Subject的接口相同的接口，这样代理可以用来代替实体。
-  Suject
    - 定义RealSubject和Proxy的共用接口，这样就在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject
    - 定义Proxy所代表的的实体。

## 代理模式的例子

首先定义一个代表“可移动”交通工具的接口：

``` java
public interface Moveable {
    /**
     * 移动指定距离, 以kilometer为单位
     * @param km 移动的距离
     */
    void move(Integer km);
}
```
接下来创建一个接口实现类:Metro(地铁)，该类实现了Moveable接口的move方法，并模拟了调用该方法后的运行时间。

```java
public class Metro implements Moveable {
    @Override
    public void move(Integer km) {
        try {
            System.out.println("Metro is moving ...");
            Thread.sleep(km * 1000);  // 模拟移动时间
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
```
然后创建一个可以测试方法性能的PerformanceProxy:

```java
public class PerformanceProxy implements Moveable {
    private Moveable m;
    public PerformanceProxy(Moveable m) {
        this.m = m;
    }
    @Override
    public void move(Integer km) {
        long start = System.currentTimeMillis(); // 记录方法调用开始时间
        m.move(km);                              // 方法调用
        long end = System.currentTimeMillis();   // 方法结束的时间
        System.out.println("耗时: " + (end - start)/1000.0); // 计算耗时
    }
}
```
测试代理模式：

``` java
public static void main(String[] args) {
    new PerformanceProxy(new Metro()).move(5);
}
```

``` 
---- 测试的结果 ----
Metro is moving ...
耗时: 5.005
```
这样一个简单的代理模式的例子就完成了。这个模式的好处是以后需要测试其他的交通工具（如飞机、高铁）的性能的时候，只需要让它们（飞机、高铁）实现Moveable接口，测试的时候将它们的实例传给PerformanceProxy代理即可（每个类不需要自己实现测试代码）。

这样，PerformanceProxy可以检测所有实现Moveable的实体类。如果还想实现其他代理，比如给某方法埋点（跟踪该方法的调用次数）、日志打印等等，只需要创建TrackingProxy、LoggingProxy等代理。当然因为这些代理都是实现的Moveable接口，所以它们可以链式使用:

```java
public static void main(String[] args) {
    Plane plane = new Plane();
    PerformanceProxy pp = new PerformanceProxy(plane);
    LoggingProxy lp = new LoggingProxy(PerformanceProxy);
    lp.move();
}
```

# 静态代理
什么是静态代理？

像上面的例子，先编辑好源代码，自己手动实现代理，再对其编译。这样，在程序运行前,代理类就已经确定，代理类的.class文件（二进制文件）就已经存在了，这就是所谓的静态代理。

# 动态代理
Java标准库提供的动态代理（Dynamic Proxy）功能，允许在运行期动态创建一个接口的实例。动态代理实际上是通过JDK在运行期动态创建class字节码并加载的过程。将上面静态代理的例子用动态代理实现如下：

```java
public static void main(String[] args) {
    final Metro metro = new Metro();
    Moveable m = (Moveable) Proxy.newProxyInstance(Metro.class.getClassLoader(), // 被代理的class loader
            new Class[]{Moveable.class}, // 传入要实现的接口
            new InvocationHandler() {  // 传入处理调用方法的InvocationHandler (匿名类)
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 1
                    long start = System.currentTimeMillis();
                    Object res = method.invoke(metro, args); // 2
                    long end = System.currentTimeMillis();
                    System.out.println("耗时: " + (end - start)/1000.0);
                    return res; // 3
                }
            });
    m.move(5);
}

// 1. invoke中的参数proxy就是动态创建的proxy；method为接口的方法：可根据不同的方法做不同的处理。
// 2. method.invoke(metro, args)等价于这里的metro.move()，这和js中的function.call(obj, param)类似。
// 3. 2中方法的返回值，如果是void方法返回null，如果是基本数据类型则会自动装箱（例如：int会自动转换成Integer）。
```

上面的代码测试可以得到和上面静态代理同样的结果。从动态代理可以看出主要是调用了`Proxy.newProxyInstance(...)`操作以及`InvocationHandler`的`invoke`的方法的实现。要查看JDK运行时做了什么，可以在测试代码上加上以下代码：

```java 
// env: JDK1.8 / IntelliJ IDEA 15 / macOS Majave
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
```
运行之后可以看到IDEA多了个`com`文件夹，动态生成的proxy就在里面:

```
# 动态生成代理的位置
...
|-- com/
| |--sun/
| | |--proxy/
| | | |--$Proxy0.class
...
```

下面是反编译`$Proxy0.class`后的部分代码：可以看出该代理继承于Proxy类并实现了Moveable接口。主要实现了Object类的equals、hashCode和toString基本方法和Moveable的接口方法move。

```java

// ----- $Proxy0.class
public final class $Proxy0 extends Proxy implements Moveable {
    ...
    public $Proxy0(InvocationHandler var1) throws  { super(var1); }
    public final void move(Integer var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1}); // 1 
        } ...
    }

    public final boolean equals(Object var1) throws  { ... }
    public final String toString() throws  { ... }
    public final int hashCode() throws  { ... }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[]{Class.forName("java.lang.Object")});
            m3 = Class.forName("proxy.lizi.Moveable").getMethod("move", new Class[]{Class.forName("java.lang.Integer")});
            ...
        } ...
    }
}

```
上面代码中（1位置）的super.h就是手动编写的InvocationHandler实例，m3可以从后面的代码看出就是move方法。所以调用动态代理的move实际就是调用InvocationHandler实例的invoke方法。下面的时序图简易的描述了上述动态代理的创建及执行过程：
![times.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228534/4abc6719-c031-9eca-93a3-e44a59f5545c.png)

注：由JDK源码可了解到动态代理的实现的方式是：Java的反射机制+[ASM](https://asm.ow2.io/)（一个可操作字节码的框架）。反射机制可动态获取类及其方法，但仅限于获取及调用；ASM是可以直接操作字节码（.class文件），以此来实现动态创建代理类。

# 动态代理的应用例子
## 动态代理的运用之MyBatis 
MyBatis是一款优秀的持久层框架，它支持定制化SQL、存储过程以及高级映射。使用MyBatis时一般只需要写好Mapper.xml配置及接口文件XXXMapper.java，如下：

```xml
<!-- XXXMapper.xml -->
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="tk.mybatis.simple.mapper.UserMapper">
    <select id="selectById" resultMap="userMap">
        SELECT * from sys_user where id = #{id}
    </select>
    <select id="selectAll" resultType="tk.mybatis.simple.model.SysUser"> 
    	select id,
		user_name userName
	from sys_user
    </select>
    ...
</mapper>
```

```java

// ---- UserMapper.java
public interface UserMapper {
	/**
	 * 通过 id 查询用户
	 * 
	 * @param id
	 * @return
	 */
	SysUser selectById(Long id);
	
	/**
	 * 查询所有用户
	 * 
	 * @return
	 */
	List<SysUser> selectAll();
}
```
Mapper接口没有实现类却能被正常调用接口方法，这是因为MyBaits在Mapper接口上使用了动态代理的。基本原理就是通过获取XML文件的配置信息再根据Mapper接口定义实现其接口方法。

## 动态代理运用之Spring
Spring框架的核心是IoC（Inversion of Control）和AOP（Aspect Oriented Programming）。Spring bean的依赖注入（DI：可以通过XML方式和注解方式）以及面向切面的方法织入，实质是使用了cglib的动态代理。cglib（Code Generation Library）的原理是动态生成了一个要代理类的子类，子类重写要代理的类的所有不是final的方法，在子类中采用方法拦截的技术拦截所有父类方法的调用，顺势织入横切逻辑。cglib的底层也是使用了ASM框架。