1.为什么要学习代理模式?
因为这就是spring AOP的底层
代理模式的分类:
- 静态代理
- 动态代理
2.静态代理
角色分析
- 抽象业务:用来指定要实现什么功能
- 真实业务:去具体实现抽象业务
- 代理角色:代理真实业务,真实业务一般就去实现核心功能,而代理角色存在的意义就是在核心业务实现的前后添加一些前戏和收尾操作,这些操作和核心功能的实现可以有关,也可以无关,都是为了完成核心功能的一系列操作
- 真实角色:为了实现某个需求,我们不能直接去调用真实业务,因为真实业务只有核心的业务实现,而核心业务前后的前戏和收尾操作真实业务并不具备,所以我们只能去调用代理角色,代理角色就在真实业务的基础上为我们添加了实现需求的全部操作,所以我们应该去调用代理角色实现需求
3.举例
例如吃饭
- 抽象业务就是"吃饭"这件事
- 在接口中定义一个抽象方法叫eat()
- 真实业务就是"我们吃饭这个过程"
- 在实现类中实现方法eat(),方法中可以是吃了红烧肉、吃了大白菜…,不管你怎么实现,这都是我们吃饭这个过程中的事情
- 而代理角色就是食堂,食堂去"买菜、做菜、准备碗筷、洗碗",而真实业务就应该嵌入在食堂做的这些事情中间,即"买菜、做菜、准备碗筷、吃饭、洗碗"
- 代理类内部包含一个要代理的真实业务类对象,并且也去实现抽象业务接口,在定义好"买菜、做菜、准备碗筷、洗碗"这些方法之后,因为同样实现了抽象接口,所以真实业务中有的方法定义代理类中都有;这些方法就按照实现逻辑顺序组合"买菜、做菜、准备碗筷、洗碗"方法和真实业务实现的eat()方法,即"买菜、做菜、准备碗筷、吃饭、洗碗"
- 而真实角色就是我们自己,我们为了实现"吃饭"这个目的,我们不能直接去调用真实业务,即吃饭这个过程,因为我们没有获取食物这个过程,所以吃饭这个过程是进行不下去的,所以为了实现"吃饭"这个目的,我们只能去调用代理业务的eat()方法,只有调用这个方法我们才能实现"吃饭"这个目的
4.代码实现
- 接口设计
package com.thhh.Demo01;
//1、抽象业务:吃饭接口设计
public interface Eat {
public void eat();
}
- 真实业务
package com.thhh.Demo01;
//2、真实业务:吃饭接口实现
public class EatImpl implements Eat{
public void eat() {
System.out.println("在食堂打菜");
System.out.println("先吃红烧肉");
System.out.println("再吃大白菜");
System.out.println("边吃菜边吃饭");
System.out.println("吃饱了放下碗筷走人");
}
}
- 代理角色
package com.thhh.Demo01;
//3、代理角色:食堂
public class canteenProxy implements Eat{
private Eat eat = new EatImpl();
public void mc(){
System.out.println("食堂买菜");
}
public void zc(){
System.out.println("食堂做菜");
}
public void wk(){
System.out.println("食堂准备碗筷");
}
public void xw(){
System.out.println("食堂洗碗");
}
//实现抽象业务中的方法
public void eat(){
mc();
zc();
wk();
System.out.println("=========核心业务开始=========");
eat.eat();//调用真实业务的核心方法
System.out.println("=========核心业务结束=========");
xw();
}
}
- 客户端访问代理角色
package com.thhh.Demo01;
//4、真实角色:食客,需要实现业务的人
public class Diners {
public static void main(String[] args) {
canteenProxy canteenProxy = new canteenProxy();
canteenProxy.eat();
}
}
5.代理模式的好处与缺点
好处
- 可以使真实业务操作更加纯粹,不用去关注实现核心功能的前戏和收尾操作
- 前戏和收尾操作交给代理类实现,实现了业务分工
- 前戏和收尾操作发生扩展的时候方便管理,比如现在要求每个方法调用都要加一个日志的输出,我们最好不要去修改真实业务的代码,最好的做法就是在代理对象中加一个方法,然后在同名方法前面把这个新增方法加进去
缺点
- 一个真实业务就会产生一个代理角色,代码量翻倍,开发效率降低
6.加深理解
假设现在开发的业务为UserService,需要提供user的增删改查操作,那么我们需要定义一个接口,然后实现这个接口,最后测试
package com.thhh.Demo02;
//抽象业务,增删改查
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
package com.thhh.Demo02;
public class UserServiceImpl implements UserService{
@Override
public void add() {
System.out.println("新增了用户");
}
@Override
public void delete() {
System.out.println("删除了用户");
}
@Override
public void update() {
System.out.println("修改了用户");
}
@Override
public void query() {
System.out.println("查询用户");
}
}
package com.thhh.Demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
userService.add();
}
}
此时新增一个需求,需要在每个被调用的方法前面调用日志,即日志记录
我们最好是不要去修改源代码,因为修改源代码可能会出现错误,造成现在的功能都不能正常提供,这就会是公司的损失
好的做法就是"加一层",没有什么是加一层不能解决的
加的这一层就是代理层,我们让代理层同样去实现抽象接口,并新定义一个log(),并将其插入到对应的方法执行之前
package com.thhh.Demo02;
public class UserServiceProxy implements UserService{
private UserService userService;
public UserServiceProxy(UserService userService) {
this.userService = userService;
}
@Override
public void add() {
log("add");
userService.add();
}
@Override
public void delete() {
log("delete");
userService.delete();
}
@Override
public void update() {
log("add");
userService.update();
}
@Override
public void query() {
log("add");
userService.query();
}
public void log(String msg){
System.out.println("方法"+msg+"被调用");
}
}
package com.thhh.Demo02;
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy userServiceProxy = new UserServiceProxy(userService);
userServiceProxy.add();
System.out.println("=============");
userServiceProxy.delete();
System.out.println("=============");
userServiceProxy.update();
System.out.println("=============");
userServiceProxy.query();
}
}