当前位置: 首页 > news >正文

Spring急速入门

Spring 是  企业级开发的一站式框架,核心是 IOC(控制反转) 和 AOP(面向切面编程)

一、Spring 核心:IOC 理论

1. 什么是 IOC?

IOC(Inversion of Control,控制反转)是一种对象创建与管理的设计思想:
传统开发中,对象由程序主动创建(如 new UserService()),而 IOC 模式下,对象的创建、依赖关系的绑定由 Spring 容器 统一管理,程序只需 “被动接收” 容器注入的对象。

核心优势:解耦(对象间不再硬编码依赖)、可维护性(配置集中管理)、方便测试(模拟对象易注入)。

2. IOC 容器的实现

Spring 中 IOC 容器的核心接口是 BeanFactory(基础容器)和 ApplicationContext(扩展容器,企业级特性更丰富)。
容器启动时会读取配置(XML / 注解),解析并创建所有 Bean(被容器管理的对象),最终通过 getBean() 方法获取对象。

二、Bean 的注入方法

Bean 的依赖注入(DI,Dependency Injection)是 IOC 的具体实现,常见方式有以下 4 种:

1. 构造器注入

通过构造方法参数注入依赖,适合 “必须依赖” 的场景(如数据库连接)。

public class UserService {

    private final UserDao userDao; // 必须依赖 UserDao

    

    // 构造器注入(@Autowired 可省略,Spring 6+ 推荐显式声明)

    @Autowired

    public UserService(UserDao userDao) {

        this.userDao = userDao;

}

}

2. Setter 方法注入

通过 Setter 方法注入依赖,适合 “可选依赖” 的场景(如日志工具)。

public class OrderService {

    private Logger logger;  //要注册bean

    

    // Setter 注入

    @Autowired

    public void setLogger(Logger logger) {

        this.logger = logger;

}

}

3. 字段注入(直接注入)

通过 @Autowired、@Resource 等注解直接标注在字段上,代码更简洁(但可能隐藏依赖关系,测试时需注意)。 建议Resource

public class ProductService {

    // @Autowired(Spring 原生) 或 @Resource(JSR-250 标准)

    @Autowired

private ProductDao productDao;

}

4. 自动注入(@Autowired 进阶)

按类型注入:默认根据类型匹配 Bean(如 UserDao 类型的 Bean)。

按名称注入:通过 @Qualifier("beanName") 指定具体 Bean(解决同类型多 Bean 的冲突)。

可选注入:@Autowired(required = false) 允许依赖为 null(避免容器启动报错)。

后面还会介绍List注入,Map注入

三、工厂模式与 Spring 的整合

工厂模式用于解耦对象的创建逻辑,Spring 支持两种工厂方式:

1. 普通工厂(FactoryBean)

通过实现 FactoryBean 接口,自定义 Bean 的创建逻辑(如复杂对象初始化)。

// 自定义工厂(创建 RedisClient 对象)

public class RedisClientFactoryBean implements FactoryBean<RedisClient> {

    @Override

    public RedisClient getObject() {

        return new RedisClient("localhost", 6379); // 复杂初始化逻辑

    }

    @Override

    public Class<?> getObjectType() {

        return RedisClient.class;

}

}

// 注册工厂到 Spring 容器(XML 或 @Bean),这里是在config.里面注册的

@Bean

public RedisClientFactoryBean redisClientFactory() {

return new RedisClientFactoryBean();

}

最终通过 getBean("redisClientFactory") 获取的是 RedisClient 对象(而非工厂本身)。

下面的体现更为明显

2. 静态工厂(静态方法创建)

通过静态方法直接返回对象(无需实例化工厂类)。

public class DataSourceFactory {

    // 静态方法创建 DataSource

    public static DataSource createDataSource() {

        HikariDataSource ds = new HikariDataSource();

        ds.setJdbcUrl("jdbc:mysql://localhost:3306/test");

        return ds;

}

}

// 注册到 Spring 容器(XML 配置)

<bean id="dataSource" class="com.example.DataSourceFactory" factory-method

="createDataSource"/>

虽然我们在这里看似注册的是DataSourceFactory,但是我们实际上注册的是HikariDataSource ,不相信可以去getbean()看看能不能获得DataSourceFactory ,

四、SpEL 表达式(Spring 表达式语言)

SpEL 是 Spring 的动态表达式语言,支持在配置或注解中动态计算值,常见场景:

