依赖注入用@Autowired、@Resource还是构造器?3分钟搞清Spring官方到底推荐谁

经常用IDEA写代码的朋友都知道,用@Autowired依赖注入总是会有如下一堆警告。

于是很多朋友改用@Resource。警告是没了,但实际上Spring官方最推荐的即不是@Autowired也不是@Resource,而是构造器注入

今天咱就来掰扯清楚,保证让你看完能明明白白!

1. 先看三种写法长啥样

@Autowired写法(字段注入):

java 复制代码
@Service
public class UserService {
    @Autowired  // 直接写在字段上
    private UserMapper userMapper;
    
    public void getUser() {
        userMapper.findUser();  // 可能空指针!
    }
}

@Resource写法:

java 复制代码
@Service
public class UserService {
    @Resource  // JSR-250标准注解
    private UserMapper userMapper;
}

构造器注入写法:

java 复制代码
@Service
public class OrderService {

    private final UserService userService;

    public OrderService(UserService userService) {
        this.userService = userService;
    }

    public void createOrder() {
        userService.saveLog("下单了");
    }
}

构造器方式可以更简洁点,用Lombok,加个@RequiredArgsConstructor注解:

java 复制代码
@Service
@RequiredArgsConstructor
public class OrderService {

    private final UserService userService;

    public void createOrder() {
        userService.saveLog("下单了");
    }
}

2. 为什么Spring官方推荐构造器注入?

来看一个例子:

上周我们线上出了事故,原因是因为@Autowired注入为null

java 复制代码
@Service
public class OrderService {
    @Autowired
    private PaymentService paymentService;  // 可能为null!
    
    public void createOrder() {
        paymentService.pay();  // 这里报空指针!
    }
}

为什么为null?

  • Spring容器启动时,Bean的创建顺序不确定
  • OrderService可能比PaymentService先创建
  • 这时候paymentService就是null

如果用构造器注入:

java 复制代码
@Service
public class OrderService {
    private final PaymentService paymentService;
    private final LogisticsService logisticsService;
    
    // 构造器注入
    public OrderService(PaymentService paymentService, 
                       LogisticsService logisticsService) {
        // 如果paymentService为null,这里直接报错!
        this.paymentService = paymentService;
        this.logisticsService = logisticsService;
    }
    
    public void createOrder() {
        paymentService.pay();  // 这里是绝对安全的!
        logisticsService.ship();
    }
}

构造器注入的四大好处:

  1. 依赖不为空 - 如果为null,启动就报错,早发现早治疗
  2. 可以用final - 防止被意外修改
  3. 代码更清晰 - 一看构造器就知道需要哪些依赖
  4. 易于测试 - 单元测试时直接new对象,不用反射设置字段

3. @Autowired和@Resource到底啥区别?

简单总结:

  • @Autowired:Spring早期推荐的方式,按类型(byType)注入
  • @Resource:Java标准注解,按名称(byName)注入

看个实际案例(代码注释有说明):

java 复制代码
public interface MessageService {
    void send();
}

@Service("smsService")  // 指定bean名称
public class SmsService implements MessageService {
    public void send() { System.out.println("发短信"); }
}

@Service("emailService")  // 另一个实现
public class EmailService implements MessageService {
    public void send() { System.out.println("发邮件"); }
}

// 案例1:@Autowired会报错
@Service
public class NotificationService {
    @Autowired  // 报错!找到两个MessageService实现
    private MessageService messageService;
}

// 案例2:@Resource可以指定名称
@Service
public class NotificationService {
    @Resource(name = "smsService")  // 明确要哪个
    private MessageService messageService;
}

// 案例3:@Autowired+@Qualifier也可以
@Service
public class NotificationService {
    @Autowired
    @Qualifier("emailService")  // 配合使用
    private MessageService messageService;
}

4. 实际开发中怎么选择?

记住这个选择指南:

场景1:必需依赖 → 用构造器注入

java 复制代码
@Service
public class UserService {
    private final UserMapper userMapper;  // 必需
    private final OrderService orderService;  // 必需
    
    public UserService(UserMapper userMapper, OrderService orderService) {
        this.userMapper = userMapper;
        this.orderService = orderService;
    }
}

场景2:可选依赖 → 用@Autowired(不影响主流程的依赖)

