Spring Boot代理问题
在 Spring Boot 2.x 中,AOP(面向切面编程)默认使用 CGLIB(Code Generation Library)来实现类的代理。CGLIB 代理是通过在运行时生成目标类的子类来增强目标类的方法。这种方式允许对没有实现接口的类进行代理。以下是一些原因和机制解释,说明为什么 Spring 在 AOP 中默认使用 CGLIB。
为什么默认使用 CGLIB
-
可用性和兼容性:
- 不依赖于接口:CGLIB 代理不需要目标类实现接口,因此它可以代理任何类,包括那些没有实现接口的类。这在某些情况下非常方便和必要。
- Spring 选择的默认策略:由于 CGLIB 代理在更多场景中更为通用和强大,因此它被设置为默认的代理方式。
-
避免困惑:
- 一致性:使用 CGLIB 可以避免开发人员在决定是否需要实现接口时遇到的困惑。无论类是否实现接口,都可以使用相同的代理机制。
-
性能:
- 性能优化:CGLIB 的性能已经过优化,足以满足大多数企业级应用的需求。虽然 JDK 动态代理在某些情况下可能略快,但差异通常对最终用户应用程序的性能没有显著影响。
Spring AOP 代理机制
Spring AOP 支持两种主要的代理模式:
-
JDK 动态代理:
- 依赖于
java.lang.reflect.Proxy
,只能对实现了一个或多个接口的类进行代理。 - 适用于所有实现接口的类。
- 依赖于
-
CGLIB 代理:
- 使用
net.sf.cglib.proxy
包进行代理,通过生成目标类的子类来实现方法增强。 - 适用于没有实现接口的类,或者可以覆盖具体类的方法以实现更广泛的应用。
- 使用
如何配置代理类型
虽然 Spring 默认使用 CGLIB,但开发人员可以通过配置来控制代理类型。使用 @EnableAspectJAutoProxy
注解可以指定代理方式:
- 默认代理设置(通常是 CGLIB):
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
复制
- 强制使用 CGLIB 代理:
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=true)
public class AppConfig {
}
复制
- 强制使用 JDK 动态代理(仅对实现接口的类有效):
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy(proxyTargetClass=false)
public class AppConfig {
}
复制
自动代理选择机制
Spring 会基于以下规则自动选择适合的代理机制:
-
如果目标类实现了至少一个接口,并且
proxyTargetClass
设置为false
:- Spring 使用 JDK 动态代理。
-
如果目标类没有实现任何接口,或者
proxyTargetClass
设置为true
:- Spring 使用 CGLIB 代理。
使用 AOP 的常见配置和限制
-
非公开方法:
- CGLIB 可以代理非公共方法(
protected
、package-private
和private
),而 JDK 动态代理只能代理公共接口的方法。
- CGLIB 可以代理非公共方法(
-
构造函数代理:
- CGLIB 在生成子类时会覆盖构造函数,而 JDK 动态代理则不涉及构造函数。
示例代码
以下是一个使用 CGLIB 代理的示例:
定义目标类(没有实现任何接口)
import org.springframework.stereotype.Component;@Component
public class MyService {public void doSomething() {System.out.println("Doing something...");}
}
定义切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Aspect
@Component
public class MyAspect {@Before("execution(* com.example.MyService.doSomething(..))")public void beforeDoSomething() {System.out.println("Before advice is being applied...");}
}
配置类
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration
@EnableAspectJAutoProxy(proxyTargetClass = true) // 强制使用 CGLIB 代理
public class AppConfig {
}
总结
Spring Boot 2.x 默认使用 CGLIB 来实现 AOP 代理,主要是因为 CGLIB 适用于更广泛的场景,包括那些没有实现接口的类。此外,Spring 允许通过配置来选择和控制代理类型,以适应特定应用程序的需求和设计。
了解这些机制和配置选项,有助于开发人员在使用 Spring AOP 时做出更明智的选择,并确保代理行为符合预期。