提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

在这里插入图片描述
创建spring项目

在这里插入图片描述

1. Bean的作⽤域

1.1 概念

@Getter
@Setter
public class Dog {
    private String name;
}

spring管理bean:
1.五大注解:类注解
2.方法注解:@Bean

@Configuration
public class DogConfig {
    @Bean
    public Dog dog(){
        Dog dog = new Dog();
        dog.setName("111");
        return dog;
    }

}

怎么拿到bean呢
在这里插入图片描述
第一可以通过在这里启动类这里拿到bean

    @Autowired
    private ApplicationContext context;

注意context这个是可以直接注入进来的
ApplicationContext 这个对象就是Spring容器

@SpringBootTest
class SpringPrincipleApplicationTests {
    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoads() {
        Dog bean = context.getBean(Dog.class);
        System.out.println(bean);
        Dog bean2 = context.getBean(Dog.class);
        System.out.println(bean2);

    }

}

在这里插入图片描述
发现就是同一个bean了

public class Dog {
    @Getter
    @Setter
    private String name;
}

也可以这样写

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

Bean 的作⽤域是指 Bean 在 Spring 框架中的某种⾏为模式

⽐如单例作⽤域: 表⽰ Bean 在整个 Spring 中只有⼀份, 它是全局共享的. 那么当其他⼈修改了这个值之后, 那么另⼀个⼈读取到的就是被修改的值

        Dog bean = context.getBean(Dog.class);
        System.out.println(bean.getName());
        bean.setName("222");
        Dog bean2 = context.getBean(Dog.class);
        System.out.println(bean2.getName());

在这里插入图片描述
所以spring管理的bean不是线程安全的

spring管理的bean是单例,它不算是线程安全的

1.2 bean的作用域

在Spring中⽀持6种作⽤域,后4种在Spring MVC环境才⽣效

  1. singleton:单例作⽤域
  2. prototype:原型作⽤域(多例作⽤域):每次使用的时候,都是一个新的bean
  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环境中, 了解)
websocket 每个WebSocket⽣命周期内, 创建新的实例(web环境中, 了解)

演示前面五种

@Configuration
public class DogConfig {
    @Bean
    public Dog dog(){
        Dog dog = new Dog();
        dog.setName("111");
        return dog;
    }

    @Bean//默认就是单例的,可以用注解Scope来声明行为模式
//    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)//两种方法都可以
    @Scope("singleton")
    public Dog singletonDog(){
        Dog dog = new Dog();
        dog.setName("singletonDog");
        return dog;
    }


    @Bean
    @Scope("prototype")//多例
    public Dog prototypeDog(){
        Dog dog = new Dog();
        dog.setName("prototypeDog");
        return dog;
    }

}
    @Autowired
    private Dog singletonDog;

这个就是根据singletonDog这个名称去找实例了

    @Resource(name = "singletonDog")
    private Dog singletonDog;

Resource就是指定name去找了,这个对象的名称name其实就是@Bean注解修饰的方法名

@RestController
@RequestMapping("scope")
public class ScopeController {
    @Resource(name = "singletonDog")
    private Dog singletonDog;

    @Resource(name = "prototypeDog")
    private Dog prototypeDog;

    @Autowired
    private ApplicationContext context;//spring容器

    @RequestMapping("single")
    public String single(){
        Dog dog = (Dog) context.getBean("singletonDog");
        return "注入对象:"+ singletonDog.toString()+"容器中context:"+dog.toString();
    }

    @RequestMapping("prototype")
    public String prototype(){
        Dog dog = (Dog) context.getBean("prototypeDog");
        return "注入对象:"+ prototypeDog.toString()+"容器中context:"+dog.toString();
    }
}

在这里插入图片描述
在这里插入图片描述

我们发现,单例中,注入对象和context中对象是同一个对象,而且多次刷新都是不变的,都是这个对象,两个都不变

多例:注入对象和context中对象是不同对象
在这里插入图片描述

