Skip to content

feat(java): detect Spring @EventListener/publishEvent() and emit HANDLES/PUBLISHES edges#591

Open
YutenC wants to merge 1 commit into
tirth8205:mainfrom
YutenC:feat/spring-event-listener-edges
Open

feat(java): detect Spring @EventListener/publishEvent() and emit HANDLES/PUBLISHES edges#591
YutenC wants to merge 1 commit into
tirth8205:mainfrom
YutenC:feat/spring-event-listener-edges

Conversation

@YutenC

@YutenC YutenC commented Jun 30, 2026

Copy link
Copy Markdown

Problem

Spring Application Events create implicit caller-callee relationships invisible to static analysis: a publisher calls publishEvent(new XxxEvent()) and Spring routes it to all @EventListener methods for that event type. CRG had no way to trace this path.

Solution

Two-phase resolution following the existing Spring DI / Temporal patterns:

Parse phase (parser.py):

  • @EventListener method → HANDLES edge to virtual event:XxxEvent node
  • publishEvent(new XxxEvent()) call → PUBLISHES edge to virtual event:XxxEvent node

Event type is inferred from (in priority order):

  1. @EventListener(OrderEvent.class) — explicit annotation arg
  2. @EventListener(classes = {A.class, B.class}) — multi-type named arg
  3. First method parameter type — implicit / no-arg form

Post-build resolver (event_resolver.py):

  • Joins PUBLISHES and HANDLES edges by event_type
  • Emits CALLS edges: publisher method → listener method

Usage after this change

# Find all listeners for OrderPlacedEvent
query_graph(callers_of="event:OrderPlacedEvent")

# Find what events a publisher emits
query_graph(callees_of="OrderService.placeOrder")

Changes

  • code_review_graph/parser.py — 2 constants + 3 new methods + 2 hooks in _extract_functions
  • code_review_graph/event_resolver.py — new post-build resolver
  • code_review_graph/incremental.py_run_event_resolver wired into build pipeline
  • tests/fixtures/SpringEvents.java — fixture with publisher + 2 listener patterns
  • tests/test_multilang.pyTestSpringEventListenerParsing (6 assertions)

…ES/PUBLISHES edges

Spring Application Events create implicit caller-callee relationships
that are invisible to static analysis: a publisher calls
publishEvent(new XxxEvent()) and Spring routes it to all methods
annotated @eventlistener for that event type.

This change adds two-phase resolution:

Parse phase:
  @eventlistener method  → HANDLES edge to virtual event:XxxEvent node
  publishEvent() call    → PUBLISHES edge to virtual event:XxxEvent node

Event type is inferred from:
  1. @eventlistener(OrderEvent.class) annotation argument
  2. @eventlistener(classes = {...}) named argument (multi-type)
  3. First method parameter type (implicit / no-arg form)

Post-build resolver (event_resolver.py):
  For each PUBLISHES edge, find all HANDLES edges with matching
  event_type → emit CALLS edge from publisher to listener.

Tests: TestSpringEventListenerParsing (6 assertions) in test_multilang.py
Fixture: tests/fixtures/SpringEvents.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant