Spring 原理

  • 🎥 个人主页:Dikz12
  • 🔥个人专栏:Spring学习之路
  • 📕格言:吾愚多不敏,而愿加学
  • 欢迎大家👍点赞✍评论⭐收藏

目录

Bean的作用域

代码实现

观察Bean的作用域

Bean的生命周期

[Spring Boot自动配置](#Spring Boot自动配置)

原理分析

[1. 元注解.](#1. 元注解.)

[2. @SpringBootConfiguration.](#2. @SpringBootConfiguration.)

[3. @EnableAutoConfiguration (开启⾃动配置).](#3. @EnableAutoConfiguration (开启⾃动配置).)

4.@ComponentScan (包扫描)

总结


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环境中, 了解) |
| websocket | 每个WebSocket⽣命周期内, 创建新的实例(web环境中, 了解) |

官方参考文档:https://docs.spring.io/spring-framework/reference/core/beans/factory-scopes.html

代码实现

定义几个不同作用域Bean.

复制代码
@Configuration
public class BeanConfig {

    @Scope("singleton")
    @Bean
    public User singleUser() {
        return new User();
    }

    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public User prototypeUser() {
        return new User();
    }
    //请求作用域
    @RequestScope
    @Bean
    public User requestUser() {
        return new User();
    }
    //会话作用域
    @SessionScope
    @Bean
    public User sessionUser() {
        return new User();
    }
    //全局作用域
    @ApplicationScope
    @Bean
    public User applicationUser() {
        return new User();
    }
}

@RequestScope 等同于 @Scope(value = WebApplicationContext.SCOPE_REQUEST , proxyMode = ScopedProxyMode.TARGET_CLASS )
proxyMode⽤来为spring bean设置代理. proxyMode = ScopedProxyMode. TARGET_CLASS
表⽰这个Bean基于CGLIB实现动态代理. Request, session和application作⽤域的Bean 需要设置
proxyMode

复制代码
@RestController
@RequestMapping("/scope")
public class BeanScopeController {
    @Autowired
    private ApplicationContext context;
    @Resource(name = "singleUser")
    private User singleUser;

    @Resource(name = "prototypeUser")
    private User prototypeUser;

    @Resource(name = "requestUser")
    private User requestUser;

    //单例作用域
    @RequestMapping("/single")
    public String single(){
        /**
         * 1. 从context获取对象
         * 2. 属性注入获取对象
         */
        User user = (User) context.getBean("singleUser");
        return "context获取的对象:"+user+",属性注入获取的对象:"+singleUser;
    }
    //原型作用域(多例)
    @RequestMapping("/prototype")
    public String prototypeUser(){
        /**
         * 1. 从context获取对象
         * 2. 属性注入获取对象
         */
        User user = (User) context.getBean("prototypeUser");
        // 栈上的引用地址
//        return "context获取的对象:"+user+",属性注入获取的对象:"+prototypeUser;
        //打印内存地址
        return "context获取的对象:"+System.identityHashCode(user)+",属性注入获取的对象:"+System.identityHashCode(prototypeUser);
    }

    //请求作用域
    @RequestMapping("/request")
    public String requestUser() {
        User user = (User) context.getBean("requestUser");
//        return "context获取的对象:"+System.identityHashCode(user)+",属性注入获取的对象:"+System.identityHashCode(requestUser);
        System.out.println(user.toString());
        System.out.println(user.getClass().getName() + "@" + Integer.toHexString(user.hashCode()));
        return "context获取的对象:"+user+",属性注入获取的对象:"+requestUser;
    }
}

观察Bean的作用域

单例作用域 :

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

多例作用域 :
观察ContextDog, 每次获取的对象都不⼀样(注⼊的对象在Spring容器启动时, 就已经注⼊了, 所以多次 请求也不会发⽣变化).

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

Bean的生命周期

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

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

  2. 属性赋值(Bean注⼊和装配,⽐如 @AutoWired )

  3. 初始化

a. 执⾏各种通知,如BeanNameAware ,BeanFactoryAware ,ApplicationContextAware 的接⼝⽅法.

b. 执⾏初始化⽅法

▪ xml定义 init-method

▪ 使⽤注解的⽅式 @PostConstruct

▪ 执⾏初始化后置⽅法( BeanPostProcessor )

  1. 使⽤Bean.

5.销毁Bean.

实例化和属性赋值对应构造⽅法和setter⽅法的注⼊. 初始化和销毁是⽤⼾能⾃定义扩展的两个阶段,可以在实例化之后,类加载完成之前进⾏⾃定义"事件"处理.

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

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

执行流程如下:

Spring Boot自动配置

SpringBoot的⾃动配置就是当Spring容器启动后,⼀些配置类,bean对象等就⾃动存⼊到了IoC容器中,不需要我们⼿动去声明,从⽽简化了开发,省去了繁琐的配置操作.

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

原理分析

SpringBoot是如何帮助我们做的呢?⼀切的来⾃起源SpringBoot的启动类开始.

@SpringBootApplication 标注的类就是SpringBoot项⽬的启动类.

复制代码
@SpringBootApplication
public class SpringIocApplication {
    public static void main(String[] args) {
         //获取Spring上下⽂对象
        ApplicationContext context = SpringApplication.run(SpringIocApplication.class, args);
         //从Spring上下⽂中获取对象
        BeanLifeComponent beanLifeComponent = context.getBean(BeanLifeComponent.class);
          beanLifeComponent.use();
   }
}

这个类和普通类唯⼀的区别就是 @SpringBootApplication 注解,这个注解也是SpringBoot实现

⾃动配置的核⼼.

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

**1.**元注解.

JDK中提供了4个标准的⽤来对注解类型进⾏注解的注解类,我们称之为meta-annotation(元注

解),他们分别是:

• @Target描述注解的使⽤范围(即被修饰的注解可以⽤在什么地⽅)

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

• @Documented描述在使⽤javadoc⼯具为类⽣成帮助⽂档时是否要保留其注解信息

• @Inherited使被它修饰的注解具有继承性(如果某个类使⽤了被@Inherited修饰的注解,则

其⼦类将⾃动具有该注解)

2.@SpringBootConfiguration.

⾥⾯就是@Configuration,标注当前类为配置类,其实只是做了⼀层封装改了个名字⽽已.

(@Indexed注解,是⽤来加速应⽤启动的,不⽤关⼼)

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

看下@EnableAutoConfiguration 注解的实现:

这个注解包含两部分:

  1. @Import({AutoConfigurationImportSelector.class}) 。

使⽤@Import注解,导⼊了实现ImportSelector接⼝的实现类 .

  • selectImports() ⽅法底层调⽤ getAutoConfigurationEntry() ⽅法,获取可⾃动配置的
  • 配置类信息集合.
  • getAutoConfigurationEntry() ⽅法通过调⽤ getCandidateConfigurations(annotationMetadata, attributes) ⽅法获取在配置⽂件中配置的所有⾃动配置类的集合.
  1. @AutoConfigurationPackage.

这个注解主要是导⼊⼀个配置⽂件 AutoConfigurationPackages.Registrar.class.

复制代码
   static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
        Registrar() {
        }

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

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

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

所以,Registrar实现了 ImportBeanDefinitionRegistrar 类,就可以被注解@Import导⼊到spring容器⾥.

4.@ComponentScan (包扫描)

可以通过 basePackageClasses 或 basePackages 来定义要扫描的特定包,如果没有定义

特定的包,将从声明该注解的类的包开始扫描,这也是为什么SpringBoot项⽬声明的注解类必须要在启动类的⽬录下.

excludeFilters⾃定义过滤器,通常⽤于排除⼀些类,注解等.

总结

SpringBoot⾃动配置原理的⼤概流程如下:

相关推荐
L.EscaRC5 分钟前
Lua语言知识与应用解析
java·python·lua
S7777777S11 分钟前
easyExcel单元格动态合并示例
java·excel
间彧14 分钟前
什么是Region多副本容灾
后端
爱敲代码的北14 分钟前
WPF容器控件布局与应用学习笔记
后端
爱敲代码的北15 分钟前
XAML语法与静态资源应用
后端
清空mega17 分钟前
从零开始搭建 flask 博客实验(5)
后端·python·flask
爱敲代码的北21 分钟前
UniformGrid 均匀网格布局学习笔记
后端
刘个Java21 分钟前
对接大疆上云api---实现直播效果
java
用户95451568116223 分钟前
== 和 equals 区别及使用方法组件封装方法
java
hashiqimiya26 分钟前
html的input的required
java·前端·html