你是否曾遇到过这样的场景?一个UserService并未实现Loggable接口,却能直接调用其log()方法。这背后的核心机制便是Spring Introduction——它能够为已有的Bean动态地扩展新的接口。本文将从源码层面深入拆解这一过程的实现逻辑,揭示Spring如何“悄无声息”地为Bean增添新能力。
一、Spring Introduction的三大核心组件
理解Introduction,关键在于掌握其协作的“三驾马车”:
1. IntroductionAdvisor:定义扩展接口的“蓝图”
IntroductionAdvisor是Spring用于描述“要为Bean添加何种接口”的组件。其核心方法是getIntroductionClass(),用于返回待扩展的接口类型。例如,若需为Bean添加Loggable接口,则需要如下自定义Advisor:
// 自定义IntroductionAdvisor
public class LoggableAdvisor implements IntroductionAdvisor {
@Override
public Class<?> getIntroductionClass() {
return Loggable.class; // 指定要扩展的接口
}
@Override
public boolean matches(Class<?> clazz) {
// 仅对UserService及其子类应用此扩展
return UserService.class.isAssignableFrom(clazz);
}
// 其他方法省略...
}
其中的matches()方法扮演着过滤器角色,确保扩展只作用于符合条件的目标Bean。
2. IntroductionInterceptor:实现扩展行为的“执行器”
IntroductionInterceptor是扩展接口的具体功能实现者,它需要同时实现IntroductionInterceptor接口以及目标扩展接口(如Loggable)。当代理对象调用扩展接口方法时,最终执行的是此处定义的逻辑:
// 自定义IntroductionInterceptor
public class LoggableInterceptor implements IntroductionInterceptor, Loggable {
@Override
public void log(String message) {
System.out.println("[Log] " + message); // 实现具体的日志功能
}
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 判断当前调用的方法是否属于扩展接口
if (Loggable.class.isAssignableFrom(invocation.getMethod().getDeclaringClass())) {
// 若是,则执行本Interceptor中的实现
return invocation.getMethod().invoke(this, invocation.getArguments());
}
// 否则,执行目标Bean原有的方法逻辑
return invocation.proceed();
}
// 其他方法省略...
}
invoke()方法是核心枢纽,它决定了调用流量是流向扩展实现还是原生Bean。
3. DefaultAdvisorAutoProxyCreator:生成代理的“发动机”
DefaultAdvisorAutoProxyCreator是Spring提供的自动代理创建器。它会扫描容器中所有的Advisor,当发现匹配的IntroductionAdvisor时,便触发动态代理的生成过程。其核心逻辑封装在getProxy()方法中:
// DefaultAdvisorAutoProxyCreator的核心方法(简化逻辑)
protected Object getProxy(Class<?> beanClass, String beanName) {
// 1. 寻找所有适用于当前Bean的Advisor(包含IntroductionAdvisor)
List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);
if (advisors.isEmpty()) {
return bean; // 若无匹配的Advisor,则直接返回原Bean
}
// 2. 创建动态代理(根据情况选择JDK动态代理或CGLIB)
return createProxy(beanClass, beanName, advisors, new SingletonTargetSource(bean));
}
当findEligibleAdvisors()方法找到了我们定义的LoggableAdvisor,Spring便会为UserService创建相应的代理对象。
二、源码追踪:Bean的动态扩展流程
我们可以通过以下UML时序图来梳理Spring Introduction的完整工作流程(图中步骤与源码关键节点对应):

