Skip to content

Spring AOP 中的 ProxyFactoryBean:创建 AOP 代理的工厂利器 🏭

引言:为什么需要 ProxyFactoryBean?

在 Spring AOP 的世界里,我们经常需要为业务对象创建代理来实现横切关注点(如日志、事务、安全等)。想象一下,如果没有一个统一的工厂来管理这些代理的创建,我们就需要手动编写大量重复的代理创建代码。这就像没有汽车工厂,每个人都要自己组装汽车一样低效!

NOTE

ProxyFactoryBean 是 Spring AOP 框架中用于创建 AOP 代理的核心工厂 Bean,它提供了一种声明式的方式来配置和创建代理对象。

核心概念解析 🎯

ProxyFactoryBean 的本质

ProxyFactoryBean 是 Spring 的一个特殊的 FactoryBean 实现,它引入了一层间接性:

IMPORTANT

当你定义一个名为 foo 的 ProxyFactoryBean 时,引用 foo 的对象看到的不是 ProxyFactoryBean 实例本身,而是由其 getObject() 方法创建的 AOP 代理对象。

核心属性配置 ⚙️

继承自 ProxyConfig 的关键属性

kotlin
@Configuration
class AopConfig {
    
    @Bean
    fun proxyFactoryBean(): ProxyFactoryBean {
        return ProxyFactoryBean().apply {
            // 设置目标对象
            setTarget(personTarget())
            
            // 配置代理行为
            isProxyTargetClass = false  // 使用接口代理
            isOptimize = false         // 不启用激进优化
            isFrozen = false          // 允许配置修改
            isExposeProxy = true      // 暴露当前代理到ThreadLocal
            
            // 设置要代理的接口
            setProxyInterfaces(arrayOf("com.example.Person"))
            
            // 设置拦截器链
            setInterceptorNames(arrayOf("myAdvisor", "debugInterceptor"))
        }
    }
}
xml
<bean id="person" class="org.springframework.aop.framework.ProxyFactoryBean">
    <!-- 目标对象 -->
    <property name="target" ref="personTarget"/>
    
    <!-- 代理配置 -->
    <property name="proxyTargetClass" value="false"/>
    <property name="optimize" value="false"/>
    <property name="frozen" value="false"/>
    <property name="exposeProxy" value="true"/>
    
    <!-- 接口配置 -->
    <property name="proxyInterfaces" value="com.example.Person"/>
    
    <!-- 拦截器链 -->
    <property name="interceptorNames">
        <list>
            <value>myAdvisor</value>
            <value>debugInterceptor</value>
        </list>
    </property>
</bean>

属性详解

属性名类型默认值说明
proxyTargetClassBooleanfalse是否代理目标类而非接口
optimizeBooleanfalse是否启用 CGLIB 优化
frozenBooleanfalse代理配置是否冻结
exposeProxyBooleanfalse是否将代理暴露到 ThreadLocal
proxyInterfacesString[]null要代理的接口名数组
interceptorNamesString[]null拦截器/通知器名称数组

TIP

exposeProxy 属性特别有用,当目标对象需要获取当前代理时,可以通过 AopContext.currentProxy() 方法获取。

代理类型选择机制 🔄

Spring 会根据不同的配置自动选择使用 JDK 动态代理还是 CGLIB 代理:

实战示例 💡

1. 接口代理示例

kotlin
// 业务接口
interface PersonService {
    fun getName(): String
    fun setName(name: String)
    fun getAge(): Int
}

// 业务实现
@Component("personTarget")
class PersonServiceImpl : PersonService {
    private var name: String = ""
    private var age: Int = 0
    
    override fun getName(): String {
        println("获取姓名: $name") 
        return name
    }
    
    override fun setName(name: String) {
        println("设置姓名: $name") 
        this.name = name
    }
    
    override fun getAge(): Int = age
}
kotlin
@Component("myAdvisor")
class LoggingAdvisor : PointcutAdvisor {
    
    private val pointcut = object : StaticMethodMatcherPointcut() {
        override fun matches(method: Method, targetClass: Class<*>): Boolean {
            return method.name.startsWith("get") 
        }
    }
    
