java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Spring注入方式与原理

Spring注入方式与原理分析

作者:北执南念

本文介绍了Spring框架中的依赖注入(DI)机制,包括基于XML和注解的依赖注入方式,如构造器注入、setter注入和基于属性(字段)注入,还讨论了如何解决接口多实现类的注入问题,以及如何通过applicationContext手动获取bean

前言

Spring 是 Java 后端程序员必须掌握得一门框架技术,Spring 的横空出世,大大简化了企业级应用开发的复杂性。

Spring 框架中最核心的技术就是:

下面将主要介绍 Spring 中的 IOC 的依赖注入。

控制反转IOC

IOC 主要由两种实现方式

1、依赖查找(Dependency Lookup)

容器中的受控对象通过容器的API来查找自己所依赖的资源和协作对象。通俗的说,容器帮我们创建好了对象,开发者需要什么就去容器中取。

2、依赖注入(Dependency Injection,简称 DI ,IOC 最常见的方式)

是对依赖查找的优化,即无需开发者手动去容器中查找对象,只要告诉容器需要什么对象,容器就会将创建好的对象进行注入。

依赖注入DI

在 Spring 中依赖注入的形式主要有两种形式

基于注解 DI 有三种表现形式

三种常规注入方式

1、 构造器注入(Constructor Injection)

通过构造函数来完成依赖注入,容器会在创建 Bean 时调用带有参数的构造函数,并传入所需的依赖对象。这种注入方式的优点是能保证依赖在对象创建时就被初始化,避免了 NPE(空指针异常)的出现。

优点

示例

public class UserService {
    private final UserRepository userRepository;

    // 构造器注入(推荐使用Java 8+的构造器自动注入)
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

构造器注入原理

构造器注入是通过 Bean 的构造函数完成依赖注入的方式。

Spring 在创建 Bean 实例时,会分析构造函数的参数,并从容器中查找匹配的依赖对象。

核心步骤

实例化前的依赖解析

当 Spring 容器启动并扫描到需要创建的 Bean 时,首先会通过反射获取该 Bean 的构造函数信息(包括参数类型和数量)。

依赖匹配

Spring 会根据构造函数参数的类型和名称,从容器中查找匹配的 Bean。如果存在多个候选 Bean,可能需要通过@Qualifier@Primary注解来明确指定。

递归创建依赖对象

如果依赖的 Bean 尚未创建,Spring 会先创建这些依赖 Bean(可能同样需要解析它们的依赖),形成递归过程。

调用构造函数

当所有依赖都准备好后,Spring 使用反射调用目标 Bean 的构造函数,传入依赖对象,完成实例化。

Spring 处理流程

  1. 扫描到UserService需要创建。
  2. 发现带参数的构造函数UserService(UserRepository)
  3. 从容器中查找类型为UserRepository的 Bean。
  4. 如果找到,将其作为参数传入构造函数创建UserService实例。

2、Setter 方法注入(Setter Injection)

通过 setter 方法来完成依赖注入,容器会在创建 Bean 后底层会通过反射机制调用属性对应的set方法,然后给属性赋值或者设置依赖对象。该方式的优势在于允许依赖对象在后期进行修改。这种方式要求属性必须对外提供set方法。

优点

示例

public class UserService {
    private UserRepository userRepository;

    // Setter方法注入
    public void setUserRepository(UserRepository userRepository) {
        this.userRepository = userRepository;
    }
}

Setter 注入原理

Setter 注入是通过 Bean 的 setter 方法完成依赖注入的方式。Spring 先创建 Bean 实例,再通过反射调用 setter 方法设置依赖,

Setter 注入在 Bean 实例化后执行,依赖对象通过公开的 setter 方法动态设置。

核心步骤

实例化 Bean

Spring 使用无参构造函数(或默认构造函数)创建 Bean 实例。此时,Bean 对象已存在,但依赖字段尚未初始化(值为null)。

依赖解析

与构造器注入类似,Spring 会通过反射获取 Bean 的方法信息,识别所有 setter 方法,再根据 setter 方法的参数类型和名称,从容器中查找匹配的依赖对象。

调用 setter 方法

通过反射调用对应的 setter 方法,将依赖对象注入到 Bean 中。

Spring 处理流程

3、基于属性(字段)注入(Field Injection)

日常开发中最常使用的方式,通过反射直接注入字段,使用@Autowired@Resource@Inject注解标记字段。

字段注入是利用反射机制直接对类的字段进行注入,无需借助构造方法或 setter 方法。虽然这种方式代码更为简洁,但它也存在一些缺点,比如无法应用于不可变对象,并且在进行单元测试时需要使用反射来设置依赖。

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private PhoneService phoneService;
    
}

@Service
public class UserServiceImpl implements UserService {

    @Resource
    private PhoneService phoneService;

}

@Autowired 注解和 @Resource 注解的区别:

@Autowired@Resource
Spring 的注解,它的包是 org.springframework.beans.factory.annotation.Autowired不是Spring的注解,它的包是 javax.annotation.Resource
只按照 byType 注入默认按照 byName 自动注入
无属性有两个重要的属性:name 和 type

@Resource的装配顺序

为何 Spring 官方推荐使用构造器注入呢?

接口多实现类注入

当我们用上面三种方式注入接口时,接口有多个实现类时,程序启动就会报错,因为 Spring 不知道要注入哪个实现类。

