俗话说:Coder不知动态代理,走在路上没人理!!!
所以本文尝试说明白java代理模式,代理中的静态代理和动态代理,java的动态代理如何写,动态代理的底层原理,spring aop中使用的cglib如何实现,以及底层原理,cglib和jdk的动态代理的区别,javassist的使用方法及底层实现原理
目录结构
1、静态代理
2、动态代理
2.1、jdk的动态代理
2.2、cglib
2.3、javassist
1、静态代理
为了便于理解动态理解,将常见的硬编码代理模式成为静态代理
代理模式的定义是给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。类似于实际生活中的中介,想必都知道,这里不赘述。
代理模式的结构图如下:
如图,我们使用JavaProgramer类实现Programer接口,ProxyJavaProgramer类也实现Programer接口,同时调用JavaProgramer类,对其code进行封装和改写。
具体代码如下:
//Programer 接口
public interface Programer {
void coding();
}
//JavaProgramer 类
public class JavaProgramer implements Programer {
public void coding() {
System.out.println("java");
}
}
//ProxyJavaProgramer 代理类
public class ProxyJavaProgramer implements Programer{
public void coding() {
JavaProgramer javaProgramer = new JavaProgramer();
System.out.println("proxy start...");
javaProgramer.coding();
System.out.println("proxy end...");
}
}
//测试类
public class Main {
public static void main(String[] args) {
Programer programer = new ProxyJavaProgramer();
programer.coding();
}
}
输出:
proxy start...
java
proxy end...
代理模式的作用:
1、隔离具体实现类,用户直接使用的是代理类,不需要知道具体实现类的实现细节
2、通过代理类对具体实现类进行增强,增加一些自定义功能
2、动态代理
上面讲述了静态代理,静态代理的缺点也是很明显的,1是需要程序员硬编码,2是如果一个接口有10个实现类,使用代理模式则需要编写10个代理模式,类数量剧增。动态代理则更加灵活一些,通过底层的实现,程序员只需要将需要代理的对象传进来,即可实现对应的代理类。将代理类的生成交给程序。
java中动态代理的实现方式有多种,这里我们只介绍三种。
分别是JDK自带动态代理,开源项目cglib和开源项目javassist。
下面分别讲述这三种如何实现动态代理及底层实现的原理
2.1JDK自带动态代理
java的自带动态代理实现的基本思想是:1:显示Invokhandler接口,2:使用Proxy类生成代理类对象,3:利用反射执行代理类的指定方法
直接上代码,一看就明白
//IProgramer接口
public interface IProgramer {
void coding();
}
//Programer实现类
public class Programer implements IProgramer{
public void coding() {
System.out.println("coding");
}
}
//统一的代理类(传入需要代理的类实例,即可)
public class JdkProxy implements InvocationHandler {
//代理对象
private Object target;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("start");
method.invoke(target,args);
System.out.println("start");
return null;
}
public JdkProxy(Object target) {
this.target = target;
}
}
//测试类
public class JdkProxyMain {
public static void main(String[] args) {
Programer programer = new Programer();
JdkProxy jdkProxy = new JdkProxy(programer);
IProgramer proxyProgramer =
(IProgramer) Proxy.newProxyInstance(
programer.getClass().getClassLoader(),
programer.getClass().getInterfaces(),
jdkProxy);
proxyProgramer.coding();
}
}
输出结果:
start
coding
start
根据代码,大家肯定很清楚动态代理是怎么回事了,下面先不着急了解底层实现的逻辑。我们紧接着看一下其他两种实现动态代理的方式:cglib和javassist
2.2cglib
cglib是一个开源的项目,使用cglib也可以实现动态代理。具体怎么实现,cglib的实现思路是,1:导入cglib依赖包,2:代理类实现MethodInterceptor,3:使用Enhance类得到动态代理的实例,并执行指定方法。
具体实现直接上代码,一看就懂
//被代理类
public class Programer {
void codeing(){
System.out.println("i am coding");
}
}
//代理类
public class Hacker implements MethodInterceptor {
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy)
throws Throwable {
System.out.println("begin");
methodProxy.invokeSuper(o,objects);
System.out.println("end");
return null;
}
}
//测试类
public class CglibMain {
public static void main(String[] args) {
Programer programer = new Programer();
Hacker hacker = new Hacker();
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(programer.getClass());
enhancer.setCallback(hacker);
Programer programerProxy = (Programer) enhancer.create();
programerProxy.codeing();
}
}
输出结果:
begin
i am coding
end
使用cglib的实现动态代理的过程更为简单。那么cglib和JDK动态代理有什么区别呢?区别主要有:
JDK动态代理和CGLIB字节码生成的区别?
1、JDK动态代理只能对实现了接口(注意必须有接口)的类生成代理,而不能针对类
2、CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法。因为是继承,所以该类或方法最好不要声明成final
3、cglib效率比JDK动态代理要差一点点,具体原因后面分析底层的实现的时候讲解
在Spring aop中使用了JDK动态代理和cglib,使用情况是,如果实现了接口则使用JDK动态代理,如果没有实现接口则使用cglib。
2.3javassist
javassist是一个字节码指令库,利用javassist可以修改.class文件的结构,以实现对类的修改和创建
下面直接上代码,看下javassist是符合修改方法和创建方法的
//被修改的类
public class Programer {
public void coding(){
System.out.println("coding");
}
}
//修改后的类
public class JavassistProgramer {
/**
* 使用javassist修改coding方法
*/
public void updateCoding() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.chanyi.ssist.Programer");
CtMethod updateCoding = cc.getDeclaredMethod("coding");
updateCoding.insertBefore("System.out.println(\"start\");");
updateCoding.insertAfter("System.out.println(\"end\");");
Object proxyClass = cc.toClass().newInstance();
Method execute = proxyClass.getClass().getMethod("coding");
execute.invoke(proxyClass);
}
/**
* 使用javassist新增一个方法
*/
public void newMethod()throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass cc = pool.get("com.chanyi.ssist.Programer");
CtMethod ctMethod = new CtMethod(CtClass.voidType, "newMethod", new CtClass[]{}, cc);
ctMethod.setModifiers(Modifier.PUBLIC);
ctMethod.setBody("{System.out.println(\"i am new method \");}");
cc.addMethod(ctMethod);
Object proxyClass = cc.toClass().newInstance();
Method personFlyMethod = proxyClass.getClass().getMethod("newMethod");
personFlyMethod.invoke(proxyClass);
}
}
//测试类
public class JavassistMain {
public static void main(String[] args) {
JavassistProgramer javassistProgramer = new JavassistProgramer();
try {
javassistProgramer.updateCoding();
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
输出结果:
start
coding
end
以上介绍了三种动态修改.class字节码,从而达到修改方法实现的方法。实际上修改.class字节码的方法还有很多。下面贴一张网上结构图,一看就懂
很明显这些方式的底层实现都是利用自己维护的指令类,修改和创建.class文件。
如果有想深入了解的可以学习一下ASM。这里不再赘述
参数资料:
1、https://www.cnblogs.com/rickiyang/p/11336268.html
2、https://blog.csdn.net/chenchaofuck1/article/details/51727605