场景说明
java很多框架都应用到了动态代理,比如拦截器,aop 等
个人开发的时候有很多功能也可以用到动态代理,比如现有代码需要加统一拦截,比如请求日志,或者校验敏感字,参数正确性等
这时候动态代理实现起来简单方便,那么都有哪些动态代理实现方式以及都有哪些区别呢,各个方式底层原理又是什么
本文主要针对这些问题进行介绍
动态代理实现方式
java proxy 方式
使用案例如下:
首先准备一个接口和一个实现类
interface IStudent {
Integer getId();
String getName();
}
public class Student implements IStudent {
private Integer id = null;
private String name = null;
public Student(){}
public Student(Integer id, String name) {
this.id = id;
this.name = name;
}
public Integer getId() {
return id;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
'}';
}
}
测试方法代码
@Test
public void javaProxyTest(){
Student student = new Student(1,"小明");
IStudent iStudent = (IStudent) Proxy.newProxyInstance(student.getClass().getClassLoader(), student.getClass().getInterfaces(),( proxy, method,args)->{
System.out.println("before method "+method.getName()+" invoke");
Object result = method.invoke(student,args);
System.out.println("after method "+method.getName()+" invoke");
return result;
});
System.out.println(iStudent.getId()+":"+iStudent.getName());
}
执行结果如下:
cglib 方式
使用如下:
@Test
public void cglibTest(){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(Student.class);
enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
System.out.println("before method "+method.getName()+" invoke");
Object result = methodProxy.invokeSuper(o,objects);
System.out.println("after method "+method.getName()+" invoke");
return result;
});
Student student = (Student) enhancer.create(new Class[]{Integer.class,String.class},new Object[]{1,"小明"});
System.out.println(student.getId()+":"+student.getName());
}
执行结果:
java proxy与 cglib 原理解析
我们我们来看生成代理以及代理类执行方法时流程两方面
proxy
proxy 生成代理类源码解析
生成代理类class 重要方法
ProxyClassFactory是重点类,生成代理
修改代码将proxy生成的代理类class保存到本地磁盘
我们修改一下代码,将生成的代理类保存到本地磁盘,修改代码如下:
//添加保存类到本地功能
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", iStudent.getClass().getInterfaces());
String path = "E:\\code\\proxy_cglib\\Proxy0.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
} catch (Exception e) {
e.printStackTrace();
}
我们再运行,看一下保存的代理类
proxy 直接通过生成的代理类class源码来看运行流程
我们将Proxy1.class拖拽到idea里看一下源码
内容太多,只看重点部分
可以看到所有的方法调用内部其实都是调用的我们传入的InvocationHandler
下面是将所有原始类的方法都反射出method
cglib
cglib 生成代理类源码解析
上面的方法时重点方法,调用asm 生成代理类
修改代码将cglib生成的代理类class保存到本地磁盘
我们添加一段代码,将cglib生成的代理类保存到本地磁盘,代码如下
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "E:\\code\\proxy_cglib");
我们再运行一下然后看一下保存的代理类
我们看到,居然生成了3个代理类文件,我们拖进idea,然后看下都是什么
这个是最重要的也就是我们实际的代理类,看看关键部分
可以看到, 实际上与proxy生成的代理类逻辑差不多,都是有代理的时候直接执行代理类对应的方法,也就是执行我们传入的MethodInterceptor./intercept方法*
来看看这个的代码
再来看看这个生成的类的代码
cglib生成fastclass 子类作用讲解
为什么cglib要生成这俩类呢,看名字可以看出,表明的是fastdfs,也就是为了加速用的,怎么加速呢? 我们通过上图可以看见,cglib将每个方法都映射成了一个hashcode 对应的数字
类似map的key-value,key是数字,value是方法本身,这样做的目的是什么呢,主要是为了避免使用java的反射执行去执行方法,我们看proxy
最后回调的时候是使用method.invoke去反射执行原始类方法的,但是cglib不想这样,所以就做了一个类似如下这样的调用:*
switch(方法hashcode)
case: 方法1 的hashcode
return 原始类.方法1
case: 方法2的hashcode
return 原始类.方法2
这样做是为了加速处理,但其实再jdk1.8之后 ,cglib的速度已经和proxy相比没有优势了
proxy与 cglib 主要区别总结(建议收藏,大家面试可能会用到)
1.proxy 是jdk提供的,生成代理类的代码是底层实现的 cglib是基于asm 字节码生成器生成的,cglib 是基于asm接口显示调用的生成代理类
2.proxy 是代理类是必须基于接口的,cglib 是不强制使用接口进行生成代理类的
3.proxy 的代理类执行方法时InvocationHandler 的method.invoke是通过java反射执行的 cglib 是通过生成了2个fastClass 进行类与方法的映射,内部通过switch(方法hashcode)然后