自定义Spring Boot Starter最佳实践问询:能否定义@Controller等组件?
Great question! Moving your shared Spring Boot patterns (like database event logging, MDC-enhanced exception handling) into a custom Starter is exactly the right call—that’s precisely what Starters are built for: encapsulating reusable, opinionated cross-cutting logic across multiple applications. Let’s break down your core questions with practical guidance:
Can I define @Controller or @Service in a Starter?
Yes, but only for generic, non-business-specific components—Starters should never contain application-specific logic. Here’s how to do it properly:
- @Service is ideal for shared utilities: For example, a
DatabaseLoggingServicethat handles persisting events with MDC context is perfect. To give consuming apps flexibility, add@ConditionalOnMissingBeanso they can override your implementation if needed:@Service @ConditionalOnMissingBean public class DefaultDatabaseLoggingService implements DatabaseLoggingService { // Implementation that writes logs to DB with MDC attributes } - Limit @Controllers to universal endpoints: Only add Controllers if they serve cross-app needs (like a shared health check or log query endpoint). Never include Controllers tied to specific business domains—those belong in your individual applications.
- Control component scanning: If you use
@ComponentScanin your Starter, explicitly restrict it to your Starter’s package (e.g.,@ComponentScan(basePackages = "com.yourorg.shared.starter")) to avoid accidentally picking up beans from consuming apps.
Can I define global exception handling logic?
Absolutely—this is one of the top use cases for a Starter! Global exception handling with @ControllerAdvice is a classic cross-cutting concern that belongs in shared infrastructure. Here’s how to implement it well:
- Build a reusable @ControllerAdvice class: Add your universal exception handling logic here—catch common exceptions, inject MDC context, log errors to the database, and return consistent error responses. Example:
@ControllerAdvice public class GlobalExceptionHandler { private final DatabaseLoggingService loggingService; public GlobalExceptionHandler(DatabaseLoggingService loggingService) { this.loggingService = loggingService; } @ExceptionHandler(RuntimeException.class) public ResponseEntity<ErrorResponse> handleRuntimeException(RuntimeException ex) { // Generate and attach error ID to MDC String errorId = UUID.randomUUID().toString(); MDC.put("errorId", errorId); // Log error to database with MDC context loggingService.logException(ex, MDC.getCopyOfContextMap()); // Return standardized error response ErrorResponse response = new ErrorResponse(errorId, "An unexpected error occurred"); return new ResponseEntity<>(response, HttpStatus.INTERNAL_SERVER_ERROR); } } - Allow customization: Let consuming apps extend or override your logic:
- Mark key handler methods as
protectedso apps can override them - Use
@Orderto set a default priority (e.g.,@Order(100))—apps can define their own@ControllerAdvicewith a higher priority (lower number) to take precedence - Add configuration properties (via
@ConfigurationProperties) to let users toggle features like database logging or adjust error response formats
- Mark key handler methods as
Bonus Starter Best Practices
To make your Starter robust and user-friendly:
- Use auto-configuration: Wrap your beans in an
@Configurationclass with conditions like@ConditionalOnClass(only load if Spring Web is present) or@ConditionalOnProperty(let users enable/disable features viaapplication.properties) - Minimize mandatory dependencies: Mark non-critical libraries as optional in your build file so consuming apps don’t pull in unused code
- Document thoroughly: Add clear Javadocs to your classes, and include a README explaining how to use the Starter, override defaults, and configure properties
内容的提问来源于stack exchange,提问作者Barium Scoorge