1. 外部属性注入(读取 application.properties

通过 @Value("${property.key}") 读取配置文件中的值。

${}是占位符

// application.properties 中配置:

redis.host=localhost

@Value("${redis.host}")

private String redisHost;

(这里的@Value还可以作为参数注入哦~)

@Servicepublic class UserService {

    // 方法参数注入(查询用户时,注入默认分页大小)

    public List<User> getUsers(@Value("${page.size}") int pageSize) {

        return userMapper.selectUsers(pageSize); // 使用注入的 pageSize

}

}

2. 集合类操作(List/Map/ 数组)

SpEL 支持直接操作集合,例如:

// 注入 List(逗号分隔)

@Value("#{'1,2,3'.split(',')}")

 private List<Integer> numbers;

// 注入 Map(键值对)

@Value("#{{'name':'张三', 'age':20}}")

private Map<String, Object> userInfo;

3. 方法调用与逻辑运算

// 调用 String 的 length() 方法

@Value("#{'hello'.length()}")

private int strLength;

// 条件判断(结果为 true)

@Value("#{2 > 1 && 'a' == 'a'}")

private boolean condition;

五、AOP(面向切面编程)

AOP 用于解决跨多个对象的通用逻辑(如日志、事务、权限校验),核心概念:

切面(Aspect):封装通用逻辑的类(如 LogAspect)。

切点(Pointcut):定义哪些方法需要被拦截(如 execution(* com.example.service.*.*(..)))。

通知(Advice):通用逻辑的执行时机(前置 / 后置 / 环绕 / 异常 / 最终通知)。

1. XML 配置 AOP(传统方式)

通过 XML 定义切面、切点和通知。

xml:

<!-- 配置目标对象 --> 注册目标为bean

<bean id="userService" class="com.example.service.UserService"/>

<!-- 配置切面类 -->注册切面类为bean

<bean id="logAspect" class="com.example.aspect.LogAspect"/>

<!-- 配置 AOP -->

<aop:config>

    <aop:aspect ref="logAspect">

        <!-- 定义切点(拦截 UserService 的所有方法*) -->

        <aop:pointcut id="userServicePointcut"

            expression="execution(* com.example.service.UserService.*(..))"/>

//这里的excution语法:(语法:execution(返回值类型 包名.类名.方法名(参数))

        

        <!-- 前置通知 -->

        <aop:before method="beforeLog" pointcut-ref="userServicePointcut"/>

</aop:aspect>

</aop:config>

目标类:

UserService 是被 AOP 拦截的业务类,包含需要被监控的方法(如 addUser、getUser)。

public class UserService {

    // 方法 1:添加用户(带参数)

    public void addUser(String username, int age) {

        System.out.println("执行 addUser:添加用户 " + username + "(年龄:" + age + ")");

    }

    // 方法 2:获取用户(带返回值)

    public String getUser(Long id) {

        System.out.println("执行 getUser:查询用户 ID=" + id);

        return "用户_" + id; // 模拟返回用户信息

}

}

//切面类:
public class LogAspect {

    // 前置通知:在 UserService 的方法执行前调用

    public void beforeLog(JoinPoint joinPoint) {

        // 获取目标方法的信息

        Signature signature = joinPoint.getSignature(); // 方法签名(包含类名、方法名)

        String className = signature.getDeclaringTypeName(); // 目标类的完整类名(如 com.example.service.UserService)

        String methodName = signature.getName(); // 方法名(如 addUser、getUser)

        Object[] args = joinPoint.getArgs(); // 方法参数数组

        // 打印前置日志

        System.out.println("【前置通知】准备执行方法:" + className + "." + methodName);

        System.out.println("【前置通知】方法参数:" + String.join(", ", parseArgs(args)));

    }

    // 辅助方法:解析参数数组(转为字符串)

    private String[] parseArgs(Object[] args) {

        if (args == null || args.length == 0) {

            return new String[]{"无参数"};

        }

        String[] argStrs = new String[args.length];

        for (int i = 0; i < args.length; i++) {

            argStrs[i] = args[i] == null ? "null" : args[i].toString();

        }

        return argStrs;

}

}

2. 接口实现 AOP(基于 MethodInterceptor)

通过实现 MethodInterceptor 接口定义环绕通知(Spring 早期方式)。

// 自定义拦截器public class PerformanceInterceptor implements MethodInterceptor {

    @Override

    public Object invoke(MethodInvocation invocation) throws Throwable {

        long start = System.currentTimeMillis();

        Object result = invocation.proceed(); // 执行目标方法

        long cost = System.currentTimeMillis() - start;

        System.out.println("方法执行耗时:" + cost + "ms");

        return result;

    }}

