代理模式是非常重要的设计模式,十分常见。本文旨在理解代理模式入门,后面到了Spring框架会再深入。
一.写在前
开发中,经常会遇到增强某个对象的功能。
比如Web的Filter过滤器组件,在过滤敏感词汇的场景下,用户在发送带有敏感词汇的请求时,需要更换掉词汇。而request对象中没有设置请求参数的API,这时候就需要过滤器对request对象进行增强,过滤掉词汇,并返回一个新request对象。
该案例在另一篇博文中: Filter案例:登录验证、敏感词汇过滤.
而如何增强对象的功能?程序员前辈们已经帮我们思考、总结好了一系列套路,我们直接学习使用即可。这些套路就称为:设计模式,一些通用的解决固定问题的方式。当然它包含了解决许多场景下的问题,不局限于增强对象的功能。
软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。一共有23种设计模式。
有两种设计模式都可以来完成对象的功能增强:
1.装饰器模式
意图:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
2.代理模式
在代理模式(Proxy Pattern)中,一个类代表另一个类的功能。
意图:为其他对象提供一种代理以控制对这个对象的访问。
代理模式相对于装饰器模式来说,更加灵活一些,本文主要来理解代理模式。
二.代理模式
先举个例子:
假如西安有用户想买一台联想电脑,由于联想公司在北京生产电脑和卖电脑,所以该用户需要前往北京联想公司进行购买电脑,这种卖电脑的能力就比较弱,局限于北京了。
现在西安有了联想电脑的代理商,代理商也可以售卖联想电脑,用户直接就可以在西安购买到联想电脑了。但是代理商真正具备卖电脑的功能吗?显然不是,代理商也得从北京的联想公司进货再进行售卖。
这里的代理商就相当于是联想公司的一个代理对象,用户调用对象的时候就只需要调用代理对象,而代理对象就会具体去调用真正的对象的方法或功能了。这就是代理模式,在卖电脑例子,可以比较容易体会到代理增强了联想公司卖电脑的能力,用户也不需要跨地域购买电脑、节约路费、省事省力。
需要理解的概念:
- 真实对象:被代理的对象,比如例子里的联想公司;
- 代理对象:代理真实对象的对象,比如西安代理商;
- 代理模式:代理对象代理真实对象,达到增强真实对象功能的目的;
代理模式实现方式有:
- 静态代理
- 动态代理
1.静态代理
静态代理:有一个类,使用一个类文件描述代理模式。
1.1 实现步骤
静态代理的实现就比较简单了,步骤:
- 创建一个接口,然后创建真实类实现该接口并且实现该接口中的抽象方法。
- 再创建一个代理类,同时使其也实现这个接口。
- 在代理类中持有一个被代理对象,即真实对象的引用,而后在代理类方法中调用该对象的方法。
静态代理的实现:以上述的卖联想电脑为例
接口:
SaleComputer.java
/**
* 共同实现的接口
*/
public interface SaleComputer {
/**
* 卖电脑方法
* @param money
* @return
*/
public String sale(double money);
/**
* show方法
*/
public void show();
}
被代理类:
Lenovo.java
/**
* 真实类
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了"+money+"元买了一台联想电脑...");
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑....");
}
}
代理类:
StaticProxy.java
public class StaticProxy implements SaleComputer {
//1.被代理对象的引用
private SaleComputer saleComputer = new Lenovo();
@Override
public String sale(double money) {
//简单的功能增强
System.out.println("Before invoke...." );
//2.在代理类方法中调用真实对象的方法。
String str = saleComputer.sale(money);
System.out.println("After invoke......");
return str;
}
@Override
public void show() {
//2.在代理类方法中调用真实对象的方法。
saleComputer.show();
}
}
代理类调用:
被代理类被传递给了代理类Lenovo,代理类在执行具体方法时通过所持用的被代理类对象完成调用。
/**
* 静态代理测试类
*/
public class Test {
public static void main(String[] args) {
StaticProxy staticProxy = new StaticProxy();
staticProxy.sale(8000);
staticProxy.show();
}
}
运行程序,控制台打印:
使用静态代理很容易就完成了对一个类的代理操作。但是静态代理的缺点也暴露了出来:由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐。
一般很少使用静态代理,重点理解下面的动态代理。
2.动态代理
动态代理:在内存中动态形成代理类,在开发中使用的更多。利用反射机制在运行时创建代理类。
2.1 实现步骤
步骤:
- 代理对象和真实对象实现相同的接口,兄弟关系。
代理对象 = Proxy.newProxyInstance();
,Proxy是JDK提供的类。- 使用代理对象调用方法。
- 增强方法
生成代理对象是用的Proxy类的静态方方法newProxyInstance
,我们了解一下这个方法:
Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);
,方法返回是代理对象- 三个参数值写法比较固定:
-
1.类加载器:
真实对象.getClass().getClassLoader()
,代理对象代理的真实对象 -
2.接口数组:
真实对象.getClass().getInterfaces()
,代理的真实对象实现的接口 -
3.处理器:
new InvocationHandler()
,核心业务逻辑的处理-
真正完成代理逻辑的是处理器中的
invoke
方法,我们重写该方法实现对象的功能增强。 -
/* 代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行 参数: 1. proxy:就是代理对象,很少使用这个对象 2. method:代理对象调用的方法,方法被封装成对象了 3. args:代理对象调用的方法时,传递的实际参数列表 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { }
-
-
动态代理的基本实现:以上述的卖联想电脑为例
共同实现的接口:SaleComputer.java
/**
* 共同实现的接口
*/
public interface SaleComputer {
/**
* 卖电脑方法
* @param money
* @return
*/
public String sale(double money);
/**
* show方法
*/
public void show();
}
真实对象类:Lenovo.java
/**
* 真实类
*/
public class Lenovo implements SaleComputer {
@Override
public String sale(double money) {
System.out.println("花了"+money+"元买了一台联想电脑...");
return "联想电脑";
}
@Override
public void show() {
System.out.println("展示电脑....");
}
}
代理测试类:ProxyTest.java
不使用代理模式,直接调用真实对象的方法的情况:
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.获取代理对象,动态代理真实对象
//3.调用对象方法
String computer = lenovo.sale(8000);
System.out.println(computer);
lenovo.show();
}
}
运行程序,控制台打印:
使用动态代理,使用代理对象来调用方法的情况,现在并未进行对象功能的增强,只是动态代理的基本实现,后面再进行增强:
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.获取代理对象,动态代理增强真实对象
SaleComputer Proxy_lenovo = (SaleComputer)Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
/*
代理逻辑编写的方法:代理对象调用的所有方法都会触发该方法执行
参数:
1. proxy:代理对象,就是Proxy_lenovo,很少使用这个对象
2. method:代理对象调用的方法,方法被封装成对象了
3. args:代理对象调用的方法时,传递的实际参数列表
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//验证invoke方法执行了
System.out.println("该方法执行了....");
//打印调用对象的方法名称
System.out.println(method.getName());
//代理对象调用的方法时,传递的实际参数
System.out.println(args[0]);
//返回值是代理对象调用方法后,接收的返回值
return null;
}
});
//3.调用对象方法
String computer = Proxy_lenovo.sale(8000);
System.out.println(computer);
}
}
运行程序,控制台打印:
通过上面控制台打印,可以发现,虽然代理对象调用了sale()
方法,但并没有执行真实对象的sale()
方法内容。
也证实前面例子中【代理商本质上不具备卖电脑的功能,需要去联想公司进货】。所以还是需要使用真实对象调用方法。
在哪里调用?其实就是在invoke()
方法中,这个方法的参数有method对象
,method对象
可以执行invoke()
方法,这里从反射机制来理解。谁执行method对象
,真实对象执行method,并且传递代理对象调用方法的参数。
真正完成代理的情况:
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.获取代理对象,动态代理增强真实对象
SaleComputer Proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
/*System.out.println("invoke方法执行了....");
System.out.println(method.getName());
System.out.println(args[0]);*/
/*
使用真实对象调用该方法,方法参数传递:
1.lenovo:真实对象来执行方法
2.args:代理对象调用方法的参数列表
*/
Object obj = method.invoke(lenovo, args);
//返回值是代理对象调用方法后,接收的返回值
return obj;
}
});
//3.再调用代理对象方法
String computer = Proxy_lenovo.sale(8000);
System.out.println(computer);
Proxy_lenovo.show();
}
}
运行程序,控制台打印:
现在可以发现代理对象调用方法和真实对象调用方法,业务逻辑都是一样的了,这里就完成代理的第一步。下面就要实现对真实对象的功能增强了,这也是代理对象的主要目的。
了解了动态代理的实现,我们来进行对象功能的增强。
2.2 增强的方式有三种
对象功能的增强,也可以说是对方法的增强,从方法的三个方面下手:
- 增强方法参数列表
- 增强方法返回值类型
- 增强方法体执行逻辑
还是以卖电脑的例子,有这样一个场景,现在联想公司的西安代理商,想要进行促销、回馈用户,原价8000元的电脑,现在打8折,现在用户只需要花6400元就能购买,还送一个鼠标垫。并且为购买电脑的用户提供专车接收和免费送货上门。
福利有点夸张,下面就用增强的三种方式来实现上述场景:
ProxyTest.java
/**
* 代理对象测试类
*/
public class ProxyTest {
public static void main(String[] args) {
//1.创建真实对象
Lenovo lenovo = new Lenovo();
//2.获取代理对象,动态代理增强真实对象
//强制转化为接口类型,两个对象实现了同一接口
SaleComputer Proxy_lenovo = (SaleComputer) Proxy.newProxyInstance(lenovo.getClass().getClassLoader(), lenovo.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//判断是否是sale方法,有参数的方法才能增强参数
if(method.getName().equals("sale")){
//1.增强参数
double money = (double) args[0];
money = money / 0.80;
//3.1 增强方法体逻辑
System.out.println("专车接送....");
//使用真实对象调用该方法
String obj = (String) method.invoke(lenovo, money);
//3.2 增强方法体逻辑
System.out.println("免费送货上门...");
//2.增强返回值
return obj+"_鼠标垫";
}else{
//其它方法原样执行
Object obj = method.invoke(lenovo, args);
return obj;
}
}
});
//3.调用代理对象方法
String computer = Proxy_lenovo.sale(6400);
System.out.println(computer);
//其它方法..
Proxy_lenovo.show();
}
}
运行程序,控制台打印:
就实现了对真实对象的sale()
方法简单增强。
看到这里,相信小伙伴对动态代理有了比较好理解。
欢迎点赞评论,指出不足,笔者由衷感谢哦!~