You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 03:15:03