19.代理模式
本章目标
- 简介
- 静态代理
- 动态代理
本章内容
一、简介
1、什么是代理
代理
这个词相信大家并不陌生,我们大家都知道代理商,简单地说就是代替厂家卖商品,厂家委托
代理为其销售商品,顾客找代理购买商品。
通过使用代理,通常有两个优点,并且能够分别与我们提到的代理商的几个特点对应起来:
- 关于代理商,首先我们从他们那里买东西时通常不知道背后的厂家究竟是谁,也就是说,
委托者
对我们来说是不可见的; - 代理可以对顾客进行
定位
,更精确的售卖给需要的客户群体
2、代理模式
代理模式是常用的java设计模式,他的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。
代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。
代理模式:为其他对象提供一种代理以控制对这个对象的访问,也就是创建一个代理对象作为客户端和目标对象之间的中介,主要目的就是保护目标对象或增强目标对象
通过使用代理模式,通常有以下两个优点:
- 可以隐藏被代理类的实现
- 可以实现客户与被代理类间的解耦,在不修改被代理类代码的情况下能够做一些额外的处理
3、分类
Proxy( /ˈprɑːksi/)模式在实际中经常应用,比如Windows系统提供的快捷方式
按照代理的创建时期,代理类可以分为两种。
- 静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了
- 动态代理:在程序运行时,运用反射机制动态创建而成。
4、Proxy模式结构图
代理模式结构图如下:
由三部分组成:
- Proxy:保存一个引用使得代理可以访问实体。控制对实体的存取,并可能负责创建和删除它,其他功能依赖于代理的类型。
- Subject:定义RealSubject和Proxy的共用接口,这样就在任何使用RealSubject的地方都可以使用Proxy。
- RealSubject:定义Proxy所代表的实体。
二、静态代理
若代理类在程序运行前就已经存在,那么这种代理方式被成为 静态代理 ,这种情况下的代理类通常都是我们在Java代码中定义的
所谓的静态代理,就是通过声明一个明确的代理类来访问源对象,一个代理只能服务于一种产品,当有n种产品时,就需要n个代理,这样就不利于业务的发展。
举例:我们有两个接口,Computer和 Phone,每个接口都有一个实现类
1、定义接口(Subject)
代理接口
public interface Computer{
public void sell();
}
2、定义接口实现类(RealSubject)
即委托类
public class HuaWeiComputer implements Computer{
@Override
public void sell() {
System.out.println("出售华为电脑");
}
}
3、定义代理类(Proxy)
代理类,最终还是调用委托类实现业务操作
现在我们要做的就是让代理在调用sell()
前输出一句售前了解
,调用后输出一句售后服务
public class ComputerProxy implements Computer {
private Computer computer;
public ComputerProxy(Computer computer) {
this.computer = computer;
}
@Override
public void sell() {
System.out.println("售前了解");
computer.sell();
System.out.println("售后服务");
}
}
4、测试
public class Test {
public static void main(String[] args) {
Computer huaWeiComputer = new HuaWeiComputer();
ComputerProxy computerProxy = new ComputerProxy(huaWeiComputer);
computerProxy.sell();
}
}
--------------------------
输出:
售前了解
出售华为电脑
售后服务
5、优缺点
Phone和computer一样再实现一遍
静态代理的代码非常简单易懂,这种模式虽好,但是也有明显的缺点:
- 会存在大量冗余的代理类,这里只有两个接口,如果有n个接口,那么就要定义n个代理类。
- 不易维护,一旦接口更改,代理类和被代理类都要更改。
那么这个时候就可以使用动态代理来解决了
三、动态代理
代理类在程序运行时创建的代理方式被成为 动态代理。 也就是说,这种情况下,代理类并不是在Java代码中定义的,而是在运行时根据我们在Java代码中的指示动态生成的 ,在程序运行时,运用反射机制动态创建而成代理对象。
代理类在程序运行时创建代理的方式叫动态代理,也就是说代理类并不是在java代码中定义的,而是在运行的时候动态生成的。
1、实现方式
Java的动态代理技术的实现主要有两种方式:
- JDK原生动态代理:是Java原生支持的,不需要外部依赖,但是它只能基于接口进行代理(需要动态代理的对象必须实现与某个接口)
- CGLIB动态代理:CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。
2、JDK动态代理
JDK从1.3版本就开始支持动态代理类的创建。
java.lang.reflect
类库中提供三个类直接支持代理模式:Proxy
,InvocationHandler
和Method
2.1、Proxy类
Proxy
提供用于创建动态代理类和实例的静态方法,它还是由这些方法创建的所有动态代理类的超类。
核心方法newProxyInstance
:返回一个指定接口的代理类实例,该接口可以将方法调用指派到指定的调用处理程序。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数说明:
loader - 定义代理类的类加载器 interfaces - 代理类要实现的接口列表 h - 指派方法调用的调用处理程序
Ps:类加载器 在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器
2.2、InvocationHandler接口
代理实例的调用处理程序 实现的接口
每个代码实例都具有一个关联的调用处理程序。对代理实例调用方法时,将对方法调用进行编码并将其指派到它的调用处理程序的 invoke
方法。
package java.lang.reflect;
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
参数说明:
Object proxy:在其上调用方法的代理实例。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。
2.3、Method
Method
提供关于类或接口上单独某个方法(以及如何访问该方法)的信息。所反映的方法可能是类方法或实例方法(包括抽象方法)。
主要方法invoke
:对带有指定参数的指定对象调用由此 Method 对象表示的基础方法
public Object invoke(Object obj,
Object... args)
参数: obj - 从中调用基础方法的对象 args - 用于方法调用的参数
2.4、定义代理类处理程序
public class JDKProxy {
public static Object getProxy(Object target) {
// 返回代理接口的代理对象,并指定代理对象的调用处理程序为当前对象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
//proxy - 在其上调用方法的代理实例
//method - 代表被调用的方法
//args - 调用方法的参数
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("售前了解");
// 执行方法,底层还是通过委托类对象调用自己的方法执行
Object invoke = method.invoke(target, args);
System.out.println("售后服务");
return invoke;
}
});
}
}
当我们通过代理对象调用业务方法时,这个调用会转送到invoke方法中,代理类对象作为proxy参数传入,参数method标识了我们具体调用的是代理类的哪个方法,args为这个方法的参数。
这样一来,我们对代理类中的所有方法的调用都会变为对invoke的调用,这样我们可以在invoke方法中添加统一的处理逻辑(也可以根据method参数对不同的代理类方法做不同的处理)。
因此我们可以在中介类的invoke方法中实现输出售前了解,再调用被代理类的方法,再输出售后服务。
2.5、执行代码
public class App{
public static void main( String[] args ){
//得到委托类
HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
//得到代理类
Computer proxy = (Computer)JDKProxy.getProxy(huaWeiComputer);
//通过代理对象调用方法
proxy.sell();
}
}
输出:
售前了解
出售华为电脑
售后服务
可以看到无论多少个接口,只需要一个代理类就可以了。
3、CGLIB动态代理
CGLIB通过继承的方式进行代理,(让需要代理的类成为Enhancer(增强剂)的父类),无论目标对象有没有实现接口都可以代理,但是无法处理Final的情况。
3.1、引入依赖
导入cglib-3.1.jar
和asm-4.2.jar
包
3.2、代理类
package com.woniuxy.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CGLIBProxy {
public static Object getProxy(Object target) {
Enhancer enhancer = new Enhancer();
//设置需要创建子类的类
enhancer.setSuperclass(target.getClass());
//设置回调程序,调用时会执行该程序
enhancer.setCallback(new MethodInterceptor() {
@Override
// o: cglib 动态生成的代理类的实例
// method:实体类所调用的都被代理的方法的引用
// objects 参数列表
// methodProxy:生成的代理类对方法的代理引用
public Object intercept(Object arg0, Method method, Object[] arg2, MethodProxy arg3) throws Throwable {
System.out.println("售前咨询");
Object invoke = method.invoke(target, arg2);
System.out.println("售后服务");
return invoke;
}
});
//创建代理对象
return enhancer.create();
}
}
3.2、测试
public class App{
public static void main( String[] args ){
//得到委托类
HuaWeiComputer huaWeiComputer = new HuaWeiComputer();
//得到代理类
Computer proxy = (Computer)CGLIBProxy.getProxy(huaWeiComputer);
//通过代理对象调用方法
proxy.sell();
}
}
4、JDK代理与CGLIB代理的区别
JDK动态代理实现接口,CGLIB动态继承思想
JDK动态代理(目标对象存在接口时)执行效率高于CIGLIB
如果对象有接口实现,选择JDK代理,如果没有接口实现选择CGILB代理
思维导图
本文来自博客园,作者:icui4cu,转载请注明原文链接:https://www.cnblogs.com/icui4cu/p/18816549