    private val advice = MethodInterceptor { invocation ->
        val startTime = System.currentTimeMillis()
        println("🚀 方法调用开始: ${invocation.method.name}")
        
        try {
            val result = invocation.proceed()
            val endTime = System.currentTimeMillis()
            println("✅ 方法调用成功: ${invocation.method.name}, 耗时: ${endTime - startTime}ms")
            result
        } catch (e: Exception) {
            println("❌ 方法调用失败: ${invocation.method.name}, 异常: ${e.message}")
            throw e
        }
    }
    
    override fun getPointcut(): Pointcut = pointcut
    override fun getAdvice(): Advice = advice
    override fun isPerInstance(): Boolean = true
}
kotlin
@Configuration
class ProxyConfig {
    
    @Bean
    fun personProxy(): ProxyFactoryBean {
        return ProxyFactoryBean().apply {
            setTarget(personTarget()) 
            setProxyInterfaces(arrayOf("com.example.PersonService")) 
            setInterceptorNames(arrayOf("myAdvisor", "debugInterceptor")) 
        }
    }
    
    @Bean
    fun personTarget(): PersonService {
        return PersonServiceImpl().apply {
            setName("张三")
        }
    }
    
    @Bean
    fun debugInterceptor(): DebugInterceptor {
        return DebugInterceptor()
    }
}

2. 使用代理

kotlin
@Service
class BusinessService {
    
    @Autowired
    @Qualifier("personProxy")
    private lateinit var personService: PersonService
    
    fun doSomething() {
        // 这里调用的是代理对象,会触发通知
        val name = personService.getName() 
        println("业务逻辑处理: $name")
        
        // 检查代理类型
        if (personService is Advised) { 
            println("这是一个被通知的代理对象")
            println("代理类型: ${personService.javaClass.simpleName}")
        }
    }
}

WARNING

注意:通过 ProxyFactoryBean 创建的代理对象可以转换为 Advised 接口,从而可以在运行时动态修改通知配置(除非设置了 frozen=true)。

3. 类代理示例(CGLIB)

kotlin
// 没有接口的类
@Component("personClassTarget")
class PersonClass {
    var name: String = ""
    var age: Int = 0
    
    open fun getName(): String { 
        println("获取姓名: $name")
        return name
    }
    
    open fun setName(name: String) { 
        println("设置姓名: $name")
        this.name = name
    }
    
    final fun getAge(): Int = age 
    // final 方法无法被 CGLIB 代理
}

@Configuration
class CglibProxyConfig {
    
    @Bean
    fun personClassProxy(): ProxyFactoryBean {
        return ProxyFactoryBean().apply {
            setTarget(personClassTarget())
            isProxyTargetClass = true
            // 强制使用 CGLIB 代理
            setInterceptorNames(arrayOf("myAdvisor"))
        }
    }
}

CAUTION

使用 CGLIB 代理时需要注意:

  • final 类无法被代理
  • final 方法无法被通知
  • private 方法无法被通知
  • 构造函数会被调用两次(目标类和代理类各一次)

全局通知器配置 🌐

ProxyFactoryBean 支持使用通配符来应用全局通知器:

kotlin
@Configuration
class GlobalAdvisorConfig {
    
    @Bean
    fun serviceProxy(): ProxyFactoryBean {
        return ProxyFactoryBean().apply {
            setTarget(myService())
            setInterceptorNames(arrayOf("global*")) 
            // 会匹配所有以 "global" 开头的通知器
        }
    }
    
    @Bean("global_debug")
    fun globalDebugInterceptor(): DebugInterceptor {
        return DebugInterceptor()
    }
    
    @Bean("global_performance")
    fun globalPerformanceInterceptor(): PerformanceMonitorInterceptor {
        return PerformanceMonitorInterceptor()
    }
    
    @Bean("global_security")
    fun globalSecurityInterceptor(): MethodInterceptor {
        return MethodInterceptor { invocation ->
            println("🔒 安全检查: ${invocation.method.name}")
            // 执行安全检查逻辑
            invocation.proceed()
        }
    }
}

高级特性 🚀

1. 匿名内部Bean配置

