1、动态代理
- 动态代理和静态代理的角色一样;
- 动态代理的代理类是动态生成的,不是我们写好的的;
- 动态代理分为两大类:
- 基于接口——>JDK动态代理(原生);
- 基于类——>cglib;
- Java字节码实现——>javasist
1.1、JDK动态代理,示例一:
需要了解两个类:
- Proxy(代理);
- InvocationHandler(调用处理程序)。
动态代理的好处:
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的业务;
- 公共的业务交给了代理角色,实现的业务的分工;
- 公共业务发生扩展的时候,方便集中管理;
- 一个动态代理类代理的是一个接口,一般就是对应一类业务;
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。
-
编写抽象角色(接口);
package com.beyond.demo03; //租房 public interface Rent { public void rent(); }
-
编写真实角色(接口实现类);
package com.beyond.demo03; //房东 public class Host implements Rent { public void rent() { System.out.println("房东要出租房子!"); } }
-
编写动态代理程序;
package com.beyond.demo03; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //自动生成代理类 public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Rent rent; public void setRent(Rent rent) { this.rent = rent; } //生成代理类 public Object getProxy(){ /* public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) 返回指定接口的代理实例,该接口将方法调用分派给指定的调用处理程序。 IllegalArgumentException will be thrown if any of the following restrictions is violated: 给定interfaces数组中的所有类对象必须表示接口,而不是类或基元类型。 interfaces数组中没有两个元素可以引用相同的类对象。 所有接口类型必须通过指定的类加载器按名称可见。 换句话说,对于类加载器cl和每个接口i ,以下表达式必须为true: Class.forName(i.getName(), false, cl) == i 指定接口的所有公共方法签名引用的所有类型以及由其超接口继承的类型必须通过指定的类加载器按名称可见。 所有非公共接口必须位于同一个包和模块中,由指定的类加载器定义,非公共接口的模块可以访问所有接口类型; 否则,代理类无法实现所有接口,无论它定义在哪个包中。 对于具有相同签名的指定接口的任何成员方法集: 如果任何方法的返回类型是基本类型或void,则所有方法必须具有相同的返回类型。 否则,其中一个方法必须具有可分配给其余方法的所有返回类型的返回类型。 生成的代理类不得超过虚拟机对类强加的任何限制。 例如,VM可以将类可以实现的接口数量限制为65535; 在这种情况下, interfaces阵列的大小不得超过65535。 请注意,指定代理接口的顺序很重要:对具有相同接口组合但顺序不同的代理类的两个请求将导致两个不同的代理类。 函数参数: loader - 用于定义代理类的类加载器 interfaces - 要实现的代理类的接口列表 h - 调度方法调用的调用处理程序 */ return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this); } //函数功能:处理代理实例上的方法调用并返回结果。在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 /* 函数参数 : proxy - 调用该方法的代理实例 method - 对应于在代理实例上调用的接口方法的方法实例。 方法对象的声明类将是声明方法的接口,它可以是代理接口继承方法的代理接口的超接口。 args - 包含代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数, null 。 原始类型的参数包含在适当的原始包装类的实例中,例如java.lang.Integer或java.lang.Boolean 。 函数结果 : 从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是基本类型,则此方法返回的值必须是相应原始包装类的实例; 否则,它必须是可分配给声明的返回类型的类型。 如果此方法返回的值为null且接口方法的返回类型为原始值, 则代理实例上的方法调用将抛出NullPointerException 。 如果此方法返回的值与上面描述的接口方法声明的返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException 。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现!!! /* public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 在具有指定参数的指定对象上调用此方法对象表示的基础方法。 各个参数自动展开以匹配原始形式参数,并且原始参数和参考参数都根据需要进行方法调用转换。 如果基础方法是静态的,则忽略指定的obj参数。 它可能为空。 如果基础方法所需的形式参数的数量为0,则提供的args数组的长度可以为0或null。 如果底层方法是实例方法,则使用动态方法查找调用它,如Java语言规范第15.12.4.4节中所述; 特别地,可能发生基于目标对象的运行时类型的覆盖。 如果底层方法是静态的,则声明该方法的类如果尚未初始化则初始化。 如果方法正常完成,则返回的值返回给invoke的调用者; 如果值具有基本类型,则首先将其适当地包装在对象中。 但是,如果值具有基本类型数组的类型,则数组的元素不会包装在对象中; 换句话说,返回一个原始类型的数组。 如果底层方法返回类型为void,则调用返回null。 函数参数 : obj - 从中调用基础方法的对象 args - 用于方法调用的参数 */ seeHouse(); Object invoke = method.invoke(rent, args); hetong(); fare(); return invoke; } //代理独有的特点,看房,收中介费,签租赁合同 public void seeHouse(){ System.out.println("中介带你看房"); } public void fare(){ System.out.println("收中介费"); } public void hetong(){ System.out.println("签租赁合同"); } }
-
客户。
package com.beyond.demo03; public class Client { public static void main(String[] args) { //真实角色 Host host=new Host(); ProxyInvocationHandler pih = new ProxyInvocationHandler(); //通过调用程序处理角色来处理我们要调用的接口对象 pih.setRent(host); Rent proxy = (Rent) pih.getProxy();//这里的proxy就是动态生成的, proxy.rent(); } }
1.2、示例二:
-
编写抽象角色(接口);
package com.beyond.demo02; public interface UserService { public void add(); public void delete(); public void update(); public void query(); }
-
编写真实角色(接口实现类);
package com.beyond.demo02; public class UserServiceImpl implements UserService{ public void add() { System.out.println("增加一个用户"); } public void delete() { System.out.println("删除一个用户"); } public void update() { System.out.println("修改一个用户"); } public void query() { System.out.println("查询一个用户"); } }
-
编写动态代理程序;
package com.beyond.demo04; import com.beyond.demo03.Rent; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; //自动生成代理类(万能的) public class ProxyInvocationHandler implements InvocationHandler { //被代理的接口 private Object target; public void setTarget(Object target) { this.target = target; } //生成代理类 public Object getProxy(){ /* public static Object newProxyInstance(ClassLoader loader, 类<?>[] interfaces, InvocationHandler h) 返回指定接口的代理实例,该接口将方法调用分派给指定的调用处理程序。 IllegalArgumentException will be thrown if any of the following restrictions is violated: 给定interfaces数组中的所有类对象必须表示接口,而不是类或基元类型。 interfaces数组中没有两个元素可以引用相同的类对象。 所有接口类型必须通过指定的类加载器按名称可见。 换句话说,对于类加载器cl和每个接口i ,以下表达式必须为true: Class.forName(i.getName(), false, cl) == i 指定接口的所有公共方法签名引用的所有类型以及由其超接口继承的类型必须通过指定的类加载器按名称可见。 所有非公共接口必须位于同一个包和模块中,由指定的类加载器定义,非公共接口的模块可以访问所有接口类型; 否则,代理类无法实现所有接口,无论它定义在哪个包中。 对于具有相同签名的指定接口的任何成员方法集: 如果任何方法的返回类型是基本类型或void,则所有方法必须具有相同的返回类型。 否则,其中一个方法必须具有可分配给其余方法的所有返回类型的返回类型。 生成的代理类不得超过虚拟机对类强加的任何限制。 例如,VM可以将类可以实现的接口数量限制为65535; 在这种情况下, interfaces阵列的大小不得超过65535。 请注意,指定代理接口的顺序很重要:对具有相同接口组合但顺序不同的代理类的两个请求将导致两个不同的代理类。 函数参数: loader - 用于定义代理类的类加载器 interfaces - 要实现的代理类的接口列表 h - 调度方法调用的调用处理程序 */ return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this); } //函数功能:处理代理实例上的方法调用并返回结果。在与其关联的代理实例上调用方法时,将在调用处理程序上调用此方法。 /* 函数参数 : proxy - 调用该方法的代理实例 method - 对应于在代理实例上调用的接口方法的方法实例。 方法对象的声明类将是声明方法的接口,它可以是代理接口继承方法的代理接口的超接口。 args - 包含代理实例上方法调用中传递的参数值的对象数组,如果接口方法不带参数, null 。 原始类型的参数包含在适当的原始包装类的实例中,例如java.lang.Integer或java.lang.Boolean 。 函数结果 : 从代理实例上的方法调用返回的值。 如果接口方法的声明返回类型是基本类型,则此方法返回的值必须是相应原始包装类的实例; 否则,它必须是可分配给声明的返回类型的类型。 如果此方法返回的值为null且接口方法的返回类型为原始值, 则代理实例上的方法调用将抛出NullPointerException 。 如果此方法返回的值与上面描述的接口方法声明的返回类型不兼容,则代理实例上的方法调用将抛出ClassCastException 。 */ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { //动态代理的本质,就是使用反射机制实现!!! /* public Object invoke(Object obj, Object... args) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException 在具有指定参数的指定对象上调用此方法对象表示的基础方法。 各个参数自动展开以匹配原始形式参数,并且原始参数和参考参数都根据需要进行方法调用转换。 如果基础方法是静态的,则忽略指定的obj参数。 它可能为空。 如果基础方法所需的形式参数的数量为0,则提供的args数组的长度可以为0或null。 如果底层方法是实例方法,则使用动态方法查找调用它,如Java语言规范第15.12.4.4节中所述; 特别地,可能发生基于目标对象的运行时类型的覆盖。 如果底层方法是静态的,则声明该方法的类如果尚未初始化则初始化。 如果方法正常完成,则返回的值返回给invoke的调用者; 如果值具有基本类型,则首先将其适当地包装在对象中。 但是,如果值具有基本类型数组的类型,则数组的元素不会包装在对象中; 换句话说,返回一个原始类型的数组。 如果底层方法返回类型为void,则调用返回null。 函数参数 : obj - 从中调用基础方法的对象 args - 用于方法调用的参数 */ log(method.getName()); Object invoke = method.invoke(target, args); return invoke; } //日志方法 public void log(String msg){ System.out.println("使用了"+msg+"方法"); } }
-
客户。
package com.beyond.demo04; import com.beyond.demo02.UserService; import com.beyond.demo02.UserServiceImpl; public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); ProxyInvocationHandler pih = new ProxyInvocationHandler(); //通过调用程序处理角色来处理我们要调用的接口对象 pih.setTarget(userService); UserService proxy = (UserService) pih.getProxy();//这里的proxy就是动态生成的, proxy.add(); } }