Spring原理

1.Bean的作用域

1.1 概念

Spring帮助管理对象的几种方式:

  1. 通过@Controller,@Service,@Repository,@Component,@Configuration,@Bean来声明Bean对象。

  2. 通过ApplicationContext或者BeanFactory来获取对象。

  3. 通过@Autowired,Setter或者构造方法来为应用程序注入所依赖的Bean对象。

代码示例:

  1. 通过@Bean声明bean,把bean存在Spring容器中
java 复制代码
public class Dog {
    private String name;
    public String getName(){
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}
java 复制代码
@Component
public class DogBeanConfig {
    @Bean
    public Dog dog(){
        Dog dog=new Dog();
        dog.setName("旺旺");
        return dog;
    }
}
java 复制代码
@SpringBootApplication
public class SpringInnerApplication {

    public static void main(String[] args) {
        ApplicationContext context =
                SpringApplication.run(SpringInnerApplication.class, args);
        Dog dog=context.getBean(Dog.class);
        System.out.println(dog);
    }

}

也可以通过在代码中直接注入ApplicationContext的方式来获取Spring容器

java 复制代码
@SpringBootTest
public class DemoApplicationTests {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    void contextLoads(){
        DogBean dog1=applicationContext.getBean(DogBean.class);
        System.out.println(dog1);
    }
}

修改代码,从Spring容器中多次获取Bean

java 复制代码
@SpringBootApplication
public class SpringInnerApplication { 
    public static void main(String[] args) {
        ApplicationContext applicationContext=
        SpringApplication.run(SpringInnerApplication.class, args);
//        Dog dog=context.getBean(Dog.class);
//        System.out.println(dog);
        Dog dog1=applicationContext.getBean(Dog.class);
        System.out.println(dog1);
        Dog dog2=applicationContext.getBean(Dog.class);
        System.out.println(dog2);
    }

}

观察运行结果:

发现输出的bean对象地址值是一样的,说明每次从Spring容器中取出来的对象都是同一个。

这也是"单例模式"

**单例模式:**确保一个类只有一个示例,多次创建也不会创建出多个实例。

默认情况下,Spring容器中的bean都是单例的,这种行为模式,我们就称之为Bean的作用域。

Bean的作用域是指Bean在Spring框架中的某种行为模式。

比如单例作用域:表示Bean在整个Spring中只有一份,它是全局共享的。那么当其他人修改了这个值之后,那么另一个人读取到的就是被修改的值。

修改上述代码:

java 复制代码
@SpringBootApplication
public class SpringInnerApplication {
    public static void main(String[] args) {
        ApplicationContext applicationContext=
        SpringApplication.run(SpringInnerApplication.class, args);
        Dog dog1=applicationContext.getBean(Dog.class);
        dog1.setName("狗狗1");
        System.out.println(dog1);
        System.out.println(dog1.getName());
        Dog dog2=applicationContext.getBean(Dog.class);
        System.out.println(dog2);
        System.out.println(dog2.getName());
    }

}

观察结果:

dog1和dog2为同一个对象,dog2拿到了dog1设置的值。

那么怎样将bean对象设置为非单例-------修改Bean的作用域。

1.2 Bean的作用域

在Spring中支持6种作用域,后4种在Spring MVC环境才生效。

  1. singleton:单例作用域

  2. prototype:原型作用域

  3. request:请求作用域

  4. session:会话作用域

  5. Application:全局作用域

