Spring Web应用抽象复用代码结构咨询:控制器与服务层实现
Hey Marcus, let's take a look at your Spring controller/service abstraction setup—there's a solid core idea here, but there are a few issues that could trip you up, and some easy fixes to make it cleaner and more robust.
First off, your core approach is sound: using abstract classes to extract repetitive logic from controllers and services is a great way to reduce code duplication in Spring apps. This pattern is widely used for common tasks like basic CRUD operations, response formatting, or security checks, so you're on the right track.
Conflicting & Useless Dependencies
- You're injecting
AbstractService serviceAbsin the parentAbstractController, but then injectingService1Service servicein the childService1Controller. This does two bad things:- Spring will create and inject both instances, wasting resources.
- The parent's
serviceAbsis markedprivate, so your child controller can't even access it—making that injected dependency completely redundant.
- If your child controller is using its own
serviceinstance, the parent's injection serves no purpose at all.
- You're injecting
No Type Safety with Generics
- Without generics, your abstract controller can't safely work with the child's specific service type. You're limited to only calling methods defined in
AbstractService, which means you can't reuse any parent logic that needs to interact with child-service-specific methods.
- Without generics, your abstract controller can't safely work with the child's specific service type. You're limited to only calling methods defined in
Questionable Use of
finalon Child Methods- Marking
addServiceasfinalmeans no subclass ofService1Controllercan ever override it. If this is a generic method that should live in the parent controller, it belongs there (and maybe markedfinalthere). If it's a child-specific business method,finalrestricts future flexibility for no good reason.
- Marking
1. Bind Controller & Service with Generics
Add generics to your abstract controller to enforce type safety and eliminate duplicate injections. This lets the parent controller work directly with the child's service type:
public abstract class AbstractController<S extends AbstractService> { // Use protected instead of private so children can access it (or wrap in helper methods) protected final S service; // Constructor injection (Spring's recommended approach) protected AbstractController(S service) { this.service = service; } // Example of a reusable generic method protected ModelAndView baseGetById(Long id) { var entity = service.findById(id); return new ModelAndView("entity-detail", "data", entity); } } @Controller @RequestMapping("/webService") public class Service1Controller extends AbstractController<Service1Service> { // Let Spring inject the specific service via constructor public Service1Controller(Service1Service service) { super(service); } @RequestMapping("/add") public ModelAndView addService(...) { // Safely call Service1Service-specific methods on the parent's service instance service.createNewServiceEntry(...); return new ModelAndView("success"); } }
2. Use Constructor Injection (Not Field Injection)
Spring officially recommends constructor injection over @Autowired field injection for a few reasons:
- It guarantees your controller has all required dependencies at initialization (no null pointers from uninitialized fields).
- It makes unit testing easier—you can manually pass mock services in tests without needing Spring's context.
- It enforces immutability (marking the service as
finalprevents accidental reassignment).
3. Fix Access Modifiers
- Avoid
privatedependencies in abstract parent classes unless you're fully encapsulating their use behind public/protected helper methods. Usingprotectedlets children reuse the service instance when needed, without exposing it to external code.
4. Reassess the final Modifier
- Move generic, reusable logic to the parent controller and mark those methods
finalif you don't want children overriding them. - Leave child-specific business methods non-
finalto allow for future subclassing or customization.
5. Extract Global Logic with @ControllerAdvice
If you have cross-cutting concerns like exception handling, request parameter validation, or response formatting, consider using @ControllerAdvice instead of putting all that in your abstract controller. It keeps your controller hierarchy cleaner and applies logic globally to all controllers.
内容的提问来源于stack exchange,提问作者Marcus Rigonati

