JAVA Proxy(代理)机制 — 静态代理、动态代理
1. 什么是代理
生活中,我们大家都知道微商代理,简单地说就是代替厂家卖商品,厂家“委托”代理为其销售商品。关于微商代理,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,“委托者”对我们来说是不可见的;其次,微商代理主要以朋友圈的人为目标客户,这就相当于为厂家做了一次对客户群体的“过滤”。我们把微商代理和厂家进一步抽象,前者可抽象为代理类,后者可抽象为委托类(被代理类)。
我们将此现象称为代理模式。
在Java中同样也存在代理模式。
通过使用代理,通常有两个优点,并且能够分别与我们提到的微商代理的两个特点对应起来:
- 优点一:可以隐藏委托类的实现;
- 优点二:可以实现客户与委托类间的解耦,在不修改委托类代码的情况下能够做一些额外的处理。
在Java中也存在代理模式:为其他对象提供一种代理以控制对这个对象的访问。
根据创建代理类的时间点,代理模式分为两种:
-
静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
-
动态:在程序运行时运用反射机制动态创建而成。
1. 静态代理
1.1 概念
若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的。 通常情况下, 静态代理中的代理类和委托类会实现同一接口或是派生自相同的父类。
1.2 角色
真实角色:被代理的角色
代理角色:代理真实角色,代理真实角色后,一般会做一些附属的操作。
抽象角色:一般会使用抽象类或者接口实现
客户:使用代理角色进行一些操作。
1.3 案例
下面我们以租房子为例,房东委中介来出租房子,中介代替房东将房子出租给客户。若你需要租房子,你会找房屋中介,然后中介带你看房,签合同。
在这个案例中,房东是真实对象,中介是代理,共同的抽象接口是房子要出租,你是客户。
代码实现:
-
抽象角色,共同接口
package org.xiao.pojo; // 共同的接口 public interface Rent { //租房的接口:抽象 void rent(); }
-
真实对象
package org.xiao.pojo; //真实对象 public class Host implements Rent{ //出租房子 public void rent() { System.out.println("房东要出租房子"); } }
-
代理对象
package org.xiao.staticproxy; import org.xiao.pojo.Host; import org.xiao.pojo.Rent; // 静态代理--房屋中介 public class Proxy implements Rent { // 房东 private Host host; public void setHost(Host host){ this.host = host; } public void rent() { lookHouse(); host.rent(); fare(); } public void lookHouse(){ System.out.println("中介带你看房"); } private void fare(){ System.out.println("收取中介费"); } }
-
测试
package org.xiao.test; import org.xiao.pojo.Host; import org.xiao.staticproxy.Proxy; public class StaticproxyTest { public static void main(String[] args) { Host host = new Host(); Proxy proxy = new Proxy(); proxy.setHost(host); proxy.rent(); } }
-
结果:
1.4 静态代理模式的优缺点
优点:
- 可以使真实角色更加纯粹,不用去关注一些公共的事情;
- 公共的业务由代理来完成,实现业务的分工;
- 公共业务的要扩展的话,可以更加集中和方便;
缺点:
- 假如我们的真实角色变得非常多,代理类也会随之增多,工作量变大,开发效率变低!
- 静态代理的局限在于运行前必须编写好代理类
然后我们想需要一种能够有静态代理的全部好处,但是又不存在这种缺点的东西。
2. 动态代理
2.1 概念
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的“指示”动态生成的。
相比于静态代理, 动态代理的优势在于可以很方便的对代理类的函数进行统一的处理,而不用修改每个代理类的函数。
动态代理和静态代理的角色都是一样。
动态代理大概分两类:
-
基于接口实现:JDK反射机制提供的代理
-
基于类实现:cglib
当今用的比较多的是 JAVAssist来生成动态代理。
java动态代理是利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler
来处理。而cglib动态代理是利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
2.2 动态代理相关的两个类
了解动态代理之前,需要掌握两个类:
- InvocationHandler
- Proxy
InvocationHandler是由代理实例的调用处理程序实现的接口 。
我们需要定义一个位于代理类与委托类之间的中介类,这个中介类被要求实现InvocationHandler
接口。
每个代理实例都有一个相关的调用处理程序。当一个方法是在一个代理实例调用,调用的方法进行编码并派遣其调用处理程序的invoke
方法。
invoke(Object proxy, 方法 method, Object[] args)
处理代理实例上的方法调用并返回结果。
Proxy
提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h)
返回指定接口的代理类的实例,该接口将方法调用分派给指定的调用处理程序。
2.3 代码实现:
-
共同接口和真实对象同上
-
动态代理类生成的接口对象
package org.xiao.dynamicproxy; import org.xiao.pojo.Rent; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class InvocationHandlerProxy implements InvocationHandler { private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //动态生成代理类 public Object getProxy(){ return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this); } //proxy:代理类 //method :代理类的调用处理程序的方法 的对象 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { lookHouse(); Object invoke = method.invoke(rent, args); fare(); return invoke; } private void lookHouse() { System.out.println("中介带客户看房子"); } private void fare(){ System.out.println("收取中介费"); } }
-
测试类
package org.xiao.test; import org.xiao.dynamicproxy.InvocationHandlerProxy; import org.xiao.pojo.Host; import org.xiao.pojo.Rent; public class DynamicproxyTest { public static void main(String[] args) { Host host = new Host(); InvocationHandlerProxy proxy = new InvocationHandlerProxy(); proxy.setRent(host); Rent proxy1 = (Rent) proxy.getProxy(); //获取代理对象 proxy1.rent(); } }
-
结果
2.3 动态代理的优点:
- 可以使真实角色更加纯粹,不用去关注一些公共的事情;
- 公共的业务由代理来完成,实现业务的分工;
- 公共业务的要扩展的话,可以更加集中和方便;
- 一个动态代理,一般代理一类的业务,一个动态代理可以代理多个类,代理接口;
2.4 应用
如果我们把对外的接口都通过动态代理来实现,那么所有的函数调用最终都会经过invoke
函数的转发,因此我们就可以在这里做一些自己想做的操作,比如日志系统、事务、拦截器、权限控制等。这也就是AOP(面向切面编程)的基本原理。