沿着源码,我们可以梳理出以下几个关键步骤:
步骤1:Bean获取触发代理创建
当通过applicationContext.getBean(UserService.class)获取Bean时,Spring会咨询DefaultAdvisorAutoProxyCreator。该创建器会检查UserService是否符合LoggableAdvisor中matches()方法设定的条件。若匹配,则启动代理创建流程。
步骤2:生成JDK动态代理
由于UserService是一个接口,Spring默认会使用JdkDynamicAopProxy来创建代理。在构建代理时,LoggableAdvisor所关联的LoggableInterceptor被整合到调用链中:
// JdkDynamicAopProxy的构造函数
public JdkDynamicAopProxy(AdvisedSupport config) throws AopConfigException {
this.advised = config;
// 将Advisor中的Interceptor整合到代理配置中
this.interceptors = config.getInterceptorsAndDynamicInterceptionAdvice(null, null);
}
步骤3:执行扩展接口的逻辑
当我们通过代理对象调用log()方法时,会触发JdkDynamicAopProxy的invoke()方法,这里是路由决策的核心:
// JdkDynamicAopProxy.invoke() 方法的关键逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 检查当前方法是否属于通过Introduction扩展的接口
IntroductionInfo introductionInfo = this.advised.getIntroductionInfo();
if (introductionInfo != null && introductionInfo.isIntroductionMethod(method)) {
// 2. 定位到对应的IntroductionInterceptor
IntroductionInterceptor interceptor = (IntroductionInterceptor) this.interceptors.get(0);
// 3. 执行Interceptor中的实现逻辑
return method.invoke(interceptor, args);
}
// 4. 非扩展方法,则委派给目标Bean执行
return method.invoke(target, args);
}
isIntroductionMethod()方法是关键判断点,它决定了调用是否应该被路由到IntroductionInterceptor。
三、实战演示:为UserService添加Loggable功能
将上述组件组合起来,进行一个完整的验证:
- 定义
Loggable接口:
public interface Loggable {
void log(String message);
}
- 配置Spring Bean(以XML配置为例):
<bean id="userService" class="com.example.UserService"/>
<bean id="loggableAdvisor" class="com.example.LoggableAdvisor"/>
<bean id="loggableInterceptor" class="com.example.LoggableInterceptor"/>
<!-- 启用自动代理 -->
<bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>
- 测试验证:
UserService userService = applicationContext.getBean(UserService.class);
// 关键:将代理对象强制转换为扩展接口类型
((Loggable) userService).log("用户登录成功"); // 控制台输出:[Log] 用户登录成功
至此,神奇的效果显现:UserService本身并未实现Loggable接口,但其代理对象却能成功调用log()方法。这正是Spring Introduction机制的强大之处。
注意:如果目标Bean是类而非接口,Spring会使用CGLIB生成代理,其核心原理与JDK动态代理一致。
四、深入源码:为何代理对象能“强转”成功?
你可能会疑惑,代理对象为何能被强制转换为Loggable类型?奥秘在于Spring在生成代理类时,已经将扩展接口添加到了代理类实现的接口列表中:
// JdkDynamicAopProxy中构建代理接口列表的逻辑
private Class<?>[] getProxyInterfaces() {
List<Class<?>> interfaces = new ArrayList<>();
// 1. 添加目标Bean原生实现的所有接口
interfaces.addAll(Arrays.asList(this.advised.getTargetSource().getTargetClass().getInterfaces()));
// 2. 添加通过Introduction扩展的所有接口
IntroductionInfo introductionInfo = this.advised.getIntroductionInfo();
if (introductionInfo != null) {
interfaces.addAll(introductionInfo.getIntroductions());
}
return interfaces.toArray(new Class<?>[0]);
}
代理类在运行时确实实现了Loggable接口,因此强制转换是合法且成功的。这便是“偷偷加接口”在底层的实现原理。
这种能力在需要对第三方库的Bean进行功能增强,而又无法修改其源码的场景下显得尤为有用,是Spring框架AOP能力的一种灵活体现。
五、核心逻辑总结
Spring Introduction机制的本质可归纳为以下四点:
- 定义:通过
IntroductionAdvisor定义“需要添加什么接口”。
- 实现:通过
IntroductionInterceptor具体实现“接口的功能逻辑”。
- 织入:借助
DefaultAdvisorAutoProxyCreator自动创建“集成了扩展接口的代理Bean”。
- 路由:在方法调用时,代理对象将扩展方法的调用路由到对应的Interceptor执行。
简而言之,其原理是 “通过代理进行转发,实现接口的动态织入”。Spring利用动态代理技术,将新接口“嫁接”到目标Bean上,实现了无需修改原始代码即可增强功能的解耦设计。
这引发了另一个有趣的技术思考:如果一个Bean被多个IntroductionAdvisor处理,Spring会如何合并这些扩展接口?其内部会妥善处理接口集合的合并,确保代理类实现所有被引入的接口,并将方法调用正确地路由到各自的Interceptor。这进一步体现了Spring AOP在设计上的健壮性与灵活性。理解Introduction机制,也为理解@Transactional、@Async等基于代理的Spring高级特性奠定了坚实的基础。