// 配置拦截器(XML)

<aop:config>

    <aop:pointcut id="servicePointcut"

        expression="execution(* com.example.service.*.*(..))"/>

<aop:advisor advice-ref="performanceInterceptor" pointcut-ref="servicePointcut"/>

</aop:config>

3. 注解实现 AOP(推荐方式)****

注解实现 AOP 是 Spring 6 推荐的主流方式,通过 @Aspect 注解 + 切点表达式 + 通知注解 简化配置,无需 XML 即可完成切面逻辑。以下从核心注解、切点表达式、通知类型、参数传递到实际案例,详细拆解实现过程。

First:

切面类的定义:@Aspect + @Component

@Aspect:声明当前类是一个 切面类(封装通用逻辑)。

@Component:将切面类注册到 Spring 容器(必须,否则 Spring 无法扫描到切面)。

(component多用于自己的项目,不好导入外部的一些依赖)

@Aspect    // 声明这是一个切面类

@Component // 注册到 Spring 容器(否则无法被扫描)

public class LogAspect {

// 切面逻辑写在这里

}

Second:

切点定义:@Pointcut

@Pointcut 用于定义可复用的切点表达式,避免重复编写相同的匹配逻辑。

语法:@Pointcut("切点表达式") + 一个无参数、无返回值的方法(方法名即切点的 “别名”)。

@Pointcut("execution(* com.example.service.*.*(..))")

public void servicePointcut() {

// 方法体为空,仅作为切点的“命名”

}

切点表达式的详细介绍:

表达式类型

说明

示例

execution()

