Spring-IOC

Spring - 容器篇

IoC、DI

注册组件

注入组件

组件生命周期

组件和容器

组件:具有一定功能的对象

容器:管理组件(创建、获取、保存、销毁)

IoC和DI

IoC:Inversion of Control(控制反转)
控制:资源的控制权(资源的创建、获取、销毁等)
反转:和传统的方式不一样了

DI :Dependency Injection(依赖注入)
依赖:组件的依赖关系,如 NewsController 依赖 NewsServices
注入:通过setter方法、构造器、等方式自动的注入(赋值)

Spring IoC / DI概念总结

  • IoC容器

    Spring IoC 容器,负责实例化、配置和组装 bean(组件)核心容器。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

  • IoC(Inversion of Control)控制反转

    IoC 主要是针对对象的创建和调用控制而言的,也就是说,当应用程序需要使用一个对象时,不再是应用程序直接创建该对象,而是由 IoC 容器来创建和管理,即控制权由应用程序转移到 IoC 容器中,也就是“反转”了控制权。这种方式基本上是通过依赖查找的方式来实现的,即 IoC 容器维护着构成应用程序的对象,并负责创建这些对象。

  • DI (Dependency Injection) 依赖注入

    DI 是指在组件之间传递依赖关系的过程中,将依赖关系在容器内部进行处理,这样就不必在应用程序代码中硬编码对象之间的依赖关系,实现了对象之间的解耦合。在 Spring 中,DI 是通过 XML 配置文件或注解的方式实现的。它提供了三种形式的依赖注入:构造函数注入、Setter 方法注入和接口注入。

SpringIoC容器介绍

Spring IoC 容器,负责实例化、配置和组装 bean(组件)。容器通过读取配置元数据来获取有关要实例化、配置和组装组件的指令。

配置元数据以 XML、Java 注解或 Java 代码形式表现。它允许表达组成应用程序的组件以及这些组件之间丰富的相互依赖关系。

获取ioc对象

@SpringBootApplication
public class SpringIocTestApplication {
    public static void main(String[] args) {
        //获取ioc对象
        ConfigurableApplicationContext ioc = SpringApplication.run(SpringIocTestApplication.class, args);
   }
}

Spring框架提供了多种配置方式:XML配置方式、注解方式和Java配置类方式

基于XML配置方式组件管理

对于xml文件的方式也要学会掌握,因为对于工程里面的一些jar包,你是无法在上面给他加注释的,只是使用xml文件方式。

定义一个简单的Java类

public class UserService {  
    public void addUser(String username) {  
        System.out.println("User " + username + " has been added.");  
    }  
}  

XML配置文件(如applicationContext.xml):

<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"  
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"  
       xsi:schemaLocation="http://www.springframework.org/schema/beans   
                           http://www.springframework.org/schema/beans/spring-beans.xsd">  

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

</beans>  

加载Spring上下文

import org.springframework.context.ApplicationContext;  
import org.springframework.context.support.ClassPathXmlApplicationContext;  

public class Main {  
    public static void main(String[] args) {  
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");  
        UserService userService = (UserService) context.getBean("userService");  
        
        userService.addUser("JohnDoe");  
    }  
}  

基于注解方式管理 Bean

注解理解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。

注册组件的各种方式

Spring 提供了以下多个注解,这些注解可以直接标注在 Java 类上,将它们定义成 Spring Bean。

注意:组件默认都是单实例的

@Component

注解 说明
@Component 该注解用于描述 Spring 中的 Bean,它是一个泛化的概念,仅仅表示容器中的一个组件(Bean),并且可以作用在应用的任何层次,例如 Service 层、Dao 层等。 使用时只需将该注解标注在相应类上即可。
@Repository 该注解用于将数据访问层(Dao 层)的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Service 该注解通常作用在业务层(Service 层),用于将业务层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
@Controller 该注解通常作用在控制层(如SpringMVC 的 Controller),用于将控制层的类标识为 Spring 中的 Bean,其功能与 @Component 相同。
//普通组件
/**
 * projectName: com.atguigu.components
 *
 * description: 普通的组件
 */
@Component
public class CommonComponent {
}


//Controller组件
/**
 * projectName: com.atguigu.components
 *
 * description: controller类型组件
 */
@Controller
public class XxxController {
}


//Service组件
/**
 * projectName: com.atguigu.components
 *
 * description: service类型组件
 */
@Service
public class XxxService {
}


//Dao组件
/**
 * projectName: com.atguigu.components
 *
 * description: dao类型组件
 */
@Repository
public class XxxDao {
}

通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。

对于Spring使用IOC容器管理这些组件来说没有区别,也就是语法层面没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用。

组件BeanName问题

每个 bean 都有一个唯一标识——id 属性的值,便于在其他地方引用。

默认情况:

类名首字母小写就是 bean 的 id。例如:SoldierController 类对应的 bean 的 id 就是 soldierController。

使用value属性指定:

@Controller(value = "tianDog")
public class SoldierController {
}
//当注解中只设置一个属性时,value属性的属性名可以省略:
@Service("smallDog")
public class SoldierService {
}

@Bean

@Bean 注解标注这些方法,Spring 会将这些方法返回的对象作为 Bean 注册到 Spring 容器中。

@Bean("chs")
public BeanTest chs(){
   return new BeanTest("chs",21,'男');
}

@Import

