代理模式顾名思义就是进行代理,简单来说就是经纪人,他直接与你沟通,并帮助做更多的事情。
在程序中,代理模式那可谓是非常重要,像Spring的aop就是动态代理,而且很多框架中都是用到了代理模式。
代理模式在我们不改变原有代码的基础上对某一个方法进行增强,这种增强可以是提前编写好的代码,也可以是自动编写的代码。这就分为静态代理和动态代理。
静态代理
静态代理是显式的帮助我们对目标类进行增强。
我们定义一个场景:12306提供最基本的卖票功能,但也有很多软件也卖票,例如美团、携程等。其实在第三方平台买票实际也是要登陆12306账号的,不过第三方会提供一些额外的功能帮你买票,比如说加速包抢票等。而这些额外的服务其实就是在12306买票的基础上增加了额外的功能,简单理解就是进行了加工,这其实就是一种代理。
/**
* 1.定义卖票接口
*/
public interface Tickets {
void sell();
}
/**
* 2、12306类
*/
public class Demo12306 implements Tickets {
@Override
public void sell() {
System.out.println("卖票");
}
}
/**
* 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能
*/
public class MeiTuan implements Tickets {
private Tickets tickets;
MeiTuan(Tickets tickets){
this.tickets = tickets;
}
@Override
public void sell() {
System.out.println("加速包开始抢票");
//这里是12306原本的卖票
tickets.sell();
System.out.println("加速包抢票成功");
}
}
public class Test {
public static void main(String[] args) {
//创建原始的12306类
Tickets demo12306 = new Demo12306();
//美团对12306进行代理,增加额外的抢票功能
Tickets meiTuan = new MeiTuan(demo12306);
/**
* 打印结果:
* 加速包开始抢票
* 卖票
* 加速包抢票成功
*/
meiTuan.sell();
}
}
这种方式虽然在不改变原本类的基础上增加了功能,但是我们如果每有代理需求的时候,就要一直创建代理类,而且很不灵活,并且在jvm运行之前.class文件就已经生成了。
动态代理
动态代理相比较于静态代理区别在于:我们不用设计一个代理类来具体的来代理某一个对象,而是程序运行的时候在jvm中帮我们生成代理类。
而代理和被代理之前有一个包含的关系,代理要从被代理那里获取到所有方法进行代理。这样以来就需要被代理类有一个统一的对外服务标准,这个标准就是接口和抽象类。
对于动态代理我们有两种实现方式,一个是基于接口的实现、另一个是基于抽象类的实现。
jdk实现动态代理
jdk本身就帮我们实现了动态代理,这种代理是基于接口方式进行实现的。
还是之前卖票的例子,但是这一次是用动态代理方式进行实现。
美团并不去实现卖票接口了,而是实现
InvocationHandler
接口,在创建代理类的时候就不是直接调用方法了(当然也调用不了,没有实现卖票接口了),这个时候使用
Proxy.newProxyInstance()
来进行创建。我们先上代码,再来进行解释。
/**
* 1.定义卖票接口
*/
public interface Tickets {
void sell();
}
/**
* 2、12306类
*/
public class Demo12306 implements Tickets {
@Override
public void sell() {
System.out.println("卖票");
}
}
前两步其实都是一样的
/**
* 3.美团类里包含了12306类,并且在12306类的基础上增加了抢票功能
* 这里实现InvocationHandler接口
* 本类不是真正的代理类了
*/
public class MeiTuan implements InvocationHandler {
private Tickets tickets;
MeiTuan(Tickets tickets){
this.tickets = tickets;
}
/***
* 该方法是代理类在执行时候调用的方法
* @param proxy 代理类本身(这里就是美团)
* @param method 代理类调用的具体方法 (我们调用的卖票所以这里就是卖票方法、通俗点就是你调用什么方法,这里就是什么方法)
* @param args 方法参数 上面方法对应的参数
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("加速包开始抢票");
//这里是12306原本的卖票,但是是使用反射进行调用
Object invoke = method.invoke(tickets, args);
System.out.println("加速包抢票成功");
return invoke;
}
}
从这一步开始就一下变的面目全非了,代理类去实现
InvocationHandler
接口,重写
invoke
方法。
public class Test {
public static void main(String[] args) {
//创建原始的12306类和代理类
Tickets demo12306 = new Demo12306();
MeiTuan proxy = new MeiTuan(demo12306);
/**
* 美团对12306进行代理,增加额外的抢票功能
* 这里通过Proxy.newProxyInstance方法在运行的时候动态创建代理类
* 这里有三个参数
* ClassLoader loader, 类加载器,这里主要是选择代理的类加载器
* lass<?>[] interfaces, 被代理类的所有接口
* InvocationHandler h 代理类
*/
Tickets meiTuan = (Tickets) Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
demo12306.getClass().getInterfaces(),
proxy);
/**
* 打印结果:
* 加速包开始抢票
* 卖票
* 加速包抢票成功
*/
meiTuan.sell();
}
}
最终代理对象是通过
Proxy.newProxyInstance
创建得出的。
其实到这里你只要跟着案例把代码敲出来,就已经实现了动态代理。但是你肯定会有个疑问以下为什么用到这个方法,并且参数又是什么意思?
首先是代理类重写的invoke方法,这里有三个参数。我们回顾一下静态代理,静态代理中我们显式的实现了所有被代理类实现的接口,所以我们有被代理类的所有方法,可能是多个。那么在增强功能的时候,我们就需要每一个方法进行增强,耗时耗力。但是我们把所有方法抽象成
Method
,在Method前后进行增强不就同时增强了所有方法吗。
这里的第二个和第三个参数就比较明朗了,既然我把所有方法抽象成
Method
,那么自然需要反射调用具体的方法。
那么第一个参数到底是什么呢?这个其实是代理类本身,它在调用不同的方法的时候把代理类本身也就是this传递了进来,为什么要传递代理类呢。
- 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
- 可以将代理对象返回以进行连续调用
当接口中方法返回值是接口类型的时候,可以在invoke方法中返回代理对象,这样在使用代理对象的时候可以进行链式调用,具体实例自行尝试。
Proxy.newProxyInstance(proxy.getClass().getClassLoader(),
demo12306.getClass().getInterfaces(),
proxy);
这里第一个参数就是类加载器,他的作用是用来加载代理对象的,因为我们的代理类是在内存中生成的,那么如果我们在内存中生成用了额外的加密手段,那么我们就要自定义类加载器来进行加载。但是这里jdk就是正常的生成内存中的.class文件,所以我们用正常的application加载器就可以了。
第二个参数就是被代理类的所有接口,第三个参数就是
InvocationHandler
,其实就是代理类在执行对应方法的时候要调用的方法。
手撕jdk动态代理源码
这里介绍几个关键的方法
首先是Proxy类的719行,查找或生成代理类
然后是Proxy类的639行,这里是具体在内存中生成.class的方法
进入到该方法后第二行
final byte[] classFile = gen.generateClassFile();
点击该方法进入
这里是ProxyGenerator类的第427行,这里详细介绍了实现步骤仔细看注释
第三步就开始在jvm中拼接代理类,这时候是.class文件,最后会使用类加载器把.class文件加载到jvm中就获得了Class对象
然后我们在回到Proxy类的719行往下看,这不就是反射生成对象吗。所以我们就拿到了代理对象。
这里.class文件由于在jvm中我们看不到,这里其实可以把内存中的.class输出到文件中然后进行反编译。
public class Test2 {
public static void main(String[] args) throws IOException {
byte[] data = ProxyGenerator.generateProxyClass("$Proxy0.class", new Class[]{Tickets.class});
FileOutputStream out = new FileOutputStream("$Proxy0.class");
out.write(data);
out.close();
}
}
这时候idea目录下已经多出了这个文件
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package $Proxy0;
import com.cstor.设计模式.代理模式.静态.Tickets;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
public final class class extends Proxy implements Tickets {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public class(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final void sell() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("com.cstor.设计模式.代理模式.静态.Tickets").getMethod("sell");
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
细心的朋友可以看到这里真实的代理类里面的属性是每个方法,不过在原有接口的方法上多出了hashCode、toString、equals。然后在方法中调用的
InvocationHandler
中的invoke方法,传递对应的
Method
自己实现动态代理
其实到这里我们就发现了,代理类其实就是把方法抽象为属性,然后依旧是实现接口,然后重写接口的方法,只不过方法中调用的是
InvocationHandler
中的invoke方法。
这里我们可以自己实现编写动态代理。
在手写动态代理前,先理清楚思路。
1.重写InvocationHandler接口
2.重写类加载器,可以进行文件的二进制加载
3.重写Proxy.newProxyInstance方法帮助我们创建.class文件,并且使用类加载器加载,用反射创建实例。
只不过我们的Proxy.newProxyInstance方法采用手动拼接方式生成类文件然后编译生成.class文件,所以具有不通用性,所以我们还是按照之前的卖票来进行编写。
/**
* 1.定义卖票接口
*/
public interface Tickets {
void sell() throws NoSuchMethodException;
}
/**
* 2、12306类
*/
public class Demo12306 implements Tickets {
@Override
public void sell() {
System.out.println("卖票");
}
}
/**
* 3、重写InvocationHandler接口
* 这里不把数组作为参数,方便拼接java文件
*/
public interface MyInvocationHandler {
Object invoke(Object proxy, Method method, Object args);
}
/**
* 4、编写自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
private File dir;
//需要加载的.class文件路径
public MyClassLoader(String path) {
dir = new File(path);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
/**
* 1.获取到该.class文件
* 2.写成流
* 3.使用defineClass加载类
*/
File clazzFile = new File(dir, name + ".class");
if (clazzFile.exists()) {
try {
FileInputStream input = new FileInputStream(clazzFile);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int len;
while ((len = input.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
return defineClass("com.demo." + name,
baos.toByteArray(),
0,
baos.size());
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
return super.findClass(name);
}
}
/**
* 自定义的动态代理Proxy类
*/
public class MyProxy {
/**
* 这里第二个参数没有做成数组是因为偷懒,也就是只能实现一个接口,有兴趣的可以改为数组
*/
public static Object newProxyInstance(MyClassLoader loader, Class interfaces, MyInvocationHandler handler) {
try {
//把java文件写入到本项目com.emo包下
String proxyClass = getClassString(interfaces);
String filePathName = "src/com/demo/$Proxy0.java";
File file = new File(filePathName);
FileWriter fw = new FileWriter(file);
fw.write(proxyClass);
fw.flush();
fw.close();
//编译上面生成的java文件生成.class
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null,
null, null);
Iterable javaFileObjects = fileManager.getJavaFileObjects(filePathName);
CompilationTask task = compiler.getTask(null, fileManager, null, null, null, javaFileObjects);
task.call();
fileManager.close();
//通过类加载器加载.class文件,使用反射创建对象
Class<?> proxy0Clazz = loader.findClass("$Proxy0");
Constructor<?> constructor = proxy0Clazz.getConstructor(MyInvocationHandler.class);
Object instance = constructor.newInstance(handler);
return instance;
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return null;
}
static String rt = "\r\n";
/**
* 手动拼接代理类
*/
private static String getClassString(Class interfaces) {
//01.使用拼凑字符串的方式将内存中的代理类拼出来
return "package com.demo;" + rt +
"import java.lang.reflect.Method;" + rt +
"public class $Proxy0 implements " + interfaces.getName() + "{" + rt +
"MyInvocationHandler h;" + rt +
"public $Proxy0(MyInvocationHandler h) {" + rt +
"this.h = h;" + rt +
"}"
+ getMethodString(interfaces) + rt + "}";
}
private static String getMethodString(Class interfaces) {
String proxyMe = "";
for (Method method : interfaces.getMethods()) {
proxyMe += "public void " + method.getName() + "() throws NoSuchMethodException {" + rt +
"Method md = " + interfaces.getName() + ".class.getMethod(\"" + method.getName() + "\",new Class[]{});" + rt +
"this.h.invoke(this,md,null);" + rt +
"}" + rt;
}
return proxyMe;
}
}
public class MeiTuan implements MyInvocationHandler {
private Tickets tickets;
MeiTuan(Tickets tickets){
this.tickets = tickets;
}
@Override
public Object invoke(Object proxy, Method method, Object args) {
System.out.println("加速包开始抢票");
try {
tickets.sell();
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
System.out.println("加速包抢票成功");
return null;
}
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException {
Tickets demo12306 = new Demo12306();
MeiTuan proxy = new MeiTuan(demo12306);
Tickets meiTuan = (Tickets) MyProxy.newProxyInstance(new MyClassLoader("src/com/demo"),
com.demo.Tickets.class,
proxy);
/**
* 打印结果:
* 加速包开始抢票
* 卖票
* 加速包抢票成功
*/
meiTuan.sell();
}
}
这里发现动态代理生效了,这里只是给大家做个小例子,实际过程中代码编写不是很完美不用纠结,体会过程最重要。
这个时候在我们的包下也生成了java类和.class文件
package com.demo;
import java.lang.reflect.Method;
public class $Proxy0 implements com.demo.Tickets {
MyInvocationHandler h;
public $Proxy0(MyInvocationHandler h) {
this.h = h;
}
public void sell() throws NoSuchMethodException {
Method md = com.demo.Tickets.class.getMethod("sell", new Class[]{});
this.h.invoke(this, md, null);
}
}
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package com.demo;
import java.lang.reflect.Method;
public class $Proxy0 implements Tickets {
MyInvocationHandler h;
public $Proxy0(MyInvocationHandler var1) {
this.h = var1;
}
public void sell() throws NoSuchMethodException {
Method var1 = Tickets.class.getMethod("sell");
this.h.invoke(this, var1, (Object)null);
}
}
cglib实现动态代理
jdk是通过接口实现代理,而cglib则是通过继承实现代理,对于final类无法代理
底层是采用字节码方式实现的
还是使用原来的例子
maven项目添加依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.2.2</version>
</dependency>
public abstract class Tickets {
abstract void sell();
}
public class Demo12306 extends Tickets {
@Override
public void sell() {
System.out.println("卖票");
}
}
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
public Object getProxy(Class clazz) {
//设置需要创建子类的类
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
//通过字节码技术动态创建子类实例
return enhancer.create();
}
//实现MethodInterceptor接口方法
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
System.out.println("加速包开始抢票");
//通过代理类调用父类中的方法
Object result = proxy.invokeSuper(obj, args);
System.out.println("加速包抢票成功");
return result;
}
}
public class Test {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Tickets tickets = (Tickets) proxy.getProxy(Demo12306.class);
tickets.sell();
}
}
最终结果和jdk的动态代理结果一致
参考博客:
https://blog.csdn.net/tanggao1314/article/details/50450459
https://www.cnblogs.com/9513-/p/8432276.html
https://www.cnblogs.com/yaphetsfang/articles/11274083.html
https://www.cnblogs.com/suizhikuo/p/13941272.html