public interface PaymentProcessor {
    void processPayment(double amount);
}


public class AlipayProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via Alipay: " + amount);
    }
}


public class WechatProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via WeChat: " + amount);
    }
}

@Component
public class OrderService {
    private final PaymentProcessor processor;  // 报错:存在多个候选Bean
    public OrderService(PaymentProcessor processor) {
        this.processor = processor;
    }
}

那么要如何解决接口多实现类的注入问题呢?

public interface PaymentProcessor {
    void processPayment(double amount);
}

@Component("alipayProcessor")
public class AlipayProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via Alipay: " + amount);
    }
}

@Component("wechatProcessor")
public class WechatProcessor implements PaymentProcessor {
    @Override
    public void processPayment(double amount) {
        System.out.println("Processing payment via WeChat: " + amount);
    }
}

@Component
public class OrderService {
    private final PaymentProcessor processor;  // 报错:存在多个候选Bean
    public OrderService(PaymentProcessor processor) {
        this.processor = processor;
    }
}

通过 @Autowired 注解结合 @Qualifier 注解

@Service
public class UserServiceImpl implements UserService {
    
    @Autowired
    @Qualifier("alipayProcessor")
    private PaymentProcessor processor;

}


@Qualifier("applePhoneServieImpl") 指定要引入的具体实现类。

通过 @Resource 注解动态获取

@Service
public class UserServiceImpl implements UserService {

    @Resource(name = "alipayProcessor")
    private PhoneService phoneService;

}

通过 @Primary 注解优先注入

@Component
@Primary  // 优先注入此类
public class AlipayProcessor implements PaymentProcessor { ... }

@Primary 注解表示当有多个 bean 满足注入条件时,会优先注入该注解修饰的 bean 。

通过 @ConditionalOnProperty 注解结合配置文件注入

@Configuration
public class PaymentConfig {
    @Bean
    @ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "alipay")
    public PaymentProcessor alipayProcessor() {
        return new AlipayProcessor();
    }

    @Bean
    @ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "wechat")
    public PaymentProcessor wechatProcessor() {
        return new WechatProcessor();
    }
}


配置文件application.properties:

payment.processor=alipay

@ConditionalOnProperty(prefix = "payment", name = "processor", havingValue = "alipay") 意指当配置文件中 payment.processor=alipay 时,AlipayProcessor 才会注入到容器中。

通过其他 @Conditional条件注解

@Component
@ConditionalOnClass(name = "com.alipay.sdk.app.PayTask")  // 存在Alipay SDK时生效
public class AlipayProcessor implements PaymentProcessor { ... }

@Component
@ConditionalOnMissingBean(PaymentProcessor.class)  // 没有其他实现时生效
public class DefaultPaymentProcessor implements PaymentProcessor { ... }
@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private List<PaymentProcessor> processors;

    @Autowired
    private Map<String, PaymentProcessor> aymentProcessorMap;

}

关键区别与适用场景

方案核心机制优点缺点
@Qualifier显式指定 Bean 名称明确、直观需要硬编码 Bean 名称
@ResourceJSR-250 标准,按名称注入兼容性好依赖命名约定
@Primary标记首选实现减少配置只能有一个主实现
@Conditional根据条件动态选择灵活、可配置条件逻辑可能复杂
集合注入注入所有实现到列表 / Map运行时动态选择需要额外代码处理集合

获取Bean的方式

在 Spring 项目中,有时候需要手动去获取 bean,手动获取的方式需要通过 applicationContext ,有以下几种形式:

通过 applicationContext 获取

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private ApplicationContext applicationContext;

    public Object getBean() {
        return applicationContext.getBean("appleService");
    }
}

实现 ApplicationContextAware 接口

@Component
public class SpringContextUtil implements ApplicationContextAware {

    public static ApplicationContext applicationContext;

    public void setApplicationContext(ApplicationContext applicationContext) {
        SpringContextUtil.applicationContext = applicationContext;
    }

    public static Object getBean(String name) {
        return applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }

    public static <T> T getBean(String name, Class<T> clazz) {
        return applicationContext.getBean(name, clazz);
    }

    public static Boolean containsBean(String name) {
        return applicationContext.containsBean(name);
    }

    public static Boolean isSingleton(String name) {
        return applicationContext.isSingleton(name);
    }

    public static Class<? extends Object> getType(String name) {
        return applicationContext.getType(name);
    }

}

PhoneService phoneService = SpringContextUtil.getBean(PhoneService.class);

继承自抽象类 ApplicationObjectSupport

@Component
public class SpringContextHelper extends ApplicationObjectSupport {

    public Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }

    public  <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }

    public  <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }

    public  Boolean containsBean(String name) {
        return getApplicationContext().containsBean(name);
    }

    public  Boolean isSingleton(String name) {
        return getApplicationContext().isSingleton(name);
    }

    public  Class<? extends Object> getType(String name) {
        return getApplicationContext().getType(name);
    }
}

继承自抽象类 WebApplicationObjectSupport

WebApplicationObjectSupport 继承了 ApplicationObjectSupport ,所以使用方法和上面一样。

总结

本文主要记录了 Spring 中基于注解的 DI 几种方式,接口多个实现的注入方式,如何通过 applicationContext 手动获取 bean 。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

您可能感兴趣的文章:
阅读全文