最常用,匹配方法执行(语法:execution(返回值类型 包名.类名.方法名(参数))

execution(* com.example.service.UserService.*(..)):拦截 UserService 所有方法

within()

匹配类范围(仅拦截指定类或包下的方法)

within(com.example.service.*):拦截 service 包下所有类的方法

this()

匹配当前代理对象的类型(基于 JDK 动态代理)

this(com.example.service.UserService):代理对象是 UserService 类型

target()

匹配目标对象的类型(基于 CGLIB 代理)

target(com.example.service.UserService):目标对象是 UserService 类型

args()

匹配方法参数类型

args(String, Integer):拦截参数为 String 和 Integer 的方法

@annotation()

匹配方法上有指定注解的情况

@annotation(com.example.annotation.Log):拦截有 @Log 注解的方法

Thrid具体的通知类型:
1. 前置通知 @Before

在目标方法执行前执行(无法阻止目标方法执行,除非抛出异常)。

参数:可通过 JoinPoint 获取目标方法的信息(如方法名、参数)。

@Before("servicePointcut()") // 使用已定义的切点

public void beforeLog(JoinPoint joinPoint) {

    String className = joinPoint.getTarget().getClass().getSimpleName(); // 目标类名

    String methodName = joinPoint.getSignature().getName(); // 方法名

    Object[] args = joinPoint.getArgs(); // 方法参数

System.out.printf("[前置通知] 类=%s,方法=%s,参数=%s\n", className, methodName, Arrays.toString(args));

}

2.后置通知 @After

在目标方法执行后执行(无论是否抛出异常,都会执行,类似 finally)

@After("servicePointcut()")

public void afterLog() {

System.out.println("[后置通知] 方法执行完毕(无论是否异常)");

}

3. 返回后通知 @AfterReturning

在目标方法成功返回后执行(若方法抛出异常则不执行)。

参数:通过 returning 属性指定变量名,获取方法的返回值。

@AfterReturning(value = "servicePointcut()", returning = "result")

public void afterReturningLog(Object result) {

System.out.printf("[返回后通知] 方法返回值=%s\n", result);

}

4. 异常后通知 @AfterThrowing

在目标方法抛出异常后执行(仅当方法抛出异常时触发)。

参数:通过 throwing 属性指定变量名,获取异常对象。

@AfterThrowing(value = "servicePointcut()", throwing = "ex")

public void afterThrowingLog(Exception ex) {

System.out.printf("[异常后通知] 方法抛出异常=%s\n", ex.getMessage());

}

5. 环绕通知 @Around(最强大)******

一、@Around 的核心机制

1. 关键参数:ProceedingJoinPoint

@Around 的通知方法必须接收一个 ProceedingJoinPoint 类型的参数(JoinPoint 的子接口),它提供了两个核心能力:

proceed():触发目标方法的执行(类似 “开关”,调用它才会执行目标方法)。

getArgs()/setArgs():获取或修改目标方法的入参。

2. 执行流程

@Around 的逻辑分为两部分:

前置逻辑:在 proceed() 调用前执行(类似 @Before)。

后置逻辑:在 proceed() 调用后执行(类似 @AfterReturning)。

若目标方法抛出异常,异常会在 proceed() 调用时抛出,可在 @Around 中捕获处理。

示例:从简单到进阶

示例 1:基础用法(记录方法耗时)

最常见的场景是监控方法执行时间,通过 @Around 可以轻松实现:

@Aspect

@Component

public class PerformanceAspect {

    // 定义切点:拦截所有 Service 方法

    @Pointcut("execution(* com.example.service.*.*(..))")

    public void servicePointcut() {}

    @Around("servicePointcut()")

    public Object logTime(ProceedingJoinPoint pjp) throws Throwable {

        // 前置逻辑:记录开始时间

        long startTime = System.currentTimeMillis();

        // 触发目标方法执行(必须调用 proceed(),否则目标方法不会执行)

        Object result = pjp.proceed();

        // 后置逻辑:计算耗时并打印

        long cost = System.currentTimeMillis() - startTime;

        String methodName = pjp.getSignature().getName(); // 方法名

        System.out.printf("方法 %s 执行耗时:%dms\n", methodName, cost);

        return result; // 返回目标方法的结果(可修改返回值)

}

}

效果:调用任意 Service 方法时,控制台会输出该方法的执行耗时。

示例 2:修改方法参数(参数校验)

若目标方法需要校验参数(如禁止传入空值),可通过 @Around 修改或拦截:

@Aspect

@Component

public class ParamCheckAspect {

    @Pointcut("execution(* com.example.service.UserService.addUser(..))")

    public void addUserPointcut() {}

    @Around("addUserPointcut()")

    public Object checkParams(ProceedingJoinPoint pjp) throws Throwable {

        // 获取原始参数(假设目标方法是 addUser(User user))

        Object[] args = pjp.getArgs();

        User user = (User) args[0];

        // 校验参数:姓名不能为空

        if (user.getName() == null || user.getName().trim().isEmpty()) {

            throw new IllegalArgumentException("用户姓名不能为空");

        }

        // 可选:修改参数(例如自动补全默认值)

        if (user.getAge() == null) {

            user.setAge(18); // 年龄默认 18 岁

            args[0] = user; // 更新参数数组

        }

        // 触发目标方法(使用修改后的参数)

        return pjp.proceed(args);

}

}

效果:调用 addUser 时,若姓名为空则抛出异常;若年龄未传则自动补全为 18 岁。

示例 3:修改返回值(数据脱敏)

若目标方法返回敏感数据(如用户手机号),可通过 @Around 对返回值脱敏:

@Aspect

@Component

public class DataMaskAspect {

    @Pointcut("execution(* com.example.service.UserService.getUser(..))")

    public void getUserPointcut() {}

    @Around("getUserPointcut()")

    public Object maskSensitiveData(ProceedingJoinPoint pjp) throws Throwable {

        // 执行目标方法,获取原始返回值

        User user = (User) pjp.proceed();

        // 对手机号脱敏(138****1234)

        if (user.getPhone() != null) {

            String maskedPhone =

user.getPhone().replaceAll("(\\d{3})\\d{4}(\\d{4})", "$1****$2");

//对中间4个数字隐藏

            user.setPhone(maskedPhone);

        }

        return user; // 返回脱敏后的对象

}

}

效果:调用 getUser 获取用户信息时,手机号会被自动脱敏。

@Around 与其他通知的对比

通知类型

能否控制目标方法执行

能否修改参数 / 返回值

适用场景

@Around

✅(通过 proceed())

✅(修改参数 / 返回值)

复杂控制(性能监控、参数校验)

@Before

❌(无法阻止执行)

❌(只能读取参数)

简单前置逻辑(日志记录)

@AfterReturning

❌(已执行完毕)

✅(仅能修改返回值)

后置处理(返回值加工)

@AfterThrowing

❌(已抛出异常)

异常处理(日志记录)

注意事项

1.必须调用 proceed():若 @Around 中不调用 proceed(),目标方法永远不会执行(可能导致业务逻辑缺失)。

2.异常处理:若 proceed() 抛出异常,异常会传递到 @Around 外部,需在 @Around 中捕获处理(否则上层调用会收到异常)。

@Around("...")

public Object handleException(ProceedingJoinPoint pjp) {

    try {

        return pjp.proceed();

    } catch (Throwable e) {

        System.out.println("方法执行异常:" + e.getMessage());

        return null; // 或返回默认值

}

}

3.内部方法调用不生效:若 A 方法调用本类的 B 方法,B 方法的 @Around 不会触发(因为代理对象调用才会触发 AOP)。

解决方案:通过 ApplicationContext 获取代理对象,再调用 B 方法(但不推荐,可能破坏代码结构)。

若一个方法被多个通知拦截,执行顺序如下(以无异常场景为例):
@Around→ @Before → 目标方法执行 → @Around→ @AfterReturning → @After

六、Spring 与 MyBatis 整合

整合目标:通过 Spring 管理 MyBatis 的 SqlSessionFactoryMapper 接口等,简化数据库操作。

1. 关键配置步骤(基于 Spring Boot)

添加依赖:spring-boot-starter-mybatis 和数据库驱动(如 MySQL)。

配置数据源:在 application.properties 中设置 spring.datasource.url、username、password。

配置 SqlSessionFactory:通过 @Bean 定义 SqlSessionFactoryBean,关联数据源和 MyBatis 配置(如别名包)。

扫描 Mapper 接口:使用 @MapperScan("com.example.mapper") 自动注册 Mapper 到容器。

// 配置类(Spring Boot 自动装配可简化)

@Configuration

@MapperScan("com.example.mapper") // 扫描 Mapper 接口

public class MyBatisConfig {

    @Bean

    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {

        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();

        factoryBean.setDataSource(dataSource);

        // 可选:设置 MyBatis 配置(如驼峰命名)

        MybatisConfiguration configuration = new MybatisConfiguration();

        configuration.setMapUnderscoreToCamelCase(true);

        factoryBean.setConfiguration(configuration);

        return factoryBean.getObject();

}

}

2. 使用示例

// Mapper 接口(无需实现类)

public interface UserMapper {

    @Select("SELECT * FROM user WHERE id = #{id}")

User selectById(Long id);

}

// Service 中直接注入 Mapper

@Service

public class UserService {

    @Autowired

    private UserMapper userMapper;

    public User getUser(Long id) {

        return userMapper.selectById(id);

}

}

七、Spring 与 JUnit 整合

整合后可在测试类中直接注入 Spring 容器的 Bean,方便测试业务逻辑。

1. 配置(Spring Boot)

添加依赖:spring-boot-starter-test(包含 JUnit 5、Mockito 等)。

测试类使用 @SpringBootTest 注解,自动加载 Spring 上下文。

@SpringBootTest // 启动 Spring 容器public class UserServiceTest {

    @Autowired

    private UserService userService;

    @Test

    public void testGetUser() {

        User user = userService.getUser(1L);

        assertNotNull(user);

        assertEquals("张三", user.getName());

}

}

