当前位置: 首页 > article >正文

Spring原理

Bean的作用域

Bean

在Spring IOC和DI阶段,我们学习了Spring是如何帮我们管理对象的。

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

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

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

1.通过Bean声明bean,把bean存在Spring容器中

public class Dog {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
@Component
public class DogBeanConfig {
    @Bean
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("旺财");
        return dog;
    }
}

 2.从Spring容器中获取Bean

@SpringBootApplication
public class SpringBlogBeanApplication {

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

}

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

@SpringBootTest
class SpringBlogBeanApplicationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoad() {
        Dog dog = context.getBean(Dog.class);
        System.out.println(dog);
    }
}

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

@SpringBootTest
class SpringBlogBeanApplicationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoad() {
        Dog dog1 = context.getBean(Dog.class);
        System.out.println(dog1);

        Dog dog2 = context.getBean(Dog.class);
        System.out.println(dog2);
    }
}

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

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

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

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

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

修改上述代码,给Dog添加属性name

@SpringBootTest
class SpringBlogBeanApplicationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void contextLoad() {
        Dog dog1 = context.getBean(Dog.class);
        dog1.setName("艾特");
        System.out.println(dog1);
        System.out.println(dog1.getName());

        Dog dog2 = context.getBean(Dog.class);
        System.out.println(dog2);
        System.out.println(dog2.getName());
    }
}

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

Bean的作用域

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

1.singleton:单例作用域

2.prototype:原型作用域(多例作用域)

3.request:请求作用域

4.session:会话作用域

5.Application:全局作用域

6.websocket:HTTP WebSocket作用域

作用域说明
singleton每个Spring IoC容器内同名称的bean只有一个实例,但是如果Bean的名称不同那么一个类也可以有多个实例.
prototype每次创建该Bean时都会创建新的实例
request每个HTTP请求声明周期内,创建新的实例
session每个HTTP Session生命周期内,创建新的实例
Application每个ServletContext生命周期内,创建新的实例
websocket每个WebSocket生命周期内,创建新的实例

定义几个不同作用域的Bean


