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

    • 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 核心注解速查表

@Resource 详解

定义与作用

@Resource 是 JSR-250 标准定义的通用资源注入注解,由 Java EE 规范提出,Spring 从 2.5 版本开始完整支持。与 Spring 专有的 @Autowired 不同,@Resource 默认**按名称(byName)装配依赖,如果按名称找不到匹配的 Bean,则回退为按类型(byType)**装配。

这一行为差异使得 @Resource 在需要精确控制 Bean 名称的场景下更加直观。在飞翔科技的学生管理系统中,运维工程师李眉偏好使用标准注解而非 Spring 专有注解,因为标准注解让代码更容易在不同 Java 容器间迁移。小崔在维护旧模块时,经常遇到用 @Resource 注入数据源、消息队列连接工厂等基础设施的场景。

适用位置与常用属性

属性类型说明
nameString指定要注入的 Bean 名称,默认取字段名或 Setter 方法名推导
typeClass<?>指定要注入的 Bean 类型,通常不需要显式设置
lookupStringJNDI 查找名称,Spring 环境下很少使用
authenticationTypeAuthenticationType安全认证类型,Spring 环境下忽略
shareableboolean是否共享,Spring 环境下忽略
mappedNameString映射名称,Spring 环境下忽略
descriptionString描述信息,Spring 环境下忽略

适用位置:字段(Field)、Setter 方法。不能用于构造器参数,这是与 @Autowired 的重要区别。

核心原理

Spring 的 CommonAnnotationBeanPostProcessor 负责处理 @Resource 注解。其解析流程与 @Autowired 有显著差异:

  1. 名称推导:如果显式指定了 name 属性,直接使用;否则,对于字段注入,取字段名作为名称;对于 Setter 注入,取Setter 方法名去掉 "set" 前缀并首字母小写作为名称
  2. 按名称查找:在 Spring 容器的 BeanFactory 中查找名称匹配的 Bean
  3. 回退按类型:如果按名称找不到,则按类型查找。如果按类型找到多个,抛出 NoUniqueBeanDefinitionException

完整示例

场景简述

飞翔科技的学生管理系统需要同时对接 MySQL 主库和 Redis 缓存。李眉在旧模块中大量使用 @Resource 注入基础设施组件。小崔在维护时发现,理解 @Resource 的名称推导规则对于排查注入问题至关重要。

操作前:手动查找 Bean 的繁琐代码

public class StudentService {
    private DataSource dataSource;
    private RedisTemplate<String, String> redisTemplate;

    public StudentService() {
        // 手动从某个工厂或上下文查找,耦合严重
        this.dataSource = LegacyFactory.getDataSource("masterDataSource");
        this.redisTemplate = LegacyFactory.getRedisTemplate();
    }
}

痛点分析:

  • 依赖查找逻辑散落在业务代码中,无法统一管理
  • 单元测试时必须初始化整个 LegacyFactory
  • 组件名称变更后,所有硬编码引用点都需要修改

使用该注解的完整代码

package com.feixiang.student.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;

import javax.sql.DataSource;

@Configuration
public class InfrastructureConfig {

    @Bean
    public DataSource masterDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://master.learnto.cn:3306/student_db");
        return new HikariDataSource(config);
    }

    @Bean
    public DataSource slaveDataSource() {
        HikariConfig config = new HikariConfig();
        config.setJdbcUrl("jdbc:mysql://slave.learnto.cn:3306/student_db");
        return new HikariDataSource(config);
    }

    @Bean
    public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, String> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);
        return template;
    }
}
package com.feixiang.student.service;

import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.sql.DataSource;

@Service
public class StudentService {

    // 按字段名 "masterDataSource" 查找容器中同名的 Bean
    @Resource
    private DataSource masterDataSource;

    // 显式指定 name,注入从库数据源
    @Resource(name = "slaveDataSource")
    private DataSource reportDataSource;

    public void printDataSourceInfo() {
        System.out.println("业务数据源:" + masterDataSource.getClass().getSimpleName());
        System.out.println("报表数据源:" + reportDataSource.getClass().getSimpleName());
    }
}
package com.feixiang.student.service;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

@Service
public class CacheService {

    private RedisTemplate<String, String> redisTemplate;

    // Setter 注入:按 "setRedisTemplate" 推导名称为 "redisTemplate"
    @Resource
    public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    public void cacheStudentName(Long studentId, String name) {
        redisTemplate.opsForValue().set("student:name:" + studentId, name);
    }
}
package com.feixiang.student;

