@Inject 详解
定义与作用
@Inject 是 JSR-330(javax.inject)标准定义的依赖注入注解,由 Google 主导的 Guice 框架推动成为 Java 依赖注入的通用规范。Spring 从 3.0 版本开始完整兼容 JSR-330,因此 @Inject 在 Spring 中的行为与 @Autowired 几乎完全一致——都是按类型自动装配依赖。
选择 @Inject 而非 @Autowired 的主要动机是代码与 Spring 解耦:如果团队未来需要从 Spring 迁移到 Guice、Weld(CDI)或其他支持 JSR-330 的容器,业务代码无需修改注入注解。在飞翔科技的学生管理系统中,架构师白歌在编写需要长期维护的基础组件时,倾向于使用 @Inject,以降低对特定框架的锁定。
适用位置与常用属性
@Inject 是纯粹的标记注解,没有任何属性。它不支持 @Autowired 的 required 功能,对于可选依赖的处理需要借助 Optional 或 Provider。
适用位置:
- 构造器
- Setter 方法
- 字段
核心原理
Spring 的 AutowiredAnnotationBeanPostProcessor 同时识别 @Autowired 和 @Inject 注解,两者的解析流程完全共享同一套代码路径。当处理器扫描到 @Inject 时,会执行与 @Autowired 相同的类型匹配、歧义解决和反射注入逻辑。
关键差异:由于 @Inject 没有 required 属性,当依赖不存在时,Spring 的行为等同于 @Autowired(required = true)——直接抛出 NoSuchBeanDefinitionException。如果需要可选依赖,必须使用 Optional<T> 或 Provider<T>(见下方示例)。
完整示例
场景简述
飞翔科技的学生管理系统中,架构师白歌设计了一套成绩分析引擎 GradeAnalyticsEngine,需要依赖 StudentDao 获取学生数据,以及可选的 CacheProvider 加速热点数据访问。白歌要求使用 JSR-330 标准注解编写,以兼容未来可能引入的 CDI 容器。
操作前:框架强耦合的代码
import org.springframework.beans.factory.annotation.Autowired;
public class GradeAnalyticsEngine {
@Autowired
private StudentDao studentDao;
@Autowired(required = false)
private CacheProvider cacheProvider; // 使用了 Spring 专有属性
}
痛点分析:
- 代码中充斥着
org.springframework包的导入,迁移到其他 DI 框架时需要全局替换 required = false是 Spring 特有语义,其他 JSR-330 容器不支持
使用该注解的完整代码
Maven 依赖:
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>
在 Spring Boot 2.x 中,
spring-boot-starter已间接包含javax.inject,通常无需显式添加。
核心类:
package com.feixiang.student.analytics;
import com.feixiang.student.dao.StudentDao;
import com.feixiang.student.entity.Student;
import javax.inject.Inject;
import javax.inject.Provider;
import java.util.List;
import java.util.Optional;
public class GradeAnalyticsEngine {
private final StudentDao studentDao;
private final Optional<CacheProvider> cacheProvider; // 可选依赖
// 构造器注入:@Inject 标注在构造器上
@Inject
public GradeAnalyticsEngine(StudentDao studentDao,
Optional<CacheProvider> cacheProvider) {
this.studentDao = studentDao;
this.cacheProvider = cacheProvider;
}
public double calculateAverageScore(Long courseId) {
List<Student> students;
// 如果缓存存在且命中,直接返回缓存数据
if (cacheProvider.isPresent()) {
CacheProvider cache = cacheProvider.get();
String cacheKey = "avg:course:" + courseId;
Double cached = cache.get(cacheKey, Double.class);
if (cached != null) {
return cached;
}
students = studentDao.findByCourseId(courseId);
double avg = students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
cache.put(cacheKey, avg, 300); // 缓存 5 分钟
return avg;
}
// 无缓存时直接查询
students = studentDao.findByCourseId(courseId);
return students.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0);
}
}
package com.feixiang.student.analytics;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.stereotype.Component;
@Component
public class SpringCacheProvider implements CacheProvider {
private final CacheManager cacheManager;
@Inject // 同样可以使用 @Inject 注入
public SpringCacheProvider(CacheManager cacheManager) {
this.cacheManager = cacheManager;
}
@Override
public <T> T get(String key, Class<T> type) {
Cache cache = cacheManager.getCache("student");
if (cache != null) {
return cache.get(key, type);
}
return null;
}
@Override
public void put(String key, Object value, int ttlSeconds) {
Cache cache = cacheManager.getCache("student");
if (cache != null) {
cache.put(key, value);
}
}
}
package com.feixiang.student.config;
import com.feixiang.student.analytics.GradeAnalyticsEngine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AnalyticsConfig {
@Bean
public GradeAnalyticsEngine gradeAnalyticsEngine() {
// 由于 @Inject 构造器需要容器解析,通常直接让 Spring 扫描类即可
// 此处仅作演示,实际推荐在 GradeAnalyticsEngine 上加 @Component
return new GradeAnalyticsEngine(null, null); // 实际应由容器管理
}
}
实际最佳实践:在
GradeAnalyticsEngine类上直接加@Component(或@Named,见 Named.md),让 Spring 组件扫描自动发现并解析@Inject构造器,无需手动@Bean注册。
操作后运行结果及分析
// 在存在 CacheProvider 的环境中
GradeAnalyticsEngine engine = ctx.getBean(GradeAnalyticsEngine.class);
double avg = engine.calculateAverageScore(101L);
System.out.println("课程 101 的平均分:" + avg);
// 输出:课程 101 的平均分:85.5
// 第二次调用相同 courseId 时,直接从缓存返回,无需查询数据库
变化分析:
- 代码中不再出现
org.springframework.beans.factory.annotation.Autowired,仅使用javax.inject.Inject,框架耦合度降低 - 可选依赖通过
Optional<CacheProvider>表达,语义明确且是标准 Java API,任何支持 JSR-330 的容器都能理解 - 构造器注入保证
studentDao不可变,对象创建完成即处于可用状态
使用 Provider<T> 实现延迟解析
JSR-330 的 Provider<T> 接口(javax.inject.Provider)允许在运行时才从容器中获取 Bean 实例,而非在构造时一次性注入。这在需要每次获取新实例(如 prototype 作用域)或避免循环依赖时非常有用。
import javax.inject.Inject;
import javax.inject.Provider;
@Service
public class ReportGenerator {
private final Provider<ReportTemplate> templateProvider;
@Inject
public ReportGenerator(Provider<ReportTemplate> templateProvider) {
this.templateProvider = templateProvider;
}
public void generateMonthlyReport() {
// 每次调用 get() 都可能获得新实例(取决于 ReportTemplate 的作用域)
ReportTemplate template = templateProvider.get();
template.render("2024-01");
}
}
易错场景与面试考点
易错场景一:误以为 @Inject 支持 required = false
public class StudentService {
@Inject
private Optional<NotificationService> notificationService; // 正确
@Inject
private NotificationService notificationService2; // 错误!若不存在则报错
}
后果:notificationService2 在容器中不存在 NotificationService 时会抛出 NoSuchBeanDefinitionException,因为 @Inject 没有 required 属性,行为等同于 @Autowired(required = true)。
正确做法:可选依赖必须使用 Optional<T> 包装,或改用 @Autowired(required = false)。
易错场景二:混淆 javax.inject 与 jakarta.inject
Spring Framework 6.0+(Spring Boot 3.x+)升级到了 Jakarta EE 9 命名空间,JSR-330 的对应包从 javax.inject 变为 jakarta.inject。如果在 Spring Boot 3.x 中仍然导入 javax.inject.Inject,编译会通过(如果依赖存在),但 Spring 的 AutowiredAnnotationBeanPostProcessor 默认扫描的是 jakarta.inject.Inject,导致注入不生效。
正确做法:
- Spring Boot 2.x / Spring 5.x:使用
javax.inject - Spring Boot 3.x / Spring 6.x:使用
jakarta.inject
// Spring Boot 3.x 正确导入
import jakarta.inject.Inject;
import jakarta.inject.Provider;
面试考点
Q:@Inject 和 @Autowired 有什么区别?
功能上几乎完全相同,都是按类型自动装配。区别在于:①
@Inject是 JSR-330 标准注解,与 Spring 解耦;②@Inject没有required属性,可选依赖需用Optional<T>;③@Autowired可以配合@Qualifier使用,@Inject配合的是 JSR-330 的@Named(功能等价)。Spring 的AutowiredAnnotationBeanPostProcessor同时处理两者,解析逻辑共享。
Q:@Inject 能否用于字段、Setter 和构造器?
可以,三个位置都支持。但 Spring 官方推荐构造器注入,这与
@Autowired的建议一致。
Q:@Inject 如何解决同类型多 Bean 的歧义?
@Inject本身不提供限定机制,需要配合@Named("beanName")使用(详见 Named.md)。@Named在 JSR-330 中的作用等价于 Spring 的@Qualifier。
Q:Provider<T> 和直接注入 T 有什么区别?
直接注入
T时,Spring 在对象构造时解析并注入实例,后续持有的是同一个引用(对于 singleton)。使用Provider<T>时,Spring 注入的是一个工厂对象,每次调用provider.get()时才从容器中获取实例。如果T是 prototype 作用域,每次get()都会获得新实例;如果T是 singleton,则效果与直接注入相同,但实现了延迟解析。