代理模式
引入:
代理模式是常用的结构性模式之一,当无法直接访问某个对象或访问某个对象困难时,我们可以通过代理对象间接访问,为了保证客户端使用的透明性,我们所被代理对象与代理对象将会实现一个共同的接口。
根据代理模式的使用目的不同,代理模式又可以分为保护代理、远程代理、虚拟代理、缓冲代理等,需求不一样应用场景不同。
在本章节可以学习到代理模式的定义和结构,并且理解代理模式的多种类型的作用以及实现原理。
一、代理模式的简述
为什么需要"代理"呢,因为在面向对象编程时,被代理对象可能任务太多了,此时就需要一个助手去帮助他完成一系列事情。
例如:在电子商务的这个背景下,就衍生出一个代购的概念,也就是找人来帮忙购买用品,肯定需要向代购人支付一定的费用。
可能"本人(被代理对象)"事情比较繁忙,这种事让"代理"对象去实现,就减少了"本人"的工作量。
代理模式的定义:
给某一个对象提供一个代理或占位符,并由代理对象来控制对原对象的访问。

Proxy Pattern : Provide a surrogate or placeholder for another object to control access to it
简单来说:代理对象起到的是一个中介作用,去掉客户不能看到的内容和服务,或者添加一些新的功能。
二、代理模式的结构
代理模式的结构比较简单,代理模式包含三个角色:
- Subject(抽象主题角色) 声明真实对象和代理对象的共同接口,保证在任意地方使用真实对象的地方都可以使用代理对象。
- Proxy(代理主题角色) 包含对真实对象的引用,从而可以在任意情况下操作真实对象,在真实对象的基础上还可以增加一些新的功能,不仅仅当真实对象的调用者。
- RealSubject(真实主题角色) 它定义了代理对象所代表的真实对象,在真实对象中实现了真实的业务操作,客户端可以通过代理对象间接调用真实对象中定义的操作。
三、代理模式的实现
提前了解相关概念:
- 透明性:代理对象和真实对象都实现了共同的接口,因此在测试中完全不必关注调用的究竟是代理对象还是真实对象。无论是直接调用真实对象还是间接对象都可以。在这里代理对象具有"透明性",就像一幅画之间放置了一块玻璃,仍然可以看见画的内容,即使在Main类和Printer之间加入一个PrinterProxy类,也不会有问题
- 代理与委托 : 代理对象只能代理他能解决的问题。当遇到他不能解决的问题时,还是会"转交"给真实对象去解决。这里的转交就是委托。从PrinterProxy类的print方法中调用real.print方法正好体现出这种委托
现在我们看一段实例:
使用Proxy模式(静态代理)
案例:带名字的打印机,将文字信息显示在控制台上即可
名字 | 说明 |
---|---|
Printer | 带名字的打印机类 |
Printable | Printer 和 PrinterProxy共同接口 |
PrinterProxy | 带名字的打印机的类(代理类) |
Main | 测试程序的类 |
public interface Printable {
void setPrinterName(String name); //设置名字
String getPrinterName(); //获取名字
void print(String name); //打印信息
}
/*
被代理类
*/
public class Printer implements Printable{
private String name;
public Printer() {
System.out.println("正在生成实例...");
}
public Printer(String name) {
this.name = name;
heavyJob("生成Printer的实例对象 (" + name + " ) ");
}
@Override
public void setPrinterName(String name) {
this.name = name;
}
@Override
public String getPrinterName() {
return name;
}
@Override
public void print(String name) {
System.out.println(name);
}
private void heavyJob(String msg){
System.out.println(msg);
for (int i = 0; i < 5; i++){
try {
//每一次工作 休息一秒
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("结束...");
}
}
/*
代理类
*/
public class PrinterProxy implements Printable{
private String name ; //名字
private Printer real; //被代理对象 真实对象
public PrinterProxy() {
}
public PrinterProxy(String name) {
this.name = name;
}
@Override
public synchronized void setPrinterName(String name) {
//设置名字
this.name = name;
if (real != null){
this.setPrinterName(name); //同时设置真实对象名字
}
}
@Override
public String getPrinterName() {
return name;
}
@Override
public void print(String name) {
realize();
real.print(name);
}
private synchronized void realize(){
if (real == null){
real = new Printer(name);
}
}
}
public class Main {
public static void main(String[] args) {
Printable p = new PrinterProxy("惠普打印机");
System.out.println("现在的名字是 : " + p.getPrinterName());
p.setPrinterName("不知名的打印机");
System.out.println("现在的名字是 : " + p.getPrinterName());
p.print("Hello World"); //调用该方法时 才生成Printer类实例 并调用带参构造器初始化对象
}
详解PrinterProxy类
是一个代理类,实现Printable接口。
name字段中保存了打印机的名字,而real字段保存的是 “被代理人”
不论setPrinterName和getPrinterName方法被调用多少次,都不会生成Printer对象。只有当真正需要真实对象,才会生成Printer实例。
realize方法很简单 当real字段为空时 会使用new Printer来生成Printer类实例。反之
最重要的是,PrinterProxy知道Printer类的存在,Printer并不知晓PrinterProxy的存在。也就是说Printer类不知道自己是被直接调用还是间接调用(PrinterProxy调用)。
四、JDK代理方式
在上述,传统的代理模式中通过客户端通过代理类调用封装好的方法print()的这种方法。如果按这种方法使用代理模式,很显然,代理类和真实主题类都应该是事先存在,代理类的接口和所代理的方法都已明确指定。每一个代理类在经过编译生成一个字节码文件,代理类所实现的接口和方法都是固定的,这样的代理方式称为静态代理
现在我们将介绍动态代理——JDK方式
动态代理:
根据实际需求来动态的创建代理类,同一个代理类能够代理多个真实主题类,并且可以代理不同的方法。尤其注意的是在SpringAOP中就采用了这种方式来实现。
介绍JDK当中相关的类和接口:
1、Proxy类
这个类提供了创建动态代理类和实例对象的方法,它是创建动态代理类的父类,常用的方法有:
a、public static Class<?> getProxyClass(ClassLoader loader,Class<?>… interfaces):该方法用于返回一个Class类型的代理类
在参数中需要指定代理的接口数组
b、public static Object newProxyInstance(ClassLoader loader,Class<?>[]… interfaces,InvocationHandler h):该方法用于返回一个动态创建的代理类实例,方法中第一个参数表示代理类的类加载器,第二个参数interfaces表示代理类所实现的接口列表,第三个参数表示调用处理的程序类
2、InvocationHandler接口
这个接口表示的是处理程序类的实现接口,该接口作为代理对象的调用处理的共父类,每一个代理类的实例都可以提供一个相关的具体调用处理者(该接口的子类)。该接口的声明了如下方法:
public Object invoke(Object proxy,Method moethod,Object[] args)
该方法用于处理对代理类实例的方法调用并返回相应的结果,当一个代理实例中的业务方法被调用时 自动调用该方法。invoke方法包含三个参数,其中第一个参数proxy表示代理类的实例,第二个参数mehtod表示需要代理的方法,第三个参数args表示代理方法的参数数组
动态代理需要在运行时指定所代理真实主题类的接口,客户端调用动态代理对象的方法时调用请求会将请求转发给InvocationHandler对象invoke()方法,由invoke()方法来实现对请求的统一处理。
实例:
类(接口) | 描述 |
---|---|
UserDao | 抽象主题角色 |
DocumentDao | 抽象主题角色 |
UserDaoImpl | 具体主题角色 |
DocumentDaoImpl | 具体主题角色 |
DaoLogHandler | 自定义请求处理程序类 |
Client | 客户端测试类 |
public interface UserDao {
Boolean findUserById(String id);
}
/*
具体主题角色
*/
public class UserDaoImpl implements UserDao{
@Override
public Boolean findUserById(String id) {
if (id.equalsIgnoreCase("张三")){
System.out.println("查询ID为:" + id);
return true;
}else {
System.out.println("查询失败");
return false;
}
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Calendar;
import java.util.GregorianCalendar;
/*
自定义请求处理程序类
*/
public class DaoLogHandler implements InvocationHandler {
private Calendar calendar;
private Object object;
public DaoLogHandler() {
}
//有参构造 注入一个需要提供代理的真实主题对象
public DaoLogHandler(Object object){
this.object = object;
}
//实现invoke方法 调用在真实主题类中定义的方法
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
beforeInvoke();
Object obj = method.invoke(object, args); //转发调用
afterInvoke();
return obj;
}
//记录方法调用事件
public void beforeInvoke(){
calendar = new GregorianCalendar();
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
String time = hour + ":" + minute + ":"+second;
System.out.println("调用时间:" + time);
}
public void afterInvoke(){
System.out.println("调用结束");
}
}
/*
文档接口 抽象主题类
*/
public interface DocumentDao {
Boolean deleteDocumentById(String id);
}
/*
具体文档类 具体主题角色
*/
public class DocumentDaoImpl implements DocumentDao{
@Override
public Boolean deleteDocumentById(String id) {
if (id.equalsIgnoreCase("001")){
System.out.println("删除ID是: " + id);
return true;
}else {
System.out.println("删除失败");
return false;
}
}
}
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
public class Client {
public static void main(String[] args) {
InvocationHandler handler = null;
UserDao dao = new UserDaoImpl();
handler = new DaoLogHandler(dao);
UserDao proxy = null;
//动态创建对象 代理UserDao类型的真实对象
proxy = (UserDao) Proxy.newProxyInstance
(UserDao.class.getClassLoader(),new Class[] {
UserDao.class},handler);
proxy.findUserById("张三");
System.out.println("------------------------");
DocumentDao documentDao = new DocumentDaoImpl();
handler = new DaoLogHandler(documentDao);
DocumentDao proxy_new = null;
proxy_new = (DocumentDao) Proxy.newProxyInstance(
DocumentDao.class.getClassLoader(),new Class[] {
DocumentDao.class},handler);
proxy_new.deleteDocumentById("1"); //调用代理对象的业务方法
}
}
五、小结
通过动态代理可以实现对多个真实主题类的统一代理和控制。
JDK中提供的动态代理只能代理一个或多个接口,如果需要动态代理具体类或抽象类,我们可以使用CGLib(Code Generation Library)工具。这是一个功能强大、性能和质量比较好的代码生成包。
面向接口的时候我们选择JDK的方式实现动态代理
面向类选择CGLib的方式。