而多例的话,每次请求的context中的对象都是不同的,注入的的对象是不会变的
注入的对象为什么不变呢,这个是因为在程序启动之初,这个对象prototypeDog就已经注入进来了,并没有再次获取,再次重新对prototypeDog赋值,所以这个就不会变了
而Dog dog = (Dog) context.getBean(“prototypeDog”);就是每次请求都从容器中获得一个对象,这个对象已经被声明成了多例,所以每次都不一样,每次都返回新的实例

    @Bean
    @RequestScope//这是一个requestscope
    public Dog requestDog(){
        Dog dog = new Dog();
        dog.setName("requestDog");
        return dog;
    }

    @Bean
    @SessionScope
    public Dog sessionDog(){
        Dog dog = new Dog();
        dog.setName("sessionDog");
        return dog;
    }

    @Bean
    @ApplicationScope
    public Dog applicationDog(){
        Dog dog = new Dog();
        dog.setName("applicationDog");
        return dog;
    }
    @Resource(name = "requestDog")
    private Dog requestDog;

    @Resource(name = "sessionDog")
    private Dog sessionDog;

    @Resource(name = "applicationDog")
    private Dog applicationDog;

    @RequestMapping("request")
    public String request(){
        Dog dog = (Dog) context.getBean("requestDog");
        return "注入对象:"+ requestDog.toString()+"容器中context:"+dog.toString();
    }

    @RequestMapping("session")
    public String session(){
        Dog dog = (Dog) context.getBean("sessionDog");
        return "注入对象:"+ sessionDog.toString()+"容器中context:"+dog.toString();
    }
    @RequestMapping("application")
    public String application(){
        Dog dog = (Dog) context.getBean("applicationDog");
        return "注入对象:"+ applicationDog.toString()+"容器中context:"+dog.toString();
    }

request就是发起新的请求才会创建新的实例
同一个请求里面就是同一个实例

    @RequestMapping("request")
    public String request(){
        Dog dog1 = (Dog) context.getBean("requestDog");
        Dog dog2 = (Dog) context.getBean("requestDog");
        System.out.println(dog1);
        System.out.println(dog2);
        return "注入对象:"+ requestDog.toString()+"容器中context:"+dog1.toString();
    }

在这里插入图片描述
在这里插入图片描述

因为这是同一个请求,所以不管是注入的,还是从context中获取的,都是同一个对象
在这里插入图片描述
这是第二次请求,是不同的对象,不同的请求中,就是不同得到对象,在同一个请求,就是同一个对象

在这里插入图片描述
多次刷新都是一样的,不变,因为同一个浏览器的session都是一样的
在这里插入图片描述

所以两个链接获取的对象都是一样的,因为在同一个浏览器,所以就是同一个session,所以就是一样的

在这里插入图片描述
换一个浏览器就变了
这样的话,多个用户同时访问,多个用户–》不同浏览器–》不同session–》不同对象—》不同线程
所以多个用户同时访问的时候,我们不用做线程考虑,因为spring这个设计就是多线程的,多个用户—》多个不同对象–》因为在不同线程,各拿各的不同对象
但是不同sesson,不同对象,也不能决定线程安全,因为我可以在一个浏览器上,同一个对象,同时多次请求–》线程不安全

application是每个severtletContext声明周期

在这里插入图片描述
在这里插入图片描述
换一个浏览器,换一个session还是一样的
不同会话,得到的也是同一个对象
这个和单例的区别是什么呢
application在⼀个应⽤中, 多次访问都是同⼀个对象

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

什么意思呢,意思就是
现在我们一个tomcat就部署了一个服务
但是实际上,一个tomcat可以部署多个服务
一个服务是一个ApplicationContext
一个web容器(tomcat)就是一个severtletContext

Application scope就是对于整个web容器来说, bean是对于整个应用而言的,了解即可

websocket就不演示了

2. Bean的生命周期

⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程, 我们把这个过程就叫做⼀个对象的⽣命周期.

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

  1. 实例化(为Bean分配内存空间),找空地
  2. 属性赋值(Bean注⼊和装配, ⽐如 @AutoWired ),比如controller里面设置很多属性字段,盖房子
  3. 初始化,装修
    a. 执⾏各种通知, 如 BeanNameAware , BeanFactoryAware ,ApplicationContextAware 的接⼝⽅法.
    b. 执⾏初始化⽅法
    ▪ xml定义 init-method ,早期使用xml定义Bean
    ▪ 使⽤注解的⽅式 @PostConstruct
    ▪ 执⾏初始化后置⽅法( BeanPostProcessor )
  4. 使⽤Bean,住房子
  5. 销毁Bean,卖房子

a. 销毁容器的各种⽅法, 如 @PreDestroy , DisposableBean 接⼝⽅法, destroymethod.

⽐如我们现在需要买⼀栋房⼦, 那么我们的流程是这样的:

  1. 先买房(实例化, 从⽆到有)
  2. 装修(设置属性)
  3. 买家电, 如洗⾐机, 冰箱, 电视, 空调等([各种]初始化,可以⼊住);
  4. ⼊住(使⽤ Bean)
  5. 卖房(Bean 销毁)

在这里插入图片描述

2.1 代码演示

bean的注入,属性的注入有三种:@Autowired属性装配,构造方法,Set方法装配

Autowired:无法打印日志,–》无法测试设置属性这个过程
构造方法:实例化和设置属性两个步骤在一起了
所以使用set方法注入

@Component
public class BeanLifeComponent implements BeanNameAware {


    private Dog dog;

    public BeanLifeComponent() {
        System.out.println("执行构造函数:实例化步骤");
    }

    @Autowired//
    public void setDog(Dog dog) {//有dog这个名字的bean
        this.dog = dog;
        System.out.println("使用set方法,实现属性装配:设置属性步骤");
    }

    @PostConstruct//初始化步骤
    public void init(){
        System.out.println("执行init方法:初始化步骤:通知和执行初始化方法");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("执行setBeanName:通知步骤"+name);
    }

    public void use(){
        System.out.println("执行use:使用bean步骤");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("执行销毁:销毁步骤");
    }
}

这样就可以演示bean的生命周期了

        BeanLifeComponent bean = context.getBean(BeanLifeComponent.class);
        bean.use();

在这里插入图片描述
在这里插入图片描述

这一级是所有的额日志,而不是那个测试函数单独的日志,这个就是spring启动的时候的日志,比如加载bean到销毁bean的整个过程的日志

2025-11-25T17:20:38.971+08:00 INFO 43616 — [spring-principle2] [ 在初始化方法的后面,表示这些bean都加载成功了,spring才算启动成功了
所以这一行之前都是spring在启动之前做的工作

2.2 源码阅读

创建Bean的代码⼊⼝在 AbstractAutowireCapableBeanFactory#createBean
打开这个类AbstractAutowireCapableBeanFactory
ctrl+N搜索,或者shiftshift

在这里插入图片描述
里面有个createBean方法

在这里插入图片描述
继续深入

在这里插入图片描述

里面有个bject beanInstance = this.doCreateBean(beanName, mbdToUse, args);
继续深入,这个doCreateBean就是主要的方法了

在这里插入图片描述
实例化就是分配了一个内存空间,初始化就是对这个空间的处理

instanceWrapper = this.createBeanInstance(beanName, mbd, args);这个就是实例化

往下走this.populateBean(beanName, mbd, instanceWrapper);这个是属性装配

在这里插入图片描述
ctrl+alt+左:回到上一页

exposedObject = this.initializeBean(beanName, exposedObject, mbd);这个就是初始化

在这里插入图片描述
this.invokeAwareMethods(beanName, bean);

在这里插入图片描述
这个就是看有没有实现这些接口–》进而进行通知

在这里插入图片描述
invokeAwareMethods这个是通知方法
后面的applyBeanPostProcessorsBeforeInitialization,invokeInitMethods,applyBeanPostProcessorsAfterInitialization就是初始化前,初始化,和初始化后的代码了

这样前面三个阶段就结束了

实例化分配内存空间,属性装配,初始化:执行通知方法,前置处理,初始化方法,后置处理,使用bean,销毁bean

总结

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