一、代理模式
- 为什么要学习代理模式?
代理模式是Spring AOP 以及 Spring MVC 的底层!!并且还是 JAVA 的23种设计模式之一!!
-
代理模式的分类:
-
静态代理
-
动态代理
-
-
什么是代理模式:
如果我们出现了诉求,但是因为某些原因我们本身不想或者无法完成这种诉求,我们就需要寻求他人的帮助,由他人来完成诉求,然后我们直接获取成果就可以了。
打个比方:学生毕业之后工作是需要租房子的,但是学生通常找不到想要把房子租出去的房主,房主也不知道那些学生想要租房子。这时候学生和房主便可以找到中介,由中介来完成租房以及找房的这个过程。最终学生和房东只需要交笔中介费便达成了自己的目的。
-
角色分析
-
抽象角色: 比如房东想要把房子租出去这个行为。通常会使用接口或者抽象类来解决。
-
真实角色: 被代理的角色,比如房东。
-
代理角色: 代理真实角色,比如中介。代理真实角色后,我们一般会做一些附属操作,比如看房、签合同等。
-
客户角色: 访问代理对象的人!比如租房子的人。
-
1.1 静态代理模式
-
静态代理模式代码演示
-
抽象角色,房东将房子租出去的诉求
public interface HouseRental { void houseRental(); }
-
真实角色,房东本身,也就是被代理的人
public class Homeowner implements HouseRental{ @Override public void houseRental() { System.out.println("房东请求中介将房子出租出去"); } }
-
代理角色,比如中介,中介代理房东将房子出租出去,并且进行相关的操作
public class Intermediary implements HouseRental{ private Homeowner homeowner; public Intermediary(Homeowner homeowner) { this.homeowner = homeowner; } @Override public void houseRental() { homeowner.houseRental(); seeHouse(); sign(); fee(); } //看房 public void seeHouse(){ System.out.println("中介带着看房子!"); } //签合同 public void sign(){ System.out.println("和中介签署租赁合同!"); } //收费用 public void fee(){ System.out.println("中介收取费用!"); } }
-
客户角色,比如想要租房子的人
public class Client { public static void main(String[] args) { // 租户无需直接面对房东 Homeowner homeowner = new Homeowner(); // 房东找到了中介,请求把房主租出去 Intermediary intermediary = new Intermediary(homeowner); // 租户找到了中介,请求租房子 intermediary.houseRental(); } }
-
代码执行结果:
-
1.2 静态代理模式的好处和缺点
-
好处
-
可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
-
公共角色就交给代理角色!实现了业务的分工!
-
公共业务发生扩展的时候,方便集中管理!
-
-
缺点
- 一个真实角色就会产生一个代理角色,代码量级会翻倍,开发效率会降低。
1.3 静态代理模式理解加深
-
业务场景:公司需要对用户进行增删改查操作
-
接口
public interface UserService { // 业务场景:公司需要对人员进行增删改查操作 void add(); void update(); void delete(); void query(); }
-
真实角色
public class UserServiceImpl implements UserService{ @Override public void add() { System.out.println("增加了一个用户"); } @Override public void update() { System.out.println("修改了一个用户"); } @Override public void delete() { System.out.println("删除了一个用户"); } @Override public void query() { System.out.println("查询用户信息"); } }
-
客户端角色访问
public class Client { public static void main(String[] args) { UserServiceImpl userService = new UserServiceImpl(); userService.update(); } }
-
执行结果:修改了一个用户
-
-
业务变更:公司需要对用户进行增删改查操作的同时需要将对应的操作行为以日志的形式打印出来
-
如果在操作的同时将日志打印出来的话其实也可以,但是会有大量的代码重复操作,并且代码原则通常是不修改原本好用的代码,如果有变动一般以新增代码为主
-
接口以及真实角色实现类内容不变,创建代理角色
public class Proxy implements UserService{ // 将真实角色进行代理 private UserServiceImpl userService; // 通过 Set 方法完成代理动作 public void setUserService(UserServiceImpl userService) { this.userService = userService; } public void add() { log("add"); userService.add(); } public void delete() { log("delete"); userService.delete(); } public void update() { log("update"); userService.update(); } public void query() { log("query"); userService.query(); } // 打印日志 public void log(String mag){ System.out.println("执行了" + mag +"方法"); } }
-
客户端调用代理角色
public class Client { public static void main(String[] args) { // 客户端调用真实角色 UserServiceImpl userService = new UserServiceImpl(); // 真实角色将部分内容交给代理角色处理 Proxy proxy = new Proxy(); proxy.setUserService(userService); proxy.add(); proxy.delete(); proxy.update(); proxy.query(); } }
-
执行结果
-
-
1.4 动态代理模式
-
动态代理和静态代理的区别
-
相同点:动态代理和静态代理的角色信息是一样的
-
不同点:静态代理的代理角色需要我们手动去维护,而动态代理的代理角色是动态生成的,不需要手动维护
-
-
动态代理的实现方式主要分为两大类: 基于接口实现动态代理,基于类实现动态代理
-
基于接口实现:JDK动态代理:基于JDK的动态代理就需要知道下面的两个类:
-
1.InvocationHandler(接口):调用处理程序。
- 实现 InvocationHandler 接口,实现对应的 public Object invoke(Object proxy, Method method, Object[] args) 方法。
-
2.Proxy(类):生成代理角色
- 方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h);
- 方法的参数:
- ClassLoader loader:表示类在具体的那个位置
- Class<?>[] interfaces:表示被代理的接口是那个
- InvocationHandler h:表示需要传入一个 InvocationHandler 或者实现 InvocationHandler 的对象
- 方法的参数:
- 方法:public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h);
-
-
基于类实现:Java 类:cglib
-
Java 字节码实现:Javasist
-
-
动态代理的底层,是通过反射机制来实现的
基于接口实现:JDK动态代理:基于JDK的动态代理就需要了解两个类:Proxy(类):代理;InvocationHandler(接口):调用处理程序。
-
实现动态代理的代码示例
-
真实角色
// 真实角色 public class Homeowner implements Rent{ @Override public void rent() { System.out.println("房东想将房子出租"); } }
-
抽象角色
public interface Rent { // 租房子的动作 void rent(); }
-
实现 InvocationHandler 接口,创建动态代理工具类(不在创建代理角色,而是根据对应需求自动生成代理角色)
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; } // 生成动态代理类 // 代理接口,通过set方法将传入的Bean注入到被代理的接口属性中,从而达成代理的条件 public Object getProxy(){ // Proxy.newProxyInstance:生成一个代理角色 // this.getClass().getClassLoader(): 表示类在具体的那个位置 // rent.getClass().getInterfaces(): 表示被代理的接口是那个 // this: 调用本类,也就是实现的接口 InvocationHandler 的 ProxyInvocationHandler return Proxy.newProxyInstance(this.getClass().getClassLoader(), rent.getClass().getInterfaces(), this); } // 处理代理示例,并返回相关结果 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 动态代理的本质,就是使用反射机制实现!Method 就是实现反射机制的 类之一 // 代理真实角色,完成真实角色对应的动作 Object result = method.invoke(rent,args); // 中介本身的动作,代理角色的相关行为 seeHose(); fee(); return result; } public void seeHose(){ System.out.println("中介带着看房子!"); } public void fee(){ System.out.println("中介收取费用!"); } }
-
客户角色
public class Client { public static void main(String[] args) { // 真实角色 Homeowner homeowner = new Homeowner(); // 代理角色:目前还未创建 ProxyInvocationHandler pin = new ProxyInvocationHandler(); // 通过调用程序来处理我们要调用的接口对象!(将真实角色传入) pin.setRent(homeowner); // 获取到真实角色之后,动态生成代理角色 Rent proxy = (Rent) pin.getProxy(); // 通过代理角色找到对应接口,通过接口调用真实角色的方法和属性 proxy.rent(); } }
-
执行结果
-
-
可以看出真实角色的动作成功被代理了
1.5 静态代理模式和动态代理模式的区别,以及动态代理模式的好处
-
区别:
-
静态代理有多少个真实角色就需要多少个代理对象
-
动态代理是不管有多少个真实角色,都会去自动生成对应的代理对象,无需手动配置
-
静态代理模式:
相当于:中介对外宣称,我的工作职责就是帮租户找房子,帮房主向外租房子(代理房主这个真实角色(类),吸引租房者来租房,完成这个动作)。 -
动态代理模式:
相当于:中介公司,租客和房主都知道这个公司,去这个公司完成帮租户找房子,帮房主向外租房子(只代理租房找房这个动作(接口),而不是代理具体的房主或者租客(实现了接口具体动作的类)),吸引房主和租客来找中介代理向外租房和找房(等待被用户调用,传入具体的真实角色)
-
-
-
动态代理模式的好处:
- 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
- 公共角色就交给代理角色!实现了业务的分工!
- 公共业务发生扩展的时候,方便集中管理!
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!