@Component
public class DogBeanConfig {
    @Bean
    public Dog dog() {
        Dog dog = new Dog();
        dog.setName("旺财");
        return dog;
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
    public Dog singleDog() {
        Dog dog = new Dog();
        return dog;
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public Dog prototypeDog() {
        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取到的对象是否一样


@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;
    }

    @RequestMapping("/session")
    public String session() {
        Dog contextDog = (Dog)applicationContext.getBean("sessionDog");
        return "dog: " + sessionDog.toString() + ",contextDog: " + contextDog;
    }

    @RequestMapping("/application")
    public String application() {
        Dog contextDog = (Dog)applicationContext.getBean("applicationDog");
        return "dog: " + applicationDog.toString() + ",contextDog: " + contextDog;
    }

}

 观察Bean的作用域

单例作用域

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

多例作用域

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

请求作用域

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

会话作用域 

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

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

 Application作用域

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

Bean的生命周期

生命周期指的是一个对象从诞生到销毁的整个生命过程,我们把这个过程叫做一个对象的生命周期。Bean的生命周期分为以下5个部分:

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

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

3.初始化

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

        b.执行初始化方法

                XML定义init-method

                使用注解的方式@PostConstruct

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

4.使用Bean

5.销毁Bean

        a.销毁容器的各种方法,如@PreDestroy,DisposableBean接口方法,destroy-method.

实例化和属性赋值对应构造方法和setter方法的注入。初始化和销毁使用户能自定义扩展的两个阶段,可以在实例化之后,类加载完成之前进行自定义”事件“处理。

Bean的生命周期就好比我们买一栋房子

1.先买房(实例化,从无到有)

2.装修(设置属性)

3.买家电,如冰箱,洗衣机,空调,电视(各种初始化,可以入住)

4.入住(使用Bean)

5.卖房(Bean销毁)

执行流程如下图:

代码演示


@Component
public class BeanLifeComponent implements BeanNameAware {

    private Dog singleDog;

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

    @Autowired
    public void setSingleDog(Dog singleDog) {
        this.singleDog = singleDog;
        System.out.println("设置属性,执行setSingletonDog....");
    }

    @Override
    public void setBeanName(String name) {
        System.out.println("setBeanName方法: " + name);
    }

    @PostConstruct
    public void init() {
        System.out.println("执行PostConstruct...");
    }

    public void use() {
        System.out.println("执行use方法...");
    }

    @PreDestroy
    public void destroy() {
        System.out.println("执行destroy方法");
    }

}

通过运行结果观察

1.先执行构造函数

2.设置属性

3.Bean初始化

4.使用Bean

5.销毁Bean 

Spring Boot自动配置

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

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

Spring加载Bean

我们现在来模拟引入第三方的包,其实就是在该项目下,引入第三方的代码,我们采用在该项目下创建不同目录来模拟第三方的代码引入。

现在我们有两个项目目录一个是com.adviser.springblogbean,这个是启动类所在的目录

一个是org.example.demo目录,这个目录模拟第三方包的代码

第三方文件代码

@Component
public class AutoConfig {
    public void use() {
        System.out.println("执行AutoConfig#use....");
    }
}

获取AutoConfig这个Bean,写测试代码

@SpringBootTest
class SpringBlogBeanApplicationTest {

    @Autowired
    private ApplicationContext context;

    @Test
    void testAutoConfig() {
        AutoConfig bean = context.getBean(AutoConfig.class);
        bean.use();
    }
}

运行程序

 观察日志:No qualifying bean of type 'org.example.demo.AutoConfig' available

没有org.example.demo.AutoConfig这个类型的Bean

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

启动类的目录为com.adviser.springblogbean,而AutoConfig这个类在org.example.demo下,所以SpringBoot没有扫描到。

但是当我们引入第三方的Jar包时,第三方的Jar代码目录肯定不在启动类的目录下,如何告诉Spring帮我们管理这些Bean呢?

我们需要指定路径或者引入文件,告诉Spring,让Spring进行扫描到

常见的两种解决方法:

1.@ComponentScan组件扫描

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

1.@ComponentScan

通过@ComponentScan注解,指定Spring扫描路径

@ComponentScan("org.example.demo")
@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        ApplicationContext context = SpringApplication.run(SpringBlogBeanApplication.class, args);
        BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
        beanLifeComponent.use();
    }

}

可以指定扫描多个包

@ComponentScan({"org.example.demo", "com.adviser.springblogbean"})

运行程序

可以看到,这次AutoConfig Bean获取到了

但是很明显,Spring没有采用这种方式,比如我们引入第三方框架时,没有加扫描路径,比如MyBatis。如果我们Spring Boot采用这种方式,当我们引入大量的第三方依赖,比如MyBatis,jackson等时,就需要在启动类上配置不同依赖需要扫描的包,这种方式会非常繁琐。

2.@Import       

@Import导入主要有以下几种方式 

1.导入类
@Import(AutoConfig.class)
@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBlogBeanApplication.class, args);
    }

}

 可以看到这种方式也能告诉Spring加载biteConfig

但是又多了一些配置项

@Component
public class AutoConfig2 {
    public void use() {
        System.out.println("执行AutoConfig#use....");
    }
}

 我们可以采用导入多个类

@Import({AutoConfig.class, AutoConfig2.class})
@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBlogBeanApplication.class, args);
    }

}

但是很明显,这种方式也很繁琐,所以Spring Boot依然没有采用 

2.导入ImportSelector接口实现类

ImportSelector接口实现类

public class MyImportSelector implements ImportSelector {
    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //需要导入全限定类名
        return new String[]{"org.example.demo.AutoConfig", "org.example.demo.AutoConfig2"};
    }
}

启动类

@Import({MyImportSelector.class})
@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBlogBeanApplication.class, args);
    }

}

 运行程序:

