Spring急速入门

[复制链接]
发表于 2025-5-14 14:52:07 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
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 = args == null ? "null" : args.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 注解。



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表