Spring Boot-style client-Java model: IoC bean container, constructor & collection injection, self-describing handlers, annotation-free extensions#6051
Merged
iliyan-velichkov merged 12 commits intoJun 22, 2026
Conversation
This was referenced Jun 19, 2026
e8fca98 to
69c9ecc
Compare
This was referenced Jun 22, 2026
Contributor
Author
|
Addressed the three CodeQL useless parameter findings: removed the unused |
…tor & collection injection, method-level callbacks Reworks the client-Java programming model around a real IoC container so it mirrors Spring Boot idioms. SDK (api-modules-java): - New @component (with optional bean name; Spring decapitalized-name default) and a client-facing Beans facade (get/get(name)/getAll) replacing direct BeanProvider use in client code. - @repository, @controller, @extension, @scheduled, @Listener, @websocket are meta-annotated @component, so every client artefact is a managed bean. - @Inject now targets constructors/parameters as well as fields. - @scheduled and @Listener gain METHOD targets (Spring @scheduled / @JmsListener method style); new @OnOpen/@OnMessage/@OnError/@onclose for websockets. Engine (engine-java, core-java): - ComponentContainer instantiates every bean per ClientClassLoader generation with recursive constructor injection, @Inject field injection, collection injection (List/Set/Collection<T> -> all impls), @PostConstruct/@PreDestroy, and construction-cycle detection. Published via ClientBeansHolder (core-java) so the SDK Beans facade resolves client beans without a module cycle. - JavaLoader builds the container between the unload and load passes; the Controller/Scheduled/Listener/Websocket consumers now fetch ready, injected instances instead of instantiating client classes themselves, and support both class-level (interface/convention) and method-level annotation styles. - Websocket dispatch precedence (WebsocketHandler -> @onx -> reflective) centralised in JavaWebsocketRegistry#dispatch; WebsocketProcessor calls it reflectively (no engine-java dependency). - Removed the now-redundant DependencyResolver SPI and data-store-java's RepositoryRegistry/RepositoryClassConsumer (repositories are ordinary beans). Templates: REST controller and event trigger now use constructor injection. Tests: new ComponentContainerTest and HTTP-only JavaComponentIT (constructor + collection injection end-to-end); existing controller/loader tests adapted to the container. Full backward compatibility retained (field @Inject, class-level annotations, reflective callbacks). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…per @component (no mixing) Aligns the strong-interface style with Spring: the typed interface now carries its own binding, so it needs no class-level annotation, and a @component class uses exactly one style — never both. - JobHandler gains cron(); MessageHandler gains destination()/kind(); WebsocketHandler gains endpoint(). A @component implementing one of these IS the job/listener/socket, no @Scheduled/@Listener/@websocket needed (like org.quartz.Job / jakarta.jms.MessageListener / TextWebSocketHandler). - @scheduled and @Listener are now @target(METHOD) only and no longer meta-@component; the method-level style is a @Scheduled/@Listener method on a @component bean. @websocket stays the class marker for the @onx annotation style (the endpoint has no method-level home, like Jakarta @serverendpoint). - Removed the reflective by-name fallback and the annotation+interface hybrid. Each consumer now rejects a class that mixes the two styles (implements the interface AND has the method annotation / @websocket), mirroring the existing @controller+JavaHandler rejection. - Event-trigger template migrated to a self-describing MessageHandler. Breaking change: hybrid and reflective handlers must move to one of the two styles; all in-repo samples/templates are migrated. Engine + data-store unit tests green; formatter and release-javadoc clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
JavaNoMixingIT drops a WebsocketHandler that also carries @Websocket/@OnMessage (a mix) plus a clean interface-style handler; asserts over HTTP that the clean one registers and the mixed one does not. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Drop the unused fqn parameter from ScheduledClassConsumer.schedule(); store the injected collaborator in the Ping/Pong cycle test fixtures so the constructor parameter is used. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Compile errors already reach the developer via the Problems view + a FAILED JavaFile artefact, but bean-wiring failures (unsatisfied/ambiguous dependency, construction cycle, duplicate bean name, throwing constructor) only hit the server log — invisible in the browser IDE. ComponentContainer now records these per client-class FQN; JavaLoader carries them on RebuildResult; JavaSynchronizer projects them onto the same Problems surface and marks the source FAILED. So a developer whose @controller can't be wired now sees e.g. "No client bean of type X to inject into demo.MyController" on the file itself. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ints are plain interfaces + @component; index beans by type Extensions no longer need a dedicated annotation: an extension point is a plain Java interface and a contribution is a @component bean implementing it (its @component name is the contribution name). Consume via List<Interface> collection injection; Extensions.find(Class) is preserved and now resolves the same beans from the container (Beans.getAll). Removed @extension, @ExtensionPoint and ExtensionClassConsumer. Performance: ComponentContainer now indexes singletons by runtime class, so instanceOf(Class) — called by every behaviour consumer for every loaded class during a rebuild — is O(1) instead of an O(n) scan (previously O(n^2) per rebuild). Dispatch-time reflection is unchanged and already minimal: the self-describing interface styles dispatch through direct virtual calls (no reflection); the method-level annotation styles cache the Method once at registration and only Method.invoke per event; all instantiation/discovery reflection is per-generation (rebuild), never per request. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… sample ITs during the API migration - IntentEngineIT: the event-trigger template now generates a self-describing MessageHandler (destination() returns the topic) instead of @Listener(name=...); assert on the destination string + `implements MessageHandler`. - The 5 Java sample ITs clone the dirigiblelabs sample repos' master, which is mid-migration to the new client-Java API (this PR's breaking changes). The four that exercise the removed reflective/@Extension/class-level paths are @disabled until the matching sample PRs merge; the engine itself stays covered by JavaEngineIT, JavaComponentIT and JavaNoMixingIT. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
52e2c28 to
2f6c8d1
Compare
…ontainer bean A class implementing JavaHandler that is also a @component was built (and constructor/field-injected) by the bean container, but the JavaHandler dispatch path still instantiated it via its no-arg constructor — so a handler with an injected collaborator failed at request time with NoSuchMethodException: `<init>()`. HandlerClassConsumer now fetches the container-built singleton (when the handler is a @component) and LoadedHandler dispatches it; a plain JavaHandler with no @component is still instantiated per request via its no-arg constructor. Regression covered by JavaComponentIT (a @component JavaHandler with a constructor-injected service served over HTTP). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…sessions - New components/engine/engine-java/CLAUDE.md: the deep guide (bean container, constructor/field/collection injection, Beans facade, two never-mixed handler styles, JavaHandler-as-bean, annotation-free extensions, error surfacing, removed internals, three-repo sequencing). - Root CLAUDE.md "Client Java code" section condensed to a summary + pointer to the module guide (matching the engine-intent / engine-native-apps pattern); removed the stale SPI/@Order/RepositoryRegistry/engine.java.annotations text. - engine-intent/CLAUDE.md: the generated trigger is a self-describing MessageHandler (not the @Listener+interface hybrid); added a handler-style note pointing at engine-java/CLAUDE.md. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cts; re-enable sample ITs
- JavaComponentIT / JavaNoMixingIT: move the inline .java source strings into real fixture
projects under src/main/resources/{JavaComponentIT,JavaNoMixingIT} and deploy them via a new
ClientJavaProjectDeployer (copies the resource project into /registry/public and forces a sync
cycle — fast, HTTP-only, no IDE). Drop the manual @AfterEach cleanup() — DirigibleCleaner
already resets DB + the dirigible folder between integration tests.
- Re-enable the four @disabled sample ITs (entity / listener / job / extension); their sample PRs
are being merged ahead of the platform PR. Assertions already match the migrated sample surface.
- JavaExtensionDecoratorSampleProjectIT: also assert the InjectingConsumer collection-injection
endpoint (the headline feature), alongside the Extensions.find(...) path.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… each sample IT Each SampleProjectRepositoryIT now exercises both the self-describing-interface and the method-level-annotation style its repo demonstrates: - job: CleanupJob (JobHandler) + Maintenance (@scheduled method) - listener: OrderListener (MessageHandler) + InvoiceListener (@Listener method → Auditor) - websocket: ChatHandler (WebsocketHandler) + TickerHandler (@Websocket/@onx) via WebsocketStatus - entity: CountryController (@Entity/@Repository/@controller) + GreetingController DI showcase (constructor injection + the Beans facade) Paired with the sample-repo edits that expose the annotation-style signal (invoice-queue trigger, two-endpoint WebsocketStatus, faster Maintenance cron, relocated GreetingController). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
iliyan-velichkov
added a commit
to dirigible-io/dirigible-io.github.io
that referenced
this pull request
Jun 22, 2026
…Spring Boot" guide, per-language split, annotation-free extensions (#128) * Develop docs: Spring-style Java model Update the Develop section for the new Spring-style Java SDK surface introduced by eclipse-dirigible/dirigible#6051: - dependency-injection.md: rewrite the Java section to @component (with Spring naming), constructor injection (preferred), field @Inject, collection injection (List<T>), @PostConstruct/@PreDestroy, and the Beans facade; client code should not use BeanProvider. Describe the single ComponentContainer per ClientClassLoader generation and remove RepositoryClassConsumer / RepositoryRegistry / DependencyResolver and the fixed @order 100/200/300 chain. - decorators-model.md: add @component to the parallel-surface table; note strong interfaces AND method-level annotations for jobs / listeners / websockets; replace the @order wiring description. - scheduled-jobs.md, message-listeners.md, websockets.md: show BOTH the strong interface and the method-level annotation on a @component (@scheduled / @Listener / @OnOpen/@OnMessage/@OnError/@onclose), keeping the reflective fallback note. - extension-providers.md: present collection injection as the recommended Spring-style way to consume all providers, keeping Extensions.find as the programmatic / cross-runtime alternative. - rest-apis.md, entities-and-persistence.md: controllers use constructor injection of the repository. - security-and-roles.md: add a Java vs TypeScript User API parity table. - languages/java.md: align its DI section with the new model. Adds Sample project and SDK reference links across the Java sections. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * Refine Java handler model to two self-describing styles Rewrite the Java sections of scheduled-jobs, message-listeners and websockets to exactly two styles, never mixed on one @component class, with no reflective by-name fallback: - Style 1 is the self-describing interface (JobHandler.cron(), MessageHandler.destination()/kind(), WebsocketHandler.endpoint()) on a @component, carrying the binding itself - no class-level @Scheduled/@Listener/@websocket. Mirrors org.quartz.Job / jakarta.jms.MessageListener / TextWebSocketHandler. - Style 2 is the method-level annotation (@Scheduled/@Listener), except websockets keep @websocket as a class annotation (the endpoint has no method-level home, like Jakarta @serverendpoint) with @OnOpen/ @OnMessage/@OnError/@onclose methods. Drop all reflective/hybrid wording and update the decorators-model parallel-surface table and handler-styles section to match. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): add "Coming from Spring Boot" page and real sample examples Add a Spring Boot -> Dirigible mapping page (annotation table + key differences) and link it from the develop index. Replace the placeholder Java snippets in the building-block pages with the actual code from the dirigiblelabs/sample-java-* repos, showing both the self-describing interface and method-level annotation styles where applicable, each with a "Sample project" link and an SDK reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): nav entry, annotation-free Java extensions, data split, per-artifact comparisons - Add "Coming from Spring Boot" to the Develop sidebar (config.mts) - Rewrite extension-providers: extension point = plain interface, contribution = @component (no @Extension/@ExtensionPoint); split Consume-at-runtime into Java (List injection + Extensions.find) and TS - working-with-data: split SQL examples into Java / TS subsections, drop the Multi-tenancy section - coming-from-spring-boot: add side-by-side per-artifact examples (Listener, Job, WebSocket, Controller, Repository, Extension) using real classes from the sample-java-* repos Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): Spring-then-Dirigible side-by-side per artifact; drop Key differences section Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): split Java vs TS/JS examples into per-language subsections Reorganize security-and-roles, working-with-files-and-cms, and working-with-git develop pages so each topic separates its Java and TypeScript/JavaScript examples under ### Java / ### TypeScript / JavaScript subheadings (Java first), matching the convention already used on the data, listeners, and jobs pages. Where a feature is config-only or facade-only, that is stated explicitly instead of forcing a split. No documented APIs or behavior changed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): separate Java/TS examples (rest-apis, entities); keep DI 'How it is wired' implementation-agnostic Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): remove tenancy sections from entities-and-persistence and working-with-files-and-cms Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sdk): align Java SDK reference with Spring-style bean container (PR #6051) - component: @component bean + field/constructor/parameter @Inject + collection injection; @repository is a plain bean; new Beans facade page + sidebar entry - job: @scheduled is method-level only; JobHandler is self-describing (cron()+run()) - messaging: @Listener is method-level only; MessageHandler is self-describing - net: @websocket + @onx, or self-describing WebsocketHandler; drop reflective fallback - extensions: remove @Extension/@ExtensionPoint; plain-interface points + @component contributions; find(Class) no longer throws checked exception - http: @controller is a managed bean (constructor injection, no no-arg ctor needed) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(sdk): use Beans.get(JavaEntityStore) for client access, not BeanProvider Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs: update client-Java model to Spring-style @component container Sweep the help docs and the Java-decorators blog so the Java model matches eclipse-dirigible/dirigible #6051: SDK annotations under org.eclipse.dirigible.sdk.*, @Component/@Inject DI with constructor injection preferred, @repository extends JavaRepository<T>, the Beans facade instead of the platform-internal BeanProvider, method-level @scheduled(expression=...), and plain-interface extension points with @component contributions consumed via List<T> / Extensions.find. Removes the retired Java surface (@Extension/@ExtensionPoint, @scheduled(cron=...), the JavaClassConsumer fan-out and the named internals) from the Java sections only. TypeScript content, the classic registry artefacts, and docs/help/develop|api|sdk are left unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): drop removed Java @Extension/@ExtensionPoint refs and stale meta-annotation list Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): add SDK imports to the Dirigible Java samples in 'Coming from Spring Boot' Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): add Spring/Jakarta imports to the Spring examples in 'Coming from Spring Boot' Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(develop): drop the feature-branch annotation from the extension sample link The sample-java-* PRs are merged to master, so the extension-provider sample now lives on master — reference the repo without the (branch feat/spring-style-extension-injection) suffix. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Reworks the client-Java development model so it follows Spring Boot idioms end to end: a real IoC bean container, constructor & collection injection, two clean handler styles, annotation-free extension points, a client-facing bean facade, and IDE-surfaced wiring errors.
Before this PR the model was a service locator (only
@Repositoryinjectable, field-only@Inject, everything else through the platform-internalBeanProvider), component callbacks bound only via class-level annotations with a reflective by-name fallback, the strong-interface style still needed a class annotation for its binding, and extensions required a dedicated@Extension/@ExtensionPointpair.Bean container & dependency injection (
api-modules-java,engine-java,core-java)@Component(sdk.component) — marks a client class a managed bean; optionalvalue()name, default = decapitalized simple class name (Spring convention).@Repository,@Controllerand@Websocketare meta-annotated@Component, so they are beans too.ClientClassLoader-generation container instantiates every bean with constructor injection (preferred),@Injectfield injection, and collection injection (List/Set/Collection<T>receives every bean assignable toT), resolving by type independent of declaration order, with construction-cycle detection and@PostConstruct/@PreDestroylifecycle callbacks.Beansfacade (sdk.component.Beans:get(Class),get(name, Class),getAll(Class)) is the client-facing way to reach platform services; client code no longer usesBeanProvider.RepositoryRegistry,RepositoryClassConsumerand theDependencyResolverSPI.Two clean handler styles — never mixed (jobs, listeners, websockets)
A
@Componentclass uses exactly one style; the engine rejects a class that mixes them.JobHandler(cron()+run()),MessageHandler(destination()/kind()+onMessage),WebsocketHandler(endpoint()+lifecycle callbacks). The interface carries its own binding, so no class annotation — mirroringorg.quartz.Job/jakarta.jms.MessageListener/TextWebSocketHandler.@Scheduled(expression=…)/@Listener(name=,kind=)on a@Componentmethod (Spring@Scheduled/@JmsListenerstyle); websockets keep a@Websocket(endpoint=…)class with@OnOpen/@OnMessage/@OnError/@OnClosemethods (the endpoint has no method-level home, like Jakarta@ServerEndpoint).The reflective by-name fallback and the annotation+interface hybrid are removed.
Extension points without annotations
@Extension/@ExtensionPointare gone. An extension point is a plain Java interface; a contribution is a@Componentimplementing it (its@Componentname is the contribution name). Consume viaList<Interface>collection injection (preferred);Extensions.find(Class)is preserved and resolves the same beans, andExtensions.getExtensions(String)stays for cross-runtime TypeScript/JavaScript.JavaHandleras a beanA
JavaHandlerthat is also a@Componentis now dispatched as the container-built (injected) singleton — so a handler with a constructor-injected collaborator works. A plainJavaHandler(no@Component) is still instantiated per request via its no-arg constructor.Developer experience & performance
Methodonce at registration; all instantiation/discovery reflection is per rebuild, never per request.ComponentContainer.instanceOf(Class)is now an O(1) type-indexed lookup (was an O(n) scan, O(n²) per rebuild).Templates
REST controller template uses constructor injection; the intent event-trigger template generates a self-describing
MessageHandler.Tests
ComponentContainerTest(constructor/collection injection, naming, cycles, lifecycle, wiring-error reporting); controller/loader tests adapted. 63 engine-java + 13 data-store-java green. CodeQL "useless parameter" findings fixed.JavaComponentIT(constructor + collection injection, plus a@ComponentJavaHandler) andJavaNoMixingIT(verifies the no-mixing rejection end-to-end);IntentEngineITupdated for the self-describing trigger. Release-profile javadoc and formatter clean.@Disabled(they clone thedirigiblelabs/sample-java-*repos' master, mid-migration to this API); re-enable after the sample PRs merge.Breaking change
The annotation+interface hybrid, the reflective by-name callbacks, and
@Extension/@ExtensionPointmust move to the new styles. All in-repo samples, templates and tests are migrated.Companion PRs (merge after this)
dirigiblelabs/sample-java-{entity,listener,job,websocket,extension}-decoratordirigible-io/dirigible-io.github.io🤖 Generated with Claude Code