静态代理
静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码 。
例如:
//声明一个接口
package com.ceshi.proxy;
public interface Moveable {
//提供一个move方法
void move();
}
package com.ceshi.proxy;
import java.util.Random;
//写一个实现类实现这个接口
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank Moving...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.ceshi.proxy;
//再写一个tank的时间代理类实现Moveable接口
public class TankTimeProxy implements Moveable {
public TankTimeProxy(Moveable t) {
super();
this.t = t;
}
Moveable t;
@Override
public void move() {
//在执行move方法前后加上有关时间的代码逻辑,以此来实现类似日志管理
long start = System.currentTimeMillis();
System.out.println("starttime:" + start);
t.move();
long end = System.currentTimeMillis();
System.out.println("time:" + (end-start));
}
}
package com.ceshi.proxy;
public class Client {
public static void main(String[] args) throws Exception {
Tank t = new Tank();
TankTimeProxy time = new TankTimeProxy(t);
time.move();
}
}
运行结果为:
starttime:1526992632651
Tank Moving...
time:3604
通过上述例子,我们看到,静态代理需要手动的取创建,并且在以后我们有可会对tank这个类进行日志管理,权限管理,事务管理等等,总之,在他前后添加代码逻辑,并且实现各种代理顺序的调换,那我们岂不是要写很多代理类去实现那个接口。代码太冗余。
动态代理
动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。
接续上面的例子,看看用动态代理怎么实现
//声明一个接口
package com.ceshi.proxy;
public interface Moveable {
//提供一个move方法
void move();
}
package com.ceshi.proxy;
import java.util.Random;
//写一个实现类实现这个接口
public class Tank implements Moveable {
@Override
public void move() {
System.out.println("Tank Moving...");
try {
Thread.sleep(new Random().nextInt(10000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package com.ceshi.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
//写一个事物操作的Handler实现jdk中的InvocationHandler。
public class TransactionHandler implements InvocationHandler {
private Object target;
//写一个构造方法将被代理的对象传进去
public TransactionHandler(Object target) {
super();
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.println("Transaction Start");
try {
method.invoke(target);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("Transaction Commit");
return null;
}
}
package com.ceshi.proxy.test;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import com.ceshi.proxy.TimeHandler;
public class Client {
public static void main(String[] args) throws Exception {
Tank tank = new Tank();
InvocationHandler h = new TransactionHandler(tank );
Moveable m = (Moveable )Proxy.newProxyInstance(Moveable .class.getClassLoader(),new Class[]{Moveable .class},h);
m.move();
}
}
运行结果为:
Transaction Start
Tank Moving...
Transaction Commit
总结
通过上面的动态代理实例我们来仔细分析研究一下动态代理的实现过程
(1)首先我要说的就是接口,为什么JDK的动态代理是基本接口实现的呢?
因为通过使用接口指向实现类的实例的多态实现方式,可以有效的将具体的实现与调用之间解耦,便于后期修改与维护。
再具体的说就是我们在代理类中创建一个私有成员变量(private修饰),使用接口来指向实现类的对象(纯种的多态体现,向上转型的体现),然后在该代理类中的方法中使用这个创建的实例来调用实现类中的相应方法来完成业务逻辑功能。
这么说起来,我之前说的“将具体实现类完全隐藏”就不怎么正确了,可以改成,将具体实现类的细节向调用方完全隐藏(调用方调用的是代理类中的方法,而不是实现类中的方法)。
这就是面向接口编程,利用java的多态特性,实现程序代码的解耦。
(2)创建代理类的过程
如果你了解静态代理,那么你会发现动态代理的实现其实与静态代理类似,都需要创建代理类,但是不同之处也很明显,创建方式不同!
不同之处体现在静态代理我们知根知底,我们知道要对哪个接口、哪个实现类来创建代理类,所以我们在编译前就直接实现与实现类相同的接口,直接在实现的方法中调用实现类中的相应(同名)方法即可;而动态代理不同,我们不知道它什么时候创建,也不知道要创建针对哪个接口、实现类的代理类(因为它是在运行时因需实时创建的)。
虽然二者创建时机不同,创建方式也不相同,但是原理是相同的,不同之处仅仅是:静态代理可以直接编码创建,而动态代理是利用反射机制来抽象出代理类的创建过程。
让我们来分析一下之前的代码来验证一下上面的说辞:
第一点:静态代理需要实现与实现类相同的接口,而动态代理需要实现的是固定的Java提供的内置接口(一种专门提供来创建动态代理的接口)InvocationHandler接口,因为java在接口中提供了一个可以被自动调用的方法invoke,这个之后再说。
第二点:private Object object;
public UserProxy(Object obj){this.object = obj;}
这几行代码与静态代理之中在代理类中定义的接口指向具体实现类的实例的代码异曲同工,通过这个构造器可以创建代理类的实例,创建的同时还能将具体实现类的实例与之绑定(object指的就是实现类的实例,这个实例需要在测试类中创建并作为参数来创建代理类的实例),实现了静态代理类中private Iuser user = new UserImpl();一行代码的作用相近,这里为什么不是相同,而是相近呢,主要就是因为静态代理的那句代码中包含的实现类的实例的创建,而动态代理中实现类的创建需要在测试类中完成,所以此处是相近。
第三点:invoke(Object proxy, Method method, Object[] args)方法,该方法是InvocationHandler接口中定义的唯一方法,该方法在调用指定的具体方法时会自动调用。其参数为:代理实例、调用的方法、方法的参数列表
在这个方法中我们定义了几乎和静态代理相同的内容,仅仅是在方法的调用上不同,不同的原因与之前分析的一样(创建时机的不同,创建的方式的不同,即反射),Method类是反射机制中一个重要的类,用于封装方法,该类中有一个方法那就是invoke(Object object,Object...args)方法,其参数分别表示:所调用方法所属的类的对象和方法的参数列表,这里的参数列表正是从测试类中传递到代理类中的invoke方法三个参数中最后一个参数(调用方法的参数列表)中,在传递到method的invoke方法中的第二个参数中的(此处有点啰嗦)。
第四点:测试类中的异同
静态代理中我们测试类中直接创建代理类的对象,使用代理类的对象来调用其方法即可,若是别的接口(这里指的是别的调用方)要调用Iuser的方法,也可以使用此法
动态代理中要复杂的多,首先我们要将之前提到的实现类的实例创建(补充完整),然后利用这个实例作为参数,调用代理来的带参构造器来创建“代理类实例对象”,这里加引号的原因是因为它并不是真正的代理类的实例对象,而是创建真正代理类实例的一个参数,这个实现了InvocationHandler接口的类严格意义上来说并不是代理类,我们可以将其看作是创建代理类的必备中间环节,这是一个调用处理器,也就是处理方法调用的一个类,不是真正意义上的代理类,可以这么说:创建一个方法调用处理器实例。
下面才是真正的代理类实例的创建,之前创建的”代理类实例对象“仅仅是一个参数
Iuser proxy = (Iuser) Proxy.newProxyInstance(Iuser.class.getClassLoader(), new Class[]{Iuser.class}, h);
这里使用了动态代理所依赖的第二个重要类Proxy,此处使用了其静态方法来创建一个代理实例,其参数分别是:类加载器(可为父类的类加载器)、接口数组、方法调用处理器实例
这里同样使用了多态,使用接口指向代理类的实例,最后会用该实例来进行具体方法的调用即可。
(3)InvocationHandler
InvocationHandler是JDK中提供的专门用于实现基于接口的动态代理的接口,主要用于进行方法调用模块,而代理类和实例的生成需要借助Proxy类完成。
每个代理类的实例的调用处理器都是实现该接口实现的,而且是必备的,即每个动态代理实例的实现都必须拥有实现该接口的调用处理器,也可以这么说,每个动态代理实例都对应一个调用处理器。
这里要区分两个概念,代理类和代理实例,调用处理器是在创建代理实例的时候才与其关联起来的,所以它与代理实例是一一对应的,而不是代理类。
(4)Proxy
Proxy类是JDK提供的用于生成动态代理类和其实例的类。
我们可以通过Proxy中的静态方法getProxyClass来生成代理类,需要的参数为类加载器和接口列表(数组),然后再通过反射调用代理类的构造器来生成代理实例,需要以一个InvocationHandler作为参数(体现出方法调用是与实例相关的,而非类)。
InvocationHandler handler = new MyInvocationHandler(...);
Class<?> proxyClass = Proxy.getProxyClass(Foo.class.getClassLoader(), Foo.class);
Foo f = (Foo) proxyClass.getConstructor(InvocationHandler.class).newInstance(handler);
我们也可以直接通过Proxy中的静态方法newProxyInstance方法来直接生产代理实例,需要提供参数为上面的三个参数,即类加载器,接口数组,InvocationHandler。
Foo f = (Foo) Proxy.newProxyInstance(Foo.class.getClassLoader(),new Class<?>[] { Foo.class },handler);
(5)、总结
我们总结下JDK动态代理的实现步骤:
第一步:创建接口,JDK动态代理基于接口实现,所以接口必不可少(准备工作)
第二步:实现InvocationHandler接口,重写invoke方法(准备工作)
第三步:调用Proxy的静态方法newProxyInstance方法生成代理实例(生成实例时需要提供类加载器,我们可以使用接口类的加载器即可)
第四步:使用新生成的代理实例调用某个方法实现功能。
我们的动态代理实现过程中根本没有涉及到真实类实例。