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⾃动配置原理的⼤概流程如下:

相关推荐
跳动的梦想家h3 小时前
黑马点评 秒杀下单出现的问题:服务器异常---java.lang.NullPointerException: null(已解决)
java·开发语言·redis
苹果醋33 小时前
前端面试之九阴真经
java·运维·spring boot·mysql·nginx
哎呦没4 小时前
Spring Boot OA:企业办公自动化的高效路径
java·spring boot·后端
真心喜欢你吖4 小时前
Spring Boot与MyBatis-Plus的高效集成
java·spring boot·后端·spring·mybatis
2401_857636394 小时前
实验室管理技术革新:Spring Boot系统
数据库·spring boot·后端
2401_857600954 小时前
实验室管理流程优化:Spring Boot技术实践
spring boot·后端·mfc
2402_857589364 小时前
企业办公自动化:Spring Boot OA管理系统开发与实践
java·spring boot·后端
G丶AEOM4 小时前
JVM逃逸分析机制
java·jvm
无聊写博客4 小时前
JDK、JRE、JVM的区别
java·开发语言·jvm
message丶小和尚5 小时前
SpringBoot升级全纪录之项目启动
java·spring boot·mybatis