Spring中不同Maven模块下事务实现类如何@Autowired调用默认服务实现
Alright, let's break down how to make your transactional implementation call the default one, even though they're in separate Maven modules with no direct dependency. Since you're using Spring (evidenced by the @Service annotation), I'll focus on Spring-friendly approaches first, plus a Java-native option for non-Spring scenarios.
1. Spring Bean Dynamic Lookup (Best for Your Use Case)
This leverages Spring's application context to find the default implementation at runtime, no direct module dependency needed.
Step 1: Define the Interface in Your API Module
package my.app.core.api; public interface MyService { boolean process(); }
Step 2: Default Implementation Module (my-app-core-impl)
Give the bean a distinct name so we can reliably look it up later:
package my.app.core.impl; import my.app.core.api.MyService; import org.springframework.stereotype.Service; @Service("defaultMyService") // Explicit bean name avoids ambiguity public class MyServiceImpl implements MyService { @Override public boolean process() { System.out.println("Running default business logic"); return true; } }
Step 3: Transactional Implementation Module (my-app-core-transactional-impl)
Inject the Spring context and dynamically fetch the default bean:
package my.app.core.transactional.impl; import my.app.core.api.MyService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class MyTransactionalServiceImpl implements MyService { private final MyService defaultService; @Autowired public MyTransactionalServiceImpl(ApplicationContext context) { // Fetch the default implementation by its bean name this.defaultService = context.getBean("defaultMyService", MyService.class); } @Override @Transactional public boolean process() { System.out.println("Starting transaction..."); boolean result = defaultService.process(); // Delegate to default logic System.out.println("Committing transaction..."); return result; } }
Key Notes
- Make sure both modules are scanned by Spring: Add their base packages to your application's
@ComponentScanor use Spring Boot's auto-scanning if they're under your main application's package hierarchy. - For safety, add fallback logic if the bean isn't found (e.g., use
context.getBeansOfType(MyService.class)to filter out the transactional implementation itself and pick the remaining one).
2. Java SPI (Service Provider Interface)
If you need a Java-native solution that works outside Spring, SPI lets you discover implementations at runtime via a configuration file.
Step 1: Interface in API Module (Same as Above)
Step 2: Default Implementation Module
- Create a file at
src/main/resources/META-INF/services/my.app.core.api.MyServicewith this content:my.app.core.impl.MyServiceImpl - The implementation class (no
@Serviceneeded if not using Spring):package my.app.core.impl; import my.app.core.api.MyService; public class MyServiceImpl implements MyService { @Override public boolean process() { System.out.println("Running default business logic"); return true; } }
Step 3: Transactional Implementation Module
Use ServiceLoader to load the default implementation:
package my.app.core.transactional.impl; import my.app.core.api.MyService; import org.springframework.transaction.annotation.Transactional; import org.springframework.stereotype.Service; import java.util.Iterator; import java.util.ServiceLoader; @Service public class MyTransactionalServiceImpl implements MyService { private final MyService defaultService; public MyTransactionalServiceImpl() { ServiceLoader<MyService> loader = ServiceLoader.load(MyService.class); Iterator<MyService> iterator = loader.iterator(); // Filter out the transactional implementation itself if needed this.defaultService = iterator.next(); } @Override @Transactional public boolean process() { System.out.println("Starting transaction..."); boolean result = defaultService.process(); System.out.println("Committing transaction..."); return result; } }
Key Notes
- SPI loads all registered implementations, so add logic to exclude your transactional class if multiple implementations exist.
- In Spring, you can combine this with
@PostConstructto initialize the default service after the bean is created.
3. Reflection (Last Resort)
Only use this if the above options don't fit—hardcoding class names makes your code brittle:
package my.app.core.transactional.impl; import my.app.core.api.MyService; import org.springframework.transaction.annotation.Transactional; import org.springframework.stereotype.Service; @Service public class MyTransactionalServiceImpl implements MyService { private final MyService defaultService; public MyTransactionalServiceImpl() { try { Class<?> defaultClass = Class.forName("my.app.core.impl.MyServiceImpl"); this.defaultService = (MyService) defaultClass.getConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Failed to load default MyService implementation", e); } } @Override @Transactional public boolean process() { System.out.println("Starting transaction..."); boolean result = defaultService.process(); System.out.println("Committing transaction..."); return result; } }
Key Notes
- This breaks if the default implementation's package or class name changes.
- Gets complicated quickly if the default class has constructor dependencies.
Final Recommendation
Go with Spring Bean Dynamic Lookup first—it's the most maintainable and fits perfectly with your existing Spring setup. Use SPI if you need a framework-agnostic solution, and avoid reflection unless you have no other choice.
内容的提问来源于stack exchange,提问作者JiKra