在 Spring 框架中,使用 @Import 注解可以将其他配置类或组件引入到当前配置类中,从而注册相关的 Bean。@Import 注解通常用于将外部的配置类或者第三方库的配置类纳入到当前的 Spring 配置中。

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
@Import(ExternalConfig.class)
@Configuration
public class MainConfig {
    // 可以在这里添加更多的 Bean 配置
}

@ComponentScan[批量扫描]

自动发现组件:@ComponentScan 会扫描指定的包及其子包中的类,并将带有特定注解的类注册为 Spring 的 bean。

简化配置:通过自动扫描,开发者不需要手动注册每个 bean,从而简化了配置过程。

import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
//@ComponentScan 指定了要扫描的基础包 com.example.myapp。
//Spring 会自动查找该包及其子包中的所有组件,并将它们注册到应用上下文中。
@ComponentScan(basePackages = "com.example.myapp")  
public class AppConfig {  
    // 其他配置  
}  

basePackages:指定要扫描的包名,可以是一个或多个包。

@ComponentScan(basePackages = {"com.example.service", "com.example.repository"})  
public class AppConfig {  
    // 其他配置  
}  

basePackageClasses:通过指定类来确定要扫描的包,通常用于避免硬编码包名。

@ComponentScan(basePackageClasses = MyService.class)  
public class AppConfig {  
    // 其他配置  
}  

@comPonentScans与@comPonentScan

  • @ComponentScan 注解用于指定要扫描的包,以便 Spring 能够找到并注册这些包中的组件。
  • 它可以放在配置类(用 @Configuration 注解标注的类)上。

示例:

import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
@ComponentScan(basePackages = "com.example.app")  
public class AppConfig {  
    // 配置相关的 Bean 定义  
}  
  • @ComponentScans 是一个复合注解,可以用于同时指定多个 @ComponentScan 注解。
  • 当需要扫描多个不相关的包时,可以使用 @ComponentScans

示例:

import org.springframework.context.annotation.ComponentScan;  
import org.springframework.context.annotation.ComponentScans;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
@ComponentScans({  
    @ComponentScan(basePackages = "com.example.app"),  
    @ComponentScan(basePackages = "com.example.other")  
})  
public class AppConfig {  
    // 配置相关的 Bean 定义  
}  

组件作用域配置(@Scope)

Bean作用域概念

bean 标签声明Bean,只是将Bean的信息配置给SpringIoC容器!

在IoC容器中,这些bean标签对应的信息转成Spring内部 BeanDefinition 对象,BeanDefinition 对象内,包含定义的信息(id,class,属性等等)!

这意味着,BeanDefinition概念一样,SpringIoC容器可以可以根据BeanDefinition对象反射创建多个Bean对象实例。

具体创建多少个Bean的实例对象,由Bean的作用域Scope属性指定

用域可选值

取值 含义 创建对象的时机 默认值
singleton 在 IOC 容器中,这个 bean 的对象始终为单实例 IOC 容器初始化时
prototype 这个 bean 在 IOC 容器中有多个实例 获取 bean 时
request 请求范围内有效的实例 每次请求
session 会话范围内有效的实例 每次会话
Application 在多用户环境中共享的是一个实例的状态信息 跨整个应用中的单例
Websocket 适合需要为每个 WebSocket 客户端维护状态的场景。 Web 环境专用

作用域配置

@Scope("singleton") //单例,默认值
@Scope("prototype") //多例  二选一
public class BeanOne {
    @Bean("chs")
	public BeanTest chs(){
   		return new BeanTest("chs",21,'男');
	}
}

容器启动的时候不会创建非单实例组件的对象,什么时候获取,什么时候创建。

容器启动的时候会创建单实例组件的对象,容器启动完成之前就会创建好。

@Lazy[懒加载]

@Lazy 是一个注解,通常用于 Java 编程语言中的 Spring 框架。它的主要作用是实现懒加载(Lazy Loading),即在需要时才初始化某个 bean,而不是在应用启动时就立即创建。

import org.springframework.context.annotation.Lazy;  
import org.springframework.stereotype.Component;  
@Component  
public class MyService {  

    @Lazy  
    private final MyRepository myRepository;  

    public MyService(MyRepository myRepository) {  
        this.myRepository = myRepository;  
    }  
}  

FactoryBean接口

FactoryBean 是 Spring 框架中的一种设计模式,用于创建复杂对象或 Bean。它提供了一种封装对象创建逻辑的方法,从而使实例化过程更加灵活和可控。

使用场景:当您需要创建需要复杂初始化的 Bean,或者希望根据某些逻辑返回不同类型的对象时,FactoryBean 特别有用。

import org.springframework.beans.factory.FactoryBean;  

public class MyBeanFactory implements FactoryBean<MyBean> {  
    @Override  
    public MyBean getObject() throws Exception {  
        return new MyBean(); // 创建并返回 MyBean 实例  
    }  

    @Override  
    public Class<?> getObjectType() {  
        return MyBean.class; // 返回对象的类型  
    }  

    @Override  
    public boolean isSingleton() {  
        return true; // 指示该 Bean 是否为单例  
    }  
}

@Conditional[条件注册]

@Conditional 是 Spring 框架中的一个注解,用于根据特定条件来控制 Bean 的创建。它允许您在应用程序上下文中有条件地注册 Bean,从而使配置更加灵活和动态。