kotlin
@Bean
fun personProxyWithInnerBean(): ProxyFactoryBean {
    return ProxyFactoryBean().apply {
        // 使用匿名内部bean作为目标对象
        setTarget(PersonServiceImpl().apply { 
            setName("李四")
        })
        setProxyInterfaces(arrayOf("com.example.PersonService"))
        setInterceptorNames(arrayOf("myAdvisor"))
    }
}

匿名内部Bean的优势

  • 只有一个 PersonService 类型的对象存在于容器中
  • 避免了获取未被通知的原始对象的可能性
  • 配置更加自包含

2. 原型作用域支持

kotlin
@Bean
@Scope("prototype")
fun prototypeProxy(): ProxyFactoryBean {
    return ProxyFactoryBean().apply {
        setTarget(PersonServiceImpl())
        setProxyInterfaces(arrayOf("com.example.PersonService"))
        setInterceptorNames(arrayOf("statefulAdvisor"))
        isSingleton = false
        // 每次获取都创建新的代理实例
    }
}

3. 代理暴露到ThreadLocal

kotlin
@Component
class SelfInvokingService {
    
    fun outerMethod() {
        println("外部方法调用")
        // 获取当前代理并调用内部方法
        val proxy = AopContext.currentProxy() as SelfInvokingService 
        proxy.innerMethod() // 这样调用会触发AOP通知
    }
    
    fun innerMethod() {
        println("内部方法调用")
    }
}

@Bean
fun selfInvokingProxy(): ProxyFactoryBean {
    return ProxyFactoryBean().apply {
        setTarget(SelfInvokingService())
        isProxyTargetClass = true
        isExposeProxy = true
        setInterceptorNames(arrayOf("loggingAdvisor"))
    }
}

性能考量 ⚡

JDK 动态代理 vs CGLIB 代理

特性JDK 动态代理CGLIB 代理
基础要求目标类必须实现接口无接口要求
性能略快略慢(但差异很小)
内存占用较少较多
创建速度慢(需要生成字节码)
方法调用通过反射直接调用

NOTE

在现代 JVM 上,JDK 动态代理和 CGLIB 代理的性能差异已经非常小,选择时应该更多考虑设计因素而非性能因素。

最佳实践 📋

1. 优先使用接口

kotlin
// ✅ 推荐:基于接口的设计
interface UserService {
    fun createUser(user: User): User
    fun findUser(id: Long): User?
}

@Service
class UserServiceImpl : UserService {
    override fun createUser(user: User): User {
        // 实现逻辑
        return user
    }
    
    override fun findUser(id: Long): User? {
        // 查询逻辑
        return null
    }
}

2. 合理配置拦截器顺序

kotlin
@Bean
fun orderedProxy(): ProxyFactoryBean {
    return ProxyFactoryBean().apply {
        setTarget(businessService())
        setInterceptorNames(arrayOf(
            "securityAdvisor",      // 1. 安全检查
            "transactionAdvisor",   // 2. 事务管理
            "loggingAdvisor",       // 3. 日志记录
            "performanceAdvisor"    // 4. 性能监控
        ))
    }
}

3. 避免过度使用frozen配置

kotlin
@Bean
fun flexibleProxy(): ProxyFactoryBean {
    return ProxyFactoryBean().apply {
        setTarget(dynamicService())
        isFrozen = false
        // 保持灵活性,允许运行时修改配置
        setInterceptorNames(arrayOf("baseAdvisor"))
    }
}

总结 📝

ProxyFactoryBean 是 Spring AOP 中创建代理对象的核心工厂,它提供了:

  • 🏭 统一的代理创建机制:通过配置而非编程方式创建代理
  • 🔧 灵活的配置选项:支持多种代理策略和行为定制
  • 🎯 智能的代理选择:自动选择最适合的代理技术
  • 🌐 强大的通知器管理:支持复杂的通知器链和全局通知器
  • 🔄 与 IoC 容器深度集成:充分利用依赖注入的优势

IMPORTANT

虽然 ProxyFactoryBean 功能强大,但在现代 Spring 应用中,更推荐使用 @AspectJ 注解或 <aop:config> 等更简洁的方式来配置 AOP。ProxyFactoryBean 更适合需要精细控制代理创建过程的场景。

通过掌握 ProxyFactoryBean,你将能够更深入地理解 Spring AOP 的工作原理,并在需要时创建高度定制化的 AOP 解决方案! 🎉