基本概念
代理模式,和我们在生活中所理解的”代理“思想并没有太大的区别。代理模式的实现需要我们设计一个代理类,在不直接访问被代理对象的前提下,通过代理类实现对被代理对象的间接访问,与此同时对被代理对象的功能进行加强。代理模式主要可分为静态代理和动态代理,下面通过实际案例进行分析。
被代理类的定义
为了避免重复粘贴不需要修改的代码,这里先贴出被代理类的接口和实现类。因为代理模式本身并不需要修改被代理类的接口和实现类(代理模式某种程度上也可以看作在不对原代码进行修改的情况进行功能的加强,或者装饰)
Animal接口
public interface Animal {
void run();
void eat();
}
Dog类实现Animal接口
public class Dog implements Animal {
@Override
public void run() {
System.out.println("小狗跑步");
}
@Override
public void eat() {
System.out.println("小狗吃shit");
}
}
静态代理
静态代理之所以称之为”静态“,是因为在编译时就已经将接口、被代理类、代理类等确定下来。在程序运行之前,代理类的.class文件就已经生成。静态代理和装饰器模式的实现比较类似,两者基本上只是存在语义或者概念上的区别,写法上趋于一致。故不对静态代理思想作过多解释,不理解的请看本人关于装饰器模式的文章:通俗易懂说java设计模式-装饰器模式_BlackCarComing的博客-优快云博客
代理类 DogProxy
public class DogProxy implements Animal {
Animal animal;
public DogProxy(Animal animal){
this.animal=animal;
}
@Override
public void run() {
System.out.print("发动技能-氮气加速:");
animal.run();
System.out.println("技能释放完成");
}
@Override
public void eat() {
System.out.print("发动技能-狼吞虎咽:");
animal.eat();
System.out.println("技能释放完成");
}
}
到这里,静态代理的定义完成,接下来进行测试。在适用代理类时,和装饰器模式类似地,我们将使用代理类创建对象来使用,DogProxy实现了Animal接口,本质上也是个Animal。
public class Test {
public static void main(String args[]){
Dog dog =new Dog();
DogProxy dogProxy=new DogProxy(dog);
dogProxy.eat();
dogProxy.run();
}
}
运行结果
发动技能-狼吞虎咽:小狗吃shit
技能释放完成
发动技能-氮气加速:小狗跑步
技能释放完成
动态代理
在程序运行时才加载代理类的代理方式称为动态代理。静态代理中,代理类是自己定义好的,在程序运行之前就已经编译完成。而动态代理中的代理类是在运行时根据我们在Java代码中的“指示”动态生成的。
实现InvocationHandler的代理器
public class AnimalHandler implements InvocationHandler {
private Animal animal;
public AnimalHandler(Animal animal){
this.animal=animal;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.print("发动技能-");
switch (method.getName()){
case "run":{
System.out.print("氮气加速:");
break;
}
case "eat":{
System.out.print("狼吞虎咽:");
break;
}
}
Object invoke=method.invoke(animal,args);
System.out.println("技能释放完成");
return invoke;
}
}
上述代码可理解成代理器,定义代理动作发生时的行为。实际上,invoke方法就是我们调用被代理对象的方法时真正执行的方法,不管是调用run方法还是eat方法,在动态代理模式下,最后都是执行这个invoke方法。但是调用run方法时,invoke方法接收到的method就会是名为“run”的Method对象(此处涉及反射的基础知识)。其中,而method.invoke(animal,args)语句执行的就是原animal对象的run方法,而其它语句则是我们代理过程的其它动作。
调用动态代理的过程
public class Test {
public static void main(String[] args) {
Dog dog=new Dog();
Animal o = (Animal) Proxy.newProxyInstance(Dog.class.getClassLoader()
,new Class[]{Animal.class}
,new AnimalHandler(dog));
o.run();
o.eat();
}
}
运行结果
发动技能-狼吞虎咽:小狗吃shit
技能释放完成
发动技能-氮气加速:小狗跑步
技能释放完成
Proxy 可以理解成“代理生成器”,返回一个代理对象。我们将被代理类的类加载器(Dog.class.getClassLoader())、被代理类实现的接口(new Class[]{Animal.class})和刚刚定义的代理器的对象(new AnimalHandler(dog)) 作为参数传给Proxy,最后生成一个对dog对象的代理(Animal的代理也是Animal),调用o.run方法时,就会执行代理器invoke方法,从而实现对原dog的run方法的代理。
Proxy的实现原理本文不加详谈,但有几点非常容易理解的是,我们已经用dog对象创建了代理器对象并传给proxy来创建proxy对象,该proxy对象中保存有这个代理器,所以该proxy对象可以调用该代理器中定义的invoke方法一点也不意外。而我们传进去的接口会被该proxy实现,从而我们可以像调用Annimal方法一样操作Proxy对象的方法也是自然的。虽然如此,Proxy对Animal中方法的实现就比较不同了,以run方法为例,调用Proxy对象的run方法时,实际上并不会调用dog的run方法,而是将run方法Method对象作为参数(涉及反射)来调用代理器的invoke方法。在invoke中反过来用run方法这个Method执行dog对象(涉及反射)。把代理使用过程的具体代码简化后的伪代码大致如下
//主程序调用Proxy.newProxyInstance创建代理对象(同时动态产生代理类)
public class Test {
public static void main(String[] args) {
Animal o = (Animal) Proxy.newProxyInstance(Dog.class.getClassLoader()
,new Class[]{Animal.class}
,new AnimalHandler(new Dog()));
o.run();
}
}
//动态生成的代理类,主程序的o对象就是该代理类对象
public final class $Proxy0 extends Proxy implements Animal{
protected InvocationHandler h;//实际上是父类Proxy所持有,为了方便理解直接写在这里
public $Proxy0(InvocationHandler paramInvocationHandler){
super(paramInvocationHandler);//给代理器h赋值
}
run(){
this.h.invoke(this, m3, null);//调用代理器的invoke方法,m3就是一个run的method对象
//该语句可理解成 Proxy0对象.代理器.invoke(Proxy0对象,run(),run方法的参数列表)
}
}
//代理器中的部分代码
private Animal animal;//代理器对象创建时调用构造方法赋值
public Object invoke(Object proxy, Method method, Object[] args){
//其它代理操作...
Object invoke=method.invoke(animal,args);//相当于 run.invoke(dog,args);,dog的原run方法在此处最终执行
//其它代理操作...
return invoke;
}
调用proxy对象的run方法时,method.invoke(animal,args)语句执行的就是原dog对象的run方法,更具体的原理有可查阅其它资料。
动态代理的优势
动态代理相比静态代理的优势之一是可以对被代理类的不同方法进行统一的代理,静态代理则需要重写每一种被代理类的方法,若对不同方法的代理动作类似则可能产生重复代码,业务逻辑也不够清晰(可对比本例的静态代理)。假设一个被代理类有100个方法,使用静态代理则要重写这100个方法(即便我们需要做的仅仅只是对被代理类的方法执行做统一而简单的操作,如统计方法运行时间,添加日志等),动态代理则不需要区分不同方法和重写,只需要在method.invoke前后进行特定操作即可。
假设Animal有100个方法,现在需要在每个方法执行时输出所消耗的时间
public interface Animal {
void run1();
void run2();
void run3();
//...
void run100();
}
则静态代理的代理类如下
public class DogProxy implements Animal {
Animal animal;
public DogProxy(Animal animal){
this.animal=animal;
}
@Override
public void run1() {
long start = System.currentTimeMillis();
animal.run1();
//...
long end = System.currentTimeMillis();
long runTime = (end - start);
System.out.println("run1的执行时间为:"+runTime);
}
@Override
public void run2() {
long start = System.currentTimeMillis();
animal.run2();
//...
long end = System.currentTimeMillis();
long runTime = (end - start);
System.out.println("run2的执行时间为:"+runTime);
}
//...重写run3~run99
@Override
public void run100() {
long start = System.currentTimeMillis();
animal.run100();
//...
long end = System.currentTimeMillis();
long runTime = (end - start);
System.out.println("run100的执行时间为:"+runTime);
}
}
动态代理的实现(只展示代理器)
public class AnimalHandler implements InvocationHandler {
private Animal animal;
public AnimalHandler(Animal animal){
this.animal=animal;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object invoke=method.invoke(animal,args);
long end = System.currentTimeMillis();
System.out.println(method.getName()+"的执行时间为:"+end);
return invoke;
}
}
此外,由于此时不需要对特定方法进行代理,所以该代理器可用于对所有类的代理,实现方法执行时间统计。修改后如下。
public class TimeHandler implements InvocationHandler {
private Object object;
public TimeHandler (Object object){
this.object=object;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
long start = System.currentTimeMillis();
Object invoke=method.invoke(object,args);
long end = System.currentTimeMillis();
System.out.println(method.getName()+"的执行时间为:"+end);
return invoke;
}
}
因此,动态代理因为其动态生成的特性,另一大优势是可以定义对所有类型对象进行代理的通用代理。
总结:
1.动态代理在对被代理对象的不同方法进行代理时,若代理动作是统一的,则相比静态代理可以大大减少代码重复。
2.动态代理可以设计出适用于所有类型对象的代理类型。