Spring Bean 与 RESTEasy Resource:两个生命周期的分界与合作
引言:一个 Resource 类,两个容器
在 RESTEasy 集成 Spring 的项目里,一个典型的 @Path Resource 往往长这样:@Autowired 注入 CustomerService,方法参数用 @QueryParam,类上还有 @Transactional。运行时一切正常——但调试启动顺序、singleton Resource 的 @Context UriInfo 行为,或 prototype Resource 为何「首请求才创建」时,很容易问:实例是谁创建的?路由是谁注册的?请求进来后谁先谁后?
答案并不在一个容器里。Spring 把它当 Bean 管(创建、DI、AOP、销毁),RESTEasy 把它当 HTTP 端点管(路由、取实例、JAX-RS 注入、方法调用)。两者既不合并,也不互相替代;resteasy-spring 通过 SpringBeanProcessor 与 SpringResourceFactory 在启动期与运行期各管一段。
全文脉络:
- Spring Bean 生命周期 — 所有 scope 共用
doCreateBean流水线;singleton 在refresh()批量创建,prototype 在每次getBean()触发 - RESTEasy Resource 生命周期 —
Registry登记ResourceFactory(取实例策略)+ResourceMethodInvoker;registered/createResource/requestFinished - resteasy-spring 桥接 — BFPP 扫描
@Path、BPP 做 JAX-RS 字段注入、ContextRefreshedEvent注册 Registry、运行期getBean()取实例 - 运行期 call chain — 单次 HTTP 请求从 Dispatcher 到
getBean()的完整路径 - WAR vs Spring Boot — 启动时序差异,以及
resteasy-spring-boot的额外编排
术语与缩写
全文反复出现下列缩写;首次出现时尽量给出全称,此处集中对照,便于查阅。
| 缩写 | 英文全称 | 含义 |
|---|---|---|
| RESTEasy | RESTful Web Services framework(JBoss RESTEasy) | JBoss 出品的 JAX-RS 实现,负责 HTTP 路由、Resource 实例获取与 JAX-RS 调用链 |
| JAX-RS | Java API for RESTful Web Services | Java 定义 REST 服务编程模型(@Path、@GET、@Provider 等)的规范 |
| IoC | Inversion of Control(控制反转) | 对象创建与依赖关系不由调用方 new,而由容器装配——Spring 容器的核心思想 |
| DI | Dependency Injection(依赖注入) | IoC 的具体手段:通过构造器、字段或 setter 注入依赖(如 @Autowired) |
| AOP | Aspect-Oriented Programming(面向切面编程) | 在 Bean 外围织入横切逻辑(事务、日志等),常表现为 CGLIB/JDK 动态代理 |
| BFPP | BeanFactoryPostProcessor | Spring 在 Bean 定义加载后、实例化前的扩展点;SpringBeanProcessor.postProcessBeanFactory 在此扫描 @Path |
| BPP | BeanPostProcessor | Spring 在每个 Bean 初始化前后的扩展点;ResteasyBeanPostProcessor 在此做 JAX-RS 字段注入 |
| Registry | ResourceMethodRegistry |
RESTEasy 的路由注册表:URL 树 + 每个 HTTP 端点对应的 ResourceMethodInvoker |
| Dispatcher | SynchronousDispatcher 等 |
RESTEasy 请求调度器:匹配 URI、取 Invoker、驱动 Filter 链与方法调用 |
| ResourceFactory | — | RESTEasy 接口:回答「每次请求如何拿到 Resource 实例」;不是 Bean 实例本身 |
| ResourceMethodInvoker | — | 每个 JAX-RS HTTP 端点(如 @GET /users)一个调用器,持有 ResourceFactory 引用 |
| ProviderFactory | ResteasyProviderFactory |
RESTEasy 的 @Provider 注册中心(MessageBodyReader/Writer、Filter 等) |
| SRF | SpringResourceFactory | resteasy-spring 桥接类:Registry 中登记的「向 Spring getBean() 取实例」策略 |
| SBP | SpringBeanProcessor | resteasy-spring 启动编排类:实现 BFPP + 监听 ContextRefreshedEvent |
| POJO | Plain Old Java Object | 普通 Java 对象;POJOResourceFactory 表示 RESTEasy 自己 new,不经 Spring |
| JNDI | Java Naming and Directory Interface | Java 命名与目录接口;JndiResourceFactory 从 JNDI 查找 Resource 实例 |
| WAR | Web Application Archive | 传统 Java Web 应用打包格式,部署到外置 Servlet 容器 |
| URI | Uniform Resource Identifier | 统一资源标识符;RESTEasy 用 URI 路径 + HTTP 动词匹配端点 |
| HTTP | Hypertext Transfer Protocol | 超文本传输协议;本文指 GET/POST 等动词与请求/响应语义 |
| Call Chain | — | 调用链:一次 HTTP 请求从 Dispatcher 到 getBean() 的完整方法调用序列 |
| Scope | — | Bean 作用域;文中主要指 Spring 的 singleton(单例缓存)与 prototype(每次新建) |
| Deployment | ResteasyDeployment |
RESTEasy 运行时部署对象,持有 Registry、ProviderFactory、Dispatcher 等 |
| MVC | Model-View-Controller | Spring Web MVC 模块;WAR 集成常通过 springmvc-resteasy.xml 注册 SpringBeanProcessor |
一、两个容器各自管什么
| 维度 | Spring | RESTEasy |
|---|---|---|
| 核心抽象 | ApplicationContext / BeanFactory |
ResteasyDeployment |
| 关键组件 | Bean 定义、BeanPostProcessor、事件 |
Registry、Dispatcher、ResteasyProviderFactory |
| 典型成员 | CustomerService、HelloResource(@Component) |
ResourceMethodRegistry 中的 ResourceFactory |
| 注解 | @Autowired、@Transactional… |
@Path、@GET、@Provider、@Context… |
Spring 容器 ≈ 对象工厂 + 业务依赖注入 + 生命周期(创建 / 销毁)
RESTEasy 容器 ≈ 路由注册表 + ResourceFactory 策略 + Provider 链(不是 BeanFactory)
RESTEasy 故意不做通用 IoC:业务 DI 交给 Spring,自己只管 HTTP 运行时——Registry 登记「怎么拿实例」,ProviderFactory 登记 @Provider 单例,Dispatcher 编排请求链。
JAX-RS 注解:Spring 产出实例,RESTEasy 消费元数据
一个常见的误解是:既然 Resource 类是 Spring Bean,Spring 是否也会处理 @Path、@GET?不会。 Spring 创建出来的 Bean,对 Spring 自身而言就是一个普通 Java 对象(可能被 AOP 代理);Spring 只关心 @Autowired、@Transactional、scope 等 Spring 语义,不会因为类上挂了 JAX-RS 注解就去注册路由或处理 HTTP。
JAX-RS 注解在这套集成里扮演的是 元数据——挂在类和方法上,解释与执行它们的始终是 RESTEasy:
| 注解 | Spring 是否处理 | RESTEasy 如何处理 |
|---|---|---|
@Path、@GET、@POST… |
❌ 不注册路由 | Registry 扫描,建立 URL → 方法映射 |
@Provider |
❌ 不加入 Filter 链 | registerProviderInstance 注册到 ProviderFactory |
@Context(字段) |
❌ 不走 @Autowired |
PropertyInjector 注入(singleton:启动期 BPP;prototype:请求期 getBean 链) |
@QueryParam、@PathParam(方法参数) |
❌ | MethodInjector 在方法调用前注入 |
@Autowired、@Transactional |
✅ Bean 创建时 DI / AOP 代理 | ❌ 不解析,调用时已注入完毕 |
因此分工可以概括为:
Spring 产出:带 JAX-RS 注解的 Bean 实例(+ 业务 DI / AOP / scope)
RESTEasy 消费:读注解建路由,按请求做 JAX-RS 注入和方法调用
SpringBeanProcessor 在 Spring 启动期 发现 带 @Path 的 Bean 并建立 SpringResourceFactory 映射——这是桥接层的扫描,不是 Spring 框架本身理解 JAX-RS。真正把 Resource 注册进 Registry、在请求进来时匹配 URI 并调用 @GET 方法的,仍是 RESTEasy(见第四节 ContextRefreshedEvent → addResourceFactory)。
二、Spring Bean 生命周期:从 refresh 到 destroy
Spring 容器的 Bean 生命周期是一个经过精心编排的流程,从 BeanDefinition 解析到实例销毁,每个阶段都有明确扩展点。1
ApplicationContext.refresh() 中的位置
refresh() 结束时发布 ContextRefreshedEvent——Resteasy-Spring 正是在这个事件上把 Resource 注册进 Registry。2
singleton 与 prototype 在启动期的差异从这里开始:
- singleton(及默认 scope):在
finishBeanFactoryInitialization()→preInstantiateSingletons()中 批量getBean(),走完整doCreateBean,实例写入一级缓存。 - prototype:
refresh()阶段 只注册BeanDefinition,不创建实例——首次getBean()(或依赖链触发)才走doCreateBean。 - Resteasy-Spring 的等待点:
SpringBeanProcessor.onApplicationEvent()注释写明需等 “spring singleton bean creation is completed”3——确保 singleton@PathResource 及其@Autowired依赖在注册进 RESTEasy Registry 前已就绪;prototype Resource 则通常不在此阶段实例化。
refresh() — AbstractApplicationContext.refresh() :582-625
├── prepareRefresh() … registerBeanPostProcessors() :590-606
│ └── SpringBeanProcessor.postProcessBeanFactory() :235-261 【仅扫描定义,不建实例】
├── finishBeanFactoryInitialization(beanFactory) :622
│ └── preInstantiateSingletons() :1102-1134
│ └── 仅 singleton:getBean() → createBean() → doCreateBean()
│ └── prototype:跳过,等首次 getBean()
└── finishRefresh() → ContextRefreshedEvent :625
└── SpringBeanProcessor.onApplicationEvent() :453-467
└── Registry.addResourceFactory(SpringResourceFactory)
└── SpringResourceFactory.registered() :44-45
容器关闭时,destroyBeans() 调用 destroySingletons()——只销毁 singleton;prototype 实例 Spring 不跟踪,SpringResourceFactory.unregistered() / requestFinished() 在集成路径下也为空实现。45
核心骨架:doCreateBean — 所有 scope 共用,触发时机不同
常见误解:doCreateBean 是 singleton 专属。实际上,无论 singleton 还是 prototype,只要容器要「新建一个 Bean 实例」,都走同一条 doCreateBean → initializeBean 流水线——scope 不改变流水线步骤,只改变 何时触发 以及 完成后是否入缓存。
createBeanInstance → populateBean → initializeBean → 【singleton 收尾:见下节】
AbstractAutowireCapableBeanFactory.doCreateBean() :556-603
createBeanInstance() :565
populateBean() :602 — @Autowired 在此
initializeBean() :603 — BPP、@PostConstruct 在此
└─ prototype: 直接 return exposedObject,容器不登记
注意:addSingleton() 不在 doCreateBean() 内部调用,而在其外层 getSingleton(beanName, ObjectFactory) 中——ObjectFactory lambda 执行 createBean() → doCreateBean(),成功返回后才 addSingleton() 写入 singletonObjects。
Singleton 缓存:DefaultSingletonBeanRegistry 三级结构与 addSingleton
DefaultSingletonBeanRegistry 是 Spring 各 BeanFactory 实现管理 singleton 实例的基类。类注释说明它 “factoring out the common management of singleton bean instances”,与 BeanDefinition、具体创建流程解耦。6
三个 Map + 一个注册表
// spring-beans/.../DefaultSingletonBeanRegistry.java:85-98
/** Cache of singleton objects: bean name to bean instance. */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
/** Creation-time registry of singleton factories: bean name to ObjectFactory. */
private final Map<String, ObjectFactory<?>> singletonFactories = new ConcurrentHashMap<>(16);
/** Cache of early singleton objects: bean name to bean instance. */
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
/** Set of registered singletons, containing the bean names in registration order. */
private final Set<String> registeredSingletons = Collections.synchronizedSet(new LinkedHashSet<>(256));
| Map | 俗称 | 存什么 | 何时写入 |
|---|---|---|---|
singletonObjects |
一级缓存 | 完全初始化完成的 singleton(含 AOP 包装后的最终对象) | addSingleton() |
singletonFactories |
二级缓存 | ObjectFactory,用于循环依赖时提前暴露半成品 |
addSingletonFactory() |
earlySingletonObjects |
三级缓存 | 从二级 ObjectFactory.getObject() 取出的早期引用 |
getSingleton() 解析循环依赖时 |
创建 singleton 的完整 call chain
AbstractBeanFactory.doGetBean() :343-356
└─ getSingleton(beanName, () -> createBean(...)) DefaultSingletonBeanRegistry :255
├─ singletonObjects 已有 → 直接返回(运行期 RESTEasy getBean 命中此路径)
└─ 未命中 → singletonFactory.getObject()
└─ createBean() → doCreateBean()
├─ createBeanInstance()
├─ addSingletonFactory(...) :587-596 【仅循环依赖场景,populateBean 之前】
├─ populateBean()
├─ initializeBean() — BPP、@PostConstruct
└─ return exposedObject
└─ addSingleton(beanName, singletonObject) :402-404
— 写入 singletonObjects,清除 singletonFactories / earlySingletonObjects
AbstractBeanFactory.doGetBean() 对 singleton 的入口:
// spring-beans/.../AbstractBeanFactory.java:343-356
if (mbd.isSingleton()) {
sharedInstance = getSingleton(beanName, () -> {
try {
return createBean(beanName, mbd, args);
}
catch (BeansException ex) {
destroySingleton(beanName); // 创建失败时清理二/三级缓存
throw ex;
}
});
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, mbd);
}
doCreateBean 中 循环依赖 的提前暴露(AbstractAutowireCapableBeanFactory.java:587-596):
// populateBean 之前:允许其他 Bean 在创建完成前先引用
boolean earlySingletonExposure = (mbd.isSingleton() && this.allowCircularReferences &&
isSingletonCurrentlyInCreation(beanName));
if (earlySingletonExposure) {
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));
}
getSingleton(beanName, allowEarlyReference) 的查找顺序——先一级,再三级,最后通过二级 ObjectFactory 生成早期引用:
// DefaultSingletonBeanRegistry.java:210-229
Object singletonObject = this.singletonObjects.get(beanName);
if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
singletonObject = this.earlySingletonObjects.get(beanName);
if (singletonObject == null && allowEarlyReference) {
ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
if (singletonFactory != null) {
singletonObject = singletonFactory.getObject();
if (this.singletonFactories.remove(beanName) != null) {
this.earlySingletonObjects.put(beanName, singletonObject);
}
}
}
}
创建成功后 addSingleton() 将 最终对象 写入一级缓存,并清理二、三级:
// DefaultSingletonBeanRegistry.java:159-167
protected void addSingleton(String beanName, Object singletonObject) {
Object oldObject = this.singletonObjects.putIfAbsent(beanName, singletonObject);
if (oldObject != null) {
throw new IllegalStateException("Could not register object [...] there is already object [...] bound");
}
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
prototype 完全不进入这三个 Map——doGetBean 的 prototype 分支 直接 createBean() 后 getObjectForBeanInstance() 返回,容器不持有引用。
与 Resteasy-Spring 的关系
- singleton
@PathResource:refresh()期间preInstantiateSingletons()→ 上述 call chain 走完 → 实例在singletonObjects;之后 RESTEasy 每次SpringResourceFactory.createResource()→getBean()命中一级缓存,不再doCreateBean。 - prototype Resource:不进入
singletonObjects;每次createResource()→getBean()走 prototype 分支,重新doCreateBean(见第四节)。
容器关闭时 destroySingletons() 清空三个 Map——仅影响 singleton;prototype 实例从未登记,无从销毁。
其中 initializeBean 是生命周期回调最密集的阶段(:1797-1821)——singleton 在 refresh() 走一次,prototype 在每次 getBean() 再走一次:
initializeBean — AbstractAutowireCapableBeanFactory.initializeBean() :1797-1821
├── invokeAwareMethods() :1803
├── applyBeanPostProcessorsBeforeInitialization :1807
├── invokeInitMethods() :1811 — @PostConstruct
└── applyBeanPostProcessorsAfterInitialization :1818
└── ResteasyBeanPostProcessor.postProcessAfterInitialization() :114-146
└── inject() — singleton 启动期;prototype 请求期(有 HttpRequest 时)
源码(AbstractAutowireCapableBeanFactory.java):
// spring-beans/.../AbstractAutowireCapableBeanFactory.java:556-603
protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object @Nullable [] args) {
BeanWrapper instanceWrapper = createBeanInstance(beanName, mbd, args);
Object bean = instanceWrapper.getWrappedInstance();
// ...
populateBean(beanName, mbd, instanceWrapper);
exposedObject = initializeBean(beanName, exposedObject, mbd);
// ...
}
// spring-beans/.../AbstractAutowireCapableBeanFactory.java:1797-1821
protected Object initializeBean(String beanName, Object bean, @Nullable RootBeanDefinition mbd) {
invokeAwareMethods(beanName, bean);
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
invokeInitMethods(beanName, wrappedBean, mbd);
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);
return wrappedBean;
}
一条完整的 Bean 生命周期时间线
创建流水线(singleton 与 prototype 相同):
| 阶段 | 事件 | 扩展点 |
|---|---|---|
| 定义 | 解析 XML / 扫描 @Component / @Bean |
BeanFactoryPostProcessor |
| 实例化 | createBeanInstance() |
InstantiationAwareBeanPostProcessor |
| 依赖注入 | populateBean() — @Autowired |
AutowiredAnnotationBeanPostProcessor |
| Aware | setBeanName、setBeanFactory… |
*Aware 接口 |
| BPP 前置 | postProcessBeforeInitialization |
自定义 BeanPostProcessor |
| 初始化 | @PostConstruct / InitializingBean |
InitDestroyAnnotationBeanPostProcessor |
| BPP 后置 | postProcessAfterInitialization |
AOP 代理、ResteasyBeanPostProcessor |
scope 决定「何时走流水线」与「收尾」(Resteasy-Spring 集成下):
| 维度 | singleton(默认) | prototype |
|---|---|---|
何时 doCreateBean |
refresh() → preInstantiateSingletons() |
每次 getBean()(含 RESTEasy createResource() 触发) |
| 实例缓存 | singletonObjects 一级缓存(DefaultSingletonBeanRegistry) |
不进入三个 singleton Map |
@Autowired 注入 |
启动期 populateBean 一次 |
每次 getBean 重新 populateBean |
JAX-RS 字段 @Context |
启动期 BPP(HttpRequest == null) |
请求期 BPP(HttpRequest 存在) |
| 销毁 | 容器关闭 destroySingletons() / @PreDestroy |
容器不跟踪,无自动 @PreDestroy |
典型 @Path Resource |
启动期实例就绪 → ContextRefreshedEvent 注册路由 |
启动期仅注册 Factory + 路由;首请求才实例化 |
AbstractBeanFactory.doGetBean() 是运行期 scope 分叉点——singleton 命中缓存则 跳过 整条 doCreateBean;prototype 每次进入 createBean():
// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton:缓存命中,不再 doCreateBean
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
beanInstance = getObjectForBeanInstance(sharedInstance, ...);
}
// :359-364 — prototype:每次 createBean → doCreateBean
else if (mbd.isPrototype()) {
prototypeInstance = createBean(beanName, mbd, args);
}
下文 RESTEasy 侧用 ResourceFactory 策略表达「取实例」;Spring 集成时 scope 仍由 Spring 决定,第四节详述 singleton / prototype 在 bridge 模式下的差异。
三、RESTEasy Resource 生命周期:Registry 与 ResourceFactory
RESTEasy 对 @Path Resource 的管理精简得多:不解析 @Autowired、不管理 AOP 代理、不处理 @PreDestroy——只关心 每次 HTTP 请求如何拿到 Resource 实例。
这里要先澄清一个常见混淆:RESTEasy 注册的不是 Resource 实例,而是 ResourceFactory——一个「取实例策略」对象。这与 Spring 在容器里登记 BeanDefinition、再按需 createBean() 是不同层级的抽象;理解这一点,是读懂 RESTEasy 与 Spring 分工的基础。
核心模式:一个 Resource 类 → 一个 ResourceFactory → 多个 Invoker
RESTEasy 采用 Strategy 模式:ResourceFactory 接口注释写得很直白——“Implementations of this interface are registered through the Registry class”。每个 @Path Resource 类在 Registry 中对应 一个 Factory 实例;Factory 回答的问题是:「每次请求来了,怎么拿到 Resource 对象?」——而不是「如何初始化一个通用 Bean」。
ResourceMethodRegistry 提供了多种 注册 API,本质是在选择不同的 Factory 实现:
// ResourceMethodRegistry.java — 按创建策略选择 Factory,而非直接注册实例
public void addPerRequestResource(Class clazz) {
addResourceFactory(new POJOResourceFactory(resourceBuilder, clazz)); // 每请求 new
}
public void addSingletonResource(Object singleton) {
ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(singleton.getClass());
addResourceFactory(new SingletonResource(singleton, resourceClass)); // 固定对象
}
public void addJndiResource(String jndiName) {
addResourceFactory(new JndiResourceFactory(jndiName)); // JNDI 查找
}
// Spring 集成:addResourceFactory(new SpringResourceFactory(beanName, beanFactory, scannable))
POJOResourceFactory 的类注释 “Allocates an instance of a class at each invocation” 进一步说明:Factory 的职责是 分配(allocate)实例,不是长期持有实例。
注册期 call chain:Factory 与 Invoker 如何绑定
ResourceMethodRegistry.addResourceFactory() 是 Registry 的核心入口,分两步:7
addResourceFactory(ResourceFactory ref, …, Class[] classes)
├─ ref.registered(providerFactory) — 启动期:预建 ConstructorInjector / PropertyInjector
└─ for each clazz:
register(ref, base, resourceClass)
└─ processMethod(rf, base, method) — 每个 @GET/@POST 方法
└─ new ResourceMethodInvoker(method, injectorFactory, rf, providerFactory)
— 同一 rf 被所有方法的 Invoker 共享
源码:
// resteasy-core/.../ResourceMethodRegistry.java:200-220
public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder, String base, Class[] classes) {
if (ref != null)
ref.registered(providerFactory);
for (Class clazz : classes) {
ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(clazz);
register(ref, base, resourceClass);
}
}
// :295-310 — 每个 HTTP 方法绑定同一个 ResourceFactory rf
protected void processMethod(ResourceFactory rf, String base, ResourceLocator method) {
// ...
ResourceMethodInvoker invoker = new ResourceMethodInvoker((ResourceMethod) method, injectorFactory, rf, providerFactory);
root.addInvoker(classExpression, fullpath, invoker);
}
要点:
- Registry 的「路由表」存的是
ResourceMethodInvoker(URL → 方法调用器),每个 Invoker 持有 同一个ResourceFactory引用(ResourceMethodInvoker字段protected ResourceFactory resource)。 registered()在 注册期 调用一次,预建注入器;不在此阶段创建 Resource 实例(SingletonResource除外——实例由调用方在addSingletonResource(singleton)时传入,Factory 仅持有引用)。- 一个
HelloResource类若有 3 个@GET方法 → 1 个 Factory + 3 个 Invoker,Factory 决定 3 个 Invoker 共享的取实例策略。
为何每个端点一个 ResourceMethodInvoker?
需要澄清:不是「每个 Java 方法」一个 Invoker,而是每个 JAX-RS HTTP 端点一个 Invoker——@GET /users 与 @POST /users 在 Registry 路由表里是两个不同叶子节点,ResourceMethodInvoker 就是这个叶子。
1. 路由匹配的目标是一个端点(Resource Method),不是一个类
registry.getResourceInvoker(request) 做的是 URI + HTTP 动词 → 唯一 Invoker,不是「先找到 Resource 类再反射找方法」。JAX-RS 规范(Section 3.7)的匹配粒度就是 Resource Method;Registry 的 URL 树(RootClassNode)在注册期把每个 @Path + @GET/@POST 组合挂到独立 Invoker 上,请求期才能直接命中,而无需每次反射扫描。
2. Invoker 绑定的是「这个方法独有」的运行时上下文
ResourceMethodInvoker 构造函数 在 注册期 为 这一个 端点预建好调用链——这些 metadata 因方法而异,无法多个端点共享:
// resteasy-core/.../ResourceMethodInvoker.java:97-141(节选)
public ResourceMethodInvoker(final ResourceMethod method, final InjectorFactory injector,
final ResourceFactory resource, final ResteasyProviderFactory providerFactory) {
this.resource = resource; // 共享:取实例策略(ResourceFactory)
this.method = method; // 独有:这个端点的 metadata
this.methodInjector = injector.createMethodInjector(method, resourceMethodProviderFactory);
requestFilters = resourceMethodProviderFactory.getContainerRequestFilterRegistry()
.postMatch(method.getResourceClass().getClazz(), method.getAnnotatedMethod());
responseFilters = resourceMethodProviderFactory.getContainerResponseFilterRegistry()
.postMatch(method.getResourceClass().getClazz(), method.getAnnotatedMethod());
// … @Produces / 异步 / SSE / Bean Validation 等也按 method 配置
}
| 注册期绑定 | 为何 per-method |
|---|---|
methodInjector |
每个方法参数列表不同(@QueryParam("id") vs @PathParam("name")) |
requestFilters / responseFilters |
@NameBinding、Filter 按 具体方法 postMatch(clazz, method) |
@Produces / @Consumes |
内容协商、是否期望 request body,因方法而异 |
| 异步 / SSE / 校验 | 返回类型、@Stream、Validation 均按方法配置 |
3. 共享 Factory,不共享 MethodInjector
运行时 Invoker 的职责拆成两步——取实例与调方法:
// ResourceMethodInvoker.java:348-356 + :555-560
public BuiltResponse invoke(HttpRequest request, HttpResponse response) {
Object resource = this.resource.createResource(...); // 共享 ResourceFactory
return invoke(request, response, resource);
}
private Object internalInvokeOnTarget(..., Object target) {
return this.methodInjector.invoke(request, response, target); // 此方法专属
}
ResourceFactory(一个 Resource 类一个):回答「怎么拿实例」MethodInjector(一个 HTTP 端点一个):回答「调哪个方法、参数怎么填」
4. 与 Spring 的分工对比
| Spring | RESTEasy | |
|---|---|---|
| 登记粒度 | BeanDefinition(一个类一条) |
ResourceFactory(一个类一条)+ ResourceMethodInvoker(一个端点一条) |
| 运行时 | getBean() 拿到对象,调用方自己决定调哪个方法 |
路由直接命中 Invoker,已知调哪个方法 |
Spring 管到 对象;RESTEasy 还要管 HTTP 路由 + 方法级 JAX-RS 语义,Invoker 粒度自然是端点而非类。
5. 设计收益:注册期预计算,请求期少反射
若在请求期才「找类 → 扫描方法 → 匹配 URI → 解析参数注解」,每次请求都要反射 + 元数据解析。RESTEasy 在 addResourceFactory 时把 Injector、Filter 链、Produces 等 固化进 Invoker;请求路径只做 URL 匹配 + createResource() + methodInjector.invoke()。
请求期 call chain:Invoker 委托 Factory 取实例
SynchronousDispatcher.invoke()
└─ registry.getResourceInvoker(request) — URL 匹配,返回 ResourceMethodInvoker
└─ ResourceMethodInvoker.invoke()
└─ this.resource.createResource(request, response, factory) — 委托 Factory 策略
├─ POJOResourceFactory: constructorInjector.construct() + propertyInjector.inject()
├─ SingletonResource: return obj
└─ SpringResourceFactory: beanFactory.getBean(beanName)
└─ methodInjector.invoke(request, response, target) — @QueryParam 等
// resteasy-core/.../ResourceMethodInvoker.java:348-356
public BuiltResponse invoke(HttpRequest request, HttpResponse response) {
Object resource = this.resource.createResource(request, response, resourceMethodProviderFactory);
// ...
return invoke(request, response, resource);
}
RESTEasy 永远通过 ResourceMethodInvoker 调用 createResource() 取实例——无论底层是 per-request new、singleton 固定对象还是 Spring getBean()。这是 RESTEasy 侧 Resource 生命周期的 唯一入口。
设计思想对比:Spring Bean 容器 vs RESTEasy Factory 模式
两者解决不同问题,抽象层级也不同:
| 维度 | Spring BeanFactory |
RESTEasy Registry + ResourceFactory |
|---|---|---|
| 设计目标 | 通用 IoC:管理应用中所有对象 | HTTP 运行时:URI 匹配 + 取 Resource 实例 + JAX-RS 调用链 |
| Registry 存什么 | BeanDefinition(名称 → 定义/metadata) |
ResourceMethodInvoker(URL 路径 → 调用器 → Factory 引用) |
| 实例从哪来 | 容器 createBean() / getBean(),scope 内置于 Definition |
委托给 ResourceFactory.createResource(),scope 由 Factory 实现类决定 |
| scope 表达 | singleton / prototype / request / 自定义 Scope |
无统一 scope 框架;选 POJOResourceFactory ≈ per-request,SingletonResource ≈ singleton,SpringResourceFactory ≈ 外包给 Spring scope |
| 元数据扫描 | 启动期扫描全部 BeanDefinition | 注册 Factory 时扫描 getScannableClass() 上的 @Path / @GET |
| 依赖注入 | @Autowired、@Value、构造器注入 |
仅 JAX-RS:@Context 字段、@QueryParam 方法参数;业务 DI 故意不做 |
| 扩展点 | BeanFactoryPostProcessor、BeanPostProcessor、*Aware |
自定义 ResourceFactory 实现;@Provider 注册到 ProviderFactory |
| 销毁 | destroySingletons()、@PreDestroy |
requestFinished() 回调——可选,由 Factory 实现决定是否 destroy |
Spring 的思路:一切皆 Bean。容器持有 定义(Definition),所有 scope 共用 doCreateBean 流水线;singleton 在 refresh() 批量实例化并缓存,prototype 在每次 getBean() 重新走流水线且不入缓存。AOP/事务/事件都挂在这条流水线上。
RESTEasy 的思路:容器只管 HTTP。Registry 持有 策略(Factory),每次请求问 Factory「给我一个实例」;Factory 可以 new、可以返回已有对象、可以查 JNDI、可以调 Spring getBean()——RESTEasy 不关心 Spring scope,只保证调用链一致(createResource → 注入参数 → 反射调用 → requestFinished)。
用一张图概括两种「登记」:
Spring RESTEasy
────── ────────
BeanDefinition ResourceFactory(策略对象)
"helloResource" SpringResourceFactory(beanName, …)
scope=singleton|prototype │
class=HelloResource ├─ registered() → 预建 Injector
│ └─ createResource() → getBean() / new
▼ │
getBean("helloResource") ▼
singleton: 缓存 or prototype: doCreateBean ResourceMethodInvoker
(同一 doCreateBean 流水线) GET /hello → invoke()
→ createResource() → 实例
→ MethodInjector → @GET 方法
ResourceFactory 接口把 注册期 与 请求期 拆成五个回调,正是这一策略模式的契约:
// resteasy-core-spi/.../ResourceFactory.java:9-44
public interface ResourceFactory {
Class<?> getScannableClass(); // :15
void registered(ResteasyProviderFactory factory); // :22
Object createResource(HttpRequest request, HttpResponse response,
ResteasyProviderFactory factory); // :32
void requestFinished(HttpRequest request, HttpResponse response, Object resource); // :41
void unregistered(); // :43
}
| 回调 | 触发时机 | 用途 |
|---|---|---|
registered() |
Registry.addResourceFactory 之后 |
创建 PropertyInjector / ConstructorInjector,为 JAX-RS 字段注入做准备 |
createResource() |
每次请求 | 获取 Resource 实例 |
requestFinished() |
请求结束 | 纯 RESTEasy 场景可触发 @PreDestroy 语义;Spring 集成为空实现 |
unregistered() |
从 Registry 移除 | Spring 集成为空实现 |
requestFinished 的接口注释写得很清楚:“usable for things like @PreDestroy if the underlying factory supports it”——Spring 集成里实例销毁归 Spring,RESTEasy 不在此回调里 destroy Bean。5
三种内置 ResourceFactory:三种「取实例策略」
以下三种实现 不是 Bean 初始化流程,而是三种不同的 createResource() 策略,由 Registry 注册 API 或集成层(如 SpringBeanProcessor)选择绑定:
| 策略 | Factory 实现 | createResource() 行为 |
近似 Spring scope |
|---|---|---|---|
| per-request | POJOResourceFactory |
每请求 construct() + JAX-RS 字段注入 |
类似 prototype(但无 Spring DI) |
| singleton | SingletonResource |
返回构造时传入的固定 obj |
类似 singleton(但无 Spring 容器) |
| 外包 | SpringResourceFactory |
beanFactory.getBean(beanName) |
由 Spring scope 决定 |
1. POJOResourceFactory —— per-request,RESTEasy 自己 new(POJOResourceFactory.java)
// :51-60 registered
public void registered(ResteasyProviderFactory factory) {
ResourceConstructor constructor = resourceClass.getConstructor();
// ...
this.constructorInjector = factory.getInjectorFactory().createConstructor(constructor, factory);
this.propertyInjector = factory.getInjectorFactory().createPropertyInjector(resourceClass, factory);
}
// :63-77 createResource
public Object createResource(HttpRequest request, HttpResponse response, ResteasyProviderFactory factory) {
Object obj = constructorInjector.construct(request, response, true);
CompletionStage<Void> propertyStage = propertyInjector.inject(request, response, obj, true);
return propertyStage == null ? obj : propertyStage.thenApply(v -> obj);
}
2. SingletonResource —— 固定对象,registered 时注入一次(SingletonResource.java)
addSingletonResource(Object singleton) 要求调用方 先把实例建好 再交给 Registry——RESTEasy 不会在此路径下 new Resource:
// resteasy-core/.../ResourceMethodRegistry.java:95-98
public void addSingletonResource(Object singleton) {
ResourceClass resourceClass = resourceBuilder.getRootResourceFromAnnotations(singleton.getClass());
addResourceFactory(new SingletonResource(singleton, resourceClass));
}
SingletonResource 类注释写得很直白:“VERY simple implementation that just returns the instance the SingletonResource was created with”。Factory 用 final 字段 持有 传入的实例引用:
// resteasy-core/.../SingletonResource.java:19-44
public class SingletonResource implements ResourceFactory {
private final Object obj;
private final ResourceClass resourceClass;
public void registered(ResteasyProviderFactory factory) {
factory.getInjectorFactory().createPropertyInjector(resourceClass, factory).inject(obj, false);
}
public Object createResource(HttpRequest request, HttpResponse response, ResteasyProviderFactory factory) {
return obj;
}
public void unregistered() { }
public void requestFinished(HttpRequest request, HttpResponse response, Object resource) { }
}
registered():启动期对 已有obj做一次 JAX-RS 字段注入(@Context等)。createResource():每次请求直接return obj,不再创建。unregistered()/requestFinished():空实现——RESTEasy 不负责销毁;实例生命周期由 传入实例的调用方 管理。
Singleton 的两种路径:谁维护 instance?
RESTEasy 没有 Spring 那样内建的 scope 框架;「singleton」语义通过 选择不同的 Factory 实现 表达。是否由 RESTEasy 维护 instance,取决于走哪条路径:
| 路径 | RESTEasy 是否持有 instance | 实例实际存放位置 | 谁创建 | 谁销毁 |
|---|---|---|---|---|
SingletonResource |
✅ Factory 内 final Object obj |
SingletonResource 对象 |
调用方 addSingletonResource(singleton) 传入 |
调用方;RESTEasy 回调为空 |
SpringResourceFactory + singleton scope |
❌ 只持有 Factory 策略 | Spring 一级单例缓存 | Spring refresh() → doCreateBean |
Spring destroySingletons() / @PreDestroy |
POJOResourceFactory |
❌ 不长期持有 | 无(每请求新建) | RESTEasy ConstructorInjector |
无自动 destroy |
纯 RESTEasy singleton:Registry 登记的是 SingletonResource 策略对象,策略对象里 握着 实例引用;RESTEasy 维护的是 引用 + 复用,不是完整的 Bean 生命周期(不 create、不 destroy)。
Spring 集成 singleton:Registry 登记的是 SpringResourceFactory(只存 beanName + beanFactory),不存 instance:
// resteasy-spring/.../SpringResourceFactory.java:35-37
public Object createResource(HttpRequest request, HttpResponse response, ResteasyProviderFactory factory) {
return beanFactory.getBean(beanName); // singleton 时命中 Spring 缓存
}
每次 createResource() 向 Spring 要 Bean;RESTEasy 侧每次返回同一对象,是因为 Spring 缓存了 singleton,不是因为 RESTEasy Factory 里有一个 obj 字段。
纯 RESTEasy singleton Spring bridge + singleton scope
───────────────────── ────────────────────────────────
调用方 new HelloResource() Spring refresh() → doCreateBean
│ │
▼ ▼
addSingletonResource(instance) SpringBeanProcessor 建 SpringResourceFactory
│ │
▼ ▼
SingletonResource.obj = instance Registry 登记 Factory(无 instance 字段)
│ │
▼ ▼
createResource() → return obj createResource() → getBean() → Spring 缓存
因此,「singleton Resource 是否由 RESTEasy 维护 instance」的准确答案是:纯 SingletonResource 路径下是——RESTEasy Factory 持有引用;Spring 集成路径下否——实例归 Spring 容器,RESTEasy 只维护取实例策略。
3. SpringResourceFactory —— 实例来源外包给 Spring(SpringResourceFactory.java)
RESTEasy 仍只持有一个 Factory 策略对象;createResource() 把取实例委托给 Spring getBean()。singleton / prototype scope 差异见 第四节。
RESTEasy 与 Spring 生命周期对照(Spring 集成,按 scope)
sequenceDiagram
participant Spring as Spring 容器
participant RESTEasy as RESTEasy Registry
participant Resource as @Path Resource
Note over Spring,Resource: 启动阶段(singleton Resource)
Spring->>Spring: preInstantiateSingletons → doCreateBean
Spring->>Spring: ResteasyBeanPostProcessor(JAX-RS 字段注入)
Spring->>Resource: singleton 实例就绪
Note over Spring,Resource: prototype Resource:此阶段通常无实例
Note over Spring,Resource: ContextRefreshedEvent(两种 scope 均注册 Factory)
Spring->>RESTEasy: addResourceFactory(SpringResourceFactory)
RESTEasy->>RESTEasy: SpringResourceFactory.registered()
Note over Spring,Resource: 每次请求
RESTEasy->>Spring: createResource() → getBean(beanName)
alt singleton
Spring-->>RESTEasy: 缓存实例(跳过 doCreateBean)
else prototype
Spring->>Spring: doCreateBean 全流程
Spring-->>RESTEasy: 新实例
end
RESTEasy->>Resource: MethodInjector 注入 @QueryParam
RESTEasy->>Resource: 调用 @GET 方法
RESTEasy->>RESTEasy: requestFinished()(Spring 路径无操作)
第三节对比了纯 RESTEasy 的三种 ResourceFactory;Spring 集成路径下,桥接逻辑集中在 第四节。
四、resteasy-spring 如何桥接
桥接由两个类分工:SpringBeanProcessor 在 Spring 启动三阶段(BFPP → BPP → ContextRefreshedEvent)扫描 @Path / @Provider、建 SpringResourceFactory 映射并注册 Registry;SpringResourceFactory 在运行期把 RESTEasy 的 createResource() 转译为 getBean()。
SpringBeanProcessor:启动期编排
SpringBeanProcessor 实现 BeanFactoryPostProcessor 与 SmartApplicationListener,在 Spring 启动的三个关键时刻插入逻辑。8 它不会自动出现在 classpath 上——WAR 需 SpringContextLoaderSupport.addBeanFactoryPostProcessor(),Spring MVC 集成 import springmvc-resteasy.xml,Spring Boot 由 ResteasyAutoConfiguration 的 @Bean 注册。9
阶段一:Bean 定义期(postProcessBeanFactory :235-261)
// resteasy-spring/.../SpringBeanProcessor.java:235-261
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
beanFactory.registerResolvableDependency(Registry.class, getRegistry());
beanFactory.registerResolvableDependency(ResteasyProviderFactory.class, getProviderFactory());
beanFactory.addBeanPostProcessor(new ResteasyBeanPostProcessor(beanFactory));
for (String name : beanFactory.getBeanDefinitionNames()) {
processBean(beanFactory, dependsOnBeans, name, beanDef);
// @Provider → providerNames (:276-286)
// 根 @Path → resourceFactories.put(name, new SpringResourceFactory(...)) (:288-289)
}
// Resource depends-on 非 MessageBody Reader/Writer 的 @Provider (:256-260)
}
此阶段 只建 SpringResourceFactory 映射,不创建 Bean 实例,也不注册进 Registry。
阶段二:每个 Bean 初始化后(ResteasyBeanPostProcessor.postProcessAfterInitialization :114-146)
类注释写明了 @Path 与 @Provider 的分工(SpringBeanProcessor.java:95-108)。
// resteasy-spring/.../SpringBeanProcessor.java:114-146
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (providerNames.contains(beanName)) {
PropertyInjector injector = getInjector(AopUtils.getTargetClass(bean));
injector.inject(bean, false);
providerFactory.registerProviderInstance(bean);
} else {
SpringResourceFactory resourceFactory = resourceFactories.get(beanName);
if (resourceFactory != null) {
inject(beanName, bean, getInjector(resourceFactory.getScannableClass()));
}
}
return bean;
}
| Bean 类型 | 本阶段 | 何时发生 | 为何如此 |
|---|---|---|---|
@Provider(singleton) |
注入 + registerProviderInstance |
refresh() 期间 |
Filter / Interceptor 须在 Resource 之前就位 |
@Path Resource(singleton) |
仅 PropertyInjector.inject |
refresh() 期间 |
JAX-RS 字段启动期注入;Registry 注册推迟到 ContextRefreshedEvent |
@Path Resource(prototype) |
— | 通常跳过(未实例化) | preInstantiateSingletons 不包含 prototype;首请求 getBean 时才走 BPP |
阶段三:容器刷新完成(onApplicationEvent :453-467)
// resteasy-spring/.../SpringBeanProcessor.java:453-467
public void onApplicationEvent(ApplicationEvent event) {
for (SpringResourceFactory resourceFactory : resourceFactories.values()) {
getRegistry().removeRegistrations(resourceFactory.getScannableClass());
}
for (SpringResourceFactory resourceFactory : resourceFactories.values()) {
getRegistry().addResourceFactory(resourceFactory, resourceFactory.getContext());
}
}
注释(:446-448):“Register all of the resources into RESTEasy only when Spring finishes its life-cycle and the spring singleton bean creation is completed.”3——此处强调 singleton 批量创建完毕,以保证 singleton @Path Resource 的 @Autowired 依赖链完整;prototype Resource 的 Factory 映射同样在此时注册进 Registry,只是实例尚未创建。
Registry 侧会回调 ResourceFactory.registered():
// resteasy-core/.../ResourceMethodRegistry.java:220-222
public void addResourceFactory(ResourceFactory ref, ResourceBuilder resourceBuilder, String base, Class<?>[] classes) {
if (ref != null)
ref.registered(providerFactory);
// ... register(ref, base, resourceClass) 扫描 @Path/@GET 建路由
}
若 Resource 依赖的 CustomerService 尚未创建,过早注册会导致依赖不完整。
sequenceDiagram
participant Spring as Spring 启动
participant SBP as SpringBeanProcessor
participant Registry as RESTEasy Registry
Note over Spring,Registry: T1 — postProcessBeanFactory
Spring->>SBP: 扫描 @Path,建 SpringResourceFactory
Note over Spring,Registry: T2 — 逐 Bean 初始化(主要 singleton)
loop 每个已实例化的 Bean
Spring->>SBP: postProcessAfterInitialization
alt @Provider singleton
SBP->>Registry: registerProviderInstance
else @Path singleton
SBP->>SBP: JAX-RS 字段注入
else @Path prototype
Note over SBP: 通常不在此 loop(尚未 getBean)
end
end
Note over Spring,Registry: T3 — ContextRefreshedEvent
Spring->>SBP: onApplicationEvent
SBP->>Registry: addResourceFactory + registered()
SpringResourceFactory:从 Startup 映射到 Registry,再到运行期调用
上一小节按 Spring 启动三阶段(T1/T2/T3)编排;本节把 SRF 对象本身 在 RESTEasy runtime 中的位置串成一条线:先进入 SBP 的本地 Map,再进入 Registry,最后被 Invoker 持有并在每次请求调用。
三阶段:SRF 何时「存在」、何时「生效」
| 阶段 | 触发点 | SRF 状态 | 是否已在 Registry | 是否可被 HTTP 命中 |
|---|---|---|---|---|
| T1 | postProcessBeanFactory |
resourceFactories.put(beanName, new SpringResourceFactory(...)) |
❌ | ❌ |
| T2 | ResteasyBeanPostProcessor.postProcessAfterInitialization |
同一 SRF 对象仍在 Map 中;singleton @Path 做字段 @Context 注入 |
❌ | ❌ |
| T3 | ContextRefreshedEvent → onApplicationEvent |
getRegistry().addResourceFactory(srf, …) |
✅ | ✅(Servlet 已就绪后) |
T1 扫描逻辑在 processBean():GetRestful.isRootResource(scannableClass) 为 true 的根 @Path Bean,按 bean 名称 建 SRF,此时既不创建 Resource 实例,也不调用 Registry.addResourceFactory——Registry 里还没有任何与该 Resource 相关的路由。
T2 对 @Path singleton:BPP 用 resourceFactories.get(beanName) 取到 T1 已建好的同一个 SRF,只对 已实例化的 Bean 做 PropertyInjector.inject;prototype Resource 通常尚未 getBean,此 loop 跳过,但 T1 里已为它建好 SRF 映射。
T3 才是 SRF 正式进入 RESTEasy runtime 的时刻:
// resteasy-spring/.../SpringBeanProcessor.java:453-467
for (SpringResourceFactory resourceFactory : resourceFactories.values()) {
getRegistry().removeRegistrations(resourceFactory.getScannableClass());
}
for (SpringResourceFactory resourceFactory : resourceFactories.values()) {
getRegistry().addResourceFactory(resourceFactory, resourceFactory.getContext());
}
addResourceFactory 内部链式发生三件事(见第三节 ResourceMethodRegistry.addResourceFactory):
srf.registered(providerFactory)— 为 SRF 关联的类创建PropertyInjector等(Spring 路径下主要注入已在 T2 BPP 完成)。register(ref, base, resourceClass)— 扫描@Path/@GET/@POST,在 URL 树(RootClassNode)上挂叶子。processMethod(...)— 每个 HTTP 端点 生成一个ResourceMethodInvoker,其protected ResourceFactory resource字段 指向同一个 SRF 引用。
因此:Registry 存的不是 HelloResource 实例,而是 SRF 策略 + 若干 Invoker;singleton 与 prototype 在 T3 注册方式相同,差异只在运行期 getBean() 是否命中缓存。
运行期:Invoker → SRF → Spring
HTTP 请求进入后,SRF 的唯一调用入口是 Invoker:
SynchronousDispatcher.invoke()
→ registry.getResourceInvoker(request) // URI + HTTP 动词 → ResourceMethodInvoker
→ ResourceMethodInvoker.invoke()
→ resource.createResource(...) // resource 即 SRF
→ beanFactory.getBean(beanName)
→ methodInjector.invoke(...) // @QueryParam 等
→ resource.requestFinished(...) // Spring 路径:空实现
SpringResourceFactory.createResource() 不含 scope 分支;singleton 返 Spring 一级缓存,prototype 每次重走 doCreateBean。RESTEasy 侧 始终 认为自己在调 ResourceFactory.createResource(),实例语义完全外包给 Spring。
WAR 与 Boot:Registry 从哪来,SRF 注册时机不变
SRF 的 T1→T3 流程在两种部署下 相同;差别在 Registry / ProviderFactory 何时挂到 ResteasyDeployment 上:
| 路径 | Registry 初始来源 | SRF 注册进 Registry |
|---|---|---|
| WAR / ServletContainerInitializer | new SpringBeanProcessor(deployment) 构造时从已有 deployment 取 Registry |
T3 ContextRefreshedEvent(同左) |
| Spring Boot | @Bean SpringBeanProcessor() 无参构造,内部 新建空 Registry |
T3 同上;Servlet 启动后 DeploymentCustomizer 把 SBP 的 Registry/ProviderFactory 合并进 ResteasyDeployment,再 deployment.start() |
Boot 下可能出现:ContextRefreshedEvent 时 SRF 已进 SBP 持有的 Registry,但 deployment 合并稍晚——合并的是 同一个 Registry 对象引用,故路由不会重复注册。运行期 Dispatcher 使用的仍是合并后的 deployment,Invoker → SRF → getBean() 链路与 WAR 一致。
Startup(两种部署 SRF 路径相同)
────────────────────────────────
T1 SBP.resourceFactories[beanName] = new SpringResourceFactory(...)
T2 BPP:singleton 字段 @Context;prototype 通常跳过
T3 Registry.addResourceFactory(SRF) → registered() + Invoker(srf)
Runtime(两种部署相同)
──────────────────────
HTTP → Invoker.invoke() → SRF.createResource() → getBean(beanName)
SpringResourceFactory:运行期取实例
SpringResourceFactory 实现 ResourceFactory,使 RESTEasy 在 Registry 中登记「向 Spring 要实例」的策略,而不是自己 new。
// resteasy-spring/.../SpringResourceFactory.java:25-29
public SpringResourceFactory(final String beanName, final BeanFactory beanFactory, final Class<?> scannable) {
this.beanName = beanName;
this.beanFactory = beanFactory;
this.scannableClass = scannable;
}
beanName+beanFactory:运行时getBean()scannableClass:RESTEasy 扫描@Path/@GET建路由用(可能与 AOP 代理类不同,故单独保存)
RESTEasy 认为自己在「每次请求创建 Resource」,实际上是在「每次请求从 Spring 取 Bean」——实例的创建、DI、AOP、销毁都由 Spring 决定。
分工要点:
createResource()只向 Spring 要 Bean,不做 JAX-RS 字段注入(那是ResteasyBeanPostProcessor在 Spring BPP 阶段或 prototype 的getBean链路上完成的)。registered()仍会创建PropertyInjector,但 Spring 路径下主要注入逻辑在 Spring 启动的 BPP 里。requestFinished()空实现——prototype Resource 的销毁靠 Spring scope,不靠 RESTEasy 回调。
Singleton vs Prototype:bridge 模式下 scope 如何改变行为
桥接机制本身不变——无论 singleton 还是 prototype,Registry 里登记的仍是同一个 SpringResourceFactory,每次请求仍走 createResource() → getBean(beanName)。区别在 Spring scope 改变了 getBean() 的返回值与创建路径,进而影响 JAX-RS 字段注入时机、AOP 代理和销毁语义。
createResource 与 getBean:入口相同,分支不同
SpringResourceFactory.createResource() 对两种 scope 不做区分,统一委托 Spring:
// resteasy-spring/.../SpringResourceFactory.java:35-37
public Object createResource(HttpRequest request, HttpResponse response, ResteasyProviderFactory factory) {
return beanFactory.getBean(beanName);
}
AbstractBeanFactory.doGetBean() 在这里分叉:
// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton 命中一级缓存
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, null);
}
// :359-364 — prototype 每次走完整 createBean → doCreateBean
else if (mbd.isPrototype()) {
prototypeInstance = createBean(beanName, mbd, args);
beanInstance = getObjectForBeanInstance(prototypeInstance, requiredType, name, beanName, null);
}
- singleton:直接返回
refresh()期间preInstantiateSingletons()已创建并缓存的实例(可能被 AOP 包装)。 - prototype:每次请求重新走
createBeanInstance→populateBean(@Autowired)→initializeBean(BPP、@PostConstruct)全流程。
启动期:prototype Resource 通常不在 refresh 时创建
DefaultListableBeanFactory.preInstantiateSingletons() 只 eagerly 初始化 singleton。prototype @Path Resource 在 refresh() 阶段 不会被创建,因此也不会在启动期经过 ResteasyBeanPostProcessor.postProcessAfterInitialization()。
SpringBeanProcessor 在 BFPP 阶段仍会为 prototype Resource 建立 SpringResourceFactory 映射并在 ContextRefreshedEvent 注册进 Registry——路由注册与 scope 无关;但 Bean 实例与 JAX-RS 字段注入推迟到第一次 HTTP 请求、createResource() 首次触发 getBean() 时才发生。
inject() 分支:JAX-RS 字段注入时机的核心开关
ResteasyBeanPostProcessor.inject() 根据 当前线程是否有 HttpRequest 以及 bean 是否为 singleton 决定注入方式:
// resteasy-spring/.../SpringBeanProcessor.java:152-163
public void inject(String beanName, Object bean, PropertyInjector propertyInjector) {
if (propertyInjector == null) {
return;
}
HttpRequest request = ResteasyContext.getContextData(HttpRequest.class);
if (request == null || isSingleton(beanName)) {
propertyInjector.inject(bean, false);
} else {
HttpResponse response = ResteasyContext.getContextData(HttpResponse.class);
propertyInjector.inject(request, response, bean, false);
}
}
| 条件 | 走哪个分支 | 典型场景 |
|---|---|---|
request == null |
inject(bean, false) |
启动期 refresh() 创建 singleton Resource |
isSingleton(beanName) |
inject(bean, false) |
运行期 getBean() 命中 singleton 缓存(即使有 HttpRequest) |
prototype 且 request != null |
inject(request, response, bean, false) |
请求线程 createResource() → getBean() 新建 prototype 实例 |
singleton 路径:字段 @Context UriInfo 等在启动期注入一次(此时 HttpRequest == null),之后所有请求复用同一对象——UriInfo 等可能 不是当前请求的值,这是集成里最容易踩坑的点。
prototype 路径:getBean() 发生在 RESTEasy 请求线程内,ResteasyContext 已绑定当前 HttpRequest,字段 @Context 按 当前请求 注入——行为更符合 JAX-RS per-request 语义,代价是每请求完整 Bean 创建。
方法参数上的 @QueryParam / @PathParam 不受此分支影响,始终由 RESTEasy MethodInjector 在方法调用前注入(见第五节)。
singleton vs prototype 完整对照
| 维度 | singleton Resource | prototype Resource |
|---|---|---|
| 桥接入口 | SpringResourceFactory → getBean() |
同左 |
| Registry 注册 | ContextRefreshedEvent 一次 |
同左(登记的是 Factory,不是实例) |
每次请求 getBean() |
命中缓存,同一实例 | createBean() → 完整 doCreateBean |
启动期 refresh() |
preInstantiateSingletons() 创建 + BPP 注入 |
通常不创建 |
字段 @Context |
启动期注入,全请求共享 | 请求期 getBean 链注入,每请求新实例 |
@Autowired |
启动期注入一次 | 每请求 populateBean 重新注入 |
@Transactional / AOP |
启动期代理,每请求复用 | 每请求新建实例(及可能的代理) |
| 性能 | 低(缓存命中) | 高(每请求完整生命周期) |
销毁:bridge 模式下 prototype 的盲区
SpringResourceFactory.requestFinished() 在 Spring 集成路径下 空实现:
// resteasy-spring/.../SpringResourceFactory.java:48-50
public void requestFinished(HttpRequest request, HttpResponse response, Object resource) {
}
对比纯 RESTEasy POJOResourceFactory——每请求 new 实例,RESTEasy 可在 requestFinished() 触发 @PreDestroy 语义;Spring bridge 路径下:
- RESTEasy 不在请求结束时 destroy 实例;
- Spring 对 prototype Bean 默认也不在
getBean()返回后调@PreDestroy(调用方负责生命周期)。
若 prototype Resource 持有连接、文件句柄等需释放的资源,不能指望框架自动清理——应在业务方法内显式释放,或避免对 Resource 使用 prototype scope。
与 POJOResourceFactory 对比
| POJOResourceFactory(纯 RESTEasy) | Spring bridge + prototype | |
|---|---|---|
| 谁创建实例 | RESTEasy ConstructorInjector.construct() |
Spring doCreateBean() |
@Autowired |
❌ | ✅ 每请求 populateBean |
字段 @Context |
每请求 PropertyInjector.inject(request, response, …) |
每请求 BPP → inject(request, response, …) |
| 请求结束 | requestFinished() 可触发销毁语义 |
空实现,Spring prototype 亦无自动 destroy |
sequenceDiagram
participant HTTP as HTTP 请求
participant Invoker as ResourceMethodInvoker
participant SRF as SpringResourceFactory
participant Spring as BeanFactory
participant BPP as ResteasyBeanPostProcessor
Note over HTTP,BPP: prototype Resource — 首次及后续每次请求
HTTP->>Invoker: invoke()
Invoker->>SRF: createResource()
SRF->>Spring: getBean(beanName)
Spring->>Spring: createBean → doCreateBean
Spring->>BPP: postProcessAfterInitialization
BPP->>BPP: inject(request, response, bean) — 字段 @Context
Spring-->>SRF: 新 prototype 实例
SRF-->>Invoker: resource
Invoker->>Invoker: MethodInjector — @QueryParam
Invoker->>Invoker: 调用 @GET
Note over SRF: requestFinished() 空实现,无 destroy
启动期桥接就绪后,下面看 运行期 单次请求的完整 call chain。
五、运行期:RESTEasy 主控,Spring 供实例
运行期 Call Chain(单次 HTTP 请求)
SynchronousDispatcher / AsynchronousDispatcher
→ ResourceMethodInvoker.invoke(request, response) :348-356
→ ResourceFactory.createResource(...) SpringResourceFactory :35-37
→ BeanFactory.getBean(beanName) AbstractBeanFactory :197-198
→ doGetBean(name, ...) :249-396
├─ singleton: getSingleton() 命中缓存 :257-268
└─ prototype: createBean() → doCreateBean() :359-364 → :556-603
→ ResourceMethodInvoker.invoke(request, response, target) :370-378
→ invokeOnTarget() :403-417
→ internalInvokeOnTarget() :555-560
→ MethodInjector.invoke(request, response, target) :560
ResourceMethodInvoker:每次请求先 createResource
// resteasy-core/.../ResourceMethodInvoker.java:348-356
public BuiltResponse invoke(HttpRequest request, HttpResponse response) {
Object resource = this.resource.createResource(request, response, resourceMethodProviderFactory);
// ...
return invoke(request, response, resource);
}
// :555-560 — JAX-RS 方法参数注入 + 反射调用
private Object internalInvokeOnTarget(HttpRequest request, HttpResponse response, Object target) {
Object methodResponse = this.methodInjector.invoke(request, response, target);
// ...
}
getBean 分支(singleton vs prototype)
与第二节相同,doGetBean() 在运行期按 scope 分叉——singleton 命中缓存则不再进入 doCreateBean,prototype 每次进入:
// spring-beans/.../AbstractBeanFactory.java:257-268 — singleton 命中缓存
Object sharedInstance = getSingleton(beanName);
if (sharedInstance != null && args == null) {
beanInstance = getObjectForBeanInstance(sharedInstance, requiredType, name, beanName, null);
}
// :359-364 — prototype 每次 createBean
else if (mbd.isPrototype()) {
prototypeInstance = createBean(beanName, mbd, args); // → doCreateBean
}
运行期注入分工
| 注入目标 | 负责方 | singleton | prototype |
|---|---|---|---|
@Autowired CustomerService |
Spring populateBean |
启动期一次 | 每次 getBean |
@QueryParam / @PathParam |
RESTEasy MethodInjector |
每次请求 | 每次请求 |
字段 @Context UriInfo |
RESTEasy PropertyInjector |
启动期 BPP | 请求期 BPP(有 HttpRequest) |
RESTEasy 不重新做 Spring DI;Spring 不解析 JAX-RS 方法参数。
Spring 能力在运行时是否保留
Resource / Service 仍是 Spring Bean(可能是 CGLIB/JDK 代理):
| 能力 | singleton Resource | prototype Resource |
|---|---|---|
@Transactional |
✅ 启动期代理,每请求复用 | ✅ 每请求新建实例(及代理) |
AOP @Aspect |
✅ 同上 | ✅ 每请求重新织入 |
@Autowired 字段 |
✅ 启动期已注入 | ✅ 每请求 populateBean |
| 每请求新建 Resource 实例 | ❌ 缓存复用 | ✅ 每次 getBean → doCreateBean |
sequenceDiagram
participant HTTP as HTTP 请求
participant Invoker as ResourceMethodInvoker
participant SRF as SpringResourceFactory
participant Spring as BeanFactory
participant Resource as @Path Bean
HTTP->>Invoker: invoke()
Invoker->>SRF: createResource()
SRF->>Spring: getBean(beanName)
alt singleton
Spring-->>SRF: 缓存实例
else prototype
Spring->>Spring: doCreateBean 全流程
Spring-->>SRF: 新实例
end
SRF-->>Invoker: resource
Invoker->>Resource: 注入 @QueryParam,调用 @GET
Resource->>Spring: 调用 @Autowired Service(可能 @Transactional)
运行期桥接逻辑在 WAR 与 Boot 下相同;差异在 启动编排——第六节对比两种部署方式。
六、传统 Spring 与 Spring Boot:启动差异与 resteasy-spring-boot
运行期桥接逻辑不变——仍是 SpringResourceFactory.createResource() → getBean()。resteasy-spring-boot 解决的是 嵌入式 Tomcat 没有 ServletContainerInitializer 时,如何把 RESTEasy deployment 与 Spring 容器按正确时序拼起来。9
传统 WAR / Spring MVC 启动时序
WAR 部署下,RESTEasy 常通过 ServletContainerInitializer 先于 Spring 完成 deployment 初始化:
ServletContainerInitializer(RESTEasy)
└─ ResteasyDeployment 创建
└─ new SpringBeanProcessor(deployment) :177-179
└─ ContextLoaderListener 启动 WebApplicationContext
└─ refresh()
├─ BFPP / BPP(同第四节)
└─ ContextRefreshedEvent → Registry.addResourceFactory
此时 SpringBeanProcessor 构造函数接收 已存在的 ResteasyDeployment,Registry 与 ProviderFactory 来自 servlet 层;Spring 的 refresh() 只负责 Bean 创建与 Resource 注册,不再创建 deployment。
Spring Boot 嵌入式 Tomcat 启动时序
Boot 路径下 Spring 容器先起,Servlet 后注册:
SpringApplication.run()
└─ ResteasyAutoConfiguration @Bean SpringBeanProcessor() :85-88
└─ refresh()
├─ BFPP / BPP(同第四节)
└─ ContextRefreshedEvent → Registry.addResourceFactory
└─ Tomcat 启动
└─ ResteasyBeanProcessorTomcat:扫描 @ApplicationPath :36-44
└─ ServletRegistrationBean 注册 HttpServletDispatcher
└─ resteasyBootstrapListener() :50-56
└─ DeploymentCustomizer.customizeRestEasyDeployment() :29-47
└─ 把 SpringBeanProcessor 的 Registry/ProviderFactory 写回 ResteasyDeployment
└─ deployment.start()
Boot 先建 空的 SpringBeanProcessor,Servlet 启动后 DeploymentCustomizer 再把 Registry / ProviderFactory 合并进 ResteasyDeployment。Resource 等到 ContextRefreshedEvent 才进 Registry——这一点 WAR 与 Boot 一致。
| 维度 | 传统 WAR | Spring Boot |
|---|---|---|
| deployment 创建时机 | Servlet 容器初始化,常早于 Spring | Servlet 注册时,Spring 已 refresh |
SpringBeanProcessor 构造 |
new SpringBeanProcessor(deployment) |
@Bean 无参构造 |
| Registry 来源 | 构造时注入 deployment | 先空 Registry,后由 DeploymentCustomizer 合并 |
| Resource 注册触发 | ContextRefreshedEvent |
同左 |
| 额外注册入口 | SpringContextLoaderSupport / springmvc-resteasy.xml |
ResteasyAutoConfiguration |
resteasy-spring-boot 额外组件
| 组件 | 类 | 关键行 | 作用 |
|---|---|---|---|
| 自动配置 | ResteasyAutoConfiguration |
:85-88 |
@Bean 注册 SpringBeanProcessor |
| Servlet 启动 | ResteasyAutoConfiguration.resteasyBootstrapListener() |
:50-56 |
DeploymentCustomizer + deployment.start() |
| BFPP 扫描 | ResteasyBeanProcessorTomcat |
:36-44 |
发现 @ApplicationPath、注册 ServletRegistrationBean |
| Deployment 合并 | DeploymentCustomizer |
:29-47 |
把 SpringBeanProcessor 的 Registry/ProviderFactory 写入 ResteasyDeployment |
七、总结:两个生命周期的分界
启动期
Spring refresh()
├── BFPP: SpringBeanProcessor 扫描 @Path → SpringResourceFactory(singleton / prototype 均建映射)
├── preInstantiateSingletons → doCreateBean(仅 singleton)
│ ResteasyBeanPostProcessor: @Provider 注册;singleton @Path 做 JAX-RS 字段注入
│ prototype @Path:通常不在此阶段实例化
└── finishRefresh → ContextRefreshedEvent
→ Registry.addResourceFactory → registered()(scope 无关,均注册路由)
Spring 管: 实例创建(doCreateBean,scope 决定时机)、@Autowired、AOP、@PostConstruct / @PreDestroy(prototype 无自动 destroy)。
RESTEasy 管: 路由元数据、@Provider 链、JAX-RS 字段 / 方法参数注入策略。
运行期
RESTEasy 管: URI 匹配、createResource()、Filter/Interceptor 链、方法调用。
Spring 管: getBean() — singleton 返缓存,prototype 再走 doCreateBean;业务依赖、事务与 AOP 随实例而来。
一句话
Spring:所有 scope 共用 doCreateBean 流水线;singleton 启动期入缓存,prototype 每次 getBean 重走
RESTEasy:Registry 登记 ResourceFactory 策略 + 每端点 ResourceMethodInvoker
SpringResourceFactory:createResource() → getBean(),scope 由 Spring 定义
References
对照源码仓库:
| 仓库 | GitHub |
|---|---|
| spring-framework | spring-projects/spring-framework |
| resteasy | resteasy/resteasy |
| resteasy-spring-boot | resteasy/resteasy-spring-boot |
-
AbstractAutowireCapableBeanFactory.doCreateBean()、initializeBean()↩ -
AbstractApplicationContext.destroyBeans()→DefaultSingletonBeanRegistry.destroySingletons()↩ -
DefaultSingletonBeanRegistry(singletonObjects:85-86、addSingleton():159-173、getSingleton():208-244;创建入口见AbstractBeanFactory.doGetBean()) ↩ -
ResourceMethodRegistry(addResourceFactory:200-220、processMethod:295-310)、ResourceMethodInvoker.invoke()↩ -
SpringBeanProcessor(BFPP:235-261,BPP:114-146,事件:453-467) ↩ -
ResteasyAutoConfiguration(:85-88注册SpringBeanProcessor,:50-56Servlet 启动) ↩ ↩2