java 复制代码
@Service
public class UserService {
    @Autowired(required = false)  // 可选的,没有也不会报错
    private BonusService bonusService;
}

场景3:多个实现选特定 → 用@Resource

java 复制代码
@Service
public class UserService {
    @Resource(name = "wechatNotify")  // 明确要微信通知
    private NotifyService notifyService;
}

场景4:Setter方法注入 → 用于可选依赖

java 复制代码
@Service
public class UserService {
    private ConfigService configService;
    
    @Autowired  // 写在setter方法上
    public void setConfigService(ConfigService configService) {
        this.configService = configService;
    }
}

5. 综合案例

来看一个完整的用户服务类就一目了然了:

java 复制代码
@Service
public class UserService {
    // 必需的核心依赖:构造器注入
    private final UserMapper userMapper;
    private final OrderService orderService;
    
    // 可选的增强功能:@Autowired
    @Autowired(required = false)
    private VIPService vipService;
    
    // 多个通知实现:用@Resource指定
    @Resource(name = "smsNotify")
    private NotifyService notifyService;
    
    // 配置信息:setter注入
    private ConfigService configService;
    
    // 构造器注入必需依赖
    public UserService(UserMapper userMapper, OrderService orderService) {
        this.userMapper = userMapper;
        this.orderService = orderService;
    }
    
    // setter注入可选配置
    @Autowired
    public void setConfigService(ConfigService configService) {
        this.configService = configService;
    }
    
    public void register(User user) {
        userMapper.save(user);
        orderService.initUserOrder(user);
        
        if (vipService != null) {
            vipService.activateTrial(user);
        }
        
        notifyService.sendWelcome(user);
    }
}

6. 单元测试

构造器的注入可以让单元测试变得超级简单:

java 复制代码
// 生产代码
@Service
public class UserService {
    private final UserMapper userMapper;
    
    public UserService(UserMapper userMapper) {
        this.userMapper = userMapper;
    }
}

// 测试代码
public class UserServiceTest {
    @Test
    public void testUserService() {
        // 直接new,不用Mockito注解
        UserMapper mockMapper = Mockito.mock(UserMapper.class);
        UserService userService = new UserService(mockMapper);
        
        // 写测试用例...
    }
}

7. 总结一下选择策略

场景 推荐方式 示例
必需依赖 构造器注入 public UserService(UserMapper mapper)
可选依赖 @Autowired(required=false) @Autowired(required=false)
按名称注入 @Resource(name="xxx") @Resource(name="smsService")
可选配置 Setter注入 @Autowired public void setXxx()

黄金法则:

  1. 首选构造器注入 - 特别是核心业务依赖
  2. 次要选择setter注入 - 用于可选配置
  3. 按名注入用@Resource - 多个实现时
  4. 尽量避免字段注入 - 容易产生空指针

现在咱们应该都明白了吧?从今天开始,试着用构造器注入你的代码,保证代码的健壮性!

公众号:程序员刘大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!

📌往期精彩

《工作 5 年没碰过分布式锁,是我太菜还是公司太稳?网友:太真实了!》

《90%的人不知道!Spring官方早已不推荐@Autowired?这3种注入方式你用对了吗?》

《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》

《终于找到 Axios 最优雅的封装方式了,再也不用写重复代码了》

相关推荐
Sally璐璐2 小时前
Go组合式继承:灵活替代方案
开发语言·后端·golang
码熔burning2 小时前
从 new 到 GC:一个Java对象的内存分配之旅
java·开发语言·jvm
Jooou2 小时前
并发:如何设计线程安全的类
java·并发
考虑考虑3 小时前
图片翻转
java·后端·java ee
十六点五3 小时前
Java NIO的底层原理
java·开发语言·python
猿究院-赵晨鹤3 小时前
Java I/O 模型:BIO、NIO 和 AIO
java·开发语言
叽哥3 小时前
Kotlin学习第 5 课:Kotlin 面向对象编程:类、对象与继承
android·java·kotlin
叽哥3 小时前
Kotlin学习第 6 课:Kotlin 集合框架:操作数据的核心工具
android·java·kotlin
心月狐的流火号3 小时前
Spring Bean 生命周期详解——简单、清晰、全面、实用
java·spring