主要特点
条件化 Bean 注册:
通过使用 @Conditional 注解,您可以指定一个或多个条件类,这些条件类会在 Spring 容器启动时进行评估。如果条件满足,则相应的 Bean 会被创建;如果不满足,则该 Bean 不会被创建。

条件类:
条件类需要实现 Condition 接口,并重写 matches 方法。该方法返回 true 表示条件满足,返回 false 表示条件不满足。

组合条件:
可以使用多个条件组合来实现更复杂的逻辑,
例如使用 @ConditionalOnProperty、@ConditionalOnClass 等注解,这些注解都是基于 @Conditional 的特定实现。

自定义条件类

import org.springframework.context.annotation.Condition;  
import org.springframework.context.annotation.ConditionContext;  
import org.springframework.core.type.AnnotatedTypeMetadata;  

public class MyCondition implements Condition {  
    @Override  
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {  
        // 例如,根据某个系统属性判断条件  
        return "true".equals(System.getProperty("my.condition"));  
    }  
}  

使用 @Conditional 注解

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Conditional;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
public class AppConfig {  

    @Bean  
    @Conditional(MyCondition.class) // 只有在 MyCondition 满足时才创建 myBean  
    public MyBean myBean() {  
        return new MyBean();  
    }  
}  
使用场景

**环境特定配置**:  根据不同的环境(如开发、测试、生产)来创建不同的 Bean。
**特性开关**:     根据应用程序的特性开关来启用或禁用某些功能。
**依赖条件**:     根据某些依赖是否存在来决定是否创建 Bean。

@Conditional 注解为 Spring 应用程序提供了强大的条件化配置能力,使得 Bean 的创建更加灵活和动态。通过自定义条件类,您可以根据特定的业务逻辑来控制 Bean 的注册。

Conditional 派生注解

@Conditional 派生注解 作用
@ConditionalOnCloudPlatform 判定是否指定的云平台,支持:NONE、CLOUD_FOUNDRY、HEROKU、SAP、NOMAD、KUBERNETES、AZURE_APP_SERVICE
@ConditionalOnRepositoryType 判定是否指定的JPA类型,支持:AUTO、IMPERATIVE、NONE、REACTIVE
@ConditionalOnJava 判断Java版本范围,支持:EQUAL_OR_NEWER、OLDER_THAN
@ConditionalOnMissingBean 容器中没有指定组件,则判定true
@ConditionalOnMissingFilterBean 容器中没有指定的Filter组件,则判定true
@ConditionalOnGraphQlSchema 如果GraphQL开启,则判定true
@ConditionalOnSingleCandidate 如果容器中指定组件只有一个,则判定true
@ConditionalOnClass 如果存在某个类,则判定true
@ConditionalOnCheckpointRestore 判断是否导入了 org.crac.Resource ,导入则判定true
@ConditionalOnNotWebApplication 如果不是Web应用,则判定true
@ConditionalOnEnabledResourceChain 如果web-jars存在或者resource.chain开启,则判定true
@Profile 如果是指定Profile标识,则判定true;【后面会说】
@ConditionalOnMissingClass 如果不存在某个类,则判定true
@ConditionalOnWebApplication 如果是Web应用,则判定true
@ConditionalOnResource 如果系统中存在某个资源文件,则判定true
@ConditionalOnNotWarDeployment 如果不是war的部署方式,则判定true
@ConditionalOnDefaultWebSecurity 如果启用了默认的Security功能,则判断true
@ConditionalOnExpression 如果表达式计算结果为true,则判定true
@ConditionalOnWarDeployment 如果是war的部署方式,则判定true
@ConditionalOnBean 如果容器中有指定组件,则判定true
@ConditionalOnThreading 如果指定的threading激活,则判定true
@ConditionalOnProperty 如果存在指定属性,则判定true
@ConditionalOnJndi 如果JNDI位置存在,则判定true

基于配置类(@Configuration) 方式管理 Bean

Spring 完全注解配置(Fully Annotation-based Configuration)是指通过 Java配置类 代码来配置 Spring 应用程序,使用注解来替代原本在 XML 配置文件中的配置。相对于 XML 配置,完全注解配置具有更强的类型安全性和更好的可读性。

Java配置类方式:从Spring 3.0版本开始提供支持,通过Java类来定义Bean、Bean之间的依赖关系和配置信息,从而代替XML配置文件的方式。Java配置类是一种使用Java编写配置信息的方式,通过@Configuration、@Bean等注解来实现Bean和依赖关系的配置。

image-20240830202355202

@Configuration
public class ConfigBean {
    @Bean("chs")
    public BeanTest chs1(){
        return new BeanTest("chs",21,'男');
    }
    @Bean("chs")
    public BeanTest chs2(){
        return new BeanTest("chs",21,'男');
    }
    @Bean
    public Dog dog1(){
        return new Dog("kunkun",25,'女');
    }
}

classpath:

资源加载: 通过 classpath: 前缀,Spring 可以方便地加载配置文件、属性文件、XML 文件、图片等资源。

模块化和可移植性: 使用 classpath: 前缀有助于简化资源的组织,使得应用程序更易于移植,因为资源可以在不同环境中保持一致。

支持 JAR 文件: classpath: 也支持从 JAR 文件中加载资源,这为使用外部库提供了便利。

注入组件的各种方式

自动装配实现

前提==>参与自动装配的组件(需要装配、被装配)全部都必须在IoC容器中。

自动装配流程(先按照类型,再按照名称)

@Autowired注解

@Autowired 注解是 Spring 框架中用于自动装配 Bean 的一种方式。它可以简化依赖注入的过程,使得开发者不需要手动配置依赖关系。

优先级@Primary:当 Spring 容器中存在多个相同类型的 Bean 时,标记为 @Primary 的 Bean 将被优先选择进行注入。

@Qualifier 配合使用:如果需要明确指定某个 Bean,可以使用 @Qualifier 注解来选择特定的 Bean,而不依赖于 @Primary

特点:

自动装配@Autowired 可以自动将 Spring 容器中的 Bean 注入到需要的地方,通常用于构造函数、字段或 setter 方法。

按类型注入:默认情况下,@Autowired 根据类型进行注入。如果容器中存在多个相同类型的 Bean,可以使用 @Qualifier 注解来指定具体的 Bean。

可选依赖:通过设置 required 属性为 false,可以使依赖变为可选。如果没有找到匹配的 Bean,Spring 不会抛出异常。

使用 @Primary 标记优先选择的Bean

//定义接口和实现类

public interface MyService {  
    void performAction();  
}  

@Component  
public class MyServiceImplA implements MyService {  
    @Override  
    public void performAction() {  
        System.out.println("Action performed by MyServiceImplA");  
    }  
}  

@Component  
@Primary // 标记为优先选择的 Bean  
public class MyServiceImplB implements MyService {  
    @Override  
    public void performAction() {  
        System.out.println("Action performed by MyServiceImplB");  
    }  
} 


//使用自动装配
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  
@Component  
public class MyController {  
    private final MyService myService;  
    @Autowired  
    public MyController(MyService myService) { // 自动注入时优先选择 MyServiceImplB  
        this.myService = myService;  
    }  
    public void execute() {  
        myService.performAction(); // 调用 MyServiceImplB 的方法  
    }  
}

使用 @Qualifier 选择特定 Bean

//定义接口和实现类
public interface MyService {  
    void performAction();  
}  

@Component  
public class MyServiceImplA implements MyService {  
    @Override  
    public void performAction() {  
        System.out.println("Action performed by MyServiceImplA");  
    }  
}  

@Component  
@Primary // 标记为优先选择的 Bean  
public class MyServiceImplB implements MyService {  
    @Override  
    public void performAction() {  
        System.out.println("Action performed by MyServiceImplB");  
    }  
} 

//使用 `@Qualifier` 选择特定 Bean
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.beans.factory.annotation.Qualifier;  
imort org.springframework.stereotype.Component;  
@Component  
public class MyOtherController {  
    private final MyService myService;  
    @Autowired  
    public MyOtherController(@Qualifier("myServiceImplA") MyService myService) { // 明确指定 MyServiceImplA  
        this.myService = myService;  
    }  
    public void execute() {  
        myService.performAction(); // 调用 MyServiceImplA 的方法  
    }  
}  

字段注入(最常用)

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    @Autowired  
    private MyRepository myRepository; // 自动注入 MyRepository  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

构造函数注入(最好)

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    private final MyRepository myRepository;  

    @Autowired  
    public MyService(MyRepository myRepository) { // 通过构造函数注入  
        this.myRepository = myRepository;  
    }  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

Setter 方法注入

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    private MyRepository myRepository;  

    @Autowired  
    public void setMyRepository(MyRepository myRepository) { // 通过 Setter 方法注入  
        this.myRepository = myRepository;  
    }  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

可选依赖

import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    private final MyRepository myRepository;  

    @Autowired(required = false) // 可选依赖 如果没有找到匹配的 Bean,Spring 不会抛出异常。
    public MyService(MyRepository myRepository) {  
        this.myRepository = myRepository;  
    }  

    public void performAction() {  
        if (myRepository != null) {  
            myRepository.doSomething();  
        } else {  
            // 处理没有注入的情况  
        }  
    }  
}  

@Resource注解

@Resource 注解是 Java EE 和 Spring 框架中用于依赖注入的一个注解。它主要用于将资源(如 Bean、数据源等)注入到类中。@Resource 注解的使用相对简单,通常用于字段或 setter 方法上。

按名称注入@Resource 默认根据名称进行注入。如果没有找到与字段名称相同的 Bean,Spring 会尝试根据类型进行注入。

兼容性@Resource 是 Java EE 的一部分,因此它可以在 Spring 和其他 Java EE 容器中使用。

可选属性@Resource 提供了 nametype 属性,可以显式指定要注入的 Bean 的名称或类型。

字段注入

import javax.annotation.Resource;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    @Resource // 根据字段名称自动注入 MyRepository  
    private MyRepository myRepository;  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

Setter 方法注入

import javax.annotation.Resource;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    private MyRepository myRepository;  

    @Resource // 根据 setter 方法的参数类型自动注入 MyRepository  
    public void setMyRepository(MyRepository myRepository) {  
        this.myRepository = myRepository;  
    }  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

指定名称

如果您希望显式指定要注入的 Bean 的名称,可以使用 name 属性:

import javax.annotation.Resource;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    @Resource(name = "myCustomRepository") // 指定要注入的 Bean 名称  
    private MyRepository myRepository;  

    public void performAction() {  
        myRepository.doSomething();  
    }  
}  