可以看到,我们采用这种方式也可以导入第三方依赖提供的Bean

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

依赖中有很多Bean,使用时需要配置哪些bean,第三方依赖最清楚,那能否由第三方依赖来做这件事?

比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都是以@EnableXXXX开头的注解,注解中封装的就是@Import注解

1.第三方依赖提供注解

package org.example.demo;

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 EnableAutoConfig {
}

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

2.在启动类上使用第三方提供的注解

@EnableAutoConfig
@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBlogBeanApplication.class, args);
    }

}

3.运行程序

可以看到,这种方式也可以导入第三方依赖提供的Bean。

并且这种方式更优雅一点。SpringBoot采用的也是这种方式

SpringBoot原理分析

 1.源码阅读

SpringBoot是如何帮我们做的呢?这一切的来自SpringBoot的启动类开始

SpringBootApplication标注的类就是SpringBoot项目的启动类

@SpringBootApplication
public class SpringBlogBeanApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringBlogBeanApplication.class, args);
    }

}

这个类和普通类唯一的区别就是@SpringBootApplication注解,这个注解也是SpringBoot实现自动配置的核心

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
    //代码省略......
}

SpringBootApplication是一个组合注解,注解中包含了:

1.元注解

JDK中提供了4个标准的用来对注解类型进行注解的注解类,我们称之为meta-annotation(元注解),它们分别是:

@Target:描述注解的使用范围(即被修饰的注解可以用在什么地方)

@Retention:描述注解保留的时间范围

@Documented:描述在使用javadoc工具为类生成帮助文档时是否要保留其注解信息

@Inherited:使被它修饰的注解具有继承性(如果某个类使用了被@Inherited修饰的注解,则其子类将自动具有该注解)

2.@SpringBootConfiguration

里面就是@Configuration,标注当前类为配置类,其实只是做了一层封装改了个名字而已。

@Indexed注解,是用来加速应用启动的,不用关心)

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
@Indexed
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}

 3.@EnableAutoConfiguration(开启自动配置)

Spring自动配置的核心注解,下面详细讲解

4.@ComponentScan(包扫描)

可以通过basePackageClasses或basePackages来定义要扫描的特定包,如果没有定义特定的包,将从声明该注解的类的包开始扫描,这也就是为什么SpringBoot项目声明的注解类必须要在启动类的目录下。

默认情况下com.adviser.springautoconfig包及其子包会被扫描到,而com.adviser.MyOtherService包下的类即使加了@Service等注解交给Spring管理,那么也会因为Spring不会扫描这个包而导致Spring容器中找不到对应的Bean。

excludeFilters自定义过滤器,通常用于排除一些类,注解等。

@EnableAutoConfiguration详解

看下@EnableAutoConfiguration注解的实现:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
	
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	Class<?>[] exclude() default {};

	String[] excludeName() default {};

}

这个注解包含两部分:

@Import(AutoConfigurationImportSelector.class)

使用@Import注解,导入了实现importSelector接口的实现类

public class AutoConfigurationImportSelector implements
DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, 
BeanFactoryAware, EnvironmentAware, Ordered {
 public String[] selectImports(AnnotationMetadata annotationMetadata) {
     if (!this.isEnabled(annotationMetadata)) {
     return NO_IMPORTS;
     } else {
     //获取⾃动配置的配置类信息
     AutoConfigurationEntry autoConfigurationEntry = 
    this.getAutoConfigurationEntry(annotationMetadata);
     return
    StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
 }
}

selectImport()方法底层调用了getAutoConfigurationEntry()方法,获取可自动配置的配置类信息集合。

点进去

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return EMPTY_ENTRY;
		}
		AnnotationAttributes attributes = getAttributes(annotationMetadata);
		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
		checkExcludedClasses(configurations, exclusions);
		configurations.removeAll(exclusions);
		configurations = getConfigurationClassFilter().filter(configurations);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
	}