  6. websocket:HTTP WebSocket作用域

|-------------|-------------------------------------------|
| 作用域 | 说明 |
| singleton | 每个Spring IoC容器内同名称的bean只有一个实例 |
| prototype | 每次使用该bean时会创建新的实例(非单例) |
| request | 每个HTTP请求生命周期内,创建新的实例(web环境中,了解) |
| session | 每个HTTP Session生命周期内,创建新的实例(web环境中,了解) |
| application | 每个每个ServletContext⽣命周期内,创建新的实例(web环境中,了解) |
| wbesocket | 每个WebSocket⽣命周期内,创建新的实例(web环境中,了解) |

代码实现实例:

java 复制代码
@Component
public class DogBeanConfig {
    @Bean
    public Dog dog(){
        Dog dog=new Dog();
        dog.setName("旺旺");
        return dog;
    }
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog prototypeDog(){
        Dog dog=new Dog();
        return dog;
    }
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Dog singleDog(){
        Dog dog=new Dog();
        return dog;
    }
    @Bean
    @RequestScope
    public Dog requestDog(){
        Dog dog=new Dog();
        return dog;
    }
    @Bean
    @SessionScope
    public Dog sessionDog(){
        Dog dog=new Dog();
        return dog;
    }
    @Bean
    @ApplicationScope
    public Dog applicationDog(){
        Dog dog=new Dog();
        return dog;
    }
}

测试不同作用域的Bean取到的对象是否一样

java 复制代码
@RestController
public class DogController {
    @Autowired
    private Dog singleDog;
    @Autowired
    private Dog prototypeDog;
    @Autowired
    private Dog requestDog;
    @Autowired
    private Dog sessionDog;
    @Autowired
    private Dog applicationDog;
    @Autowired
    private ApplicationContext applicationContext;
    @RequestMapping("/single")
    public String single(){
        Dog contextDog = (Dog)applicationContext.getBean("singleDog");
        return "dog:"+singleDog.toString()+",contextDog:"+contextDog;
    }
    @RequestMapping("/prototype")
    public String prototype(){
        Dog contextDog = (Dog)applicationContext.getBean("prototypeDog");
        return "dog:"+prototypeDog.toString()+",contextDog:"+contextDog;
    }
    @RequestMapping("/request")
    public String request(){
        Dog contextDog = (Dog)applicationContext.getBean("requestDog");
        return
                "dog:"+requestDog.toString()+",contextDog:"+contextDog.toString();
    }
    @RequestMapping("/session")
    public String session(){
        Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
        return
                "dog:"+sessionDog.toString()+",contextDog:"+contextDog.toString();
    }
    @RequestMapping("/application")
    public String application(){
        Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
        return
                "dog:"+applicationDog.toString()+",contextDog:"+contextDog.toString();
    }
}

观察Bean的作用域

单例作用域:http://127.0.0.1:8080/single

多次访问,得到的都是同一个对象,并且@Autowired和applicationContext.getBean()也是同一个对象。

多例作用域:http://127.0.0.1:8080/prototype

观察ContextDog,每次获取的对象都不一样。(注入的对象在Spring容器启动时,就已经注入了,所以多次请求也不会发生变化)。

请求作用域:http://127.0.0.1:8080/request

在一次请求中,@Autowired和applicationContext.getBean()也是同一个对象。但每次请求,都会重新创建对象。

会话作用域:http://127.0.0.1:8080/session

在session中,多次请求,获取到的对象都是同一个。

换一个浏览器访问,发现会重新创建对象。(另一个session)

Application作用域:http://127.0.0.1:8080/application

在同一个应用中,多次访问都是同一个对象

Application scope就是对于整个web容器来说,bean的作用域是ServletContext级别的。这个和singleton有点类似,区别在于:Application scope是ServletContext的单例,singleton是一个 ApplicationContext的单例。在一个web容器中ApplicationContext可以有多个。

2. Bean的生命周期

生命周期是指一个对象从诞生到销毁的整个生命过程,我们把这个过程叫做一个对象的生命周期。

Bean的生命周期分为以下5个部分:

  1. 实例化(为Bean分配内存空间)

  2. 属性赋值(Bean注入和装配,比如@Autowired)

  3. 初始化

a.执行各种通知,如BeanNameAware,BeanFactoryAware,ApplicationContextAware 的接口方法。

b. 执行初始化方法

xml定义init-method

使用注解的方式@PostConstruct

执行初始化后置方法(BeanPostProcessor)

  1. 使用Bean

  2. 销毁Bean

a.销毁容器的各种方法,如@PreDestroy,DisposableBean 接口方法,destroymethod。

执行流程如下图所示:

2.1 代码演示

java 复制代码
@Component
public class BeanLifeComponent implements BeanNameAware {
    private UserComponent userComponent;

    public BeanLifeComponent() {
        System.out.println("执⾏构造函数");
    }

    @Autowired
    public void setUserComponent(UserComponent userComponent) {
        System.out.println("设置属性userComponent");
        this.userComponent = userComponent;
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("执⾏了 setBeanName ⽅法:" + s);
    }

    /**
     * 初始化
     */
    @PostConstruct
    public void postConstruct() {
        System.out.println("执⾏ PostConstruct()");
    }

    public void use() {
        System.out.println("执⾏了use⽅法");
    }

    /**
     * 销毁前执⾏⽅法
     */
    @PreDestroy
    public void preDestroy() {
        System.out.println("执⾏:preDestroy()");
    }
}

执行结果

通过运行结果观察

  1. 先执行构造函数

  2. 设置属性

  3. Bean初始化

  4. 使用Bean