@Resource@Autowired 的比较

  • 注入方式@Autowired 默认按类型注入,而 @Resource 默认按名称注入。
  • 兼容性@Resource 是 Java EE 的一部分,适用于 Java EE 环境,而 @Autowired 是 Spring 特有的注解。
  • 灵活性@Autowired 提供了更多的灵活性,例如可以使用 @Qualifier 指定具体的 Bean,而 @Resource 主要依赖于名称。
  • @Resource注解用在属性上、setter方法上。
  • @Autowired注解用在属性上、setter方法上、构造方法上、构造方法参数上。
  • @Resource注解默认根据Bean名称装配,未指定name时,使用属性名作为name。通过name找不到的话会自动启动通过类型装配。
  • @Autowired注解默认根据类型装配,如果想根据名称装配,需要配合@Qualifier注解一起用。

@Value注解

@Value 注解是 Spring 框架中用于注入外部配置值的一个注解。它可以用于将属性文件、环境变量或其他配置源中的值注入到 Spring Bean 的字段、方法或构造函数中。@Value 注解使得配置管理变得更加灵活和方便。

注入简单@Value 可以直接将字符串、数字、布尔值等基本类型的值注入到 Bean 中。

import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    @Value("你好") 	// 将字符串、数字、布尔值等基本类型的值注入到 Bean 中
    private String myProperty;  

    public void printProperty() {  
        System.out.println("Property value: " + myProperty);  
    }  
} 

占位符

支持占位符:可以使用 ${} 语法从配置文件中读取值,支持 Spring 的占位符解析。

import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  
    
    //注意默认只能获取application.properties配置文件的值
    @Value("${my.property}") // 从配置文件中注入 my.property 的值 
    private String myProperty;  

    public void printProperty() {  
        System.out.println("Property value: " + myProperty);  
    }  
}

默认值

默认值:可以为注入的值提供默认值,以防配置中没有找到对应的值。

import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  
@Component  
public class MyService {  
    @Value("${my.int.property:10}") // 注入整数值,默认值为 10  
    private int myIntProperty;  

    @Value("${my.boolean.property:true}") // 注入布尔值,默认值为 true  
    private boolean myBooleanProperty;  

    public void printProperties() {  
        System.out.println("Integer property: " + myIntProperty);  
        System.out.println("Boolean property: " + myBooleanProperty);  
    }  
}

SpEL表达式

表达式支持:可以使用 Spring 表达式语言(SpEL)#{}进行更复杂的值注入。

基本语法:
    访问属性:person.name
    调用方法:person.getName()
    访问集合:list[0]
    访问数组:array[1]

条件表达式:
    使用三元运算符:age > 18 ? 'Adult' : 'Minor'
    
函数调用:
	调用静态方法:T(java.lang.Math).random()
	
集合操作:
过滤集合:T(java.util.stream.Stream).of(list).filter(x -> x > 10).collect(T(java.util.stream.Collectors).toList())
import org.springframework.beans.factory.annotation.Value;  
import org.springframework.stereotype.Component;  

@Component  
public class MyService {  

    @Value("#{SpEL}") 	// 使用 Spring 表达式语言(SpEL)`#{}`进行更复杂的值注入
    private String myProperty;  

    public void printProperty() {  
        System.out.println("Property value: " + myProperty);  
    }  
} 

@PropertySource

@PropertySource 是 Spring 框架中的一个注解,用于加载外部属性文件中的属性到 Spring 的环境中。通过使用 @PropertySource,你可以将配置文件中的键值对注入到 Spring 的 Environment 中,从而在应用程序中使用这些属性。

@PropertySource 只能加载 .properties 文件,不能直接加载 .yml.yaml 文件。

如果属性文件的路径不正确,Spring 将抛出异常。

可以使用多个 @PropertySource 注解来加载多个属性文件

  1. 创建属性文件
    首先,创建一个属性文件,例如 app.properties,内容如下:

    app.name=My Application  
    app.version=1.0.0  
    
  2. 创建配置类
    然后,创建一个配置类,并使用 @PropertySource 注解来加载属性文件:

    import org.springframework.context.annotation.Configuration;  
    import org.springframework.context.annotation.PropertySource;  
    
    @Configuration  
    @PropertySource("classpath:app.properties")  
    //加载属性文件
    public class AppConfig {  
        // 其他配置  
    }  
    
  3. 注入属性
    你可以使用 @Value 注解将属性注入到 Spring 管理的 Bean 中:

    import org.springframework.beans.factory.annotation.Value;  
    import org.springframework.stereotype.Component;  
    @Component
    public class MyService {  
        @Value("${app.name}")  
        private String appName;  
    
        @Value("${app.version}")  
        private String appVersion;  
    
        public void printAppInfo() {  
            System.out.println("Application Name: " + appName);  
            System.out.println("Application Version: " + appVersion);  
        }  
    }  
    

@PropertySource与@PropertySources

  • @PropertySource 注解用于指定一个或多个外部属性文件(.properties 文件),并将其加载到 Spring 的环境中。
  • 这个注解可以用于配置类(用 @Configuration 注解标注的类),通常在启动类或特定的配置类中使用。

示例:

import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.PropertySource;  

