Java基础之Jdk动态代理与CGLIB动态代理
一、代理模式
代理模式(Proxy Pattern)是一种结构性模式。代理模式为一个对象提供了一个替身,以控制对这个对象的访问。即通过代理对象访问目标目标对象,可以在目标对象实现的基础上,增强额外的功能操作,即扩展目标对象的功能。
代理模式主要有三种形式,分别是静态代理、动态代理(也称JDK代理、接口代理)和cglib代理(在内存动态创建对象而不需要实现接口,也可属于动态代理得范畴)
1.1 静态代理
静态代理是一种代理模式,代理模式是一种比较好的理解的设计模式。
具体可以参考设计模式之代理模式
1.2 动态代理
Spring AOP的实现原理是基于动态代理和字节码操作的,而动态代理是基于反射机制的。
在编译时,Spring会使用AspectJ编译器将切面代码编译成字节码文件。在运行时,Spring会使用Java动态代理或CGLIB代理生成代理类,这些代理类会在目标对象方法执行前后插入切面代码,从而实现AOP的功能。
相比于静态代理来说,动态代理更加灵活。我们不需要针对每个目标类都单独创建一个代理类,并且也不需要我们必须实现接口,我们可以直接代理实现类(CGLIB动态代理机制)。从JVM角度来说,动态代理是在运行时动态生成类字节码,并加载到JVM中的。
说到动态代理,SpringAOP、RPC框架是两个不得不的提的,它们的实现都依赖了动态代理。动态代理在日常开发中使用的相对较小,但是在框架中的几乎是必用的一门技术。学会了动态代理之后对于理解和学习各种框架的原理也非常有帮助。
SpringAOP可以使用两种代理方式:JDK动态代理和CGLIB代理。如果目标对象实现了至少一个接口,则使用JDK动态代理;否则,使用CGLIB代理。
二、JDK动态代理
2.1 简介
jdk动态代理:使用java反射包中的类和接口实现动态代理的功能。
反射包java.lang.reflect里面有三个类:InvocationHandler,Method,Proxy
在Java动态代理机制中InvocationHandler接囗和Proxy类是核心。
Proxy类中使用频率最高的方法是:newProxyInstance(),这个方法主要用来生成一个代理对象,。
public class Proxy implements java.io.Serializable {
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException {
//...
}
}
这个方法一共有3个参数:
- loader:类加载器,用于加载代理对象,传入被代理类的类加载器
- interfaces:被代理类实现的一些接口;被代理的类必须要实现这个接口
- h:实现了
InvocationHandler接囗的对象,我们自己写的,代理类要完成的功能。
要实现动态代理的话,还必须需要实现InvocationHandler来自定义处理逻辑。当我们的动态代理对象调用一个方法时候,这个方法的调用就会被转发到实现了InvocationHandler接口的实现类中重写了的invoke方法来调用。
示例:
还没使用JDK动态代理机制时,创建的对象就是被代理类自己
//被代理类要实现的接口IUserService
public interface IUserService {
void add();
}
//被代理类UserService
public class UserService implements IUserService {
@Override
public void add() {
System.out.println("增加用户");
}
}
//测试类TestJDKProxy
public class TestJDKProxy {
@Test
public void test() {
IUserService userService = new UserService();
//class com.lt.autoProxy.UserService
//UserService就是被代理类自己的对象
System.out.println(userService.getClass());
}
}
这时候还没有使用动态代理来获取代理类,所以这里IUserService userService = new UserService();创建的对象不是动态生成的代理对象
2.2 使用步骤
- 创建接口,定义目标类要完成的功能
- 创建目标类实现接口
- 创建
InvocationHandler接口的实现类,在invoke方法中完成代理类的功能- 调用目标方法
- 增强功能
- 使用
Proxy类的静态方法,创建代理对象。并把返回值转为接口类型。
第一步和第二步我们前面还没使用JDK动态代理机制时已经完成了,现在只需要完成第三步和第四步
第三步:创建InvocationHandler接口的实现类MyHandler
public class MyHandler implements InvocationHandler {
Object target; //target代表被代理类的实例对象
//通过这个方法可以把被代理类的实例对象,也就是目标对象传递进来赋值给target,
//后面调用被代理类里面的方法要用到
public MyHandler(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
//实现增强代码的地方,这里就用一个简单的打印语句代替一下了
System.out.println("增强的代码:前置通知");
//执行目标方法,这里是基于反射机制来实现的
Object returnValue = method.invoke(target, args);
System.out.println("增强的代码:后置通知");
//返回目标方法的返回值
return returnValue;
}
}
第四步:使用Proxy类的静态方法,创建代理对象
//测试类TestJDKProxy
public class TestJDKProxy {
@Test
public void test() {
IUserService userService = (IUserService) Proxy.newProxyInstance(
//目标对象(UserService)的类加载器,负责向内存中加载对象的,这里使用反射机制来获取
UserService.class.getClassLoader(),
//接口,目标对象(UserService)实现的接口,也是反射获取的
UserService.class.getInterfaces(),
//自己写的实现了 InvocationHandler 接口的实现类,在里面写代理类要完成的功能,
new MyHandler(new UserService())
);
// 生成出来的代理实现了IUserService接口,
// 这就是为什么上面的方法一定要传入目标对象(UserService)实现的接口IUserService
//打印看看现在拿到的对象是不是动态创建的代理对象
System.out.println(userService.getClass());
//执行这个代理对象的方法,他会执行代理类中的增强方法和被代理类中的add方法
userService.add();
}
}
运行结果:
class jdk.proxy2.$Proxy8
增强的代码:前置通知
增加用户
增强的代码:后置通知
所以在SpringAOP底层原理中,SpringAOP就是帮我们做了上面的第三步和第四步,以此来实现JDK动态代理机制为我们创建代理类对象,然后把这个代理类对象放入到IOC容器当中。我们去IOC容器中拿被代理类(就相当于上面例子中的UserService类)的Bean时,实际上拿的是他的代理类(相当于上面例子中的class jdk.proxy2.$Proxy8)的Bean,这就是为什么可以把增强的代码以切面编程的方式加到目标对象中去运行的原因
总之,JDK动态代理是Java自带的动态代理实现方式。使用JDK动态代理时,需要目标对象实现至少一个接口。JDK动态代理会在运行时生成一个实现了目标对象接口的代理类,该代理类会在目标对象方法执行前后插入切面代码。
三、CGLIB动态代理
3.1 简介
cglib动态代理是针对类来实现代理的,它的原理是对指定的目标类(被代理类)生成一个子类,并覆盖其中方法实现增强。而JDK的动态代理机制只能代理实现了接口的类,而没有实现接口的类就不能实现JDK的动态代理。所以使用cglib实现动态代理,完全不受代理类必须实现接口的限制。
cglib底层采用ASM字节码生成框架,使用字节码技术生成代理类,比使用Java反射效率要高。因为cglib动态代理采用的是继承,所以不能对final修饰的类进行代理。
由于cglib是一个第三方的框架,不是JDK自带的,所以要引入maven依赖。但是在Spring3版本之后cglib就被整合进Spring中了,就不再需要引入cglib的maven依赖了
引入cglib的maven依赖
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.6</version>
</dependency>
3.2 核心类
CGLIB的核心类主要包括以下几个:
Enhancer(代理工厂类,也可以叫做增强类):是CGLIB中的主要类之一,用于创建代理对象。通过Enhancer可以指定被代理类、回调方法和拦截器等信息,然后生成代理对象的字节码。MethodInterceptor(拦截类):是CGLIB中的一个接口,用于实现代理对象的拦截逻辑。当代理对象的方法被调用时,MethodInterceptor中的intercept方法会被调用,开发者可以在该方法中添加额外的逻辑。类似于JDK动态代理中的InvocationHandler接口Callback:Callback是一个接口,它定义了代理对象的回调方法。MethodInterceptor就是继承了Callback接口的一个更成熟的接口,用于拦截方法调用。CGLIB还提供了其他类型的Callback,如NoOp、LazyLoader等,用于实现不同的代理逻辑。
MethodInterceptor继承了Callback接口在源码中如下:
public interface MethodInterceptor extends Callback {
Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy)
throws Throwable;
}
3.3 实现步骤
- 引入
CGLIB依赖(好像Spring3版本之后cglib就被整合进Spring中了,就不需要引入依赖了) - 定义一个被代理类(这个被代理类可以实现接口,也可以不实现接口)
- 定义一个拦截类并实现接口
MethodInterceptor - 创建代理工厂类实例
- 生成代理类对象,并通过代理对象调用方法
示例:
//定义一个没有实现任何接口的被代理类UserService
public class UserService {
public void add() {
System.out.println("增加用户");
}
}
//定义一个拦截类MyMethodInterceptor并实现接口 MethodInterceptor
public class MyMethodInterceptor implements MethodInterceptor {
Object target; //target代表被代理类的实例对象
//通过这个方法可以把被代理类的实例对象,也就是目标对象传递进来赋值给target,
//后面调用被代理类里面的方法要用到
public MyMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method,
Object[] args, MethodProxy proxy)
throws Throwable {
//实现增强代码的地方,这里就用一个简单的打印语句代替一下了
System.out.println("增强的代码:前置通知");
//执行目标方法,这里是直接调用父类方法(就是直接调用被代理类的方法),相当于执行super.方法()
Object returnValue = proxy.invoke(target, args);
System.out.println("增强的代码:后置通知");
//返回目标方法的返回值
return returnValue;
}
}
//创建代理工厂类的对象,然后通过代理对象调用方法
public class TestCGLIBProxy {
@Test
public void test() {
//创建代理工厂类实例
Enhancer enhancer = new Enhancer();
//设置被代理的类
enhancer.setSuperclass(UserService.class);
//设置处理类
enhancer.setCallback(new MyMethodInterceptor(new UserService()));
//生成的代理类,这个代理类是继承自UserService的
UserService userService = (UserService) enhancer.create();
//打印结果为:class com.lt.autoProxy.cglib.UserService$$EnhancerByCGLIB$$beeda0dc
System.out.println(userService.getClass());
userService.add(); //通过代理对象调用方法
}
}
运行结果:
class com.lt.autoProxy.cglib.UserService$$EnhancerByCGLIB$$beeda0dc
增强的代码:前置通知
增加用户
增强的代码:后置通知
四、区别
JDK动态代理只能代理实现了接口的类,因为他生成出来的代理类是采用实现目标类的接口的方式,比如目标类UserService实现了接口lUserService,代理类的生成方式就是public proxy$0 implements IUserService{} ,proxy$0就是JDK动态代理生成出来的代理类(当然这只是一个例子,proxy$0是我为了好理解而取的代理类类名)
而CGLIB可以代理未实现任何接口的类。因为他生成出来的代理类是采用继承目标类的方式,比如目标类为UserService,代理类的生成方式就是public $xxxxCqlib$ extends UserServce{},另外CGLIB动态代理是通过生成一个被代理类的子类来拦截被代理类的方法调用,因此不能代理声明为final类型的类和方法。
补充:
所以可以根据是否有实现接口来选择JDK动态代理或CGLIB,但是用SpringAOP来实现的话,无需关心,因为底层会自动根据被代理目标类是否实现接口自动选择JDK或CGLIB
在Spring中默认用JDK动态代理判断目标类是否实现接口,如果实现了接口就用JDK动态代理,否则就使用CGLIB动态代理
可以通过配置来改变Spring使用哪一种动态代理机制:
@EnableAspectAutoProxy(proxyTargetClass=false) = jdk动态代理 (默认)
@EnableAspectJAutoProxy(proxyTargetClass=true) = cglib
在SpringBoot2.x版本后默认是使用CgLib作为默认的动态代理实现
@EnableAspectAutoProxy注解在SpringBoot的默认启动类上有一个,默认启动类上的这个注解设置的是proxyTargetClass=true,也就说默认是使用cglib动态代理的。如果想把他改成使用jdk动态代理,就可以在SpringBoot的配置文件application.properties里配置:spring.aop.proxy-target-class=false
五、总结
- 若目标类实现了接口,优先使用
JDK动态代理(原生支持,无需额外依赖)。 - 若目标类无接口或需代理非接口方法,使用
CGLIB动态代理(需引入依赖)。 - 主流框架(如
Spring)会自动选择代理方式:有接口时用JDK代理,无接口时用CGLIB代理(可配置强制使用CGLIB)。
动态代理的核心价值在于解耦:将通用逻辑(如日志、事务)与业务逻辑分离,通过代理在运行时动态增强目标方法,是AOP的核心实现技术。

浙公网安备 33010602011771号