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 通过 SpringBeanProcessorSpringResourceFactory 在启动期与运行期各管一段。

全文脉络:

  1. Spring Bean 生命周期 — 所有 scope 共用 doCreateBean 流水线;singleton 在 refresh() 批量创建,prototype 在每次 getBean() 触发
  2. RESTEasy Resource 生命周期Registry 登记 ResourceFactory(取实例策略)+ ResourceMethodInvokerregistered / createResource / requestFinished
  3. resteasy-spring 桥接 — BFPP 扫描 @Path、BPP 做 JAX-RS 字段注入、ContextRefreshedEvent 注册 Registry、运行期 getBean() 取实例
  4. 运行期 call chain — 单次 HTTP 请求从 Dispatcher 到 getBean() 的完整路径
  5. 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、事件 RegistryDispatcherResteasyProviderFactory
典型成员 CustomerServiceHelloResource@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(见第四节 ContextRefreshedEventaddResourceFactory)。

二、Spring Bean 生命周期:从 refresh 到 destroy

Spring 容器的 Bean 生命周期是一个经过精心编排的流程,从 BeanDefinition 解析到实例销毁,每个阶段都有明确扩展点。1

ApplicationContext.refresh() 中的位置

refresh() 结束时发布 ContextRefreshedEvent——Resteasy-Spring 正是在这个事件上把 Resource 注册进 Registry2

singleton 与 prototype 在启动期的差异从这里开始

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 实例」,都走同一条 doCreateBeaninitializeBean 流水线——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 的关系

容器关闭时 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 setBeanNamesetBeanFactory *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);
}

要点

为何每个端点一个 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);  // 此方法专属
}

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 故意不做
扩展点 BeanFactoryPostProcessorBeanPostProcessor*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 Bean5

三种内置 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 自己 newPOJOResourceFactory.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) { }
}

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 —— 实例来源外包给 SpringSpringResourceFactory.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 实现 BeanFactoryPostProcessorSmartApplicationListener,在 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 ContextRefreshedEventonApplicationEvent 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,只对 已实例化的 BeanPropertyInjector.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):

  1. srf.registered(providerFactory) — 为 SRF 关联的类创建 PropertyInjector 等(Spring 路径下主要注入已在 T2 BPP 完成)。
  2. register(ref, base, resourceClass) — 扫描 @Path / @GET / @POST,在 URL 树(RootClassNode)上挂叶子。
  3. 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;
}

RESTEasy 认为自己在「每次请求创建 Resource」,实际上是在「每次请求从 Spring 取 Bean」——实例的创建、DI、AOP、销毁都由 Spring 决定。

分工要点:

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);
}

启动期: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
桥接入口 SpringResourceFactorygetBean() 同左
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 路径下:

若 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 实例 ❌ 缓存复用 ✅ 每次 getBeandoCreateBean
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
  1. AbstractAutowireCapableBeanFactory.doCreateBean()initializeBean() 

  2. AbstractApplicationContext.refresh()finishRefresh() 

  3. SpringBeanProcessor.onApplicationEvent() 注释  2

  4. AbstractApplicationContext.destroyBeans()DefaultSingletonBeanRegistry.destroySingletons() 

  5. ResourceFactory.requestFinished() 接口注释  2

  6. DefaultSingletonBeanRegistrysingletonObjects :85-86addSingleton() :159-173getSingleton() :208-244;创建入口见 AbstractBeanFactory.doGetBean()) 

  7. ResourceMethodRegistryaddResourceFactory :200-220processMethod :295-310)、ResourceMethodInvoker.invoke() 

  8. SpringBeanProcessor(BFPP :235-261,BPP :114-146,事件 :453-467) 

  9. ResteasyAutoConfiguration:85-88 注册 SpringBeanProcessor:50-56 Servlet 启动)  2