2. 进阶:模拟外部依赖(Mockito)

使用 @MockBean 模拟 Mapper 等外部依赖,避免真实数据库调用。

@SpringBootTestpublic class UserServiceTest {

    @Autowired

    private UserService userService;

    @MockBean // 模拟 UserMapper

    private UserMapper userMapper;

    @Test

    public void testGetUser() {

        // 模拟 Mapper 返回数据

        User mockUser = new User(1L, "张三");

        when(userMapper.selectById(1L)).thenReturn(mockUser);

        User user = userService.getUser(1L);

        assertEquals("张三", user.getName());

}

}

add:一个类多个注册,如何确定我们需要的?

Bean是默认按照Type匹配的

一、@Qualifier:通过名称精准匹配

@Qualifier 是 Spring 提供的显式指定 Bean 名称的注解,用于在注入时明确选择目标 Bean。

1. 注册多个同类型 Bean

通过 @Bean 的 name/value 属性为每个 Bean 命名(或通过 @Component 的 value 属性)。

// 配置类:注册两个 RedisClient 实例(不同配置)

@Configuration

public class RedisConfig {

    @Bean("redisMaster") // 指定 Bean 名称为 "redisMaster"

    public RedisClient redisMaster() {

        return new RedisClient("master.redis.com", 6379);

    }

