一、代理模式
代理模式(Proxy Pattern)是一种结构型设计模式,它的概念很简单,它通过创建一个代理对象来控制对原始对象的访问。代理模式主要涉及两个角色:代理角色和真实角色 。代理类负责代理真实类,为真实类提供控制访问的功能,真实类 则完成具体的业务逻辑 。这样,当我们不方便或者不能直接访问真实对象时,可以通过代理对象来间接访问。使用代理模式主要有两个目的:一是保护目标对象,二是增强目标对象。
1.1、代理模式的实现方式
实现代理模式有两种方案:静态代理和动态代理 。静态代理 是指代理类在编译期 就已经确定,即需要事先手动编写一个代理类。动态代理则是在运行时动态生成代理类。
动态代理方案有两种实现:其一,通过Java本身自带java.lang.reflect.Proxy 类 来实现动态代理功能。其二,通过额外引入一个开源的高性能代码生成包CGlib来动态生成代理类。二者底层实现上都应用了反射和操作字节码技术
JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。
CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类 作为真实类的子类去代理父类,即代理类继承自真实类 。这种方式不需实现接口,可以作为JDK代理方式的补充方案。
二、静态代理的实现
抽象接口:定义视频播放器接口Player
java
public interface Player {
void loadVideo(String filename);
void playVideo(String filename);
}
真实类 :定义接口实现类VPlayer
java
public class VPlayer implements Player {
@Override
public void loadVideo(String filename) {
System.out.println("加载MP4视频文件:"+filename);
}
@Override
public void playVideo(String filename) {
System.out.println("播放MP4视频:"+filename);
}
}
代理类:定义代理类VPlayerProxy,实现同样的接口
java
public class VPlayerProxy implements Player {
private Player player;
public VPlayerProxy(Player player) {
this.player = player;
}
@Override
public void loadVideo(String filename) {
player.loadVideo(filename);
}
@Override
public void playVideo(String filename) {
player.playVideo(filename);
}
}
客户端调用
java
public class Client1 {
public static void main(String[] args) {
//直连方式
Player vplay=new VPlayer();
vplay.playVideo("aaa.mp4");
System.out.println();
//代理方式
Player proxy=new VPlayerProxy(vplay);
proxy.loadVideo("aaa.mp4");
proxy.playVideo("aaa.mp4");
}
}
最终得到的结果:
2.1、分析
客户端调用中,采用代理方式时,Player proxy=new VPlayerProxy(vplay);采用多态 的形式创建了代理类的对象 proxy,此时通过有参构造,传入的参数是真实类 的对象vplay。
通过上图可以知道,传入的真实类 的对象vplay赋值给了成员变量player。
再去调用代理对象的方法时,实际是使用真实类的对象去调用真实类的方法 。
三、JDK 实现动态代理
JDK动态代理是Java标准库中提供的一种代理方式,它可以在运行时动态生成一个代理对象,代理对象实现和原始类一样的接口,并将方法调用转发给被代理对象,同时还可以在方法调用前后执行额外的增强处理。JDK动态代理通过反射机制实现代理功能。
抽象接口:定义明星接口Star
java
package com.itheima.proxy;
public interface Star {
String sing(String name);
void dance();
}
真实类 :定义接口实现类BigStar
java
package com.itheima.proxy;
public class BigStar implements Star {
private String name;
public BigStar(String name) {
this.name = name;
}
public String sing(String name) {
System.out.println(this.name + "正在唱:" + name);
return "谢谢!";
}
public void dance() {
System.out.println(this.name + "正在优美的跳舞~~");
}
}
工具类 :定义生成代理类的工具类ProxyUtil,此时的代理类是在运行时动态生成的。
java
package com.itheima.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class ProxyUtil {
public static Star createProxy(BigStar bigStar) {
//ClassLoader loader,
//Class<?>[] interfaces,
//InvocationHandler h)
Star starProxy = (Star) Proxy.newProxyInstance(ProxyUtil.class.getClassLoader(),
new Class[]{Star.class},
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("sing")) {
System.out.println("准备话筒,收钱20万");
} else if (method.getName().equals("dance")) {
System.out.println("准备场地,收钱100万");
}
return method.invoke(bigStar, args);
}
});
return starProxy;
}
}
客户端调用
java
package com.itheima.proxy;
import org.springframework.cglib.proxy.Enhancer;
public class Test1 {
public static void main(String[] args) {
BigStar star = new BigStar("大明星");
Star starProxy = ProxyUtil.createProxy(star);
String rs = starProxy.sing("七里香");
System.out.println(rs);
}
}
最终得到的结果:
3.1、分析
JDK动态代理是基于反射 实现的,所以导入的包是import java.lang.reflect.Proxy;
采用的是Proxy 的静态方法newProxyInstance去创建代理对象。
Proxy.newProxyInstance中的参数含义:
1、ClassLoader loader:指定一个类加载器,把生成的代码对象加载到内存中。
一般采用 当前类.class**.getClassLoader()**
2、Class<?>[] interfaces:指定真实类实现的接口 ,用于指定生成的代理类有哪些方法
3、**InvocationHandler h:**这是一个接口,所以直接new出来的是它的匿名内部类。
4、整个流程如下:
4.1、首先创建代理对象starProxy **:**Star starProxy = ProxyUtil.createProxy(star);
4.2、调用代理对象的sing方法:String rs = starProxy.sing("七里香");
4.3、调用了代理对象的任何方法都会调用代理对象的invoke 方法,因此invoke 方法被称为回调方法 。
4.4、进入了invoke方法后就会进行判断:
if (method.getName().equals("sing")) {
System.out.println("准备话筒,收钱20万");
}
就会打印:准备话筒,收钱20万
4.5、调用真实对象的方法:
method.invoke(bigStar, args);会去调用真实对象的方法,其中bigStar是传入的真实对象,
method 是真实对象中的方法,args 是传入的参数,会传到真实对象的方法中。
四、cglib实现动态代理
CGLIB(Code Generation Library)是一个基于ASM(Java字节码操作框架)实现的代码生成库,它可以在运行时动态生成目标类的子类作为代理类 ,并覆盖其中的方法来实现代理功能 。与Java自带的JDK动态代理不同,CGlib动态代理可以代理没有实现接口的类。
真实类: 没有实现任何接口
java
//音频播放器
public class APlayer {
public void loadAudio(String filename) {
System.out.println("加载MP3音频文件:"+filename);
}
public void playAudio(String filename) {
System.out.println("播放MP3:"+filename);
}
}
工具类 :定义生成代理类的工具类CglibProxyFactory,此时的代理类是在运行时动态生成的。
java
public class CglibProxyFactory implements MethodInterceptor {
@SuppressWarnings("unchecked")
public <T> T getProxy(Class<T> clazz) {
Enhancer en = new Enhancer();
//设置代理的父类
en.setSuperclass(clazz);
//设置方法回调
en.setCallback(this);
//创建代理实例
return (T)en.create();
}
@Override
//参数中的object是目标对象,method和args是目标对象的方法和参数,methodProxy是方法代理
public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
Object result = null;
if ("loadAudio".equals(method.getName())) {
//通过继承的方法实现代理,因此这里调用invokeSuper
result = methodProxy.invokeSuper(object, args);
}
if ("playAudio".equals(method.getName())) {
result = methodProxy.invokeSuper(object, args);
}
return result;
}
}
客户端调用
java
public class Client3 {
public static void main(String[] args) {
APlayer aplayer=new APlayer();
APlayer proxy = new CglibProxyFactory().getProxy(aplayer.getClass());
//验证代理类的父类
System.out.println("代理类的父类:"+proxy.getClass().getSuperclass().getSimpleName());
System.out.println();
proxy.loadAudio("荷塘月色.mp3");
proxy.playAudio("荷塘月色.mp3");
}
}
最终的结果:
4.1、分析
整个流程如下:
1、
APlayer proxy = new CglibProxyFactory().getProxy(aplayer.getClass());
通过CglibProxyFactory工具类生成代理对象proxy。
2、
proxy.loadAudio("荷塘月色.mp3");
此时调用了代理对象中的loadAudio方法, 就会被代理对象中intercept方法拦截。
所有调用代理对象中的方法都会被intercept方法拦截。
3、 if ("loadAudio".equals(method.getName())) {
//通过继承的方法实现代理,因此这里调用invokeSuper
result = methodProxy.invokeSuper(object, args);
}
判断为true,就会进行result = methodProxy.invokeSuper(object, args);方法
result = methodProxy.invokeSuper(object, args);调用的是真实对象中的方法。
object表示真实对象,
methodProxy表示真实对象中的方法,
args表示传过去的参数
五、总结
1、 实现代理模式有两种方案:静态代理和动态代理。
2、静态代理 是指代理类在编译期 就已经确定,即需要事先手动编写一个代理类。
3、动态代理则是在运行时动态生成代理类。
4、JDK动态代理是基于接口实现的代理,只能代理实现了接口的类。
5、****CGlib方式是基于继承实现的代理,它不是指真实类需要继承某个父类,而是生成的代理类 作为真实类的子类去代理父类,即代理类继承自真实类 。这种方式不需实现接口。