@Configuration  
@PropertySource("classpath:application.properties")  
public class AppConfig {  
    // 配置相关的 Bean 定义  
}  
  • @PropertySources 是一个复合注解,用于同时指定多个 @PropertySource 注解。
  • 当你需要加载多个属性文件时,使用 @PropertySources 更为方便。

示例:

import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.PropertySources;  
import org.springframework.context.annotation.PropertySource;  

@Configuration  
@PropertySources({  
    @PropertySource("classpath:application.properties"),  
    @PropertySource("classpath:other.properties")  
})  
public class AppConfig {  
    // 配置相关的 Bean 定义  
}  

@Profile

@Profile 是 Spring 框架中的一个注解,用于表示某个组件或配置类只在特定的环境(Profile)中激活或可用

这在不同的环境(如开发、测试、生产等)中管理配置和 bean 定义时非常有用。

如何使用 @Profile

  1. 标注在类或方法上:
    • 你可以将 @Profile 注解应用于 @Component@Service@Repository@Controller 等注解的类上,或者直接在配置方法上。
  2. 定义环境:
    • 要启用某个环境,可以在启动 Spring 应用时指定。可以通过 VM 参数、环境变量或应用的配置文件中设置。

定义一个 Profile

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
import org.springframework.context.annotation.Profile;  

@Configuration  
public class AppConfig {  

    @Bean  
    @Profile("dev") // 这个 Bean 只在 "dev" 环境下可用  
    public MyService devService() {  
        return new DevService();  
    }  

    @Bean  
    @Profile("prod") // 这个 Bean 只在 "prod" 环境下可用  
    public MyService prodService() {  
        return new ProdService();  
    }  
}  

启用 Profile
要启用特定的 Profile,你可以通过以下方式之一:

  1. 在运行应用时通过 VM 参数指定:

    -D spring.profiles.active=dev  
    
  2. application.properties 中设置:

    spring.profiles.active=dev  
    

多个 Profiles

你也可以通过逗号来指定多个 Profiles,实现更复杂的配置:

@Profile({"dev", "test"})  
public class MyTestService {  
    // 这个 Bean 在 "dev" 或 "test" 环境下可用  
}  

感知接口(xxxAware)

在 Spring 框架中,感知接口(Aware interfaces)是一些特定的接口,允许开发者获取 Spring 容器的各种对象。这些接口通常用于在 Spring Beans 中提供对容器的某些功能的访问。这种设计使得 Bean 可以在需要时感知并利用 Spring 容器的特性。

常见的感知接口

  1. ApplicationContextAware
  • 功能:允许 Bean 获取其所属的 ApplicationContext

  • 应用场景:在 Bean 中需要访问其他 Bean 或上下文相关的方法时,常用这个接口。

    public class MyBean implements ApplicationContextAware {  
        private ApplicationContext applicationContext;  
    
        @Override  
        public void setApplicationContext(ApplicationContext applicationContext) {  
            this.applicationContext = applicationContext;  
        }  
    }  
    
  1. BeanFactoryAware
  • 功能:允许 Bean 获取其所用的 BeanFactory

  • 应用场景:当需要对 BeanFactory 有更精细的控制或访问其他 Bean 时使用。

    public class MyBean implements BeanFactoryAware {  
        private BeanFactory beanFactory;  
    
        @Override  
        public void setBeanFactory(BeanFactory beanFactory) {  
            this.beanFactory = beanFactory;  
        }  
    }  
    
  1. BeanNameAware
  • 功能:允许 Bean 获取在容器中的名称。

  • 应用场景:如果 Bean 需要知晓自己在容器中的名称,可以使用这个接口。

    public class MyBean implements BeanNameAware {  
        private String beanName;  
    
        @Override  
        public void setBeanName(String name) {  
            this.beanName = name;  
        }  
    }  
    
  1. ResourceLoaderAware
  • 功能:允许 Bean 获取 ResourceLoader 的引用,从而加载外部资源。

  • 应用场景:当 Bean 需要加载文件、URL 或 classpath 中的资源时使用。

    public class MyBean implements ResourceLoaderAware {  
        private ResourceLoader resourceLoader;  
    
        @Override  
        public void setResourceLoader(ResourceLoader resourceLoader) {  
            this.resourceLoader = resourceLoader;  
        }  
    }  
    
  1. MessageSourceAware
  • 功能:允许 Bean 获取 MessageSource,以便进行国际化支持。

  • 应用场景:需要从资源文件中加载消息时使用。

    public class MyBean implements MessageSourceAware {  
        private MessageSource messageSource;  
    
        @Override  
        public void setMessageSource(MessageSource messageSource) {  
            this.messageSource = messageSource;  
        }  
    }  
    
  1. ApplicationEventPublisherAware
  • 功能:允许 Bean 获取 ApplicationEventPublisher,用于发布事件。

  • 应用场景:当 Bean 需要在特定情况下发布事件时使用。

    public class MyBean implements ApplicationEventPublisherAware {  
        private ApplicationEventPublisher publisher;  
    
        @Override  
        public void setApplicationEventPublisher(ApplicationEventPublisher publisher) {  
            this.publisher = publisher;  
        }  
    }  
    
  1. EnvironmentAware
  • 功能:允许 Bean 获取 Environment 对象。

  • 应用场景:当需要访问应用程序的环境属性(如配置参数、系统属性等)时使用。

    public class MyBean implements EnvironmentAware {  
        private Environment environment;  
    
        @Override  
        public void setEnvironment(Environment environment) {  
            this.environment = environment;  
        }  
    }  
    