    @Bean("redisSlave") // 指定 Bean 名称为 "redisSlave"

    public RedisClient redisSlave() {

        return new RedisClient("slave.redis.com", 6379);

}

}

2. 注入时使用 @Qualifier 指定名称

在 @Autowired 时,通过 @Qualifier("beanName") 明确选择要注入的 Bean。

@Service

public class OrderService {

    // 注入名称为 "redisMaster" 的 Bean

    @Autowired

    @Qualifier("redisMaster")

    private RedisClient redisMaster;

    // 注入名称为 "redisSlave" 的 Bean

    @Autowired

    @Qualifier("redisSlave")

    private RedisClient redisSlave;

    public void syncData() {

        redisMaster.set("order:1", "data"); // 使用主库

        redisSlave.get("order:1"); // 使用从库

}

}

适用场景

需要明确区分不同实例(如主从数据库、不同环境配置)。

第三方类无法修改(通过 @Bean 命名)。

二、@Primary:标记主选 Bean

@Primary 用于标记一个同类型 Bean 作为默认选择,当未显式指定 @Qualifier 时,Spring 会优先注入被 @Primary 标记的 Bean。

示例:主从数据源

@Configuration

public class DataSourceConfig {

    @Primary // 主数据源(默认注入)

    @Bean("mainDataSource")

    public DataSource mainDataSource() {

        return new HikariDataSource(new HikariConfig("main-db.properties"));

    }

    @Bean("slaveDataSource") // 从数据源(需显式指定)

    public DataSource slaveDataSource() {

        return new HikariDataSource(new HikariConfig("slave-db.properties"));

}

}

注入时不指定 @Qualifier

@Service

public class UserService {

    // 未指定 @Qualifier,自动注入 @Primary 标记的 mainDataSource

    @Autowired

    private DataSource dataSource;

    public User getUser(Long id) {

        return dataSource.getConnection().query("SELECT * FROM user WHERE id=?", id);

}

}

注意

若存在多个 @Primary Bean,仍会冲突(需结合 @Qualifier 解决)。

@Primary 的优先级低于 @Qualifier(即 @Qualifier 明确指定时,@Primary 失效)。


三、@Resource:JSR-250 标准的名称匹配

@Resource 是 JSR-250 标准注解( 规范),默认通过 Bean 名称 注入(若未指定 name,则通过类型匹配)。

示例:通过名称注入

@Service

public class ProductService {

    // 直接通过 name 指定 Bean(等价于 @Autowired + @Qualifier)

    @Resource(name = "redisMaster")

    private RedisClient redisMaster;

    // 未指定 name 时,通过类型匹配(若存在多个同类型 Bean 会报错)

    // @Resource

// private RedisClient redisClient; // 报错!

}

与 @Autowired 的区别

特性

@Autowired(Spring 原生)

@Resource(JSR-250 标准)

默认匹配方式

类型(Type)

名称(Name)

支持指定名称

需结合 @Qualifier

通过 name 属性直接指定

依赖注入阶段

支持构造器、字段、方法参数

仅支持字段和 setter 方法

第三方库兼容性

强(Spring 生态)

弱(需依赖 JSR-250 实现)


四、Map/List 自动注入:获取所有同类型 Bean

Spring 支持将所有同类型的 Bean 注入到 Map 或 List 中(Map 的 Key 是 Bean 名称,Value是实例,List 是所有实例)。

示例 1:Map 注入(Key 为 Bean 名称)

@Service

public class RedisManager {

    // Map 的 Key 是 Bean 名称(如 "redisMaster"、"redisSlave"),Value 是实例

    @Autowired

    private Map<String, RedisClient> redisClients; //会自动获取

    public void printAllClients() {

        redisClients.forEach((name, client) -> {

            System.out.println("Bean 名称:" + name + ",地址:" + client.getHost());

        });

}

}

输出:

Bean 名称:redisMaster,地址:master.redis.com

Bean 名称:redisSlave,地址:slave.redis.com

示例 2:List 注入(按优先级排序)

通过 @Order 或 Ordered 接口指定 Bean 的优先级,List 会按优先级升序排列(数值越小优先级越高)。

// 定义优先级(数值越小越优先

@Bean("redisMaster")

@Order(1)

public RedisClient redisMaster() { ... }

@Bean("redisSlave")

