摘要:在Spring Boot开发中,我们天天和@RestController、@Autowired打交道,但有些注解就像被遗忘在工具箱角落的螺丝刀——平时不起眼,关键时刻却能解决大问题。今天就来盘点5个极易被忽视但超实用的注解,学会它们,代码能少写20%,性能还能
在Spring Boot开发中,我们天天和@RestController、@Autowired打交道,但有些注解就像被遗忘在工具箱角落的螺丝刀——平时不起眼,关键时刻却能解决大问题。今天就来盘点5个极易被忽视但超实用的注解,学会它们,代码能少写20%,性能还能提升一个档次!
你是否遇到过这样的坑:在一个单例Service里注入了原型Bean,结果每次调用都是同一个实例?这是因为Spring默认只会在初始化时注入一次依赖,单例Bean的依赖也会保持单例特性。
@Lookup注解就是专门解决这个问题的!它能让Spring在运行时动态生成代理方法,每次调用都从容器中获取最新的原型Bean实例,相当于给单例Bean开了个"动态获取原型对象"的后门。
// 1. 定义原型作用域Bean(每次获取都是新实例)@Component@Scope("prototype")public class OrderNumberGenerator {private String orderNo;// 构造方法,每次实例化生成新订单号public OrderNumberGenerator {this.orderNo = "ORDER-" + System.currenttimeMillis;}public String getOrderNo {return orderNo;}}// 2. 在单例Bean中使用@Lookup获取原型对象@Servicepublic abstract class OrderService {// 抽象方法,Spring会自动生成实现@Lookuppublic abstract OrderNumberGenerator getOrderNumberGenerator;public void createOrder {// 每次调用都会获取新的原型实例OrderNumberGenerator generator = getOrderNumberGenerator;System.out.println("生成订单号:" + generator.getOrderNo);}}调用createOrder三次,会输出三个不同的订单号,完美解决单例Bean中获取原型对象的问题。这在订单生成、分布式ID生成等场景中特别有用,避免多线程下的并发问题。
(示意图:原型Bean通过@Lookup动态注入流程,图源:CSDN博客)
开发通用组件(如Starter)时,如何让用户既能用你的默认实现,又能自定义替换?比如你写了一个日志Starter,默认用Logback,但用户可能想用Log4j。
@ConditionalOnMissingBean直译就是"当容器中没有某个Bean时才生效"。它能帮你实现"用户有自定义Bean就用用户的,没有就用默认的"的智能配置,是Spring Boot自动配置的核心秘密之一。
@Configurationpublic class LogAutoConfiguration {// 当用户没自定义Logger时,用默认实现@Bean@ConditionalOnMissingBean(Logger.class)public Logger defaultLogger {return new DefaultLogger; // 你的默认日志实现}}// 用户自定义Logger(会覆盖默认实现)@Servicepublic class CustomLogger implements Logger {@Overridepublic void log(String message) {System.out.println("[自定义日志] " + message);}}Spring Boot的RedisAutoConfiguration就大量使用了这个注解:如果用户没配置RedisTemplate,就自动配置一个默认的;如果用户自定义了,就用用户的配置。这就是"约定大于配置"的精髓!
(配置截图:Spring Boot自动配置类中使用@ConditionalOnMissingBean,图源:腾讯云开发者社区)
线上接口响应变慢,想知道每个接口的耗时分布?传统做法是写拦截器、埋点日志,又麻烦又容易漏。
@Timed注解(基于Micrometer)能自动统计方法执行时间,配合Actuator和Prometheus,分分钟生成性能监控图表,连Histogram分布都给你算好!
代码示例@RestController@RequestMapping("/api/orders")public class OrderController {// 统计订单查询接口耗时@Timed(value = "order.query.time", // 指标名称description = "订单查询接口响应时间", // 描述histogram = true, // 开启直方图统计(分位数、分布)extraTags = {"module", "order"} // 自定义标签(方便多维度分析))@GetMapping("/{id}")public Order getOrder(@PathVariable Long id) throws InterruptedException {// 模拟业务逻辑耗时Thread.sleep(new Random.nextInt(500)); return orderService.getById(id);}}配置开启只需在application.yml中开启注解支持:
management:observations:annotations:enabled: true # 启用@Timed注解访问/actuator/prometheus接口,就能看到类似这样的指标:
order_query_time_seconds_count{module="order",} 120.0order_query_time_seconds_sum{module="order",} 30.5 # 总耗时30.5秒order_query_time_seconds_bucket{le="0.1",} 45.0 # 100ms内的请求数配合Grafana,直接生成接口耗时趋势图,性能瓶颈一目了然!
(监控截图:Prometheus展示@Timed指标,图源:51CTO博客)
想给一个老系统的类新增功能,又不想改源码?比如给UserService加个日志记录功能,但UserService已经在线上跑了三年,改代码风险太高。
注解救场@DeclareParents是AOP的"引入通知"注解,能不修改源码就给类动态实现新接口。相当于给手机插了个外接镜头——硬件没变,功能却升级了!
代码示例// 1. 定义要新增的接口public interface Loggable {void logAction(String action);}// 2. 实现接口(新功能)public class LoggableImpl implements Loggable {@Overridepublic void logAction(String action) {System.out.println("[日志] 用户执行了:" + action);}}// 3. AOP动态引入(给UserService插上Loggable接口)@Aspect@Componentpublic class LogAspect {@DeclareParents(value = "com.example.UserService", // 目标类defaultImpl = LoggableImpl.class // 接口实现)public Loggable loggable; // 要引入的接口}// 4. 使用新功能(UserService自动拥有logAction方法)@Servicepublic class UserService {public void updateUser {// ... 原有业务逻辑 ...((Loggable) this).logAction("更新用户"); // 调用动态新增的方法}}核心价值这种"无侵入增强"特别适合老系统改造,或者给第三方组件加功能。比如给RedisTemplate加个缓存统计接口,完全不用改RedisTemplate源码!
想知道"今日登录接口被调用了多少次"、"支付接口失败率多少"?@Counted注解能帮你一行代码实现调用次数统计,比自己写计数器优雅10倍!
代码示例@Servicepublic class UserService {// 统计登录次数,区分成功/失败@Counted(value = "user.login.count", // 指标名称description = "用户登录次数统计",extraTags = {"result", "#result"} // 动态标签:成功/失败)public boolean login(String username, String password) {try {// ... 登录逻辑 ...return true; // 成功} catch (Exception e) {return false; // 失败}}}统计效果通过Actuator暴露指标后,你会得到:
user_login_count_total{result="true",} 1560.0 // 成功登录1560次user_login_count_total{result="false",} 89.0 // 失败89次轻松计算成功率:1560/(1560+89)≈94.6%,产品经理要数据再也不用临时查库了!
这些注解可能平时用得少,但每一个都藏着Spring Boot"约定大于配置"的设计哲学。@Lookup解决单例与原型的矛盾,@ConditionalOnMissingBean实现灵活配置,@Timed和@Counted让监控变得简单,@DeclareParents实现无侵入增强——学会它们,你的代码会更优雅、更灵活,还能少写一堆重复逻辑!
来源:祁丶祁一点号