组件生命周期

组件生命周期图

image-20240903093638357

具体的生命周期过程

  • bean对象创建(调用无参构造器)

  • 给bean对象设置属性

  • bean的后置处理器(初始化之前)

  • bean对象初始化(需在配置bean时指定初始化方法)

  • bean的后置处理器(初始化之后)

  • bean对象就绪可以使用

  • bean对象销毁(需在配置bean时指定销毁方法)

  • IOC容器关闭

Bean指定生命周期

在Spring框架中,可以使用@Bean注解定义一个Bean,并指定其生命周期方法(初始化和销毁方法)。可以通过initMethoddestroyMethod属性来指定这些方法。

import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  

@Configuration  
public class AppConfig {  

    @Bean(initMethod = "init", destroyMethod = "cleanup")  
    public MyBean myBean() {  
        return new MyBean();  
    }  
}  

class MyBean {  

    public void init() {  
        // 初始化逻辑  
        System.out.println("MyBean is initialized");  
    }  

    public void cleanup() {  
        // 清理逻辑  
        System.out.println("MyBean is destroyed");  
    }  
}  

Bean初始化之前操作

InitializingBean接口

InitializingBean 是 Spring 框架提供的一个接口,允许开发者在 Bean 初始化后执行特定的逻辑。实现该接口的类需要重写 afterPropertiesSet() 方法,Spring 容器在 Bean 的属性设置完毕之后会自动调用这个方法。

import org.springframework.beans.factory.InitializingBean;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBean implements InitializingBean {  

    private String name;  

    public void setName(String name) {  
        this.name = name;  
    }  

    @Override  
    public void afterPropertiesSet() throws Exception {  
        // 在 Bean 属性设置后执行的逻辑  
        System.out.println("MyBean initialized with name: " + name);  
    }  
}  

@PostConstruct 注解

除了实现 InitializingBean,你也可以使用 @PostConstruct 注解来实现类似的功能。

import javax.annotation.PostConstruct;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBean {  

    private String name;  

    public void setName(String name) {  
        this.name = name;  
    }  

    @PostConstruct  
    public void init() {  
        // 在 Bean 属性设置后执行的逻辑  
        System.out.println("MyBean initialized with name: " + name);  
    }  
}  

Bean销毁之前操作

DisposableBean接口

DisposableBean 接口是 Spring 框架提供的一个接口,用于在 Bean 被销毁之前进行清理活动。实现这个接口的类需要重写 destroy() 方法,Spring 容器在销毁该 Bean 时会自动调用此方法。

import org.springframework.beans.factory.DisposableBean;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBean implements DisposableBean {  

    public void performTask() {  
        // 执行一些任务  
        System.out.println("Performing task in MyBean.");  
    }  

    @Override  
    public void destroy() throws Exception {  
        // 在 Bean 被销毁之前执行的清理逻辑  
        System.out.println("Cleaning up resources in MyBean.");  
    }  
}