@Order(2)

 public RedisClient redisSlave() { ... }

// 注入 List(顺序:redisMaster → redisSlave)

@Autowired

private List<RedisClient> redisClientList; //会自动获取

适用场景

需要动态选择 Bean(如根据参数从 Map 中获取对应实例)。

需要批量操作同类型 Bean(如遍历所有数据源执行健康检查)。


五、自定义 Qualifier 注解:语义化标记

通过元注解(如 @Target、@Retention)自定义 Qualifier 注解,替代硬编码的字符串,提高代码可读性和类型安全。

1. 定义自定义 Qualifier

// 元注解:标记该注解是一个 Qualifier

@Target({ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER})

@Retention(RetentionPolicy.RUNTIME)

@Qualifier // 关键:继承 Spring 的 Qualifier 语义

public @interface RedisType {

String value(); // 支持传入参数(如 "master"、"slave")

}

2. 注册 Bean 时标记自定义 Qualifier

@Configuration

public class RedisConfig {

    @Bean

    @RedisType("master") // 标记为 "master" 类型

    public RedisClient redisMaster() {

        return new RedisClient("master.redis.com", 6379);

    }

    @Bean

    @RedisType("slave") // 标记为 "slave" 类型

    public RedisClient redisSlave() {

        return new RedisClient("slave.redis.com", 6379);

}

}

3. 注入时使用自定义 Qualifier

@Service

public class OrderService {

    // 通过 @RedisType("master") 注入对应 Bean

    @Autowired

    @RedisType("master")

    private RedisClient redisMaster;

    // 通过 @RedisType("slave") 注入对应 Bean

    @Autowired

    @RedisType("slave")

private RedisClient redisSlave;

}

优势

避免硬编码字符串(如 "redisMaster"),减少拼写错误。

语义更清晰(@RedisType("master") 比 @Qualifier("redisMaster") 更直观)。


总结:方案对比与选择

方案

核心机制

适用场景

优点

@Qualifier

按 Bean 名称显式匹配

明确区分多个同类型 Bean(如主从实例)

灵活、通用

@Primary

标记默认 Bean

大多数场景使用默认 Bean,偶尔需要其他实例

简化默认注入逻辑

@Resource

JSR-250 标准名称匹配

兼容  标准规范

与 Spring 解耦

Map/List 注入

批量获取同类型 Bean

动态选择或批量操作 Bean(如策略模式)

无需硬编码名称,支持动态扩展

自定义 Qualifier

语义化标记 Bean 类型

需要类型安全和清晰语义的场景(如多环境配置)

代码更易读、类型安全

建议:

常规场景优先使用 @Qualifier(灵活且通用)。

需要默认 Bean 时结合 @Primary。

动态选择或批量操作时使用 Map/List 注入。

复杂业务场景(如多类型、多环境)推荐自定义 Qualifier 注解。

相关文章:

  • YOLOv5推理代码解析
  • KUKA机器人安装包选项KUKA.PLC mxAutomation软件
  • Shell脚本编程3(函数+正则表达式)
  • 3d模型的添加与设置
  • 西电 | 2025年拟录取研究生个人档案录取通知书邮寄通知
  • 猫咪如厕检测与分类识别系统系列~进阶【三】网页端算法启动架构及数据库实现
  • 大语言模型训练的两个阶段
  • 阿里云人工智能大模型通义千问Qwen3开发部署
  • 在IDEA中导入gitee项目
  • Vue学习百日计划-Gemini版
  • C++匿名函数
  • 【爬虫】12306查票
  • 笔记本电脑升级实战手册[3]:扩展内存与硬盘
  • 案例数据清洗
  • 智能网联汽车“内外协同、虚实共生”的通信生态
  • logicflow 操作xml文件 为bpmn:serviceTask标签里存在title匹配的内容后添加指定标签内容。
  • 智能手表测试用例文档
  • MySQL 事务(一)
  • bootstrap自助(抽样)法
  • 第三章 仿真器介绍
  • 智能手表眼镜等存泄密隐患,国安部提醒:严禁在涉密场所使用
  • 金科股份重整方案通过,正式进入重整计划执行环节
  • 1156万+1170万,静安、宝山购彩者击中大乐透头奖
  • 著名蒙古族音乐学者马•斯尔古愣逝世,享年86岁
  • 美国再工业化进程需要中国的产业支持
  • 司法部:持续规范行政执法行为,加快制定行政执法监督条例