JBoss EAP 09: understand Spring XML and verify Java configuration behavior
Replacing a long applicationContext.xml with @Bean methods can make the configuration shorter without preserving transaction behavior. Identify object ownership and proxy crossings first. This lesson runs one service through XML configuration, Java configuration, and Java importing XML, all using the same behavioral test body.
Download lesson 09: XML/Java configuration labThe lab fixes Java 21, Spring 6.2.19, and H2. Boot 3.5.16 supplies build/dependency management only; there is no SpringApplication or web server. All configurations run on the same new framework, not legacy Spring 3/4, so this establishes configuration behavior rather than enterprise migration compatibility.
Trace objects through bean, ref, and @Bean
<bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
<bean id="ticketService" class="com.keeponfirst.xml.TicketService">
<constructor-arg ref="jdbc"/>
</bean>In this excerpt, id names a bean, ref references another bean, and constructor-arg supplies the constructor dependency. Follow ticketService → jdbc → dataSource to locate the resource. Here XML creates an H2 data source; it is not an EAP JNDI lookup.
@Bean
public TicketService ticketService(JdbcTemplate jdbc) {
return new TicketService(jdbc);
}The Java form receives the managed JdbcTemplate as a method parameter and constructs the same service. JavaConfig uses proxyBeanMethods=false, so it relies on parameter injection rather than direct calls between @Bean methods. Distinguish container-managed dependencies from manually constructed objects that may lack proxying and lifecycle processing.
Component scanning can coexist with XML
Both configurations scan only com.keeponfirst.xml.entry for the @Component TicketEntry and declare other beans explicitly. Expanding scanning to the whole root package can load another configuration or service definition. Broader scanning is not a substitute for understanding a missing dependency.
@Configuration(proxyBeanMethods = false)
@ImportResource("classpath:context.xml")
public class ImportedXmlConfig { }The third mode imports the same XML without also loading JavaConfig or a second annotation-driven transaction configuration. This supports retaining understood XML during gradual migration. ImportResource is a loading mechanism, not a repair for removed classes, custom namespace handlers, or incompatible dependencies.
Align transaction declarations with the same methods
<tx:advice id="ticketAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="createThenCheckedRollback" rollback-for="java.lang.Exception"/>
<tx:method name="create*"/>
</tx:attributes>
</tx:advice>The XML advisor separately applies this advice to public create* methods; declaring advice does not attach it everywhere. Java configuration enables the annotations through EnableTransactionManagement. XML mode has no annotation-driven transaction setup, so its rules come from XML despite the shared service carrying annotations. This avoids stacking two transaction configurations.
This lab has no global rollback override: runtime exceptions roll back, while checked exceptions do not by default. Its explicit checked-exception method therefore declares rollback rules in both forms. A real migration also needs to align propagation, isolation, timeout, readOnly, manager selection, and exception policies.
Predict the row counts, then run the tests
mvn test
mvn packageEach case starts with an empty table, and the test itself owns no transaction. Three configurations execute the same test body, plus one manual-construction control: four JUnit invocations. The table describes six observations within each configuration, not 18 separate JUnit tests.
| Call | Rows remaining | Transaction observed |
|---|---|---|
| Normal create | 1 | Yes |
| Runtime exception | 0 | Yes |
| Checked exception, default rule | 1 | Yes |
| Explicit checked rollback | 0 | Yes |
| Self-invocation of failing method | 1 | No |
| Entry calls proxied service | 0 | Yes |
Why self-invocation is a different route
Caller → Spring proxy → createThenFail → rollback
Caller → selfInvokeFailure → this.createThenFail
(no new proxy crossing)
TicketEntry → injected Service proxy → createThenFail → rollbackselfInvokeFailure has no surrounding transaction and calls its own method without re-entering the proxy. The local test observed no active transaction and one row. TicketEntry crosses the injected proxy and rolls back. An already transactional outer method or AspectJ weaving changes the conditions; self-invocation does not universally mean no transaction exists.
Preserve the transaction contract before choosing its notation. Changing checked-exception rollback while converting to annotations is a behavioral change. For this migration, the useful outcome is an explainable object/proxy path and a validating case for each relevant XML section.
Next: MDB, DB, and redelivery boundaries