MyBean实现了DisposableBean` 接口。

重写了 destroy() 方法,以便在 Bean 被销毁时执行清理逻辑(在这里是打印日志)。

当 Spring 容器关闭或该 Bean 被销毁时,destroy() 方法将会被调用。

@PreDestroy 注解

除了实现 DisposableBean,你也可以使用 @PreDestroy 注解来实现类似的功能。

import javax.annotation.PreDestroy;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBean {  

    public void performTask() {  
        // 执行一些任务  
        System.out.println("Performing task in MyBean.");  
    }  

    @PreDestroy  
    public void cleanup() {  
        // 在 Bean 被销毁之前执行的清理逻辑  
        System.out.println("Cleaning up resources in MyBean.");  
    }  
}

BeanPostProcessor接口

BeanPostProcessor 是 Spring 框架中的一个非常重要的接口,它允许开发者在 Bean 实例化和初始化的过程中执行一些操作。它提供了两个回调方法,分别是在 Bean 初始化前和初始化后进行处理。主要用途包括:

  • 修改 Bean 属性。

  • 执行一些额外的逻辑。

  • 在 Bean 实例化时添加额外的功能。

  • 配置日志 AOP,自动为所有 Bean 增加日志功能。

  • 实现代理模式,在 Bean 中添加一些跨切面关注点(如事务管理、安全检查等)。

  • 在 Bean 初始化前后修改 Bean 属性或配置。

方法介绍

BeanPostProcessor 接口包含以下两个方法:

  • postProcessBeforeInitialization(Object bean, String beanName):在 Bean 的初始化方法(例如 @PostConstructafterPropertiesSet() 等)调用之前执行。
  • postProcessAfterInitialization(Object bean, String beanName):在 Bean 的初始化方法调用之后执行。
import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanPostProcessor;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBeanPostProcessor implements BeanPostProcessor {  

    @Override  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {  
        System.out.println("Before Initialization of Bean: " + beanName);  
        // 可以根据需要修改 bean 返回  
        return bean; // 返回修改后的 Bean 或原 Bean  
    }  

    @Override  
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
        System.out.println("After Initialization of Bean: " + beanName);  
        // 可以根据需要修改 bean 返回  
        return bean; // 返回修改后的 Bean 或原 Bean  
    }  
}

可以指定bean类型==》进行操作

import org.springframework.beans.BeansException;  
import org.springframework.beans.factory.config.BeanPostProcessor;  
import org.springframework.stereotype.Component;  

@Component  
public class MyBeanPostProcessor implements BeanPostProcessor {  

    @Override  
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 
     	if (bean instanceof  User user){  //可以指定bean类型==》进行操作
     	  System.out.println("Before Initialization of Bean: " + beanName);  
       	}
        // 可以根据需要修改 bean 返回  
        return bean; // 返回修改后的 Bean 或原 Bean  
    }  

    @Override  
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {  
        if (bean instanceof  User user){ //可以指定bean类型==》进行操作
     	  System.out.println("After Initialization of Bean: " + beanName);  
       	}
        // 可以根据需要修改 bean 返回  
        return bean; // 返回修改后的 Bean 或原 Bean  
    }  
}

SpringBoot单元测试

@SpringBootTest
class SpringIocApplicationTests {

    @Test
    void contextLoads() {
        //测试代码
    }

}

IOC源码

Spring IoC容器三级缓存机制

在 Spring 框架中,IoC 容器使用三级缓存来提高性能和效率。这些缓存主要用于存储 Bean 实例和其相关信息,以减少重复的对象创建。

  1. 一级缓存(Singleton Objects)

    • 这是最基本的缓存层,用于存储单例 Bean 的实例。
    • 每次请求相同的单例 Bean 时,IoC 容器会从一级缓存中直接返回已有的实例,避免多次创建。
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>();  
    
  2. 二级缓存(Early References)

    • 用于存储正在创建中的单例 Bean。在某些情况下,正常的循环依赖检查可能会导致问题,而这个缓存可以提供部分创建的 Bean。
    • 当一个 Bean 在被创建、但还未完成初始化时,将其放入二级缓存中,允许其他 Bean 进行引用。
    private final Map<String, Object> earlySingletonObjects = new HashMap<>();  
    
  3. 三级缓存(Singleton Factories)

    • 用于存储提供单例 Bean 的工厂,实际上存储的是一个 ObjectFactory,用以容纳尚未完成实例化的 Bean(通过工厂方法创建的实例)。
    • 三级缓存允许在 Bean 依赖关系中有些循环依赖的情况,从而解决“无法完成依赖注入”错误。
    private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>();  
    

    image-20240903191608892

缓存机制的工作原理示例

  1. 高效创建单例 Bean

    • 当请求一个单例 Bean 时,首先检查一级缓存。如果找到了,则直接返回。
    • 如果一级缓存未找到,再检查二级缓存是否有正在被创建的实例。
    • 如果还没有找到,那么 IoC 容器会创建该 Bean,并在其完成前将其放入二级缓存中。如果 Bean 是懒加载,则会放入三级缓存中。
  2. 解决循环依赖

    • 假设有 AB 两个 Bean 互相依赖。通过三级缓存,A 在创建时可以使用 B 的工厂产生一个“半初始化”的实例,而非直接依赖于完整的 B 实例,避免了循环依赖的问题。

    • 在 Spring Boot 2.x 之前,Spring 默认会允许单例 bean 之间的循环依赖。但是在 Spring Boot 2.6 版本之后,allowCircularReferences 默认值变为 false,这意味着 Spring Boot 在某些情况下不再支持循环依赖。

      spring.main.allow-circular-references=true ==》启循环依赖

Spring IOC容器启动整体流程

IOC整体流程
Spring IOC的核心工厂类其实就是BeanFactory,从名字上也很好理解,生产 bean 的工厂,它负责生产和管理各个 bean 实例。在项目中我们更多的是使用它的子类或者实现类,像常见的AbstractApplicationContext、ApplicationContext、DefaultListableBeanFactory、ClassPathXmlApplicationContext、AnnotationConfigApplicationContext等等。

在ClassPathXmlApplicationContext的构造方法中,我们可以看到有一个refresh()方法,它是整个IOC容器的核心。

refresh()主要做了下述12件事情
1、prepareRefresh()
容器刷新前的一些预处理工作。

2、obtainFreshBeanFactory()
创建DefaultListableBeanFactory工厂,给bean工厂设置一些属性,加载配置文件信息,封装成bean定义信息。

3、prepareBeanFactory(beanFactory)
同样,设置bean工厂的一些属性,如添加一些BeanPostProcessor增强器等。

4、postProcessBeanFactory(beanFactory)
模板方法,留给子类扩展实现。

5、invokeBeanFactoryPostProcessors(beanFactory)
执行BeanFactoryPostProcessor的postProcessBeanFactory ()增强方法。

6、registerBeanPostProcessors(beanFactory)
注册BeanPostProcessor增强器,注意这里只是注册,真正是在初始化阶段的前后执行。

7、initMessageSource()
初始化MessageSource,国际化处理。

8、initApplicationEventMulticaster()
初始化事件多播器。

9、onRefresh()
模板方法,留给子类扩展实现。

10、registerListeners()
注册一些监听器。

11、finishBeanFactoryInitialization(beanFactory)
IOC容器创建最重要的一个步骤:完成非懒加载的单例bean对象的实例化,包括反射创建bean对象、属性填充、循环依赖的处理、bean的初始化等等。

12、finishRefresh()
容器刷新完成之后的一些处理工作。

image-20240903193119540

posted @ 2024-09-25 09:38  CH_song  阅读(1)  评论(0编辑  收藏  举报