• 面试总结之Spring篇


    一、Spring基础

    1、Spring特性

    • Spring是一个轻量级的、非侵入式的、控制反转(IOC)和面向切面编程(AOP)的框架,现在企业开发的标配基本就是 Spring5 + Spring Boot 2 + JDK 8
    • Spring特性:
      1)IOC和DI的支持
      IOC:Inversion of Control(控制反转),是一种实现对象解耦的设计思想将对象交给 Spring 中 IoC 容器管理,在其他类中不直接 new 对象,而是通过将对象传递到当前类的方式来实现解耦的,而非让程序主动去创建依赖对象,控制对象外部资源的获取 ==> 控制
      交由IOC容器帮我们查找、注入依赖对象,对象只是被动地接受依赖对象,而非我们主动控制区直接获取依赖对象 ==> 反转
      DI:Dependency Injection,依赖注入,是一种具体的技术实现,在IOC容器运行期间,动态地将某个依赖对象注入到当前对象
      2)AOP的支持
    • AOP:将那些与业务无关、却被业务模块共同调用的逻辑封装起来(权限认证、日志输出监控等),便于减少系统的代码重复,以及后期的维护和扩展,降低模块间的耦合度 ==》 在不侵入原有程序的基础上实现对原有功能的增强
      3)声明式事务的支持
    • 支持通过配置来完成对事务的管理,而不需要进行硬编码,之前关于事务提交、回滚的JDBC代码不需要再编写
      4)快速测试的支持
    • Spring对Junit提供支持,可通过注解 快速地测试Spring应用
      5)快速集成功能
    • 方便集成各种优秀框架,其内部提供了对各种优秀框架的直接支持(如:Mybatis、Quartz等)
      6)复杂API模板的封装
    • Spring对JavaEE中非常难用的API(如JDBC、JavaMail、远程调用等)都提供了模板化的封装,使应用开发难度大大降低

    2、Spring主要模块

    • Spring Core:Spring核心,是最基础的部分,提供IOC和DI特性
    • Spring Context:Spring上下文容器,是BeanFactory功能加强的子接口
    • Spring Web:提供Web应用开发的支持
    • Spring MVC:针对Web应用中MVC思想的实现
    • Spring DAO:提供对JDBC的抽象,简化了JDBC编码,使其更具有健壮性
    • Spring ORM:支持用于流行的ORM框架,如:Spring+Hibernate、Spring+iBatis等
    • Spring AOP:面向切面编程,提供了与AOP联盟兼容的编程实现

    3、Spring常用注解

    4、Spring中应用的设计模式

    Spring框架中广泛使用了不同类型的设计模式,如下:

    • 工厂模式:Spring容器本质上就是一个工厂,使用工厂模式通过BeanFactory、ApplicationContext完成Bean对象的创建
      代理模式:Spring AOP功能通过代理模式实现,具体分为动态代理和静态代理
      单例模式:Spring中的Bean默认都是单例的,如此有利于容器对Bean的管理
      模板模式:Spring中 JdbcTemplate、RestTemplate等以Template结尾,对数据库、网络等进行操作的模板类,就使用到了模板模式
      观察者模式:Spring事件驱动模型是观察者模式的经典应用
      适配器模式:Spring AOP的增强或通知(Advice)、Spring MVC中适配具体的Controller都使用到了适配器模式
      策略模式:Spring中 Resource接口的不同实现类,会根据不同的策略访问资源

    二、IOC

    1、Spring IOC的实现机制

    • Spring IOC 本质就是一个大工厂,我们想想⼀个工厂是怎么运行的,就不难理解IOC的实现机制了
      在这里插入图片描述

    • 生产产品:⼀个工厂最核⼼的功能就是⽣产产品。Spring不用Bean自行实例化,而是统一
      交给 Spring通过反射实现

    • 库存产品:工厂的生产管理通过工厂模式来实现,工厂⼀般都是有库房的,用来存取产品,毕竟生产的产品不能⽴马就运走。Spring容器中存的就是对象,不能每次来取对象,都得现场来反射创建对象,需要把创建出的对象存起来

    • 订单处理:工厂通过订单来提供产品,经过处理,指导工厂的出货
      在 Spring中,也有这样的订单,即bean 的定义和依赖关系,可以是 xml,也可以是注解的形式

    mini版Spring IOC实现

    如下,我们来简单地来实现一个mini版的Spring IOC:

    • bean通过配置文件定义,使用需将其解析成Java类
    userDao:com.example.interviewStudy.ioc.UserDao
    hobby:com.example.interviewStudy.ioc.Hobby
    
    • 1
    • 2
    package com.example.interviewStudy.ioc;
    
    public class UserDao {
    
        public Object getUserInfo(){
            System.out.println("Spring AOP mini implementing....");
            return UserDao.class.getName();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    package com.example.interviewStudy.ioc;
    
    public class Hobby {
        public void play(){
            System.out.println("play basketball...");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • bean的定义类,配置文件中bean定义对应的实体:
    import lombok.Builder;
    import lombok.Data;
    
    @Builder
    @Data
    public class BeanDefinition {
    
        private String beanName;
    
        private Class beanClass;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • ResourceLoader :资源加载器,⽤来完成配置⽂件中配置加载
    import java.io.IOException;
    import java.io.InputStream;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * 资源加载器,⽤来完成配置⽂件中配置加载
     */
    public class ResourceLoader {
    
        public static Map<String, BeanDefinition> getResource() {
            Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>(16);
            Properties properties = new Properties();
            try {
                // 1.指定从配置文件中读取配置
                InputStream resourceStream = ResourceLoader.class.getResourceAsStream("/beans.properties");
                properties.load(resourceStream);
                Iterator<String> iterator = properties.stringPropertyNames().iterator();
                // 2.依次遍历beans.properties中的配置,并将其存储到容器中
                while (iterator.hasNext()) {
                    String key = iterator.next();
                    String className = properties.getProperty(key);
    
                    Class<?> clazz = Class.forName(className);
    
                    BeanDefinition beanDefinition = BeanDefinition.builder()
                            .beanName(key)
                            .beanClass(clazz)
                            .build();
                    // 将bean记录到容器中
                    beanDefinitionMap.put(key,beanDefinition);
                }
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
            }
            // 3.返回容器对象
            return beanDefinitionMap;
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • BeanRegister:对象注册器,使用单例 bean 的缓存,这里简化为默认所有 bean 都是单例的(往 HashMap 里存取对象)
    import java.util.HashMap;
    import java.util.Map;
    
    public class BeanRegister {
    
        // 单例Bean缓存
        private Map<String,Object> singletonMap = new HashMap<>(32);
    
        public Object getSingletonBean(String beanName){
            return singletonMap.get(beanName);
        }
    
        public void registerSingletonBean(String beanName,Object bean){
            if (singletonMap.containsKey(beanName)){
                return;
            }
            singletonMap.put(beanName,bean);
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • BeanFactory:对象工厂,实现IOC的核心,在其初始化的时候,创建 bean 注册器,完成资源的加载;获取 bean 的时候,先从单例缓存中取,如果没有取到,就创建并注册⼀个 bean
    import java.util.HashMap;
    import java.util.Map;
    
    public class BeanFactory {
    
        private Map<String,BeanDefinition> beanDefinitionMap = new HashMap<>();
    
        private BeanRegister beanRegister;
        public BeanFactory(){
            // 创建Bean的注册器
            beanRegister = new BeanRegister();
            // 加载资源
            this.beanDefinitionMap = ResourceLoader.getResource();
        }
    
    
        /**
         *  先从缓存中获取bean,缓存没有存储bean,则创建bean
         */
        public Object getBean(String beanName){
            Object bean = beanRegister.getSingletonBean(beanName);
            if (bean != null){
                System.out.println("current bean is exist,getting bean from the cache");
                return bean;
            }
            return createBean(beanDefinitionMap.get(beanName));
        }
    
        private Object createBean(BeanDefinition beanDefinition) {
            System.out.println("bean is creating...");
            try {
                Object bean = beanDefinition.getBeanClass().newInstance();
                beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
                return bean;
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
            return null;
        }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 测试:
        @Test
        public static void test() {
            BeanFactory beanFactory = new BeanFactory();
    
            UserDao userDao1 = (UserDao) beanFactory.getBean("userDao");
            Object info1 = userDao1.getUserInfo();
            System.out.println(info1);
    
            UserDao userDao2 = (UserDao) beanFactory.getBean("userDao");
            Object info2 = userDao2.getUserInfo();
            System.out.println(info2);
    
            Hobby hobby = (Hobby) beanFactory.getBean("hobby");
            System.out.println(hobby);
    
            Hobby hobby1 = (Hobby) beanFactory.getBean("hobby");
            System.out.println(hobby1);
    
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 运行结果:
      在这里插入图片描述

    由于时间+篇幅的限制,当前demo比较简陋,没有面向接口、没有解耦、边界检查、异常处理,扩展性、健壮性都有很大不足,但基本存取bea的功能已然实现

    2、BeanFactory和ApplicationContext

    • 可以理解为 BeanFactory 是 Spring 的“心脏”,ApplicantContext 是完整的“身躯”

    3、Spring容器启动阶段会做什么

    • Spring IOC容器工作的过程可以大致分为 容器启动阶段和Bean实例化阶段两个阶段,容器启动阶段主要是加载 + 解析配置文件,保存到对应的Bean定义中
      在这里插入图片描述
    • 1)在Spring容器启动阶段,会先加载ConfigurationMetadata,大部分情况下,容器需要依赖某些工具类(如BeanDefinitionReader接口对加载的ConfigurationMetadata进行解析、分析,并将分析后得打的信息组合为相应的BeanDefinition
    • 2)将保存了Bean必要信息的 BeanDefinition 注册到相应的 BeanDefinitionRegistry,如此,Spring容器的启动阶段就此完成
      在这里插入图片描述

    4、Spring Bean的生命周期

    • 在Spring中,基本容器BeanFactory和扩展容器ApplicationContext 实例化Bean的时机不同,BeanFactory采用延迟初始化的方式,只在第一次getBean()时,才会实例化Bean;ApplicationContext在Spring启动后会实例化所有的Bean
    • Spring IOC中Bean的生命周期可以大致分为四个阶段:实例化(Instantiation)、属性赋值(Population)、初始化(Initialization)、销毁(Destruction)
      在这里插入图片描述
    • 1)实例化:实例化一个Bean对象
    • 2)属性赋值:为Bean设置相关属性和依赖
    • 3)初始化:真正初始化的前置处理 => 初始化(检查是否实现InitializingBean接口,以此决定是否调用afterPropertiesSet方法) =》初始化的后置处理
    • 4)销毁:检查实现DisposableBean销毁接口,配置自定义的destroy方法
      具体的实现细节如下:
      在这里插入图片描述
      下面通过代码的方式来进行验证:
    • 定义⼀个 PersonBean 类,实现 DisposableBean、InitializingBean、BeanFactoryAware、BeanNameAware四个接口,并实现其接口的方法,还需要自定义init-method 和 destroy-method来完成bean的初始化和销毁操作
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.*;
    
    public class PersonBean implements InitializingBean, BeanFactoryAware, BeanNameAware, DisposableBean {
    
        // 身份证号
        private String perNo;
    
        // 姓名
        private String pname;
    
        public String getPerNo() {
            return perNo;
        }
    
        public void setPerNo(String perNo) {
            this.perNo = perNo;
        }
    
        public String getPname() {
            return pname;
        }
        
        public PersonBean(){
            System.out.println("1.调用构造方法,创建Bean ==> 我出生了");
        }
    
        public void setPname(String pname) {
            this.pname = pname;
            System.out.println("2.为属性赋值,我的名字是: " + pname);
        }
    
        @Override
        public void setBeanName(String pname) {
            System.out.println("3.调⽤BeanNameAware#setBeanName方法:" + pname + "要上学了..");
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("4.调⽤BeanFactoryAware # setBeanFactory方法:选好学校了\"");
        }
    
        // 5.实现BeanPostProcessor接口,通过postProcessBeforeInitialization方法完成 初始化的前置操作
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("6.实现InitializingBean # afterPropertiesSet方法,入学登记了..");
        }
    
        public void init(){
            System.out.println("7.自定义init方法,努力上学中...");
        }
    
        // 8.实现BeanPostProcessor接口,通过 postProcessAfterInitialization 方法完成 初始化的后置操作
    
    
        @Override
        public void destroy() throws Exception {
            System.out.println("9.实现DisposableBean # destroy方法,平淡的圆满谢幕了!");
        }
    
        public void destroyMethod(){
            System.out.println("10.自定义destroy方法,累了困了,让我在土里躺一会儿...");
        }
    
        public void work(){
            System.out.println("使用/管理Bean中, 我要努力工作,只有对社会没用的人才放假");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 实现BeanPostProcessor接口,完成Bean初始化的前置和后置操作
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    
    public class MyBeanPostProcessor implements BeanPostProcessor {
    
        @Override
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("5.BeanPostProcessor.postProcessBeforeInitialization方法:到学校报名啦");
            System.out.println("bean: " + bean.toString());
            System.out.println("beanName: " + beanName);
            return bean;
        }
    
        @Override
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("8.BeanPostProcessor#postProcessAfterInitialization方法:终于毕业,拿到毕业证啦!");
            return bean;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 通过配置⽂件(spring-config.xml),指定 init-method 和 destroy-method 属性
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean name="myBeanPostProcessor" class="com.example.interviewStudy.bean.MyBeanPostProcessor"/>
        <bean name="personBean" class="com.example.interviewStudy.bean.PersonBean"
              init-method="init" destroy-method="destroyMethod">
            <property name="perNo" value="42607619450872234"/>
            <property name="pname" value="张晓静"/>
        bean>
    beans>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 测试:
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestLifeCycle {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
            PersonBean personBean = (PersonBean) context.getBean("personBean");
            personBean.work();
            ((ClassPathXmlApplicationContext)context).destroy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Bean 创建过程见AbstractBeanFactory # doGetBean方法,在该方法中可见
    Bean 的实例化,赋值、初始化的过程,Bean的销毁,可见ConfigurableApplicationContext # close()
    在这里插入图片描述

    5、Bean的定义和依赖注入方式

    • 有三种方式:直接编码、配置文件、注解
      在这里插入图片描述
    • 直接编码:我们一般接触不到直接编码,但其他方式最终都要通过直接编码来实现
    • 配置文件:通过xml、properties类型的配置文件,配置相应的依赖关系,Spring读取配置文件,完成依赖关系的注入
    • 注解:在相应的地方使用注解修饰,Spring会扫描注解,完成依赖关系的注入

    6、Spring自动装配的方式

    • byName:根据Bean的名称自动匹配,假设Person有⼀个名为 car 的属性,如果容器中刚好有⼀个名为 car 的 bean,Spring 就会⾃动将其装配给Person的 car 属性
    • byType:根据Bean的类型自动匹配,假设Person有⼀个 Car 类型的属性,如果容器中刚好有⼀个Car 类型的 Bean,Spring 就会⾃动将其装配给Person这个属性
    • constructor:与byType类似,只不过是针对构造函数而言的,如果Person有⼀个构造
      函数,构造函数包含⼀个 Car 类型的⼊参,如果容器中有⼀个 Car 类型的 Bean,则 Spring 将自动把这个 Bean 作为 Person构造函数的⼊参;如果容器中没有找到和构造函数⼊参匹配类型的Bean,则 Spring 将抛出异常。
    • autodetect:根据 Bean 的自省机制决定采用byType 还是 constructor进行自动装配,如果Bean 提供了默认的构造函数,则采用byType,否则采⽤ constructor

    7、Spring中Bean的作用域

    Spring中的Bean支持五种作用域:

    • singleton:Spring容器中仅存在一个Bean实例,是Bean默认的作用域
    • prototype:每次都会从容器中重新生成Bean,即每次都返回一个新实例
    • request:每次HTTP请求都会产生一个新的Bean,该Bean只会在当前HTTP Request内有效
    • session:同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean
    • globalSession:同一个全局Session共享一个Bean,只用于基于Protlet的Web应用,Spring5已不存在

    8、Spring中的单例Bean是否存在线程安全问题

    • Spring中的单例Bean不是线程安全的
    • 所谓单例Bean,即全局只有一个Bean,所有线程共享,如果单例Bean是无状态的,即线程中的操作不会对Bean中的成员变量执行查询以外的操作,则该单例Bean是线程安全的,比如Spring MVC中的Controller、Service、Dao等,这些Bean大多是无状态的,只需关注于方法本身;但如果该Bean是有状态的,即 会对Bean中的成员变量进行写操作,则可以存在线程安全问题
      在这里插入图片描述
      单例Bean的线程安全问题如何解决?
    • 将 Bean 定义为多例
      这样每⼀个线程请求过来都会创建⼀个新的 Bean,但会造成容器不好管理 Bean,不推荐
    • 将Bean中的成员变量保存在ThreadLocal中
      ThreadLocal能保证多线程环境下线程的隔离性,可在类中定义一个ThreadLocal成员变量,将可变的成员变量保存在ThreadLocal中,这是推荐的方式
      如使用ThreadLocal保存登录用户的基本信息(user的DTO对象)
    public class UserHolder {
        private static final ThreadLocal<UserDTO> tl = new ThreadLocal<>();
    
        public static void saveUser(UserDTO user){
            tl.set(user);
        }
    
        public static UserDTO getUser(){
            return tl.get();
        }
    
        public static void removeUser(){
            tl.remove();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    9、Spring循环依赖以及解决方案

    在这里插入图片描述

    • Spring循环依赖:即Bean自己依赖自己,或别的Bean相互依赖的情况
    • 只有单例Bean才会存在循环依赖的情况,多例Bean,Spring则会直接抛出异常,因为AB循环依赖,A实例化的时候,发现依赖B,创建B实例的时候发现需要A,于是乎,再去创建A1实例…无限套娃,直到干跨系统
    Spring可以解决哪些情况的循环依赖
    • Spring不支持基于构造器注入的循环依赖,但如果是AB循环依赖,一个是构造器注入,一个是setter注入,则情况会有所不同,需要具体情况具体分析:
      在这里插入图片描述
      如上,第四种支持但第五种不支持是因为Spring在创建Bean时 会根据字母的自然排序进行创建,所以A先于B被创建
      当循环依赖的实例采用setter方法注入时,Spring可以支持;当实例都采用构造器注入时,Spring不支持;当构造器注入和setter方法注入都存在时,需要视具体情况而定(如上图所示)
    Spring如何解决循环依赖
    • 单例Bean的初始化需要经历实例化、属性赋值、初始化三个步骤,其中Bean的注入就发生在属性赋值,结合该过程,Spring通过三级缓存解决了循环依赖:在这里插入图片描述
    • 一级缓存:Map
      singletonObjects,单例池,用于保存实例化、属性赋值(注入)、初始化完成的Bean实例
    private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    • 1
    • 二级缓存:Map earlySingletonObjects,早期曝光对象,用于保存实例化完成的Bean实例
     private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
    • 1
    • 三级缓存:Map> singletonFactories,早期曝光对象工厂,用于保存Bean创建工厂,便于后续扩展创建代理对象
     private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    • 1

    Spring缓存实现见DefaultSingletonBeanRegistry类:

    public class DefaultSingletonBeanRegistry extends SimpleAliasRegistry implements SingletonBeanRegistry {
    
    	/** Maximum number of suppressed exceptions to preserve. */
    	private static final int SUPPRESSED_EXCEPTIONS_LIMIT = 100;
    
    
    	/** Cache of singleton objects: bean name to bean instance. */
    	private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
    
    	/** Cache of singleton factories: bean name to ObjectFactory. */
    	private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
    
    	/** Cache of early singleton objects: bean name to bean instance. */
    	private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
    
        // 略
    }    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述
    一级缓存中保存着从实例化到初始化都完成的对象

    • A、B两个类发生循环依赖的过程如下:
      1)创建A实例,实例化时将A对象工厂放入三级缓存,表示我已经开始实例化了,即便我是个不完整的对象,但还是曝光出来让大家知道
      2)A注入属性时,发现依赖B,就去实例化B
      3)B注入属性时发现依赖A,会从缓存中查找A对象,依次从一级缓存到三级缓存查询A在三级缓存中从对象工厂取到A发现A存在但不完整,就将A放入二级缓存中,同时删除掉三级缓存中的A,如此,B就完成了实例化到初始化的过程,B被放入到一级缓存中
      4)A继续进行属性赋值,顺利从一级缓存中获取到实例化到初始化都完成的B对象,A对象也被创建完成,之后A被放入一级缓存中,且删除掉二级缓存中的A
      5)最后,一级缓存中保存着实例化到初始化都完成的A、B对象
      基于此,就不难理解为什么Spring 能解决 setter注入的循环依赖了,因为实例化和属性赋值是分开的,有缓冲回旋的余地,但如果都是构造器注入,则都在实例化完成注入,所以自然无法支持
      在这里插入图片描述
      在这里插入图片描述
      在这里插入图片描述

    10、Spring为什么要三级缓存,二级缓存可还行?

    • 使用三级缓存主要是为了生成代理对象(保证任何时候使用的都是同一个对象)==> 三级缓存中存放的是生成具体对象的匿名内部类,获取Object时,可以生成代理对象,也可以返回普通对象
    • 假设只有二级缓存的情况,往二级缓存中放入的是一个普通的Bean对象,Bean初始化过程中,通过BeanPostProcessor生成代理对象后,会覆盖掉二级缓存中的普通Bean对象,则可能会导致取到的Bean对象不一致
      在这里插入图片描述

    一、AOP

    1、什么是AOP

    1.1、概述

    • AOP(Aspect-Oriented Programming):面向切面编程,即把一些业务逻辑中的相同代码抽取出来,让业务逻辑更加简练清爽
      在这里插入图片描述
    • 如果要CRUD写一堆业务,可如何实现业务代码前后进行打印日志和参数的校验?
      可以把日志记录和数据校验可重用的功能模块分离出来,然后在程序合适的位置动态地植入这些代码并执行,如此,让业务逻辑只包含核心的业务代码,而没有通用逻辑的代码,使业务模块更简洁,实现了业务逻辑和通用逻辑的代码分离,便于维护和升级,降低了业务逻辑和通用逻辑的耦合性
      在这里插入图片描述
    • AOP可以将遍布应用的功能分离出来形成可重用的组件,在编译期间、装载期间或运行期间实现给原程序动态添加功能(在不修改源代码的情况下),从而实现对业务逻辑的隔离,提高代码的模块化能力
      在这里插入图片描述
    • AOP的核心是动态代理,如果实现了接口,就使用JDK的动态代理,不然就使用CGLIB代理,主要应用于处理具有横切性质的系统级功能,如日志收集、事务管理、安全检查、缓存、对象池管理等

    1.2、AOP的核心概念

    • 目标对象(Target):代理的目标对象
      在这里插入图片描述
    • 切面(Aspect):类是对物体特征的抽象,切面就是对横切关注点的抽象,在Spring中,通过@Aspect注解声明当前类为切面,一般要在切面定义切入点和通知
    • 连接点(JoinPoint):被拦截的点,由于Spring只支持方法类型的连接点,所以在Spring中,连接点指的是被拦截到的方法,实际上连接点还可以是字段或者构造器
    • 切点(PointCut):带有通知的连接点,在程序中主要体现为书写切入点表达式
      在这里插入图片描述
    // 以自定义注解 @CustomLog为切点
    @Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
    public void logPcut() {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 通知(Advice):拦截到连接点之后要执行的代码,也称作增强
    • 织入(Weave):将切面/ 切面类和目标类动态接入
      编译器织入:切面在目标类编译时织入
      类加载期织入:切面在目标类加载到JVM时织入,需要特殊的类加载器,可以在目标类被引入应用之前增强该目标类的字节码,AspectJ采用编译期织入和类加载器织入
      运行期织入:切面在应用运行的某时刻被织入,一般情况下,在织入切面时,AOP容器会为目标对象动态地创建一个代理对象,这也是SpringAOP织入切面的方式
    • 增强器(advisor):筛选类中的哪些方法是连接点(哪些方法需要被拦截)
    • 引介(introduction):⼀种特殊的增强,可以动态地为类添加⼀些属性和方法

    1.3、AOP的环绕方式

    AOP有五种通知的方式:

    • 前置通知 (@Before):在切入点方法执行之前执行
    • 环绕通知 (@Around):手动调用切入点方法并对其进行增强的通知方式
    • 后置通知 (@After):在切入点方法执行之后执行,无论切入点方法内部是否出现异常,后置通知都会执行
    • 异常通知 (@AfterThrowing):在切入点方法执行之后执行,只有当切入点方法内部出现异常之后才执行
    • 返回通知 (@AfterReturning):在切入点方法执行之后执行,如果切入点方法内部出现异常将不会执行

    当有多个切面的情况下,可以通过 @Order指定先后顺序,数字越小,优先级越高

    2、AOP在项目中的运用

    2.1、日志输出

    • 在SpringBoot项目中,使用AOP 打印接口的入参和出参日志,以及执行时间
      1)引入依赖
      <parent>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-parentartifactId>
            <version>2.7.8version>
            <relativePath/> 
        parent>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
      <dependency>
          <groupId>org.springframework.bootgroupId>
          <artifactId>spring-boot-starter-aopartifactId>
      dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 2)自定义注解 作为切入点
    import java.lang.annotation.*;
    
    @Target({ElementType.METHOD})   // 指定注解使用在方法上
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface CustomLog {
    
        String info();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 3)配置AOP切面
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    
    import javax.servlet.http.HttpServletRequest;
    
    @Aspect   // 标识当前类为切面
    @Component
    public class CustomLogAspect {
    
        // getLogger(Class clazz)
        public static final Logger logger = LoggerFactory.getLogger(CustomLogAspect.class);
    
        // 以自定义注解 @CustomLog为切点
        @Pointcut("@annotation(com.example.interviewStudy.annotation.CustomLog)")
        public void logPcut() {
        }
    
        // 前置通知: 在切点之前织入
        @Before("logPcut()")
        public void doBefore(JoinPoint joinPoint) throws JsonProcessingException {
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            logger.info("========== 开始打印请求参数 ===========");
            logger.info("URL: {}", request.getRequestURL().toString());
            logger.info("HTTP Method: {}", request.getMethod());
            logger.info("Controller的全路径 和 执行方法: {} , {}方法", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
            logger.info("请求入参:{}", new ObjectMapper().writeValueAsString(joinPoint.getArgs()));
        }
    
        // 后置通知,在切入点之后织入
        @After("logPcut()")
        public void doAfter() {
            logger.info("======== 请求日志输出完毕 ========");
        }
    
        /**
         * 环绕通知: ProceedingJoinPoint对象调用proceed方法,实现 原本目标方法的调用
         *   ProceedingJoinPoint 只支持环绕通知,如果其他通知也采用ProceedingJoinPoint作为连接点,就会出现异常
         *   ==> Caused by: java.lang.IllegalArgumentException: ProceedingJoinPoint is only supported for around advice
         * */
        @Around("logPcut()")
        public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
    
            long start = System.currentTimeMillis();
    
            Object result = joinPoint.proceed();
            long end = System.currentTimeMillis();
    
            logger.info("请求结果: {}", new ObjectMapper().writeValueAsString(result));
            logger.info("请求处理耗时: {} ms", (end - start));
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 4)在接口上添加自定义注解
    @RestController
    @RequestMapping("/aop")
    public class CustomAspectController {
    
        @GetMapping("/hello")
        @CustomLog(info = "hello,使用AOP实现请求日志输出")
        public String hello(String uname) {
            return "Hello,welcome to studing AOP, your name is " + uname;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行结果:
    在这里插入图片描述

    3、JDK和CGLIB的动态代理

    • 动态代理主要有JDK动态代理和CGLIB的动态代理

    1)JDK动态代理

    • Interface:对于JDK动态代理,目标类需要实现一个Interface
    • InvocationHandler:通过实现InvocationHandler接口,定义横切逻辑,再通过反射机制(invoke)调用目标类的方法,在此过程,可能包装逻辑,对目标方法进行前置/ 后置处理
    • Proxy:利用InvocationHandler动态创建一个符合目标类实现接口的实例,生成目标类的代理对象

    我们来看⼀个常见的⼩场景,客服中转,解决⽤户问题:
    在这里插入图片描述
    代码实现:
    在这里插入图片描述

    • 接口
    public interface ISolver {
        public String solve();
    }
    
    • 1
    • 2
    • 3
    • 目标类:需要实现对应接口
    public class ProblemSolver implements ISolver{
        @Override
        public String solve() {
            System.out.println("ProblemSolver,solve方法 ==> 问题正在解决中...");
            return "OKK";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 动态代理工厂:ProxyFactory,直接用反射生成一个目标对象的代理对象,如下是用匿名内部类的方式重写了InvocationHandler的方法
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    public class ProxyFactory {
    
        // 维护一个目标对象
        private Object target;
    
        public ProxyFactory(Object target){
            this.target = target;
        }
    
        // 为目标对象生成代理对象
        public Object getProxyInstance(){
            return Proxy.newProxyInstance(
                    target.getClass().getClassLoader(),
                    target.getClass().getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("请描述您的问题:");
                            // 通过反射机制 调用目标对象方法
                            Object result = method.invoke(target, args);
                            System.out.println("问题已经得到解决!!");
                            // 返回 目标对象方法的返回值
                            return result;
                        }
                    });
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 客户端:Client,生成一个代理对象实例,通过代理对象 调用目标对象的方法
    public class Client {
        public static void main(String[] args) {
            ISolver developer = new ProblemSolver();
            // 创建代理对象实例
            ISolver instance = (ISolver) new ProxyFactory(developer).getProxyInstance();
            // 代理对象调用目标对象方法,得到目标方法的返回值并输出
            String res = instance.solve();
            System.out.println(res);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    执行结果:
    在这里插入图片描述

    2)CGLIB动态代理

    • 目标类(不需要像JDK动态代理一样实现接口):
    public class CglibSolver {
    
        public String solve(){
            System.out.println("Testing implement proxy by cglib");
            return "CglibSolver ==> solve方法";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 动态代理工厂:
    import org.springframework.cglib.proxy.Callback;
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class ProxyFactory implements MethodInterceptor, Callback {
    
        private Object target;
    
        public ProxyFactory(Object target){
            this.target = target;
        }
    
        public Object getProxyInstance(){
            Enhancer enhancer = new Enhancer();
            // 设置父类
            enhancer.setSuperclass(target.getClass());
            // 设置回调函数,用于监听当前事件
            enhancer.setCallback(this);
            // 创建子类对象代理
            return enhancer.create();
        }
    
        @Override
        public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
            System.out.println("请问有什么可以帮到您?");
            // 调用目标对象的方法
            Object result = method.invoke(target, args);
            System.out.println("问题得到解决啦哈!");
    
            return result;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 客户端:Client
    public class CgClient {
        public static void main(String[] args) {
            CglibSolver solver = new CglibSolver();
            // 创建代理对象
            CglibSolver proxy = (CglibSolver) new ProxyFactory(solver).getProxyInstance();
            // 通过代理对象实例调用目标对象方法
            String result = proxy.solve();
            System.out.println("result : " + result);
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    执行结果(代理对象替目标对象执行调用方法):
    在这里插入图片描述

    4、Spring AOP和AspectJ AOP的区别

    1) Spring AOP

    Spring AOP属于运行时增强,主要具有如下特点:

    • 基于动态代理来实现,默认如果使用接口的方式来实现,则使用JDK提供的动态代理;如果是方法,则使用CGLIB来实现
    • Spring AOP需要依赖IOC容器来管理,并且只能作用于Spring容器,使用纯Java代码实现
    • 在性能上,由于Spring AOP是基于动态代理来实现的,在容器启动时需要生成代理实例,在方法调用上也会增加栈的深度,使得Spring AOP的性能不如Aspect好

    2)AspectJ

    • AspectJ是功能强大的AOP框架,属于编译时增强,可以单独使用,也可以整合到其他框架中,是AOP编程的完全解决方案

    • AspectJ属于静态织入,通过修改代码来实现,在实际运行之前就完成了织入,生成的类没有额外运行时开销,可织入时机如下:
      A、编译期织入(Compile-time weaving):如 A类使用AspectJ添加了某属性,B类引用了A类,该场景就需要编译期进行织入,否则没法编译B类
      B、编译后织入(Post-compile weaving):在已生成了字节码/ class文件,或已经打包成jar包后,该情况需要增强,就需要使用到编译后织入
      C、类加载后织入(Load-time weaving):在加载类时进行织入

    • 两者整体对比如下:
      在这里插入图片描述

    二、事务

    1、Spring事务的种类

    Spring支持编程式事务声明式事务管理两种方式:
    1)编程式事务管理:使用TransactionTemplate,需要显示地执行事务
    2)声明式事务管理:建立在AOP之上,其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,即在目标方法开始之前启动事务,在执行完目标方法之后根据执行情况 进行提交或回滚事务

    • 优点:不需要在业务逻辑代码中掺杂事务管理的代码,只需要在配置文件中进行相关的事务规则声明或通过 @Transactional注解声明事务(以及在启动类上添加@EnableTransactionManagement注解开启事务管理),将事务规则应用到业务逻辑中,减少业务代码的侵入
    • 缺点:最细粒度只能作用到方法级别,无法做到像编程式事务那样作用到代码块级别

    2、声明式事务的失效情况

    • 1)@Transactional 应用在非public修饰的方法上
      在Spring AOP代理时,TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的intercept方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource
      的 computeTransactionAttribute方法,获取 Transactional 注解的事务配置信息,
      computeTransactionAttribute会检查目标方法的修饰符是否为public,不是public则不会获取@Transactional的属性配置信息
      在这里插入图片描述
    • 2)@Transactional 注解属性 propagation设置错误
      TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
      TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则将当前事务挂起
      TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常
      在这里插入图片描述
    • 3)@Transactional 注解属性 rollbackFor 设置错误
      rollbackFor可以指定能够触发事务回滚的异常类型,Spring默认抛出了未检查Unchecked异常(继承自RuntimeException的异常)或者Error才回滚事务,其他异常不会触发回滚事务
      在这里插入图片描述
    // 希望自定义的异常可以进行回滚
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = MyException.class)
    
    • 1
    • 2

    如果在目标方法中抛出的异常为rollbackFor指定的异常子类,事务同样回滚

    • 4)事务方法被同类中的其他方法调用
      开发中避免不了会对同一个类中的方法调用,比如
      当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理(使用Spring AOP动态代理造成)
      ==》 举例来说:
      Test类中的方法A调用本类的方法B(无论方法B是用public还是private修饰),B方法有声明注解事务,但A方法没有声明注解事务,当外部调用方法A后,方法B的事务不会起作用
      如果B方法内部抛了异常,而A方法此时通过try-catch捕获了B方法的异常,则该事务就不能正常回滚,会抛出异常:
    org.springframework.transaction.UnexpectedRollbackException:Transaction rolled back because i t has been marked a s rollback - only
    
    • 1

    3、声明式事务的实现原理

    4、Spring事务的隔离级别

    • Spring的接口TransactionDefinition定义了表示隔离级别的常量,主要是对应后端数据库的事务隔离级别:
    • ISOLATION_DEFAULT:使用后端数据库默认的隔离级别,MySQL默认可重复读,Oracle默认读已提交
    • ISOLATION_READ_UNCOMMITTED:读未提交
    • ISOLATION_READ_COMMITTED:读已提交
    • ISOLATION_REPEATABLE_READ:可重复读
    • ISOLATION_SERIALIZABLE:串行化

    5、Spring事务的传播机制

    未完待续…

    三、MVC

    1、SpringMVC的工作流程

    在这里插入图片描述

    • 1)客户端向服务端发送一次请求,该请求会先到前端控制器/ 中央控制器 DispatcherServlet
    • 2)DispatcherServlet 接收到请求后会调用HandlerMapping处理器映射器,由此得知,该请求由哪个Controller来处理(此时并不调用Controller)
    • 3)DispatcherServlet调用HandlerAdapter处理器适配器,告诉处理器适配器应该要去执行哪个Controller
    • 4)HandlerAdapter处理器适配器去执行Controller并得到ModelAndView(数据和视图),并层层返回给DispatcherServlet
    • 5)DispatcherServlet 将 ModelAndView 交给 ViewResolver视图解析器解析,然后返回真正的视图
    • 6)DispatcherServlet将模型数据填充到视图中
    • 7)DispatcherServlet将结果响应给客户端

    SpringMVC虽然整体流程复杂,但大部分的组件不需要开发人员创建和管理,只需要通过配置文件的方式完成配置即可,真正需要开发人员处理的只有Handler(Controller)、View、Model

    2、SpringMVC的核心组件

    • 1)DispatcherServlet:前置控制器,是整个流程控制的核心,控制其他组件的执行,进行统一调度,降低组件之间的耦合性,相当于总指挥
    • 2)Handler:处理器,完成具体的业务逻辑,相当于Servlet或Action
    • 3)HandlerMapping:DispatcherServlet接收到请求后,通过HandlerMapping将不同的请求映射到不同的Handler
    • 4)HandlerInterceptor:处理拦截器的接口,如果需要完成一些拦截处理,可以实现该接口
    • 5)HandlerExecutionChain:处理器执行链,包括两部分内容:Handler 和 HandlerInterceptor (系统有默认的HandlerInterceptor,如果需要额外设置拦截,可以添加拦截器)
    • 6)HandlerAdapter:处理器适配器,Handler执行业务方法之前,需要进行一系列的操作,包括表单数据的验证、数据类型的转换、将表单数据封装到JavaBean等,这些操作都是由HandlerAdapter来完成,开发者只需要关注于业务逻辑的处理即可,DispatcherServlet通过HandlerAdapter执行不同的Handler
    • 7)ModelAndView:装载了模型数据和视图信息,作为Handler的处理结果,返回给DispatcherServlet
    • 8)ViewResolver:视图解析器,DispatcherServlet通过视图解析器将逻辑视图 解析为物理视图,最终将渲染结果响应给客户端

    3、SpringMVC Restful风格的接口流程

    • Restful接口,响应格式是json,需要使用@ResponseBody注解:
    @GetMapping("/user")
    @ResponseBody
    public User user() {
        return new User(1,"张三");
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述

    • 加入@ResponseBody注解后,整体流程和ModelAndView大致相同,只是在细节上有所不同:
      1)客户端向服务端发送请求,该请求会先到达前端控制器DispatcherServlet
      2)DispatcherServlet接收到请求后会调用HandlerMapping处理器映射器,由此得知,该请求应由哪个Controller来处理
      3)DispatcherServlet 调用HandlerAdapter处理器适配器,告知处理器适配器应该要去执行哪个Controller
      4)Controller被封装成了 ServletInvocableHandlerMethod,HandlerAdapter处理器适配器去执行invokeAndHandle方法,完成对Controller的请求处理
      5)HandlerAdapter执行完对Controller的请求,会调用HandlerMethodReturnValueHandler去处理返回值,主要的过程如下:

    • A、调用RequestResponseBodyMethodProcessor,创建ServletServerHttp实例(该实例是Spring对原生ServerHttpResponse的封装)

    • B、使用HttpMessageConverter的write方法,将返回值写入ServletServerHttpResponse的OutputStream 输出流中

    • C、在写入的过程中,使用JsonGenerator(默认使用Jackson)对返回值进行Json序列化
      6)执行完请求后,返回的ModelAndView为null,ServletServerHttpResponse中也写入响应,所以不用关心View的处理

    四、SpringBoot

    1、介绍下SpringBoot

    • Spring Boot本身不提供Spring框架的核心特性和扩展功能,而是用于快速、敏捷地开发新一代基于Spring框架的应用程序,是和Spring紧密结合,用于提升Spring开发者体验的工具
    • Spring Boot以约定大于配置的思想来实现,相比Spring的优势如下:
      1)Spring Boot可以快速地创建独立的Spring应用程序
      2)Spring Boot内嵌了如Tomcat、Jetty 和 Undertow等容器,可以直接运行,不需要再部署
      3)Spring Boot无需再像Spring使用一堆繁琐的xml文件配置,而改为使用Java配置,将bean注入改为使用注解注入的方式(如@Autowired),并将多个xml、properties配置浓缩在一个application.yml配置文件中
      4)Spring Boot可以快速整合常用依赖(开发库,如spring-webmvc、jackson-json)、validation-api 和Tomcat等,提供的pom可以简化Maven配置,当开发者引入核心依赖时,SpringBoot会自动引入其他依赖

    2、SpringBoot自动配置原理

    • 启动类上添加的@SpringBootApplication注解是一个复合注解,包含@EnableAutoConfiguration
      在这里插入图片描述
    • 由上图可见,自动装配核心功能 实际上是通过AutoConfigurationImportSelector类实现的
    package org.springframework.boot.autoconfigure;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Inherited;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
    import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
    import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
    import org.springframework.context.annotation.Conditional;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.core.io.support.SpringFactoriesLoader;
    
    /**
     * @see ConditionalOnBean
     * @see ConditionalOnMissingBean
     * @see ConditionalOnClass
     * @see AutoConfigureAfter
     * @see SpringBootApplication
     */
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Inherited
    @AutoConfigurationPackage
    @Import(AutoConfigurationImportSelector.class)   // 加载自动装配类
    public @interface EnableAutoConfiguration {
    
    	/**
    	 * 当自动配置生效时,可用于重写环境属性
    	 */
    	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    
    	/**
    	 * 排除那些没有被使用的、具体的自动配置类
    	 * @return the classes to exclude
    	 */
    	Class<?>[] exclude() default {};
    
    	/**
    	 * 排除那些没有被使用的、具体的自动配置类的类名
    	 * @return the class names to exclude
    	 */
    	String[] excludeName() default {};
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • AutoConfigurationImportSelector实现了ImportSelector接口,用来收集需要导入的配置类,配合@Import注解就可以将相应的类导入到Spring容器中
    • 获取注入类的方法是selectImports方法,实际调用的是getAutoConfigurationEntry,该方法是获取自动装配类的关键,主要流程可以分为如下几步:
      1)获取注解的属性,用于后面的排除
      2)获取所有需要自动装配的配置类的路径(从 META-INF/spring.factories 获取自动配置类的路径
      3)去掉重复的配置类和需要排除的重复类,把需要自动加载的配置类的路径存储起来
        @Override
    	public String[] selectImports(AnnotationMetadata annotationMetadata) {
    		if (!isEnabled(annotationMetadata)) {
    			return NO_IMPORTS;
    		}
    		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(annotationMetadata);
    		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    	/**
    	 * @return the auto-configurations that should be imported
    	 *         返回需要导入的自动装配
    	 */
    	protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    		if (!isEnabled(annotationMetadata)) {
    			return EMPTY_ENTRY;
    		}
    		// 1.获取到注解的属性
    		AnnotationAttributes attributes = getAttributes(annotationMetadata);
    		// 2.获取需要自动装配的所有配置类  ==》读取META-INF/spring.factories,读取自动配置类的路径
    		List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    	    // 3.1、移除重复的配置
    		configurations = removeDuplicates(configurations);
    		// 3.2、处理需要排除的配置
    		Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    		checkExcludedClasses(configurations, exclusions);
    		configurations.removeAll(exclusions);
    		configurations = getConfigurationClassFilter().filter(configurations);
    		fireAutoConfigurationImportEvents(configurations, exclusions);
    		return new AutoConfigurationEntry(configurations, exclusions);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    3、如何自定义SpringBoot Starter

    4、SpringBoot启动原理

    • SpringApplication 所做事情如下:
      1)推断应用类型是普通的项目还是Web项目
      2)查找并加载所有可用初始化器,设置到initializers属性中
      3)查找所有的应用程序监听器,设置到listeners属性中
      4)推断并设置main方法的定义类,找到运行主类
      SpringBoot启动大致流程如下:
      在这里插入图片描述
  • 相关阅读:
    LVGL---按钮(lv_btn)
    在win10中下载桌面版的docker并在docker中搭建运行基于linux的容器
    多级缓存(笔记)
    使用playright自动下载vscode已安装插件
    C语言实现根据用户输入的整数求和(两种方法)
    【AT模式连接ONENET】ONENET可视化平台的使用
    Win11快速助手在哪里?Win11打开快速助手的方法
    nvm的下载与使用
    vue的孙组件获取祖组件数据的方法(this.$parent)
    Egg 1. 快速开始 Quick Start 1.3 一步步 Step by Step 1.3.3 添加静态资源 & 1.3.4 添加渲染模板
  • 原文地址:https://blog.csdn.net/FlyingFish868/article/details/133325128