乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@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,则效果与直接注入相同,但实现了延迟解析。

上一页
@Resource 详解
下一页
@Named 详解