  5. 销毁Bean

3. Spring Boot自动配置

SpringBoot的自动配置就是当Spring容器启动后,一些配置类,bean对象等就自动存入到了IoC容器中,不需要我们手动去声明,从而简化了开发,省去了繁琐的配置操作。

SpringBoot自动配置,就是指SpringBoot是如何将依赖jar包中的配置类以及Bean加载到Spring IoC容器中的。

3.1 Spring加载Bean

3.1.1 问题描述

需求:使用Spring管理第三方的jar包的配置

数据准备:

java 复制代码
@Component
public class BiteConfig {
    public void study(){
        System.out.println("start study...");
    }
}

获取BiteConfig这个Bean

写测试代码:

java 复制代码
@SpringBootTest
public class DemoApplicationTests {
    @Autowired
    private ApplicationContext applicationContext;
    @Test
    void contextLoads(){
        Dog dog1=applicationContext.getBean(Dog.class);
        System.out.println(dog1);
        Dog dog2=applicationContext.getBean(Dog.class);
        System.out.println(dog2);
    }
}

观察日志:No qualifying bean of type 'com.bite.autoconfig.BiteConfig' available没有com.bite.autoconfig.BiteConfig 这个类型的Bean。

3.1.2 原因分析

Spring通过五大注解和 @Bean 注解可以帮助我们把Bean加载到SpringIoC容器中,以上有个前提就是这些注解类需要和SpringBoot启动类在同一个目录下( @SpringBootApplication 标注的类就 是SpringBoot项目的启动类)。

3.1.3 解决方案

指定文件的路径:

  1. @ComponentScan组件扫描

  2. @Import导入(使用@Import导入的类会被Spring加载到IoC容器中)

3.1.3.1 @ComponentScan
java 复制代码
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan("com.bite.autoconfig")
@SpringBootApplication
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}

运行程序:

3.1.3.2 @Import
  1. 导入类

  2. 导入ImportSelector实现类'

1. 导入类

java 复制代码
@Import(BiteConfig.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}

运行程序:

2. 导入ImportSelector接口实现类

java 复制代码
public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //需要导⼊的全限定类名 
        return new String[]
                {"com.bite.autoconfig.BiteConfig","com.bite.autoconfig.BiteConfig2"};
    }
}

启动类:

java 复制代码
@Import(MyImportSelector.class)
@SpringBootApplication
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}

运行程序:

但是他们都有明显的问题,就是使用者需要知道第三方依赖中哪些Bean对象或配置类。如果漏掉了其中一些Bean,很可能导致我们项目中出现重大的事故。

解决方法:

  1. 第三方依赖提供注解
java 复制代码
import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)//指定要导⼊哪些类
public @interface EnableBiteConfig {
}

注解中封装@Import注解,导入MyImportSelector.class

  1. 在启动类上使用第三方提供的注解
java 复制代码
@EnableBiteConfig
@SpringBootApplication
public class SpringAutoconfigApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringAutoconfigApplication.class, args);
    }
}
  1. 运行程序

3.2 总结

Spring Boot自动配置的原理大概流程如下:

当SpringBoot程序启动时,会加载配置文件当中所定义的配置类,通过 @Import 注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

4. 总结

  1. Bean的作用域共分为6种:singleton,prototype,request,session,application和websocket 2. Bean的生命周期共分为5大部分:实例化,属性复制,初始化,使用和销毁

  2. SpringBoot的自动配置原理源码口是@SpringBootApplication 注解,这个注解封装了3个注解 :

@SpringBootConfiguration 标志当前类为配置类

@ComponentScan 进行包扫描(默认扫描的是启动类所在的当前包及其子包)

@EnableAutoConfiguration

@Import 注解:读取当前项目下所有依赖jar包中 META-INF/spring.factories , META-INF/spring/org.springframework.boot.autoconfigure.AutoConfigurat ion.imports 两个文件里面定义的配置类(配置类中定义了 @Bean 注解标识的方法)

@AutoConfigurationPackage:把启动类所在的包下面所有的组件都注入到Spring容器中。

相关推荐
教练、我想打篮球2 小时前
122 Hession,FastJson,ObjectInputStream的序列化反序列化相同引用的处理
java·config·utils
酷柚易汛3 小时前
酷柚易汛ERP 2025-12-26系统升级日志
java·前端·数据库·php
侠客行03173 小时前
Mybatis入门到精通 一
java·mybatis·源码阅读
消失的旧时光-19433 小时前
微服务的本质,其实是操作系统设计思想
java·大数据·微服务
Coder_Boy_3 小时前
基于SpringAI的智能平台基座开发-(四)
java·人工智能·spring boot·langchain·springai
码界奇点3 小时前
基于Spring Boot的内容管理系统框架设计与实现
java·spring boot·后端·车载系统·毕业设计·源代码管理
墨雪不会编程4 小时前
C++【string篇1遍历方式】:从零开始到熟悉使用string类
java·开发语言·c++
蒂法就是我4 小时前
有一张表,只有一个字段没有插入主建,能插入成功吗? 隐藏的 rowid除了在这里用到还在哪里用到了?
java
a努力。4 小时前
字节Java面试被问:系统限流的实现方式
java·开发语言·后端·面试·职场和发展·golang