前言:
小伙伴们,大家好,我是狂奔の蜗牛rz,当然你们可以叫我蜗牛君,我是一个学习Java半年多时间的小菜鸟,同时还有一个伟大的梦想,那就是有朝一日,成为一个优秀的Java架构师。
这个Spring基础学习系列是用来记录我学习Spring框架基础知识的全过程 (这个系列是参照B站狂神的Spring5最新教程来写的,由于是之前整理的,但当时没有发布出来,所以有些地方可能有错误,希望大家能够及时指正!)
之后我将会以一天一更的速度更新这个系列,还没有学习Spring5框架的小伙伴可以参照我的博客学习一下;当然学习过的小伙伴,也可以顺便跟我一起复习一下基础。
最后,希望能够和大家一同进步吧!加油吧!少年们!
废话不多说,让我们开始今天的学习内容吧,今天我们来到了Spring基础学习的第十站:代理模式!
10.代理模式
为什么要学习代理模式?
因为这是Spring AOP的底层实现原理,因此我们要有所了解
代理模式又分为几种?
- 静态代理
- 动态代理
10.1 静态代理
10.1.1 角色分析
- 抽象角色:一般会使用接口或者抽象类来实现 (例如将租房业务抽象成一个接口)
- 真实角色:被代理的角色 (例如租房的房东)
- 代理角色:代理真实角色 (例如帮助房东租房的中介),代理真实角色后,一般会做一些附属操作 (例如带客户看房,签合同,收中介费等)
- 客户端:访问代理对象的人 (来租房的客户)
10.1.2 用静态代理实现租房案例
1.编写租房的接口
// 定义(抽象角色)租房接口
public interface Rent {
// 租房rent()方法
public void rent();
}
2.编写真实角色房东
// 实现租房接口的房东实体类(真实角色)
public class Host implements Rent {
// 重写租房rent()方法
public void rent() {
System.out.println("房东要出租房子!");
}
}
3.编写代理角色中介
// 创建实现租房接口的代理角色(中介)
public class Proxy implements Rent {
private Host host;
// 无参构造器
public Proxy() {
}
// 有参构造方法
public Proxy(Host host) {
this.host = host;
}
// 重写租房rent()方法
public void rent() {
host.rent(); // 租房子
seeHouse(); // 看房子
contract(); // 签合同
fare(); // 收中介费
}
// 看出租房
public void seeHouse() {
System.out.println("中介带你看房子");
}
// 签合同
public void contract() {
System.out.println("签订租赁合同");
}
// 收中介费
public void fare() {
System.out.println("向房东收中介费");
}
}
4.客户端访问代理角色
// 客户实体类
public class Client {
public static void main(String[] args) {
// 房东要租房子
Host host = new Host();
// 进行代理:中介帮房东租房子,但是代理角色一般会有一些附属操作
Proxy proxy = new Proxy(host);
// 客户不用面对房东,直接找中介租房即可
proxy.rent();
}
}
5.测试结果
10.1.3 静态代理的优缺点
1.好处
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的事务 (例如真实角色房东只需租房就可以了)
- 公共事务就交给代理角色处理,实现了业务的分工,使耦合性降低 (看房和签合同等公共事务交由代理角色中介进行处理)
- 公共业务发进行扩展时,方便集中管理 (例如房东有新的业务要拓展,比如收缴水电费,也可以交由代理角色中介处理,无需添加自身业务数量)
2.缺点
- 一个真实角色就意味着会产生一个代理角色,因此代码量会翻倍,开发效率也会变低
10.1.4 静态代理的加深理解
1.编写抽象角色
// 创建用户服务接口(抽象角色)
public interface UserService {
// 定义增删改查方法
public void add();
public void delete();
public void update();
public void query();
}
2.编写真实角色
// 用户服务接口的实现类(真实角色)
public class UserServiceImpl implements UserService {
// OOP有七大原则,这里符合开闭原则:即软件实体对拓展开放,对修改关闭
public void add() {
System.out.println("增加一个用户");
}
public void delete() {
System.out.println("删除一个用户");
}
public void update() {
System.out.println("修改加一个用户");
}
public void query() {
System.out.println("查询一个用户");
}
// 改动原有的业务代码,在公司中是大忌
}
3.编写代理角色
// 实现用户服务接口的代理角色
public class UserServiceProxy implements UserService {
// 引入真实角色
private UserServiceImpl userService;
// 设置用户服务方法
public void setUserService(UserServiceImpl userService) {
this.userService = userService;
}
/**
* 重写用户服务接口的增删改查方法
*/
public void add() {
// 调用日志log()方法
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 msg) {
System.out.println("使用了"+msg+"方法");
}
}
4.模拟客户端访问代理角色
public class Client {
public static void main(String[] args) {
// 实例化真实角色
UserServiceImpl userService = new UserServiceImpl();
// 实例化代理角色
UserServiceProxy proxy = new UserServiceProxy();
// 代理角色代理业务,但同时也会有其他附属操作
proxy.setUserService(userService);
// 客户端直接访问代理角色,无需面对真实角色
proxy.query();
}
}
5.测试结果
6.聊聊AOP
10.2 动态代理
10.2.1 动态代理初步了解
1.动态代理特点
-
动态代理和静态代理的角色一样 (即真实角色,抽象角色,代理角色和客户端)
-
动态代理的代理类是动态生成的,不是提前直接写好的
-
动态代理分为两大类:基于接口的动态代理,基于类的动态代理
1.基于接口:JDK动态代理
2.基于类:cglib
3. java字节码实现:javassist
2.基于接口的动态代理
在实现JDK动态代理之前,我们需要来了解两个类:
一个是Proxy (代理),另一个是InvocationHandler (调用处理程序)
- InvocationHandler:是由代理对象实例用来调用处理程序所实现的一个接口
- Proxy:它是一个提供创建动态代理类和实例的静态方法
10.2.2 动态代理实现租房案例
1.编写抽象角色
// 抽象角色:租房接口
public interface Rent {
// 租房rent()方法
public void rent();
}
2.编写真实角色
// 真实角色房东:其实现了租房接口
public class Host implements Rent {
// 重写租房rent()方法
public void rent() {
System.out.println("房东要出租房子!");
}
}
3.编写动态代理工具类
// 该类能够自动生成代理类,相当于中介所 (因为它并不能直接进行代理,而是负责提供中介)
public class ProxyInvocationHandler implements InvocationHandler {
// 被代理的租房接口类
private Rent rent;
// 使用set方法实现动态实现值的注入
public void setRent(Rent rent) {
this.rent = rent;
}
// 生成得到代理类
public Object getProxy() {
// 返回值为调用代理类的newProxyInstance方法获取代理角色实例
// 其中有三个参数:第一个参数是当前类的类加载器,第二个参数是创建实例的实现类的接口
// 第三个参数是InvocationHandler调用处理程序接口
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
rent.getClass().getInterfaces(),this);
}
// 处理代理实例,进行增强,需要返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制实现
// 前增强:看房子
seeHouse();
// 目标类的方式
Object result = method.invoke(rent,args);
// 后增强:收中介费
fare();
// 返回结果对象
return result;
}
// 前增强方法:看房子
public void seeHouse() {
System.out.println("中介带客户看房子");
}
// 后增强方法:收中介费
public void fare() {
System.out.println("收中介费");
}
}
4.模拟客户端访问代理角色
// 客户端访问代理角色
public class Client {
public static void main(String[] args) {
// 获取真实角色房东
Host host = new Host();
// 获取动态代理工具类(中介所):目前代理角色中介还没有出现
ProxyInvocationHandler pih = new ProxyInvocationHandler();
// 设置代理对象:通过调用动态代理工具类(中介所)的setRent方法来实现代理对象host的注入
pih.setRent(host);
// 动态生成代理角色类proxy(中介):代理角色中介这时才出现
Rent proxy = (Rent) pih.getProxy();
// 当调用代理类的rent方法时,会通过反射机制调用invoke方法来实现方法增强
// 客户端直接访问中介来进行租房
proxy.rent();
}
}
5.测试结果
10.2.3 动态代理的好处
- 可以使真实角色的操作更加纯粹,不用去关注一些公共的事务 (真实角色房东只需关注租房业务即可)
- 公共事务就交给代理角色,实现了业务的分工,使耦合性降低 (看房子,签合同等公共事务,交由代理角色中介进行处理)
- 公共业务发生扩展的时候,方便集中管理 (如果房东要扩展新业务,比如收缴水电费,交给代理角色中介就可以了,无需添加自身业务)
- 一个动态代理类代理的就是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可
10.2.4 动态代理 (单个真实对象)
1.编写抽象角色
// 创建用户服务接口(抽象角色)
public interface UserService {
// 定义增删改查方法
public void add();
public void delete();
public void update();
public void query();
}
2.编写真实角色
// 用户服务接口的实现类(真实角色)
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("查询一个用户");
}
}
3.动态代理工具类
// ProxyInvocationHandler作用是生成动态代理实例,相当于中介所
// InvocationHandler作用是调用处理程序并返回结果
public class ProxyInvocationHandler implements InvocationHandler {
//被代理的接口类
private Object target;
// 使用set方法实现动态值的注入
public void setTarget(Object target) {
this.target = target;
}
// 生成得到代理类
public Object getProxy() {
// 返回值为代理对象实例:由代理对象Proxy调用newProxyInstance方法获得
// 有三个参数:第一个是当前类的类加载器,第二个是创建实例的实现类的接口
// 第三个是InvocationHandler调用处理程序接口
return Proxy.newProxyInstance(this.getClass().getClassLoader(),
target.getClass().getInterfaces(),this);
}
// 处理代理实例,进行方法增强,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 动态代理的本质,就是使用反射机制实现
// 前增强:log日志方法
log(method.getName());
// 目标类的方式
Object result = method.invoke(target,args);
// 返回结果对象
return result;
}
// 日志方法
public void log(String msg) {
System.out.println("执行了"+msg+"方法");
}
}
4.模拟客户端访问代理对象
// 客户端角色:租房的人
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();
// 调用代理类的方法时,通过反射机制自动调用invoke方法来实现方法增强
// 代理类实例执行业务
proxy.add();
}
}
5.测试结果
10.2.5 动态代理 (多个真实对象)
1.编写抽象角色
- 同10.2.4的抽象角色接口
2.编写真实角色
- 在10.2.4的一个真实角色基础上,再添加一个真实角色UserServiceImpl2
// 用户服务接口的实现类(第二个真实角色)
public class UserServiceImpl2 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("查询一个用户信息");
}
}
3.编写动态代理工具类
- 同10.2.4动态代理工具类
4.模拟客户端访问代理角色
public class Client {
public static void main(String[] args) {
// 获取多个真实角色
UserServiceImpl userService = new UserServiceImpl();
UserServiceImpl2 userService2 = new UserServiceImpl2();
// 获取动态代理工具类(中介所): 此时代理角色中介还未出现
ProxyInvocationHandler pih= new ProxyInvocationHandler();
ProxyInvocationHandler pih2= new ProxyInvocationHandler();
// 设置多个要代理的对象:通过调用动态代理工具类的setTarget方法来实现代理角色的注入
pih.setTarget(userService);
pih2.setTarget(userService2);
// 动态生成多个代理类:此时代理角色中介才出现
UserService proxy = (UserService) pih.getProxy();
UserService proxy2 = (UserService) pih2.getProxy();
// 多个代理类实例代理执行业务
proxy.add();
proxy2.delete();
}
}
5.测试结果
好了,今天的有关代理模式的学习就到此结束啦,欢迎小伙伴们积极学习和讨论,喜欢的可以给蜗牛君点个关注,顺便来个一键三连,我们下期见,拜拜啦!
参考视频链接:https://www.bilibili.com/video/BV1WE411d7Dv(【狂神说Java】Spring5最新教程IDEA版通俗易懂)