静态代理、JDK和CGLIB动态代理区别

本文详细介绍了Java中的静态代理、JDK动态代理和CGLIB动态代理,分析了它们的优缺点、使用场景以及实现原理。静态代理虽然简单但易产生冗余代码,JDK动态代理基于接口生成代理类,CGLIB则通过字节码技术实现类的动态代理。文中还探讨了何时选择JDK或CGLIB,以及Spring在AOP中的代理选择策略。最后通过代码示例展示了JDK和CGLIB动态代理的实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、 静态代理、JDK和CGLIB动态代理

1、静态代理

需要代理对象和目标对象实现一样的接口
优点:可以在不修改目标对象的前提下扩展目标对象的功能。
缺点:
冗余。由于代理对象要实现与目标对象一致的接口,会产生过多的代理类。
不易维护。一旦接口增加方法,目标对象与代理对象都要进行修改。
这点类似于装饰者模式

//把InputStreamReader装饰成BufferedReader来成为具备缓冲能力的Reader。
BufferedReader bufferedReader = new BufferedReader(inputStreamReader);

Java IO流装饰者模式就是给一个对象增加一些新的功能,而且是动态的,要求装饰对象和被装饰对象实现同一个接口,装饰对象持有被装饰对象的实例(各种字符流间装饰,各种字节流间装饰)。

1、JDK动态代理

利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口匿名类,在调用具体方法前调用InvokeHandler来处理。

2、CGLIB动态代理

Code Generation Library,利用ASM开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。

3、何时使用JDK还是CGLIB?

1)如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP
2)如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3)如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。

因为JVM动态生成的代理类有如下特性:
继承了Proxy类,实现了代理的接口,最终形式如下(HelloInterface为被代理类实现的接口):
public final class $Proxy0 extends Proxy implements HelloInterface{

}
然而由于java不能多继承,这里已经继承了Proxy类了,不能再继承其他的类,所以JDK的动态代理不支持对实现类的代理,只支持接口的代理

4、如何强制使用CGLIB实现AOP?

1)添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
2)在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>

5、静态代理、JDK动态代理和CGLIB字节码生成的区别?

1)JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
2)CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的
3)静态代理在编译时就已经实现,编译完成后代理类是一个实际的class文件
动态代理是在运行时动态生成的,即编译完成后没有实际的class文件,而是在运行时动态生成类字节码,并加载到JVM中
总结:
1)静态代理实现较简单,只要代理对象对目标对象进行包装,即可实现增强功能,但静态代理只能为一个目标对象服务,如果目标对象过多,则会产生很多代理类。
2)JDK动态代理需要目标对象实现业务接口,代理类只需实现InvocationHandler接口。
3)动态代理生成的类为 lass com.sun.proxy.$Proxy4,cglib代理生成的类为class com.cglib.UserDao$$EnhancerByCGLIB$$552188b6。
4)静态代理在编译时产生class字节码文件,可以直接使用,效率高。
动态代理必须实现InvocationHandler接口,通过反射代理方法,比较消耗系统性能,但可以减少代理类的数量,使用更灵活。
cglib代理无需实现接口,通过生成类字节码实现代理,比反射稍快,不存在性能问题,但cglib会继承目标对象,需要重写方法,所以目标对象不能为final类。

6、CGlib比JDK快?

1)使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,
在jdk6之前CGLib比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
2)在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,

总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。

7、Spring如何选择用JDK还是CGLIB?

1)当Bean实现接口时,Spring就会用JDK的动态代理。
2)当Bean没有实现接口时,Spring使用CGlib是实现。
3)可以强制使用CGlib(在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>)。

8、什么时候使用代理模式

1、当我们想要隐藏某个类时,可以为其提供代理。
2、当一个类需要对不同的调用者提供不同的调用权限时,可以使用代理类来实现(代理类不一定只有一个,我们可以建立多个代理类来实现,也可以在一个代理类中进行权限判断来进行不同权限的功能调用)。
3、当我们要扩展某个类的某个功能时,可以使用代理模式,在代理类中进行简单扩展(只针对简单扩展,可在引用委托类的语句之前与之后进行)。

二、代码验证

1、核心代码目录结构红色框已标注

代码下载地址:
整体代码下载
在这里插入图片描述

2、IUserManager接口

package com.proxy;
//被代理的接口  ---用户管理接口
public interface IUserManager {
	void addUser(String id, String password);
}

3、UserManagerImpl接口实现类

package com.proxy;
//被代理的实现类  ----用户管理接口实现
public class UserManagerImpl implements IUserManager {
	@Override
	public void addUser(String id, String password) {
		// TODO Auto-generated method stub
		System.out.println("======调用了UserManagerImpl.addUser()方法======");
		System.out.println(id+"------"+password);
	}
}

4、JDKProxy动态代理类

package com.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
//JDK动态代理类
//利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理
public class JDKProxy implements InvocationHandler{
	//需要代理的目标对象
    private Object targetObject;
    //将目标对象传入进行代理
    public Object newProxy(Object targetObject) {
    	this.targetObject = targetObject;
    	return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(),targetObject.getClass().getInterfaces(), this);
    }
    
	@Override
	//InvocationHandler接口的invoke方法
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		// 一般我们进行逻辑处理的函数,比如这个地方是打印方法名
        printMethod();
        // 设置方法的返回值
        Object ret = null;
        // 调用invoke方法,ret存储该方法的返回值
        ret  = method.invoke(targetObject, args);
        return ret;
	}
    //逻辑处理,比如打印方法名
	private void printMethod() {
        System.out.println("======打印方法名()======");
    }
}

5、CGLibProxy动态代理

package com.proxy;
import java.lang.reflect.Method;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
public class CGLibProxy implements MethodInterceptor {
	/** CGLib需要代理的目标对象 */
	private Object targetObject;
	public Object createProxyObject(Object obj) {
		this.targetObject = obj;
		Enhancer enhancer = new Enhancer();
		enhancer.setSuperclass(obj.getClass());
		enhancer.setCallback(this);
		Object proxyObj = enhancer.create();
		// 返回代理对象
		return proxyObj;
	}
	@Override
	public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
		Object obj = null;
		// 过滤方法
		if ("addUser".equals(method.getName())) {
			// 一般我们进行逻辑处理的函数,比如这个地方是打印方法名
			printMethod();
		}
		obj = method.invoke(targetObject, args);
		return obj;
	}
	// 逻辑处理,比如打印方法名
	private void printMethod() {
		System.out.println("======打印方法名()======");
	}
}

6、ClientTest测试类

package com.proxy;
//代理模式: 客户端--》代理对象--》目标对象
public class ClientTest {
	public static void main(String[] args) {
		System.out.println("**********************CGLibProxy**********************");
        CGLibProxy cgLibProxy = new CGLibProxy();
        IUserManager userManager = (IUserManager) cgLibProxy.createProxyObject(new UserManagerImpl());
        userManager.addUser("张三丰", "123456");
        System.out.println("*********分割线*************分割线**************分割线********");
        System.out.println("**********************JDKProxy**********************");
        JDKProxy jdkPrpxy = new JDKProxy();
        IUserManager userManagerJDK = (IUserManager) jdkPrpxy.newProxy(new UserManagerImpl());
        userManagerJDK.addUser("张三丰", "123456");
	}
}

测试结果如下
在这里插入图片描述
参考文章
https://blog.csdn.net/weixin_39666581/article/details/103899190
https://blog.csdn.net/yhl_jxy/article/details/80635012
https://blog.csdn.net/doujinlong1/article/details/80680149
https://blog.csdn.net/yhl_jxy/article/details/80586785

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值