Spring Boot 底层原理基础深度解析

目录
  • 1. 底层注解@Configuration
  • 2. 底层注解@Import
  • 3. 底层注解@Conditional

1. 底层注解@Configuration

@Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法:

项目基本结构:

两个Bean组件:

User.java

package com.menergy.boot.bean;

/**
 * 用户
 */
public class User {
    private String name;
    private Integer age;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    public String getName() {
        return name;
    public void setName(String name) {
    public Integer getAge() {
        return age;
    public void setAge(Integer age) {
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
}

Pet.java

package com.menergy.boot.bean;

/**
 * 宠物
 */
public class Pet {
    private String name;
    public Pet() {
    }
    public Pet(String name) {
        this.name = name;
    public String getName() {
        return name;
    public void setName(String name) {
    @Override
    public String toString() {
        return "Pet{" +
                "name='" + name + '\'' +
                '}';
}

以前Spring 配置文件方式是这样给容器添加组件的:

<beans>

    <bean id="user01" class="com.menergy.boot.bean.User">
        <property name="name" value="dragon"></property>
        <property name="age" value="18"></property>
    </bean>
    <bean id="pet01" class="com.menergy.boot.bean.Pet">
        <property name="name" value="dragonPet"></property>
</beans>

现在Spring Boot 已经不写上面的xml配置了,在Spring Boot 底层可以用@Configuration 注解给容器中添加组件。如下:

注解类MyConfig.java

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = true)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        return new User("dragon",18);
    }
    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
}

主类MainApplication.java 中测试调用:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;
/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
    public static void main(String[] args) {
//        SpringApplication.run(MainApplication.class, args);
        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        // 3. 从容器中获取组件
        Pet pet1 = run.getBean("tomcatPet", Pet.class);
        Pet pet2 = run.getBean("tomcatPet", Pet.class);
        System.out.println("组件: " + (pet1 == pet2));
        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);
        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
        User user01 = myConfig.user01();
        User user02 = myConfig.user01();
        System.out.println(user01 == user02);
    }
}

输出的部分结果:

上面的例子,重点落在@Configuration(proxyBeanMethods = true) 注解。 该注解告诉SpringBoot ,被注解的类是一个配置类, 相当于以前的配置文件xml中的“bean配置”。该注解有如下特性:

1. 该注解的配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的。

2. 被这个注解的配置类本身也是组件。

3. 该注解的属性proxyBeanMethods 可以通过“true” 和 “false” 配置值,来控制使用的模式:

(1)Full模式(proxyBeanMethods = true): 为true时,外部无论对配置类中的组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象。

(2)Lite模式(proxyBeanMethods = false): 为false时,在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象。

这两种模式的存在主要用于解决组件依赖场景。

1和2 两点特性上面的例子中都有体现, 接下来重点实践第三点特性:

实践proxyBeanMethods:

基于上面的例子,首先修改User.java类,加上宠物Pet的依赖:

package com.menergy.boot.bean;

/**
 * 用户
 */
public class User {
    private String name;
    private Integer age;
    private Pet pet;
    public User() {
    }
    public User(String name, Integer age) {
        this.name = name;
        this.age = age;
    public User(String name, Integer age, Pet pet) {
        this.pet = pet;
    public String getName() {
        return name;
    public void setName(String name) {
    public Integer getAge() {
        return age;
    public void setAge(Integer age) {
    public Pet getPet() {
        return pet;
    public void setPet(Pet pet) {
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", pet=" + pet +
                '}';
}

在配置类MyConfig.java 中加入user01对象对用pet对象,同时使用Full模式(proxyBeanMethods = true):

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = true)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;
    }
    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
}

主类MainApplication.java:

package com.menergy.boot;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import com.menergy.boot.config.MyConfig;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.SpringBootConfiguration;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.ComponentScan;
import sun.awt.geom.AreaOp;
/**
 * 主程序类
 * 这个注解相当于告诉Spring Boot: 这是一个Spring boot 应用
 */
//@SpringBootApplication
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.menergy.boot")
public class MainApplication {
    public static void main(String[] args) {
//        SpringApplication.run(MainApplication.class, args);
        // 1.返回IOC容器
        ConfigurableApplicationContext run = SpringApplication.run(MainApplication.class, args);
        // 2.查看容器里面的容器
        String[] names = run.getBeanDefinitionNames();
        for (String name : names) {
            System.out.println(name);
        }
        // 3. 从容器中获取组件
        Pet pet1 = run.getBean("tomcatPet", Pet.class);
        Pet pet2 = run.getBean("tomcatPet", Pet.class);
        System.out.println("组件: " + (pet1 == pet2));
        // 4. com.menergy.boot.config.MyConfig$$EnhancerBySpringCGLIB$$3779496a@67a056f1
        MyConfig myConfig = run.getBean(MyConfig.class);
        System.out.println(myConfig);
        //如果@Configuration(proxyBeanMethods = true)代理对象调用方法, Spring Boot 总会检查这个组件是否在容器中有,如果有则不会新建,保持组件单实例。
        User user01 = myConfig.user01();
        User user02 = myConfig.user01();
        System.out.println(user01 == user02);
        //测试 @Configuration(proxyBeanMethods = true/false)
        User user011 = run.getBean("user01", User.class);
        Pet tomcatPet = run.getBean("tomcatPet", Pet.class);
        System.out.println("用户的宠物:" + (user011.getPet() == tomcatPet));
    }
}

运行结果:

可以看出,Full模式(proxyBeanMethods = true)时,输出true,说明是从容器中获取的同一个组件(用户的宠物就是容器中的宠物)。

接下来,改用Lite模式(proxyBeanMethods = false):即基于上面实例,将配置类MyConfig.java 中的注解的属性proxyBeanMethods 改成false值,如下:

MyConfig.java:

package com.menergy.boot.config;

import com.menergy.boot.bean.Pet;
import com.menergy.boot.bean.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
 * 1. 配置类里面使用@Bean标注在方法上给容器注册组件,默认也是单实例的
 * 2. 配置类本身也是组件
 * 3. proxyBeanMethods: 代理Bean 方法:
 *      Full模式(proxyBeanMethods = true): 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
 *      Lite模式(proxyBeanMethods = false): 在容器中不会保留代理对象,外部多次调用这些组件时,每次调用都会产生一个新的对象
 *      用于解决组件依赖场景
 */
@Configuration(proxyBeanMethods = false)  //告诉SpringBoot 这是一个配置类 == 以前的配置文件
public class MyConfig {
    /**
     * 外部无论对配置类中的这个组件注册方法调用多少次,获取的都是之前注册容器中的单实例对象
     * @return
     */
    @Bean   //给容器中添加组件,以方法名作为主键id,返回类型就是组件类型,返回值就是组件在容器中的实例
    public User user01(){
        User dragonUser = new User("dragon",18);
        // User 组件依赖了Pet 组件,当proxyBeanMethods 为 true 时,这种依赖关系成立
        dragonUser.setPet(pet01());
        return dragonUser;
    }
    @Bean("tomcatPet")
    public Pet pet01(){
        return new Pet("dragonPet");
}

运行结果:

可以看出,Lite模式(proxyBeanMethods = false)时,输出false,说明是从容器中获取的不是同一个组件(用户的宠物不是容器中的宠物, 相当于new 了另一个对象)。

总结:配置类包括了全模式(Full)和轻量级模式(Lite)两种。当proxyBeanMethods 是true时,Spring Boot 每次都会检查容器中是否有相应的组件,如果proxyBeanMethods 是false, 则不检查容器中是否有没有相应的组件,而是直接new一个。这也是Spring Boot 新增的一个很重要的特性。

最佳实战:如果只是向容器中增加组件,别的地方也不会调用这个组件,我们可以将其调为false 模式,这样Spring Boot 启动起来非常快,加载起来也非常快。 如果别的地方明显要用,要依赖,我们就把其调成true,保证依赖的组件就是容器中的组件。

注: 前面的例子中,在配置类中用到@Been 注解来指定组件, 其实Spring Boot 底层还用到了其他一些以前常用的注解来指定组件,包括@Component、@Controller、@Service、@Repository。这些类似于@Been 原理,也是用于向容器中注册组件。

除此之外,底层还用到@ComponentScan 注解来说明容器的包扫描,还有@Import 和@Conditional 来向容器添加组件。很多注解是以前常用的,接下来主要说明@Import 和@Conditional 注解。

2. 底层注解@Import

首先,从@Import 注解类中可以看到该注解的定义,以及知道其属性是一个Class类型的数组,说明这个注解的作用是向容器中导入一批组件

接下来,实践一下:

首先在配置类上加入@Import 注解,并向容器中导入两个组件,一个是自己定义的类,一个是从第三方Jar 包中任意的一个类:

主类加入如下测试:

运行结果:

结果说明:

“com.menergy.boot.bean.User” 是通过@Import 导入的组件。(默认的组件名称是全类名

“user01” 是之前用@Bean 方法添加进去的

“org.apache.logging.log4j.util.StringBuilders@4482469c” 也是通过@Import 导入的组件。

3. 底层注解@Conditional

@Conditional 是条件装配:当满足@Conditional指定的条件时, 才向容器中注入组件。

在全局Jar包中搜索@Conditional 类:双击Shift键,选择Classes,输入@Conditional搜索。

注:如果调不出这个窗口,请参考: (98条消息) IDEA 操作与设置笔记_龙泉太阿的博客

-CSDN博客https://blog.csdn.net/menergy/article/details/123827363?spm=1001.2014.3001.5501

打开Conditional 类后,“Ctrl + H” 键调出这个类的继承树:

注:如果快捷键失效,请确定如下快捷键设置:

从前面的@Conditional 的继承树可以看出,@Conditional 有非常多的派生注解,每个注解都代表不同的功能,从派生注解的注解名称可以大概知道其功能用意,例如@ConditionalOnBean 注解代表当容器中存在某个Bean时才干某些事情, @ConditionalOnMissingBean 注解代表当容器中不存在某个Bean时才干某些事情。

接下来,以@ConditionalOnBean 为例,进行实践:

到此这篇关于Spring Boot 底层原理基础的文章就介绍到这了,更多相关Spring Boot 底层原理内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 深入研究spring boot集成kafka之spring-kafka底层原理

    目录 前言 简单集成 引入依赖 添加配置 测试发送和接收 Spring-kafka-test嵌入式KafkaServer 引入依赖 启动服务 创建新的Topic 程序启动时创建TOPIC 代码逻辑中创建 PS:其他的方式创建TOPIC 引入依赖 api方式创建 命令方式创建 消息发送之KafkaTemplate探秘 获取发送结果 异步获取 同步获取 KAFKA事务消息 REPLYINGKAFKATEMPLATE获得消息回复 Spring-kafka消息消费用法探秘 @KAFKALISTENER的

  • SpringBoot整合log4j日志与HashMap的底层原理解析

    一,SpringBoot与日志 1.springboot整合log4j日志记录 1.在resources目录下面创建日志文件,并引入: 代码如下(示例): #log4j.rootLogger=CONSOLE,info,error,DEBUG log4j.rootLogger=info,error,CONSOLE,DEBUG log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=o

  • Spring Boot 底层原理基础深度解析

    目录 1. 底层注解@Configuration 2. 底层注解@Import 3. 底层注解@Conditional 1. 底层注解@Configuration @Configuration 注解主要用于给容器添加组件(Bean),下面实践其用法: 项目基本结构: 两个Bean组件: User.java package com.menergy.boot.bean; /** * 用户 */ public class User { private String name; private Inte

  • Spring Boot启动过程完全解析(二)

    上篇给大家介绍了Spring Boot启动过程完全解析(一),大家可以点击参考下 该说refreshContext(context)了,首先是判断context是否是AbstractApplicationContext派生类的实例,之后调用了强转为AbstractApplicationContext类型并调用它的refresh方法.由于AnnotationConfigEmbeddedWebApplicationContext继承自EmbeddedWebApplicationContext,所以会

  • Spring Boot启动过程完全解析(一)

    之前在排查一个线上问题时,不得不仔细跑了很多遍Spring Boot的代码,于是整理一下,我用的是1.4.3.RELEASE. 首先,普通的入口,这没什么好说的,我就随便贴贴代码了: SpringApplication.run(Application.class, args); --> public static ConfigurableApplicationContext run(Object source, String... args) { return run(new Object[]

  • Spring Boot启动过程全面解析(三)

    我已经很精简了,两篇(Spring Boot启动过程(一).spring Boot启动过程(二))依然没写完,接着来. refreshContext之后的方法是afterRefresh,这名字起的真...好.afterRefresh方法内只调用了callRunners一个方法,这个方法从上下文中获取了所有的ApplicationRunner和CommandLineRunner接口的实现类,并执行这些实现类的run方法.例如Spring Batch的JobLauncherCommandLineRu

  • Spring Boot 整合 Druid过程解析

    这篇文章主要介绍了Spring Boot 整合 Druid过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 概述 Druid 是阿里巴巴开源平台上的一个项目,整个项目由数据库连接池.插件框架和 SQL 解析器组成.该项目主要是为了扩展 JDBC 的一些限制,可以让程序员实现一些特殊的需求,比如向密钥服务请求凭证.统计 SQL 信息.SQL 性能收集.SQL 注入检查.SQL 翻译等,程序员可以通过定制来实现自己需要的功能. Druid 是

  • Spring boot整合log4j2过程解析

    这篇文章主要介绍了Spring boot整合log4j2过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 以前整合过log4j2,但是今天再次整合发现都忘记了,而且也没有记下来 1.pom.xml中 (1)把spring-boot-starter-web包下面的spring-boot-starter-logging排除 <dependency> <groupId>org.springframework.boot</gr

  • spring boot整合kafka过程解析

    这篇文章主要介绍了spring boot整合kafka过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 一.启动kafka 启动kafka之前一定要启动zookeeper,因为要使用kafka必须要使用zookeeper. windows环境下启动,直接使用kafka自带的zookeeper: E:\kafka_2.12-2.4.0\bin\windows zookeeper-server-start.bat ..\..\config\z

  • java基于spring boot本地上传图片示例解析

    前几天项目中刚好需要上传图片的需求,当时想的是用七牛云,因为我用七牛云也用了好几次,就是把图片上传到七牛云空间里面,数据库里面保存的是这张上传图片的url地址 那么页面访问也就很方便,考虑到项目部署的环境我就用了本地上传,不牵涉数据库的操作.我就花了半个小时写了个本地上传图片的小demo.非常的简单. 下面是需要的依赖 pom.xml文件: <?xml version="1.0" encoding="UTF-8"?> <project xmlns=

  • spring aop底层原理及如何实现

    前言 相信每天工作都要用spring框架的大家一定使用过spring aop,aop的概念是面向切面编程,相对与传统的面向对象编程oop,aop更关注的是横向的逻辑,比如说一个大型系统中的日志记录,异常处理,性能监控等等,都是各个模块都需要的操作,那样代表着这些操作会散落在系统的各个地方,不易管理且杂乱无章,而aop就是关注的这些,aop将这些操作与业务代码分离,统一成一个个的切面,针对这些个切面进行编程处理.spring aop使得我们的aop开发工作变得简单,这次我就给大家讲讲spring

  • Spring AOP底层原理及代理模式

    目录 Spring AOP底层原理代理模式 一.什么是 AOP 二.AOP 底层原理 1. 什么是代理? 2. 什么是静态代理 3. 什么是动态代理 Spring AOP底层原理代理模式 一.什么是 AOP AOP 就是面向切面编程,是 OOP(面向对象编程)的延续. 利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序可用性,同时也提高了开发效率. 通俗一点说,不用修改原代码,可以给原代码增加新的功能. 二.AOP 底层原理 AOP 底层原理是使用动

随机推荐