文章目录
23种设计模式——代理模式
1、什么是代理模式
背景:
一个客户不能或者不想直接访问另一个对象,这时需要找一个中介帮忙完成某项任务,这个中介就是代理对象。例如,购买火车票不一定要去火车站买,可以通过 12306 网站或者去火车票代售点买。又比如找女朋友、找保姆、找工作等都可以通过找中介完成。
代理模式的定义:
- 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时,访问对象不适合或者不能直接引用目标对象,代理对象作为访问对象和目标对象之间的中介。
代理模式的目的:
- 为其他对象提供一种代理以控制对这个对象的访问。
代理模式的作用:
- 解决了在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层(中间层)。
2、代理模式的优缺点
代理模式的主要优点有:
- 代理模式在客户端与目标对象之间起到一个中介作用和保护目标对象的作用;
- 代理对象可以扩展目标对象的功能;
- 代理模式能将客户端与目标对象分离,职责清晰,在一定程度上降低了系统的耦合度,增加了程序的可扩展性
其主要缺点是:
- 代理模式会造成系统设计中类的数量增加
- 在客户端和目标对象之间增加一个代理对象,会造成请求处理速度变慢;
- 增加了系统的复杂度;
那么如何解决以上提到的缺点呢?答案是可以使用动态代理方式
3、代理模式的结构
代理模式的主要角色如下。
- 抽象主题(Subject)类:通过接口或抽象类声明真实主题和代理对象实现的业务方法。
- 真实主题(Real Subject)类:实现了抽象主题中的具体业务,是代理对象所代表的真实对象,是最终要引用的对象(被代理的角色)。
- 代理(Proxy)类:提供了与真实主题相同的接口,其内部含有对真实主题的引用,它可以访问、控制或扩展真实主题的功能。
- 客户类:访问代理类的人
其结构图如图所示。
在这里我们举一个租房的例子,用来理解代理模式
在这个例子里面,租房是抽象主题类,房产中介是代理类,房东是真实主题类,租房的人就是客户类,房东就是中介所代表的的真实对象。
4、代理模式的分类
根据代理的创建时期,代理模式分为静态代理和动态代理。
- 静态代理:由程序员创建代理类或特定工具自动生成源代码再对其编译,在程序运行前代理类的 .class 文件就已经存在了。
- 动态代理:在程序运行时,运用反射机制动态创建而成
4.1、静态代理
静态代理代码实现租房:
1.接口
//租房
public interface Rent {
public void rent();
}
2.真实角色
//房东,被代理的真实角色 实现租房Rent这个接口
public class Host implements Rent{
@Override
public void rent() {
System.out.println("房东要把房子租出去");
}
}
3.代理角色
package com.cheng.demo01;
//中介代理
public class Proxy implements Rent{
//多用组合,少用继承
private Host host;
public Proxy() {
}
public Proxy(Host host) {
this.host = host;
}
@Override
public void rent() {
host.rent();
seeHost();
charge();
}
//看房,这件事中介能实现,房东不能实现
public void seeHost(){
System.out.println("中介带你看房子");
}
//收中介费,这件事也只有中介能实现
public void charge(){
System.out.println("中介收你中介费");
}
}
4.客户类
//要租房的人
public class Client {
public static void main(String[] args) {
Host host = new Host();//获得房东
//因为房东不能直接出租房子,所以我们要找中介(代理),通过代理访问房东
//代理一般会有一些附属操作,例如看房,收中介费
Proxy proxy = new Proxy(host);
proxy.rent();//本质还是调用了房东的rent方法
}
}
静态代理代码实现二:
1.接口,只有增删改查方法
//抽象角色
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
2.真实角色,实现了增删改查方法
package com.cheng.demo02;
import java.util.Random;
//真实角色
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("查询了一个用户");
}
}
要求:如果在不改变原有业务代码的情况下,想要在执行接口中的方法的时候,打印出日志,这时候最好的方法就是用静态代理实现。
3.代理类
package com.cheng.demo02;
//代理类
public class UserServiceProxy implements UserService{
private UserServiceImpl userService;
//使用set注入对象
public void setUserService(UserServiceImpl 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("update");
userService.update();
}
@Override
public void query() {
log("query");
userService.query();
}
//日志的方法
public void log(String msg){
System.out.println("使用了"+msg+"方法");
}
}
4.客户类
//客户类
public class Client {
public static void main(String[] args) {
UserServiceImpl userService = new UserServiceImpl();
UserServiceProxy proxy = new UserServiceProxy();
proxy.setUserService(userService);
proxy.add();
}
}
4.2、动态代理
动态代理:在程序运行时,运用反射机制动态创建而成
动态代理分类:
-
基于接口的动态代理
- JDK动态代理
-
基于类的动态代理
- cglib类
- java字节码实现:javasist类库
java动态代理机制中有两个重要的类和接口InvocationHandler
(接口)和Proxy
(类),这一个类Proxy和接口InvocationHandler是我们实现动态代理的核心
4.2.1、InvocationHandler接口
-
InvocationHandler
是由proxy代理实例的调用处理程序实现的接口 。 -
每个代理实例都有一个关联的调用处理程序。 当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的
invoke
方法。
invoke方法详细信息:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return null;
}
处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。
参数
- proxy 调用该方法的代理实例
- method 我们所要调用某个对象真实的方法的Method对象
- args:指代代理对象方法传递的参数
4.2.2、Proxy类
Proxy类提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
它提供了很多方法,但是我们最常用的是newProxyInstance方法。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
这个方法的作用就是创建一个代理类对象,它接收三个参数:
- loader:一个classloader对象,定义了由哪个classloader对象对生成的代理类进行加载
- interfaces:一个interface对象数组,表示我们将要给我们的代理对象提供一组什么样的接口,如果我们提供了这样一个接口对象数组,那么也就是声明了代理类实现了这些接口,代理类就可以调用接口中声明的所有方法。
- h:一个InvocationHandler对象,表示的是当动态代理对象调用方法的时候会关联到哪一个InvocationHandler对象上,并最终由其调用。
4.2.3、动态代理实现
实现一:租房
接口
//租房
public interface Rent {
public void rent();
}
真实角色
//房东,被代理的真实角色 实现租房Rent这个接口
public class Host implements Rent {
public void rent() {
System.out.println("房东要把房子租出去");
}
}
由proxy代理实例的调用处理程序
package com.cheng.demo03;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们将会用这个类自动生成代理类,这个类(ProxyInvocationHandler)只是proxy代理实例的调用处理程序
public class ProxyInvocationHandler implements InvocationHandler {
private Rent rent;
public void setRent(Rent rent) {
this.rent = rent;
}
//生成代理类
public Object getProxy(){
//this.getClass().getClassLoader()代表加载类 rent.getClass().getInterfaces()代表要实现的接口 this代表当前InvocationHandler接口对象
return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
seeHose();
fare();//这两个方法会通过反射动态的加载到代理类中04
//动态代理的本质还是反射机制
Object result = method.invoke(rent,args);
return result;
}
public void seeHose(){
System.out.println("中介带你看房子");
}
public void fare(){
System.out.println("收中介费");
}
}
客户类
public class Client {
public static void main(String[] args) {
//真实角色
Host host = new Host();
//代理角色,暂时还没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//通过处理程序来实现接口对象 这里用了多态,相当于 Rent rent = new Host();
pih.setRent(host);
//动态生成的代理类
Rent proxy = (Rent) pih.getProxy();
proxy.rent();
}
}
测试
实现二:用动态代理优化4.1静态代理中的代码实现二
接口
//抽象角色
public interface UserService {
public void add();
public void delete();
public void update();
public void query();
}
由proxy代理实例的调用处理程序,我们可以把它封装成一个工具类
package com.cheng.demo04;
import com.cheng.demo03.Rent;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//我们将会用这个类自动生成代理类,这只是一个处理程序
public class ProxyInvocationHandler implements InvocationHandler {
private Object target;
public void setTarget(Object target) {
this.target = target;
}
//生成代理类
public Object getProxy(){
//this.getClass().getClassLoader()代表加载类 rent.getClass().getInterfaces()代表要实现的接口 this代表当前InvocationHandler接口对象
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
//处理代理实例,并返回结果
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//动态代理的本质还是反射机制
log(method.getName());//用反射动态的获取方法名
Object result = method.invoke(target,args);
return result;
}
//增加日志功能
public void log(String msg){
System.out.println("[Debug]:使用了"+msg+"方法");
}
}
客户类
public class Client {
public static void main(String[] args) {
//获得真实角色
UserServiceImpl userService = new UserServiceImpl();
//获得代理角色,暂时没有
ProxyInvocationHandler pih = new ProxyInvocationHandler();
//处理程序处理接口对象
pih.setTarget(userService);
UserService proxy = (UserService) pih.getProxy();
proxy.add();
}
}
相比静态代理,动态代理的优点:
- 一个动态代理类代理的是一个接口,一般就是对应的一类业务
- 一个动态代理类可以代理多个类,只要是实现了同一个接口即可。
例如:在上面的例子中,只要实现了UserService这个接口的类,都可以被动态代理类代理。
5、代理模式的应用场景
当无法或不想直接引用某个对象或访问某个对象存在困难时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
前面分析了代理模式的结构与特点,现在来分析以下的应用场景。
- 远程代理,这种方式通常是为了隐藏目标对象存在于不同地址空间的事实,方便客户端访问。例如,用户申请某些网盘空间时,会在用户的文件系统中建立一个虚拟的硬盘,用户访问虚拟硬盘时实际访问的是网盘空间。
- 虚拟代理,这种方式通常用于要创建的目标对象开销很大时。例如,下载一幅很大的图像需要很长时间,因某种计算比较复杂而短时间无法完成,这时可以先用小比例的虚拟代理替换真实的对象,消除用户对服务器慢的感觉。
- 安全代理,这种方式通常用于控制不同种类客户对真实对象的访问权限。
- 智能指引,主要用于调用目标对象时,代理附加一些额外的处理功能。例如,增加计算真实对象的引用次数的功能,这样当该对象没有被引用时,就可以自动释放它。
- 延迟加载,指为了提高系统的性能,延迟对目标的加载。例如,Hibernate 中就存在属性的延迟加载和关联表的延时加载。