静态代理和动态代理的区别
泛域名ssl证书 239元1年送1个月、单域名39元1年,Sectigo(原Comodo证书)全球可信证书,强大的兼容性,高度安全性,如有问题7天内可退、可开发票
加微信VX 18718058521 备注SSL证书
【腾讯云】2核2G4M云服务器新老同享99元/年,续费同价
静态代理
静态代理,设计模式的代理模式举例通常是用这种方式实现的,对于每一个要代理的类为了添加相同的操作,需要分别去实现其接口,容易造成代理类过多
public interface Subject { public void doSomething(); } public class RealSubject inplements Subject { public void doSomething() { // haha } } public class SubjectProxy implements Subject { Subject origin = new RealSubject(); public void doSomething(){ // 这里添加要在方法执行前执行的逻辑 origin.doSomething(); // 这里添加要在方法执行后执行的逻辑 } }
静态代理是在编译时就将接口、实现类、代理类一股脑儿全部手动完成,但如果我们需要很多的代理,每一个都这么手动的去创建实属浪费时间,而且会有大量的重复代码,此时我们就可以采用动态代理,动态代理可以在程序运行期间根据需要动态的创建代理类及其实例,来完成具体的功能。
动态代理
Java中的动态代理有两种实现方式:
Jdk动态代理
自Java 1.3以后,Java提供了动态代理技术,允许开发者在运行期创建接口的代理实例,后来这项技术被用到了Spring的很多地方。
JDK动态代理主要涉及java.lang.reflect包下边的两个类:Proxy和InvocationHandler。其中,InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。
public interface Subject { void doSomething(); } public static class RealSubject implements Subject { @Override public void doSomething() { System.out.println("haha"); } } public static class MyInvocationHandler implements InvocationHandler { //目标对象 private Subject sub; public MyInvocationHandler(Subject sub) { this.sub = sub; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("切面方法"); //调用目标类的目标方法 Object obj = method.invoke(this.sub, args); System.out.println("切面方法"); return obj; } } public static class Test { public static void main(String[] args) { Subject subject = new RealSubject(); MyInvocationHandler invocationHandler = new MyInvocationHandler(subject); //生成代理对象,此代理对象实现了Subject接口 Subject proxyInstance = (Subject) Proxy.newProxyInstance( subject.getClass().getClassLoader(), subject.getClass().getInterfaces(), invocationHandler); proxyInstance.doSomething(); } }
JDK动态代理有一个限制,就是它只能为接口创建代理实例,而对于没有通过接口定义业务方法的类,如何创建动态代理实例?答案就是CGLib。
Cglib
CGLib采用底层的字节码技术(ASM),全称是:Code Generation Library,CGLib可以为一个类创建一个子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑。
public class Base { //被代理类 public void add() { System.out.println("add ------------"); } } public class CglibProxy implements MethodInterceptor { //此为代理类,用于在pointcut处添加advise public Object intercept(Object object, Method method, Object[] args, MethodProxy proxy) throws Throwable { //拦截器 // 添加切面逻辑(advise),此处是在目标类代码执行之前,即为MethodBeforeAdviceInterceptor。 System.out.println("before-------------"); // 执行目标类add方法 proxy.invokeSuper(object, args); // 添加切面逻辑(advise),此处是在目标类代码执行之后,即为MethodAfterAdviceInterceptor。 System.out.println("after--------------"); return null; } } public class Factory { //工厂类,生成加强过的目标类 //获得增强之后的目标类,即添加了切入逻辑advice之后的目标类 public static Base getInstance(CglibProxy proxy) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(Base.class); //回调方法的参数为代理类对象CglibProxy,最后增强目标类调用的是代理类对象CglibProxy中的intercept方法 enhancer.setCallback(proxy); // 此刻,base不是单纯的目标类,而是增强过的目标类 Base base = (Base) enhancer.create(); return base; } } public class Test { //测试类 public static void main(String[] args) { CglibProxy proxy = new CglibProxy();//代理类 // base为生成的增强过的目标类 Base base = Factory.getInstance(proxy); base.add(); } }
两者区别
JDK动态代理是面向接口的。
CGLib动态代理是通过字节码底层继承要代理类来实现(如果被代理类被final关键字所修饰,那么会失败)
JDK动态代理所创建的代理对象,在以前的JDK版本中,性能并不是很高,虽然在高版本中JDK动态代理对象的性能得到了很大的提升,但是他也并不是适用于所有的场景。主要体现在如下的两个指标中:(以下是旧版本JDK与Cglib对比)
(jdk<1.8)CGLib所创建的动态代理对象在实际运行时候的性能要比JDK动态代理高不少,有研究表明,大概要高10倍;
(jdk<1.8)但是CGLib在创建对象的时候所花费的时间却比JDK动态代理要多很多,有研究表明,大概有8倍的差距;
JDK动态代理的版本优化中主要是针对虚拟机对反射调用的优化,优化如下:
在jdk1.6中,反射方法的调用在15次以内是调用本地方法,即是java到c++代码转换的方法,这种方式比直接生成字节码文件要快的多,而在15次之后则开始使用java实现的方式。(生成比较快,执行相对慢)
在1.8的版本优化中,反射调用的次数达到阈值(也就是发射调用的类成为热点时)之后采用字节码的方式,因为字节码的方式只有在第一次生成字节码文件时比较消耗时间。
总结
jdk需要接口,生成的代理和目标类都需要实现接口,生成的是兄弟。在生成类的过程比较高效
Cglib不需要接口,不需要更改目标类,生成的代理是目标类的子类,是儿子。在生成类之后类的执行过程中比较高效(适合单例、有实例池的对象)
Mybatis用jdk动态代理实现,而Spring两种都用,有接口时用动态代理,没有接口用cglib