阿里二面:为什么SpringBoot默认使用CGLIB作为代理的实现方式?

971 阅读4分钟

SpringBoot是一种基于Spring框架的开发工具,它可以快速创建和运行独立的、生产级的Spring应用程序。

SpringBoot提供了很多特性,如自动配置、嵌入式服务器、外部化配置、开发者工具等,来简化和优化Spring开发过程。

什么是CGLIB?

CGLIB是一种基于ASM的代码生成库,它可以在运行时动态地生成和修改Java字节码,从而实现对Java类和接口的扩展和代理。CGLIB是一种高性能、高质量的代码生成工具,被广泛应用于Hibernate、Spring AOP等框架中。

CGLIB动态代理是通过继承机制来实现的,它不要求目标类必须实现接口,而是通过
net.sf.cglib.proxy.Enhancer类来创建子类对象作为代理对象,并通过net.sf.cglib.proxy.MethodInterceptor接口来实现方法的拦截和增强。

示例:

假设我们有一个简单的类 UserService:

public class UserService {
    public void saveUser() {
        System.out.println("保存用户信息");
    }
}

我们可以使用 CGLIB 来创建它的代理类:

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class UserServiceProxy implements MethodInterceptor {

    private Object target; // 被代理的目标对象

    public UserServiceProxy(Object target) {
        this.target = target;
    }

    // 创建代理对象
    public Object getProxy() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(target.getClass());
        enhancer.setCallback(this);
        return enhancer.create();
    }

    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("代理前处理...");
        Object result = proxy.invokeSuper(obj, args);
        System.out.println("代理后处理...");
        return result;
    }
}

在上述示例中,UserServiceProxy 类实现了 CGLIB 的 MethodInterceptor 接口,它会在方法调用前后进行处理。通过 Enhancer 类,我们可以生成一个代理对象。

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserService();
        UserService proxy = (UserService) new UserServiceProxy(userService).getProxy();
        proxy.saveUser();
    }
}

运行这段代码,会输出:

代理前处理...

保存用户信息

代理后处理...

这个例子中,我们使用 CGLIB 创建了 UserService 的代理类,并在方法调用前后添加了额外的处理逻辑。

什么是JDK代理?

JDK动态代理是通过反射机制来实现的,它要求目标类必须实现一个或多个接口,然后通过java.lang.reflect.Proxy类来创建代理对象,并通过
java.lang.reflect.InvocationHandler接口来实现方法的拦截和增强。

示例:

假设我们有一个接口 UserService:

public interface UserService {
    void saveUser();
}

然后我们有一个实现了该接口的类 UserServiceImpl:

public class UserServiceImpl implements UserService {
    @Override
    public void saveUser() {
        System.out.println("保存用户信息");
    }
}

接下来,我们可以创建一个代理处理器 MyInvocationHandler,实现InvocationHandler接口:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler {

    private Object target; // 被代理的目标对象

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前处理...");
        Object result = method.invoke(target, args);
        System.out.println("代理后处理...");
        return result;
    }
}

在这个例子中,MyInvocationHandler 类实现了 InvocationHandler 接口,它会在方法调用前后进行处理。

最后,我们使用Proxy.newProxyInstance方法创建代理对象:

import java.lang.reflect.Proxy;

public class Main {
    public static void main(String[] args) {
        UserService userService = new UserServiceImpl();
        UserService proxy = (UserService) Proxy.newProxyInstance(
                userService.getClass().getClassLoader(),
                userService.getClass().getInterfaces(),
                new MyInvocationHandler(userService)
        );
        proxy.saveUser();
    }
}

运行这段代码,会输出:

代理前处理...

保存用户信息

代理后处理...

我们使用了JDK动态代理创建了 UserService 的代理对象,并在方法调用前后添加了额外的处理逻辑。

为什么SpringBoot默认使用CGLIB作为代理的实现方式呢?这里有几个原因:

  • 兼容性

CGLIB动态代理可以适用于任何类型的目标类,无论它是否实现了接口;

而JDK动态代理只能适用于实现了接口的目标类。

这意味着CGLIB动态代理可以覆盖JDK动态代理的所有场景,而JDK动态代理不能覆盖CGLIB动态代理的所有场景。

因此,为了保证SpringBoot中的AOP(面向切面编程)功能可以应用于任何类型的Bean(无论它是否实现了接口),SpringBoot默认使用CGLIB作为代理的实现方式。

  • 性能

CGLIB动态代理在生成代理对象时需要消耗更多的时间和内存资源,因为它需要操作字节码;而JDK动态代理在生成代理对象时相对较快,因为它只需要操作反射。

但是,在执行代理方法时,CGLIB动态代理比JDK动态代理要快得多,因为它直接调用目标方法,而不需要通过反射。

而在SpringBoot中,通常只会在容器启动时生成一次代理对象,并缓存起来;而在运行时会频繁地执行代理方法。因此,在整体性能上,CGLIB动态代理比JDK动态代理要优越。

  • 灵活性

CGLIB动态代理可以实现对目标类的任何方法的代理,无论它是公有的、私有的、静态的、最终的等;

而JDK动态代理只能实现对目标类的公有非最终方法的代理。这意味着CGLIB动态代理可以实现更多的功能和需求,而JDK动态代理则受到很多限制。

因此,为了保证SpringBoot中的AOP功能可以对任何类型的方法进行增强,SpringBoot默认使用CGLIB作为代理的实现方式。