import com.feixiang.student.config.InfrastructureConfig;
import com.feixiang.student.service.CacheService;
import com.feixiang.student.service.StudentService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class StudentApplication {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx =
                new AnnotationConfigApplicationContext(InfrastructureConfig.class);
        ctx.scan("com.feixiang.student.service");
        ctx.refresh();

        StudentService studentService = ctx.getBean(StudentService.class);
        studentService.printDataSourceInfo();

        CacheService cacheService = ctx.getBean(CacheService.class);
        cacheService.cacheStudentName(1L, "小崔");
        System.out.println("缓存写入成功");

        ctx.close();
    }
}

操作后运行结果及分析

业务数据源:HikariDataSource
报表数据源:HikariDataSource
缓存写入成功

变化分析:

  • StudentService 中的 masterDataSource 字段名与 @Bean 方法名一致,@Resource 按名称精确注入主库数据源
  • reportDataSource 通过 @Resource(name = "slaveDataSource") 显式指定,注入从库数据源,实现读写分离
  • CacheService 通过 Setter 注入,@Resource 根据方法名 setRedisTemplate 推导出名称 redisTemplate,与容器中的 Bean 名称匹配
  • 所有依赖由容器管理,业务代码无需关心对象如何创建

@Resource 与 @Autowired 的对比

维度@Resource@Autowired
规范来源JSR-250(Java 标准)Spring 专有
默认装配策略按名称(byName)按类型(byType)
回退策略名称找不到则按类型类型有多个则按名称/Qualifier
可用位置字段、Setter 方法字段、Setter、构造器
required 控制无显式属性,找不到即报错有 required = false
配合 Qualifier不支持(自身 name 属性即限定)需配合 @Qualifier
构造器注入不支持支持(官方推荐)

易错场景与面试考点

易错场景一:字段名与 Bean 名称不一致导致意外回退

@Bean
public DataSource masterDataSource() { ... }

@Service
public class StudentService {
    @Resource
    private DataSource dataSource; // 字段名是 dataSource,不是 masterDataSource
}

后果:按名称 dataSource 找不到 Bean,回退按类型 DataSource 查找,找到 masterDataSource 和 slaveDataSource 两个候选,抛出 NoUniqueBeanDefinitionException。

正确做法:保持字段名与 Bean 名称一致,或显式指定 @Resource(name = "masterDataSource")。

易错场景二:Setter 方法命名不规范导致注入失败

@Service
public class StudentService {
    private DataSource masterDataSource;

    // 错误的方法名:没有遵循 setXxx 规范
    @Resource
    public void injectMaster(DataSource ds) {
        this.masterDataSource = ds;
    }
}

后果:@Resource 推导出的名称是 injectMaster,容器中不存在该名称的 Bean,回退按类型查找又遇到多个 DataSource,最终报错。

正确做法:Setter 方法必须遵循 JavaBean 命名规范 setPropertyName,或显式指定 @Resource(name = "masterDataSource")。

易错场景三:试图在构造器上使用 @Resource

@Service
public class StudentService {
    private final DataSource dataSource;

    @Resource // 编译错误或不生效
    public StudentService(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

后果:JSR-250 规范未定义 @Resource 在构造器上的行为,Spring 会忽略构造器上的 @Resource,导致依赖未被注入(若存在默认无参构造器则使用该构造器,否则报错)。

正确做法:构造器注入必须使用 @Autowired(或 Spring 4.3+ 单构造器省略)。

面试考点

Q:@Resource 默认按名称装配,那它如何推导名称?

对于字段注入,名称就是字段名本身;对于 Setter 注入,名称是去掉 "set" 前缀并将首字母小写后的字符串。例如 setMasterDataSource(DataSource ds) 推导出的名称是 masterDataSource。如果显式指定了 @Resource(name = "xxx"),则直接使用该值。

Q:@Resource 和 @Autowired 在只有一个同类型 Bean 时的行为是否相同?

行为结果相同(都能成功注入),但查找路径不同:@Resource 先按名称找,找不到再按类型找;@Autowired 直接按类型找。如果字段名恰好与 Bean 名一致,@Resource 在第一步就命中,效率略高。

Q:@Resource 能否解决同类型多 Bean 的歧义?

可以,但方式与 @Autowired 不同。@Resource 通过 name 属性精确指定 Bean 名称,本质上是"按名称直接定位",而非在类型匹配的候选集中筛选。如果 name 指定错误,即使存在同类型的其他 Bean 也不会回退注入(除非按名称找不到才回退按类型,但回退后若仍有多个候选仍会报错)。

Q:为什么 Spring 官方推荐 @Autowired 而非 @Resource?

① @Autowired 支持构造器注入,这是官方推荐的最佳实践;② @Autowired 配合 @Qualifier 可以实现更灵活的限定策略(如自定义限定符注解);③ @Autowired 的 required = false 对可选依赖的支持更明确。@Resource 的优势在于它是 Java 标准注解,代码不绑定 Spring API,适合需要跨容器移植的场景。

上一页
@Primary 详解
下一页
@Inject 详解