简单理解动态代理
动态代理其实可以看成两部分组成,动态和代理,动态体现在自动生成,不用我们自己操心,代理则是找个人帮你做完事,也不用自己操心。
在日常开发程序中,其实存在大部分类是不能够轻易修改,也不敢改,但该类确实已无法满足现在的功能,那么怎么样进行功能增强呢?这里我们使用到动态代理模式,降低代码耦合度。
什么是静态代理?
有动态代理自然而然少不了静态代理,这里我们先演示下什么静态代理,对于动态代理就非常的好理解了。
场景演示:
先编写对库存进行减少操作的实现类
public interface IStockService {
void reduce();
}
public class StockServiceImpl implements IStockService {
@Override
public void reduce() {
System.out.println("reduce stock...");
}
}
上述代码我们可以当做是日常中已经编写很久的类(不好修改的类),现在要在reduce
功能前后加上日志输出,这里不推荐继承,直接使用 组合方式
,接着定义好一个新类,既然要进行功能增强,肯定就要去实现这个接口IStockService
提供的原有所有的功能,然后在在原有功能上扩展我们自己的新功能;定义好该类就可以看成代理类,这也就是我们所述的静态代理模式
提示:若结合工厂模式会有奇效
public class StockLogServiceProxy implements IStockService {
//将存在很久的类IStockService通过组合方式传入
private IStockService stockService;
public StockLogServiceProxy (IStockService stockService) {
this.stockService = stockService;
}
public void reduce() {
System.out.println("reduce before info...");
stockService.reduce();
System.out.println("reduce after info...");
}
}
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
//组合方式传入我们原来的库存对象
StockLogServiceLProxy proxy = new StockLogServiceLProxy(stockService);
//库存原始方法
//stockService.reduce();
//调用代理类方法
proxy.reduce();
}
}
从输出结果可以看出此功能已经okay
reduce before info...
reduce stock...
reduce after info...
但是随着时间推移,现在又要实现一个数据校验功能, 因为在扣减库存之前肯定是要判断数据是否正确。检验完之后=>日志输出=>扣减库存。于是我们又是与同样的方式再新定义个有检验功能的代理类
public class StockVerfiyServiceProxy implements IStockService {
//依然是通过组合注入原始类
private IStockService stockService;
public StockVerfiyServiceProxy(IStockService stockService) {
this.stockService = stockService;
}
@Override
public void reduce() {
System.out.println("reduce verify before...");
stockService.reduce();
System.out.println("reduce verify after...");
}
}
测试结果发现也已经okay,但是如果每次来一个新功能都这样去定义一个新的代理类,这样做很不友好,从而衍生出了动态代理,即自动帮我们生成XxxProxy
代理类,然后我们只需要写新功能即可。
reduce verify before...
reduce before info...
reduce stock...
reduce after info...
reduce verify after...
动态代理模式
上述的StockLogServiceLProxy
、StockVerfiyServiceProxy
都是自定义代理类,但因为每有一个新功能来就要写一个代理类很不友好,java
中提供了一种 动态代理模式
,自动帮我们生成代理类,而我们只需要专注于写新功能实现
实现InvocationHandler接口
因为代理类不需自己写,但需要实现的新功能需写上,那么写在哪呢?jdk动态代理提供了一个接口InvocationHandler
,实现该接口,然后将需要被代理的接口通过组合方式注入,接着调用invoke
方法即可
- 实现接口
InvocationHandler
- 注入被代理接口(原始类)
- 调用
invoke
方法
下面对reduce
方法添加日志输出功能、校验功能,总之你之后新功能都可以往这里写
//第一步:实现InvocationHandler 接口
public class StockServiceHandler implements InvocationHandler {
//第二步:通过组合方式注入进来
//IStockService是被代理的接口,reduce方式需要被增强
private IStockService target;
public StockServiceHandler(IStockService target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//...新功能
System.out.println("reduce verify before info...");
System.out.println("reduce before info...");
//第三步:调用invoke方法,前后输出语句表示reduce方法的日志输出功能
Object invoke = method.invoke(target);
//....新功能
System.out.println("reduce after info...");
System.out.println("reduce verify after info...");
return invoke;
}
}
构建代理类
上述我们的日志、检验代理类不用手写,可以通过java提供方法自动生成,方法如下:
@CallerSensitive
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
可以看到需要传入三个参数,类加载器、Clazz
接口、上述的InvocationHandler
实现类,下面先演示怎么生成,然后在具体分析这三个参数
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
//这里生成的并不是接口IStockService,而是新的代理类$Proxy0,该代理类加强了IStockService接口中的reduce方法
IStockService iStockService = (IStockService)Proxy.newProxyInstance(
IStockService.class.getClassLoader(), //类加载器
new Class[]{IStockService.class},//Clazz接口
new StockServiceHandler(stockService));//InvocationHandler 功能实现类
//调用代理类生成的reduce方法,也可以说是继承接口IStockService的reduce方法,角度不一样理解不一样
//调用reduce方法,reduce里面会调用invoke方法
iStockService.reduce();
}
}
运行结果如下所示和上述静态代理模式一样
reduce verify before info...
reduce before info...
reduce stock...
reduce after info...
reduce verify after info...
反编译代理类
虽然我们不用再手写代理类了,但是我们还是想到知道代理类中发生了啥,通过JDK-GUI反编译得到$Proxy0
代理类的结构,这里只需要关注两个方法,$Proxy0
构造方法、IStockService接口定义的方法
,通过代码将生成的代理类存储到本地磁盘
import mapper.impl.StockLogServiceLProxy;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
/**
* 代理类的生成工具
*
* @author ChenHao
* @since 2019-4-2
*/
public class ProxyGeneratorUtils {
/**
* 把代理类的字节码写到硬盘上
*
* @param path 保存路径
*/
public static void writeProxyClassToHardDisk(String path) {
// 第一种方法
// System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", true);
// 第二种方法
// 获取代理类的字节码
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", StockLogServiceLProxy.class.getInterfaces());
FileOutputStream out = null;
try {
out = new FileOutputStream(path);
out.write(classFile);
out.flush();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ProxyGeneratorUtils.writeProxyClassToHardDisk("D:/$Proxy0.class");
}
}
通过代码可以发现代理类自动实现了接口IStockService
(被代理接口),然后通过super
调用父类Proxy
构造方法注入我们自定义的InvocationHandler
实现类,然后覆写reduce
方法,reduce
方法里面调用我们在InvocationHandler
类中实现的invoke
方法,看完这段描述,接着看代码里面的注释
import mapper.*;
import java.lang.reflect.*;
public final class $Proxy0 extends Proxy implements IStockService
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
//Proxy0的构造方法是通过调用父类Proxy构造方法完成InvocationHandler的注入
//那么怎么注入InvocationHandler的实现类呢?待会后面会说到
public $Proxy0(final InvocationHandler invocationHandler) {
super(invocationHandler);
}
//覆写了IStockService接口中的reduce方法,然后里面通过调用我们实现的InvocationHandler子类实现功能的增强
//这就是为什么在spring、mybatis中为什么在找一个方法被谁调用的时,发现这个方法死活找不到调用的地方,其实这个方法
//的调用实在自动生成的代理类中被调用,而我们debuge的时候是看不到这个调用的
//但是我们知道了动态代理之后,我们就应该明白代理类调用的方法是被加强过的方法,不是你之前那个原始方法
public final void reduce() {
try {
super.h.invoke(this, $Proxy0.m3, null);
}
catch (Error | RuntimeException error) {
throw;
}
catch (Throwable t) {
throw new UndeclaredThrowableException(t);
}
}
//下面所有方法不用关心
public final boolean equals(final Object o) {
return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[]);
}
public final String toString() {
return (String)super.h.invoke(this, $Proxy0.m2, null);
}
public final int hashCode() {
return (int)super.h.invoke(this, $Proxy0.m0, null);
}
static {
$Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
$Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
$Proxy0.m3 = Class.forName("mapper.IStockService").getMethod("reduce", (Class<?>[])new Class[0]);
$Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
}
}
看完这个反编译之后的代理类,可以总结一点就是:自己会去实现接口,然后覆写接口中的方法,然后覆写的方法中再去调用我们自定义的invoke
方法即可,流程图如下所示:
Proxy类
$Proxy0名字的由来?
点进Proxy.newProxyInstance
方法查看源码,会看到一行代码
Class<?> cl = getProxyClass0(loader, intfs);
然后继续跟踪进入内部实现会发现apply
方法,然后进入ProxyClassFactory
类找到apply
方法,清晰可以看见一行代码
proxyPkg
表示全包名proxyClassNamePrefix
写死的字符串常量$Proxynum
是自增的长整型数
所以可以知道$Proxy0
这个代理类的名字是怎么来的
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
紧接着下面一行通过ProxyGenerator
来加载代理类文件,然后在通过defineClass0
定义$Proxy0
代理类,自始至终,我们的代理类Clazz
就已经自动生成完成(有了字节码文件就可以做任何事情了),至此getProxyClass0
生成字节码的方法就已经结束,接下来就是生成代理来对象的过程,那么问题来了?
我们自己定义的InvocationHandler
实现类什么时候被注入进去的呢?下面开始会说到
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
注入InvocationHandler实现类
前面已经通过方法把代理类的字节码文件都给生成好了,下面就是开始怎么样生成代理类对象的过程?我们回到Proxy.newProxyInstance
方法中,紧接着后面一行代码
return cons.newInstance(new Object[]{h});
cons
是代理类的Clazz
对象,newInstance
方法把我们自定义的InvocationHandler
实现类给注入进来,在继续进入内部实现, 发现是通过反射中有参构造方法进行new代理类实例
此处ca
是代理类,前面通过反编译知道代理类中$Proxy0
有参构造方法就是需要传入InvocationHandler
实现类,就是在这一行代码实现的,所以InvocationHandler
实现类的注入是在代理类通过反射中有参构造方法中注入的
T inst = (T) ca.newInstance(initargs);
return inst;
解析newProxyInstance()三大参数
前面Proxy.newProxyInstance
方法参数如下:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
参数一:ClassLoader loader 类加载器
所有的类被加载都是需要通过类加载进行引导加载进入内存,然而我们动态代理中代理类是在运行时生成的,所以这里也就是为什么需要传入一个类加载器的原因
大部分情况下类加载器只需要注入接口的类加载即可,如果不填接口的也行,我这里测试发现只要在你的根目录(classes
文件夹)下面能够找到的类就都可以,最好不要写那种敏感包下的类(java
、javax
、sun
等核心包)
参数二:Class[] interfaces Clazz接口数组
只要传入接口对象即可:被代理接口的Clazz
文件,可以通过两种方式:
方式一:new Class[]{} 方式
在数组中传入接口的Clazz
文件,一定要是接口,否则会报错说不是一个interface
异常
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
IStockService iStockService = (IStockService)Proxy.newProxyInstance(
A.class.getClassLoader(),
new Class[]{IStockService.class},
new StockServiceHandler(stockService));
iStockService.reduce();
}
}
方式二:getClass() 方式
getClass()
方法是一个实例方法,那么必然是由对象才可以调用,所以得先要有对象才可以调用此方法,这个对象我们可以使用被代理类的对象即可,如果是通过getClass()
方法,配合getInterfaces()
方法一起使用
public final native Class<?> getClass();
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
IStockService iStockService = (IStockService)Proxy.newProxyInstance(
A.class.getClassLoader(),
stockService.getClass().getInterfaces(),
new StockServiceHandler(stockService));
iStockService.reduce();
}
}
在这里需要注意方法getInterfaces()
的使用,查看JDK1.8API,返回值是包含表示该类实现的所有接口的对象的数组,这句话值得品味,举个反例,代码如下:
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
IStockService iStockService = (IStockService)Proxy.newProxyInstance(
A.class.getClassLoader(),
IStockService.class.getInterfaces(),
new StockServiceHandler(stockService));
iStockService.reduce();
}
}
执行抛出异常:
Exception in thread "main" java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to mapper.IStockService
at mapper.StockTest.main(StockTest.java:14)
因为通过IStockService
本身就一个接口,上面已经没有接口,所以getInterfaces()
获取到一个数组长度为0的数组,长度为0,其实压根就是没有获取到值,自然而然底层反射也不会成功。反正你最终给到newProxyInstance()
方法就是一个接口数组里面包含着所有接口的Clazz
▶推荐这个参数使用自己 new
数组方式 (new Class[]{IStockService.class}
),绝对不会报错。
参数三:InvocationHandler 功能实现类
InvocationHandler
接口就是给我们自定义功能的一个规范,抽象功能的接口,实现invoke
方法即可
代码优化
Proxy.newProxyInstance()
这个方法调用其实是一个公用部分,我们可以将其抽取出一个共有工具类,专门用于生成代理类
public class StockTest {
public static void main(String[] args) {
IStockService stockService = new StockServiceImpl();
IStockService proxyInstance = getProxyInstance(IStockService.class, new StockServiceHandler(stockService));
proxyInstance.reduce();
}
public static <T> T getProxyInstance(Class<T> clazz, InvocationHandler handler) {
return (T)Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, handler);
}
}
InvocationHandler
也可以定义成泛型,用于多个接口使用,做成通用的工具类,大部分情况下是一个接口有很多的实现类,不写泛型也可以
package mapper.impl;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class StockServiceHandler<T> implements InvocationHandler {
private T target;
public StockServiceHandler(T target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("reduce verify before info...");
System.out.println("reduce before info...");
Object invoke = method.invoke(target);
System.out.println("reduce after info...");
System.out.println("reduce verify after info...");
return invoke;
}
}