Version: Next
代理模式
源发问题
代理有远程代理、虚代理、保护代理、智能指引:
- 远程代理 负责同远端目标对象进行通信,客户端则直接访问本机的远程代理
- 虚代理 在真正创建开销很大的目标对象前,可起到临时替身的作用
- 保护代理 用来对目标对象的访问进行权限检查
- 智能指引 取代了简单指针,它在访问目标对象前执行注入引用计数等附加操作
为什么要学习代理模式?因为这是Spring AOP的底层
SpringAOP 必问
SpringMVC 必问
分类
- 静态代理
- 动态代理
解决方案
目标对象和代理对象拥有共同的抽象类,进而拥有共同的访问接口。这样,对于客户端代码而言,访问代理和访问目标对象之间没有明显的区别。此外,代理对象还应具有目标对象的引用。
UML类图
代码
1. 静态代理
角色分析:
- 抽象角色Subject:一般会使用接口或者抽象类
- 真实角色RealSubject:被代理的角色
- 代理角色Proxy:代理真实角色后,我们一般会做一些附属操作
- 客户:访问代理对象,用代理对象做一些操作的人
新建一个Maven模块
类
public class Landlord implements com.bsx.client.demo01.interfaces.Landlord {public void rent() {System.out.println("房东要出租房子");}}public class Proxy implements com.bsx.client.demo01.interfaces.Landlord {private Landlord landlord;public Proxy() {}public Proxy(Landlord landlord) {this.landlord = landlord;}//加一些方法,体现代理对象可以对源对象做增强public void rent() {this.landlord.rent();checkHouse();collectFee();}public void checkHouse(){System.out.println("中介代理带你看房");}public void collectFee(){System.out.println("收中介费");}}public class Client {public static void main(String[] args) {//为了让代理对象有东西可代理,创建一个真实房东//代理可以对被代理对象做增强Landlord landlord = new Landlord();Proxy proxy = new Proxy(landlord);proxy.rent();}}
接口
/**** 房东接口*/public interface Landlord {public void rent();}
1.1 应用实例
新建包demo02
接口
public interface UserService {public void add();public void delete();public void update();public void query();}实现类
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("查询了一个用户");}}客户端
public class Client {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();userService.add();}}假如头头要求我们在方法调用前输出一条日志,那么输出日志的代码在每个方法都得添加 ↓
public class UserServiceImpl implements UserService{public void add() {System.out.println("日志:使用了add方法");System.out.println("增加了一个用户");}public void delete() {System.out.println("日志:使用了add方法");System.out.println("删除了一个用户");}public void update() {System.out.println("日志:使用了add方法");System.out.println("更新了一个用户");}public void query() {System.out.println("日志:使用了add方法");System.out.println("查询了一个用户");}}这显然是不好的写法,依据开闭原则,我们可以用代理在不改变源代码的情况下,扩展出日志功能
代理类
public class UserServiceProxy implements UserService {private UserServiceImpl userService;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 message) {System.out.println("[Debug] 使用了" + message + "方法");}}客户端
public class Client {public static void main(String[] args) {UserServiceImpl userService = new UserServiceImpl();UserServiceProxy userServiceProxy = new UserServiceProxy();userServiceProxy.setUserService(userService);userServiceProxy.add();}}
2. 动态代理
- 动态代理类是动态生成的,不是直接写好的
- 动态代理分为两大类:基于接口的动态代理,基于类的动态代理
- 基于接口:JDK动态代理
- 基于类:cglib
- Java字节码实现:javassist
2.1 JDK动态代理
需要了解两个东西:Proxy
类、InvocationHandler
接口
InvocationHandler
接口 —— 用来指定如何增强对象- 方法(只有一个)
invoke(Object proxy, 方法 method, Object[] args)
- proxy:你要代理谁
- method:代理这个类里的哪一个方法
- args:给代理方法里传的参数
- 方法(只有一个)
Proxy
类 (反射) —— 用来生成代理对象实例Proxy.getProxyClass(Xxx.class.getClassLoader(), Xxx.class)
,返回代理类Proxy.getInvocationHandler(Object proxy)
Proxy.newProxyInstance(ClassLoader loader, 类[] interfaces, InvocationHandler h)
- interfaces:类和代理类实现的共同接口
- invocationHandler:new 一个扔进去
真实类
/**** 真实房东*/public class Landlord implements LandlordInterface {public void rent() {System.out.println("房东要出租房子");}}接口
/**** 房东接口*/public interface LandlordInterface {public void rent();}动态代理类
public class DynamicProxyLandlord implements InvocationHandler {//1.真实对象private Landlord landlord;/**** 增强对象* @param proxy* @param method 要优化/拦截的方法* @param args* @return* @throws Throwable*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {EnhanceLogPre();//真实对象landlord调用方法Object result = method.invoke(landlord, args);EnhanceLogSuf();return result;}/**** 通过Proxy获取动态代理的对象* - 第二个参数用来实现和被代理对象一样的接口* - 第三个参数用来指定一个InvocationHandler,指定如何增强对象*/public Object getProxyInstance() {return Proxy.newProxyInstance(landlord.getClass().getClassLoader(),landlord.getClass().getInterfaces(), this);}public void setLandlord(Landlord landlord) {this.landlord = landlord;}private void EnhanceLogPre() {System.out.println("[info] 动态代理日志,增强方法执行,前");}private void EnhanceLogSuf() {System.out.println("[info] 动态代理日志,增强方法执行,后");}}客户端
public class Client {public static void main(String[] args) {//1.创建真实对象Landlord landlord = new Landlord();//2.创建用来动态生成代理类的 类的对象DynamicProxyLandlord dynamicProxyLandlord = new DynamicProxyLandlord();//3.把真实对象扔进去dynamicProxyLandlord.setLandlord(landlord);//4.获取代理对象,强转成接口LandlordInterface proxyLandlord =(LandlordInterface) dynamicProxyLandlord.getProxyInstance();//5.通过代理对象执行方法proxyLandlord.rent();}}
优缺点
- 优点 可以在改变原有对象的情况下对对象做增强
- 缺点 代理对象和源对象实现了共同的接口,当源对象发生改变,代理对象也要跟着改变。如果代理数量多且部署分散,则更新困难