Spring 原理

发布于:2024-05-06 ⋅ 阅读:(34) ⋅ 点赞:(0)

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

目录

Bean的作用域

代码实现

观察Bean的作用域

Bean的生命周期

Spring Boot自动配置

原理分析 

1. 元注解.

2.  @SpringBootConfiguration.

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 )
4. 使⽤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) ⽅法获取在配置⽂件中配置的所有⾃动配置类的集合.

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