getAutoConfigurationEntry()方法通过调用getCandidateConfigurations(annotationMetadata, attributes)方法获取在配置文件中配置的所有自动配置类的集合

点进去:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
		List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())
			.getCandidates();
		Assert.notEmpty(configurations,
				"No auto configuration classes found in "
						+ "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
						+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
}

以前的版本

protected List < String > getCandidateConfigurations(AnnotationMetadata metadata,
        AnnotationAttributes attributes) {
        List < String > configurations = new
        ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderF actoryClass(), this.getBeanClassLoader()));
        ImportCandidates.load(AutoConfiguration.class,
            this.getBeanClassLoader()).forEach(configurations::add);
        Assert.notEmpty(configurations, "No auto configuration classes found in 
            META - INF / spring.factories nor in META INF / spring / org.springframework.boot.autoconfigure.AutoConfiguration.imports.If you are using a custom packaging, make sure that file is correct.
            ");
            return configurations;
        }

 getCandidateConfigurations方法的功能:获取所有基于META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件,META-INF/spring.factories⽂件中配置类的集合。

在引入的起步依赖中,通常都有包含以上两个文件

 META-INF/spring.factories⽂件是Spring内部提供的一个约定俗成的加载方式,只需要在模块的 META-INF/spring.factories文件中配置即可,Spring就会把相应的实现类注入到Spring容器中。

注:回家再所有jar包下的classpath路径下的 META-INF/spring.factories⽂件,这样的文件不止一个。

 2.@AutoConfigurationPackage

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

	String[] basePackages() default {};
	Class<?>[] basePackageClasses() default {};

}

这个注解主要是导入一个配置文件AutoConfigurationPackages.Registrar.class

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

		@Override
		public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
			register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
		}

		@Override
		public Set<Object> determineImports(AnnotationMetadata metadata) {
			return Collections.singleton(new PackageImports(metadata));
		}

	}

Registrar实现了ImportBeanDefinitionRegistrar接口,就可以被注解@Import导入到spring容器里。

new PackageImports(metadata).getPackageNames().toArray(new String[0]):当前启动类所在包名。

所以@AutoConfigurationPackage就是将启动类所在的包下面所有的组件都扫描注册到spring容器中。

总结

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

        


http://www.kler.cn/a/372344.html

相关文章:

  • C# 企业微信机器人推送消息 windows服务应用程序的使用
  • kubernetes——part2-3 使用RKE构建企业生产级Kubernetes集群
  • 使用和删除数据库
  • 爬虫利器playwright
  • 引入RFID技术,焕新消防应急物资管理方式
  • 软件压力测试有多重要?北京软件测试公司有哪些?
  • 基于Spring Boot+Unipp的卤肉店小程序(图形化分析)
  • trueNas 24.10 docker配置文件daemon.json无法修改(重启被覆盖)解决方案
  • HBuilder X 中Vue.js基础使用4->表单输入绑定(三)
  • 【数据结构与算法】之队列详解
  • MFC工控项目实例二十七添加产品参数
  • DRC-20开发指南:新一代区块链代币标准
  • 微信小程序面试题全攻略:10 大板块深度解析,附丰富案例代码
  • 2024-网鼎杯第二次模拟练习-web02
  • 【数据结构与算法】力扣 23. 合并 K 个升序链表
  • 正则表达式:强大的文本匹配与处理工具
  • 【数据库】数据库管理(上)事务 视图 索引分类以及生效规则
  • 【计算机网络 - 基础问题】每日 3 题(五十九)
  • Spring Boot框架实现的学生宿舍信息综合管理
  • 100种算法【Python版】第28篇——扩展欧几里得算法
  • Unity3D 鼠标移动到按钮上显示信息
  • vue3+vant实现视频播放(含首次禁止进度条拖拽,视频看完后恢复,保存播放视频进度,刷新及下次进入继续播放,判断视频有无全部看完等)
  • 记第一次本地编译seatunnel源码
  • 常见网安面试题
  • git log 用法详解
  • mysql遇到的问题