ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

用SpringBoot写接口,先理清这些依赖和注解

用SpringBoot写接口,先理清这些依赖和注解 你用Spring Boot写接口第一件事不该是打开IDE敲代码而是先想清楚一个问题你写的是接口不是Java类。很多新手甚至干了三五年的开发把Controller当成业务逻辑的收容所把Service当成万能的数据库操作层最后接口越写越臃肿排错越来越痛苦。根源在于他们对依赖和注解的理解停留在“能用就行”的层面从未深究这些工具在你和机器之间到底立下了什么规矩。先说依赖。Spring Boot最迷人的地方不是“自动配置”而是它帮你把“选择”的权力提前剥夺了。你引入spring-boot-starter-web它自动带进Tomcat、Jackson、Spring MVC你引入spring-boot-starter-validation它连Hibernate Validator都给你备好。这套组合拳看似方便但如果你不了解每个starter背后挂了哪些传递依赖迟早会在某个深夜被NoClassDefFoundError逼疯。记住一个原则依赖不是越多越好而是越精确越好。你写一个纯粹返回JSON的接口就没必要拖一个spring-boot-starter-thymeleaf进来你只做简单的参数校验也别冲动地引入整套spring-boot-starter-security。每多一个依赖就意味着多一层被黑、被拖慢、被埋雷的可能性。先拆解starter-web这根骨头spring-boot-starter-web是所有HTTP接口的基石。它替你解决了三个核心问题接收请求、转换参数、返回响应。但注意它默认携带的嵌入式容器是Tomcat这个选择不是神圣不可侵犯的。如果你追求极致的启动速度或内存占用完全可以用spring-boot-starter-webflux走响应式路线或者把Tomcat换成Undertow、Jetty。换容器不是炫技而是对资源利用率的尊重。一个简单的做法在pom.xml里排除掉spring-boot-starter-tomcat再手动添加spring-boot-starter-undertow。别小看这个动作在高并发场景下Undertow对内存的吝啬会让Tomcat相形见绌。然后是Jackson。这个JSON神器默认参与所有序列化和反序列化但它不是万能的。你写一个LocalDateTime字段直接返回给前端Jackson会抛异常——除非你配置了JavaTimeModule。Spring Boot已经自动帮你注册了这个模块但默认的日期格式是ISO格式前端不一定认。你必须在application.yml里显式指定spring.jackson.date-format或者用JsonFormat注解做局部覆盖。这不是麻烦而是你对契约的敬畏。接口不是给你自己看的是给那些可能用Python、Go、甚至Excel调用你的人看的。字段叫userName还是user_name日期是yyyy-MM-dd HH:mm:ss还是时间戳这都得在类的设计阶段就定死。注解的核心是定位而非装饰很多人把注解当魔法其实注解就是标记和元数据。RestController告诉Spring这个类直接处理HTTP请求并且返回的对象会被序列化为JSON。它的兄弟Controller则返回视图名配合ResponseBody才等价于RestController。用RestController是态度用Controller是情怀混着用是给自己挖坑。一个Controller里不要既有返回JSON的接口又有返回视图的接口职责混乱会让测试和维护都变成噩梦。RequestMapping是家族GetMapping、PostMapping、PutMapping、DeleteMapping是它的具体化表达。请务必使用后四个不要用宽泛的RequestMapping。为什么因为GetMapping不仅限定URL还强制限定HTTP方法你的接口意图一目了然。如果你用RequestMapping默认支持所有方法别人GET也能进、POST也能进这不是开放是失控。接口的语义化是优雅的第一步。真正的深度陷阱在参数绑定上。RequestParam用于获取查询参数PathVariable用于路径变量RequestBody用于JSON报文这三个必须分清。有人图省事把所有参数都塞进一个Map接收这等于放弃了类型安全。写接口不写DTO就跟不系安全带开车一样迟早出事故。一个规规矩矩的RequestBody应当对应一个经过验证的DTO类类里每个字段都有明确的类型、命名和验证注解。别用Map偷懒你偷的每一次懒都是未来线上故障的伏笔。校验注解是接口的安检门既然引入spring-boot-starter-validation就别让它吃灰。Valid或Validated加在参数前面然后配合NotNull、NotBlank、Size、Pattern等注解你的接口就拥有了第一道防线。校验注解不是可有可无的装饰而是你对调用方最冷酷的温柔。试想一个POST /api/user接口如果不去校验email字段的格式数据库里就会混入一堆垃圾数据后续清洗成本远高于你写一行Email的成本。这里有个容易被忽视的坑Validated在Controller层只能做基础校验如果你要在Service层做跨字段的复杂逻辑校验就需要把Validated加到Service实现类上并在方法参数前用Valid。分层校验不是重复劳动而是职责分离的体现。Controller只管报文协议的正确性Service管业务规则的正确性两者互不越界。很多人喜欢在Controller里写一大堆if判断然后抛异常这种做法不是不行但它把业务代码和接口代码揉在一起让Controller变成一个脏乱差的加工厂。更好的做法是定义自己的业务异常类配合RestControllerAdvice做全局异常处理。这样你的接口代码里不会出现任何try-catch错误逻辑被收敛到一个地方清爽得让人感动。依赖注入的三种姿势你选哪种Spring的依赖注入有字段注入、构造器注入、Setter注入三种方式。如果你还在用Autowired直接在字段上加注解请扪心自问你的类真的需要被外部替换实现吗字段注入导致类与容器紧密耦合单元测试时无法轻易地替换依赖而构造器注入则天然支持不可变性依赖在对象创建时就固定下来这种确定性让人安心。所以新写代码一律用构造器注入用RequiredArgsConstructorLombok生成构造器既能减少样板代码又能保证依赖不可变。这不仅是习惯更是对SOLID原则的尊重。接下来是Component、Service、Repository、Controller这四个注解的区别。它们本质都是Component的衍生但语义不同Controller管HTTP层Service管业务逻辑Repository管数据访问。别用Component一股脑地标注所有类那是把螺丝刀当成锤子用。Repository还额外提供持久化异常转换这对Spring Data JPA来说尤为重要——你没写RepositorySQL异常就不会被转换成Spring的DataAccessException你辛辛苦苦catch到的异常可能是一条让人摸不着头脑的SQLIntegrityConstraintViolationException而不是一个友好的业务错误提示。Autowired的歧义和解决之道当你有一个接口UserService和两个实现类UserServiceImpl和AdminUserServiceImpl时Autowired靠什么选定答案是Primary或Qualifier。Primary定义默认优先Qualifier定义精确指定。但更好的做法是在构造器参数上直接用Qualifier或者干脆不要用接口注入直接注入具体实现类。很多人觉得面向接口编程就必须用接口注入那是走火入魔。接口的意义在于抽象但如果你的项目里这个接口只有一个实现接口注入只是徒增复杂度。不要为未来可能发生的扩展而牺牲当下的简单等真正有两个实现时再引入接口注入也不迟。你还得认识ConfigurationProperties。这个注解用于绑定配置文件中的自定义属性比如app.token.expire-time。相比Value一个个字段注入ConfigurationProperties可以把一组相关配置封装成一个POJO同时支持类型安全校验和复杂嵌套结构。如果你有超过三个配置项需要读取就别再用Value零散地往类里塞了那是对你自己的不负责任。一个ConfigurationProperties类配上Component或EnableConfigurationProperties再启用spring-boot-configuration-processorIDE里还能自动提示配置项这体验甩Value几条街。Bean的生命周期与条件装配PostConstruct和PreDestroy是管理Bean初始化和销毁的钩子。有些初始化逻辑比如加载缓存、连接外部服务可以放在PostConstruct里。但注意这个时机是在依赖注入完成之后构造方法执行之后但尚未对外提供服务。如果你在构造方法里直接去连数据库很可能依赖还没注入完毕会得到空指针。所以初始化逻辑务必放在PostConstruct或者InitializingBean接口里销毁资源用PreDestroy。条件装配的几个注解也值得深究ConditionalOnProperty、ConditionalOnBean、ConditionalOnMissingBean。它们让Spring Boot的自动配置成为可能。你自己写组件时用ConditionalOnMissingBean来让用户能覆盖你的默认配置这是一种开放姿态用ConditionalOnProperty根据配置项的值来决定是否启用某个Bean这能让你的组件灵活适配不同环境。这些注解的价值在于它们让“配置即代码”这一理念落地。当你看到ConditionalOnMissingBean时你该明白Spring官方在给你机会——它不强行占坑它把主动权交到你手上。终极主题当你从零开始写一个接口依赖和注解的选择应当是一气呵成的。先确定你需不需要响应式再去选spring-boot-starter-web还是webflux接着定义你的DTO和返回结构想清楚错误码体系再动手写Controller。Controller层的注解就那么几个但组合方式千变万化而每个组合都对应一种协议。RestControllerPostMappingValidRequestBody这是一套完整的写JSON接口的标准姿势RestControllerGetMappingRequestParamMin又是另一套查询接口的经典做法。没有哪种注解组合是绝对的真理但有乱用注解的后果是绝对清晰——你的代码会变得难以阅读更难以测试。依赖的数量不代表项目的强大注解的堆砌也不说明代码的优雅。每一次引入依赖都是一次信任投票每一个注解都是一条契约声明。你写接口时其实是你和未来的维护者、调用者、甚至网络另一端某个陌生技术栈的人之间的对话。这场对话的语法就是这些依赖和注解。你用得越精准这场对话就越顺畅。最后送你一个自查清单写接口前问问自己这个接口是否依赖了不必要的starter是否每个注解都在表达确定的语义是否用DTO代替了散装参数校验是否覆盖了所有必填项异常处理是否统一如果答案都是肯定的那你已经领悟了Spring Boot接口开发的精髓——不是把所有功能都塞进来而是在最小必要依赖中用精确的注解刻画出清晰的边界。这才是专业与业余的分水岭。
返回列表