Spring Boot 2.5.0 泛型Controller注入泛型Service的实现问题咨询
Spring Boot 泛型Service与Controller层实现问题解答
现有代码存在的问题
- 泛型Controller基类错误添加Spring MVC注解:
ClassifierController作为抽象基类被标注了@RestController和@RequestMapping,Spring会将其作为实际的Controller实例化,泛型T类型不确定直接导致启动失败。 - 抽象Service基类错误添加Bean注解:
ClassifierService是抽象类无法实例化,标注@Service会让Spring尝试初始化该Bean,抛出实例化异常。 - 泛型无约束:
ClassifierController的泛型T未设置继承上限,基类无法直接调用Service层的公共抽象方法,降低了代码复用性。 - 日志初始化错误:基类Logger硬编码绑定
UserController.class,所有子类的日志都会错误输出为UserController,不利于问题排查。 - 潜在SQL注入风险:具体Service实现中直接拼接分页参数到SQL语句,存在注入风险,建议改用占位符传参。
正确实现方案
1. 改造抽象Service基类
移除抽象类上的@Service注解,可增加泛型适配不同的实体、DTO类型,统一抽象公共方法:
// 抽象基类不加@Service,避免Spring尝试实例化 // 泛型第一个参数为实体类型,第二个为返回DTO类型,可根据业务调整 public abstract class ClassifierService<T, DTO> { public final JdbcTemplate jdbcTemplate; public ClassifierService(final JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } protected abstract Integer count(); // 抽象通用分页查询方法,所有子类统一实现规范 public abstract Page<DTO> getAllValues(Pageable pageable); // 可按需增加其他通用抽象方法,比如按ID查询、删除等 }
2. 改造泛型Controller基类
移除基类上的@RestController和@RequestMapping注解,给泛型加上限约束,调整日志初始化逻辑,还可以直接在基类实现通用接口,所有子类自动继承无需重复开发:
// 基类不加Controller相关注解,避免被Spring实例化 // 泛型上约束必须继承自ClassifierService,基类可直接调用Service的公共方法 public class ClassifierController<T extends ClassifierService<?, ?>> { // 用getClass()动态获取子类实际类型,日志输出正确 protected final Logger LOG = LoggerFactory.getLogger(getClass()); public final T classifierService; protected final JwtTokenFilter jwtTokenFilter; protected final JwtTokenUtil jwtTokenUtil; public ClassifierController(final JwtTokenFilter jwtTokenFilter, final JwtTokenUtil jwtTokenUtil, final T classifierService) { this.jwtTokenFilter = jwtTokenFilter; this.jwtTokenUtil = jwtTokenUtil; this.classifierService = classifierService; } // 通用分页查询接口,所有子类自动拥有该接口 @GetMapping("/list") public ResponseEntity<?> getPageList(@RequestParam(value = "page", defaultValue = "0") int page, @RequestParam(value = "size", defaultValue = "20") int pageSize) { return ResponseEntity.ok(classifierService.getAllValues(PageRequest.of(page, pageSize))); } }
3. 具体业务实现
业务Service实现
@Service public class DictionaryService extends ClassifierService<Dictionary, DictionaryDTO> { private static final String GET_SQL = "select * from dictionary "; public DictionaryService(JdbcTemplate jdbcTemplate) { super(jdbcTemplate); } @Override public Page<DictionaryDTO> getAllValues(Pageable page) { // 分页参数用占位符传参,避免SQL注入 List<DictionaryDTO> result = jdbcTemplate.query(GET_SQL + " LIMIT ? OFFSET ?", new Object[]{page.getPageSize(), page.getOffset()}, new DictionaryRowMapper()); return new PageImpl<>(result, page, count()); } @Override protected Integer count() { return jdbcTemplate.queryForObject("select count(*) from dictionary", Integer.class); } // Service专属方法,子类Controller可直接调用 public List<DictionaryDTO> getByType(String type) { return jdbcTemplate.query(GET_SQL + " where type = ?", new Object[]{type}, new DictionaryRowMapper()); } }
子类Controller实现
你原有的注入逻辑本身是正确的:子类继承时明确指定了泛型T的具体类型为DictionaryService,Spring实例化子类Controller时,会自动从容器中匹配对应类型的Service Bean注入到父类属性中,既可以继承基类的通用接口,也可以扩展调用Service的专属方法:
@RestController @RequestMapping("/ss01dictionary") @CrossOrigin(origins = "*", allowedHeaders = "*") public class SS01WasteDictionaryController extends ClassifierController<DictionaryService> { // Spring 4.3+ 单构造函数可省略@Autowired注解 public SS01WasteDictionaryController(JwtTokenFilter jwtTokenFilter, JwtTokenUtil jwtTokenUtil, DictionaryService classifierService) { super(jwtTokenFilter, jwtTokenUtil, classifierService); } // 扩展Service专属方法对应的接口 @GetMapping("/type/{type}") public ResponseEntity<?> getByType(@PathVariable String type) { return ResponseEntity.ok(classifierService.getByType(type)); } }
内容的提问来源于stack exchange,提问作者Andrei Gavrilov
相关产品推荐
相关产品推荐

