1. 意图(Intent)
控制对其它对象的访问。
代理模式是最常使用的模式之一了,用一个代理来隐藏具体实现类的实现细节,通常还用于在真实的实现的前后添加一部分逻辑。
既然说是代理,那就要对客户端隐藏真实实现,由代理来负责客户端的所有请求。当然,代理只是个代理,它不会完成实际的业务逻辑,而是一层皮而已,但是对于客户端来说,代理必须表现得就是客户端需要的真实实现。
理解代理这个词,这个模式其实就简单了。
2. 类图(Class Diagram)
代理有以下四类:
- 远程代理(Remote Proxy):控制对远程对象(不同地址空间)的访问,它负责将请求及其参数进行编码,并向不同地址空间中的对象发送已经编码的请求。
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,它可以缓存实体的附加信息,以便延迟对它的访问,例如在网站加载一个很大图片时,不能马上完成,可以用虚拟代理缓存图片的大小信息,然后生成一张临时图片代替原始图片。
- 保护代理(Protection Proxy):按权限控制对象的访问,它负责检查调用者是否具有实现一个请求所必须的访问权限。
- 智能代理(Smart Reference):取代了简单的指针,它在访问对象时执行一些附加操作:记录对象的引用次数;当第一次引用一个对象时,将它装入内存;在访问一个实际对象前,检查是否已经锁定了它,以确保其它对象不能改变它。
3. 实现(Implementation)
代理模式:食物作坊
生产食物接口
public interface FoodService {Food makeChicken();Food makeNoodle();}
生产食物实现类
public class FoodServiceImpl implements FoodService {public Food makeChicken() {Food f = new Chicken()f.setChicken("1kg");f.setSpicy("1g");f.setSalt("3g");return f;}public Food makeNoodle() {Food f = new Noodle();f.setNoodle("500g");f.setSalt("5g");return f;}}
生产食物代理
// 代理要表现得“就像是”真实实现类,所以需要实现 FoodServicepublic class FoodServiceProxy implements FoodService {// 内部一定要有一个真实的实现类,当然也可以通过构造方法注入private FoodService foodService = new FoodServiceImpl();public Food makeChicken() {System.out.println("我们马上要开始制作鸡肉了");// 如果我们定义这句为核心代码的话,那么,核心代码是真实实现类做的,// 代理只是在核心代码前后做些“无足轻重”的事情Food food = foodService.makeChicken();System.out.println("鸡肉制作完成啦,加点胡椒粉"); // 增强food.addCondiment("pepper");return food;}public Food makeNoodle() {System.out.println("准备制作拉面~");Food food = foodService.makeNoodle();System.out.println("制作完成啦")return food;}}
客户端调用,注意,我们要用代理来实例化接口:
public class Client {public static void main(String[] args) {// 这里用代理类来实例化FoodService foodService = new FoodServiceProxy();foodService.makeChicken();}}
我们发现没有,代理模式说白了就是做 “方法包装” 或做 “方法增强”。在面向切面编程中,其实就是动态代理的过程。比如 Spring 中,我们自己不定义代理类,但是 Spring 会帮我们动态来定义代理,然后把我们定义在 @Before、@After、@Around 中的代码逻辑动态添加到代理中。
说到动态代理,又可以展开说,Spring 中实现动态代理有两种,一种是如果我们的类定义了接口,如 UserService 接口和 UserServiceImpl 实现,那么采用 JDK 的动态代理,感兴趣的读者可以去看看 java.lang.reflect.Proxy 类的源码;另一种是我们自己没有定义接口的,Spring 会采用 CGLIB 进行动态代理,它是一个 jar 包,性能还不错。
代理模式:图片虚拟代理
以下是一个虚拟代理的实现,模拟了图片延迟加载的情况下使用与图片大小相等的临时内容去替换原始图片,直到图片加载完成才将图片显示出来。
public interface Image {void showImage();}
public class HighResolutionImage implements Image {private URL imageURL;private long startTime;private int height;private int width;public int getHeight() {return height;}public int getWidth() {return width;}public HighResolutionImage(URL imageURL) {this.imageURL = imageURL;this.startTime = System.currentTimeMillis();this.width = 600;this.height = 600;}public boolean isLoad() {// 模拟图片加载,延迟 3s 加载完成long endTime = System.currentTimeMillis();return endTime - startTime > 3000;}@Overridepublic void showImage() {System.out.println("Real Image: " + imageURL);}}
public class ImageProxy implements Image {//真正做事情的苦力private HighResolutionImage highResolutionImage;public ImageProxy(HighResolutionImage highResolutionImage) {this.highResolutionImage = highResolutionImage;}@Overridepublic void showImage() {//全是装逼的假行为while (!highResolutionImage.isLoad()) {try {System.out.println("Temp Image: " + highResolutionImage.getWidth() + " " + highResolutionImage.getHeight());Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}}//让苦力干真正的活highResolutionImage.showImage();}}
public class ImageViewer {public static void main(String[] args) throws Exception {String image = "http://image.jpg";URL url = new URL(image);//苦力HighResolutionImage highResolutionImage = new HighResolutionImage(url);//要初始化成代理Image imageProxy = new ImageProxy(highResolutionImage);imageProxy.showImage();}}
