1 Spring

1.1 [1]IOC定义

01.说明
    a.SpringlOC容器:通过容器来实现对象组件的装配和管理
        a.创建对象
        b.给对象的属性赋值
    b.IOC优点
        它将最小化应用程序中的代码量。
        它以最小的影响和最少的侵入机制促进松耦合。
        它支持即时的实例化和延迟加载 Bean 对象。
        它将使您的应用程序易于测试,因为它不需要单元测试用例中的任何单例或 JNDI 查找机制。
    c.IoC的实现机制
        工厂模式 + 反射机制

02.DI是IOC的一种具体实现方式
    a.SpringIOC发展史
        new -> 简单工厂 -> SpringIOC控制反转,创建->拿,更名为“DI(依赖注入)”
        <1>Spring中“直接利用接口方法的多态性”来“产生不同的对象”
        <2>Spring中“利用工厂中使用接口方法的多态性”来“产生不同的对象”,再由Student使用“工厂”获取“不同的对象”
        <3>先向IOC容器中注入对象,再Spring中利用“工厂”获取“IOC容器的对像”,再通过“接口方法的多态性”获取不同的对象
        <4>直接从IOC容器中获取对象,再通过“接口方法的多态性”获取不同的对象
        IOC/DI,无论要什么对象,都可以直接去SpringIOC容器中获取,而不需要自己操作(new\setXxx)
        因此,之后IOC可以分为2步:1.先给springIOC中存放对象并赋值【依赖注入】 2.拿【控制反转】
        -----------------------------------------------------------------------------------------------------
        阶段一:类 -> new      ->对象
        阶段二:类 -> 工厂模式 ->对象:虽然可以解耦合,但问题是,需要自己编写工厂
        阶段三:类 -> IOC      ->对象:IOC提供一个超级工厂(注入对象,XML/注解)
    b.DI(依赖注入)是IOC(控制反转)的一种具体实现方式:
        DI就是取Bean,从Spring容器中获取Bean实例,并将其注入到需要的地方
        a.基本方法
            通过ApplicationContext或BeanFactory获取Bean实例
        b.注解方法
            属性注入
            setter注入
            构造方法注入
            注:@Autowired可以用于属性注入、setter注入、构造方法注入的依赖注入形式

1.2 [1]IOC实现原理

01.介绍
    a.说明
        IoC(Inversion of Control)即控制(权)反转,它是一种编程思想,它的核心理念是将对象的创建和管理权力从对象本身转移到外部的容器或框架。
        IoC 的主要目的是降低代码之间的耦合度,提高代码的重用性、可测试性和灵活性。在 IoC 模式下,对象不需要自己创建或者查找它们所依赖的对象,这些工作由外部的容器(Spring)完成。
        IoC 实现方式有很多种,例如依赖注入(DI)或依赖查找等,但 DI 是实现 IoC 的一种常见实现方法,它通过将依赖项注入到对象中来实现控制反转。
    b.总结
        Spring 中的 IoC 底层是通过【工厂模式+反射】实现的

02.底层原理
    a.第1步
        通过以下代码初始化 IoC 容器
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    b.第2步
        之后会创建一个工厂类,工厂类中有一个创建 Bean 的方法 createBean。
    c.第3步
        createBean 中首先会通过读取配置文件,获取到全类名
        <beans>
          <bean id="myBean" class="com.example.MyBean" />
        </beans>
    d.第4步
        之后通过反射,将获取到的全类名进行加载,创建对象存放到 IoC 容器中。
    e.第5步
        当有代码使用了 DI 时,从容器中找到(根据类名或类型查找)此实例进行使用
        @Component
        public class MyBean {

            @Autowired
            private MyBean myBean;
            public void doSomething() {
                System.out.println("Bean: " + myBean);
            }
        }

1.3 [1]IOC两种容器

01.Spring BeanFactory 容器
    BeanFactory ,就像一个包含 Bean 集合的工厂类。它会在客户端要求时实例化 Bean 对象。

02.Spring ApplicationContext 容器
    ApplicationContext 接口扩展了 BeanFactory 接口,它在 BeanFactory 基础上提供了一些额外的功能。内置如下功能:
    MessageSource :管理 message ,实现国际化等功能。
    ApplicationEventPublisher :事件发布。
    ResourcePatternResolver :多资源加载。
    EnvironmentCapable :系统 Environment(profile + Properties)相关。
    Lifecycle :管理生命周期。
    Closable :关闭,释放资源
    InitializingBean:自定义初始化。
    BeanNameAware:设置 beanName 的 Aware 接口。

03.区别
    BeanFactory                  ApplicationContext
    它使用懒加载                 它使用即时加载
    它使用语法显式提供资源对象    它自己创建和管理资源对象
    不支持国际化                 支持国际化
    不支持基于依赖的注解          支持基于依赖的注解
    另外,BeanFactory 也被称为低级容器,而 ApplicationContext 被称为高级容器。

1.4 [1]IOC容器启动/Bean管理过程

00.分为四个主要阶段
    阶段1:启动                            加载配置文件、创建容器
    阶段2:Bean定义注册                    解析BeanDefinitions、注册BeanDefinitions
    阶段3:实例化和依赖注入                实例化、依赖注入
    阶段4:初始化                          BeanPostProcessor处理、Aware接口调用、初始化方法调用、代理切面处理、发布事件、完成启动

01.阶段1:启动
    a.加载配置文件
        Spring启动时首先会读取配置文件(如XML配置文件、Java配置类等),包括配置数据库连接、事务管理、AOP配置等。
    b.创建容器
        Spring创建IOC容器(如BeanFactory、ApplicationContext),准备加载和管理Bean。

02.阶段2:Bean定义注册
    a.解析BeanDefinitions
        Spring容器会解析配置文件中的BeanDefinitions,即声明的Bean元数据,包括Bean的作用域、依赖关系等信息。
    b.注册BeanDefinitions
        BeanDefinitionReader读取解析配置中的Bean定义,并将其注册到容器中,形成BeanDefinition对象。

03.阶段3:实例化和依赖注入
    a.实例化
        Spring根据BeanDefinitions实例化Bean对象,将其放入容器管理。
    b.依赖注入
        Spring进行依赖注入,将Bean之间的依赖关系进行注入,包括构造函数注入、Setter注入或字段注入。

04.阶段4:初始化
    a.BeanPostProcessor处理
        容器定义了很多BeanPostProcessor,处理其中的自定义逻辑,例如postProcessBeforeInitialization会在Bean初始化前调用,postProcessAfterInitialization则在之后调用。
    b.Aware接口调用
        如果Bean实现了Aware接口(如BeanNameAware、BeanFactoryAware),Spring会回调这些接口,传递容器相关信息。
    c.初始化方法调用
        调用Bean的初始化方法(如通过@PostConstruct注解标注的方法,或实现InitializingBean接口的bean会被调用afterPropertiesSet方法)。
    d.代理切面处理
        Spring根据配置注册AOP切面,生成代理对象,将切面织入到目标对象中。
    e.发布事件
        Spring可能会在启动过程中发布一些事件,比如容器启动事件。
    f.完成启动
        当所有Bean初始化完毕、依赖注入完成、AOP配置生效等都准备就绪时,Spring容器启动完成。

1.5 [2]AOP定义

01.什么是AOP
    a.OOP,Object-Oriented Programming
        面向对象编程,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。
    b.AOP,Aspect-Oriented Programming
        面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,
        但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为“切面”(Aspect)
        减少系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。可用于权限认证、日志、事务处理等。

02.面向对象/面向切面
    a.面向对象缺陷
        1.方法名:父类一改,其他子类都得跟上改动
        2.编写逻辑:父类一改,其他子类都得跟上改动
    b.面向切面
        1.方法名:采取引用x()
        2.编写逻辑:切入点,只需要声明-每次调用dd后自动执行x3()

1.6 [2]AOP场景

01.AOP应用场景
    场景一:记录日志
    场景二:监控方法运行时间(监控性能)
    场景三:权限控制
    场景四:缓存优化(第一次调用查询数据库,将查询结果放入内存对象,第二次调用,直接从内存对象返回,不需要查询数据库)
    场景五:事务管理(调用方法前开启事务,调用方法后提交关闭事务,如声明式事务)
    场景六:注解解析(项目中涉及注解的场景,大部分都利用AOP进行解析)

1.7 [2]AOP名词:7个

01.7个名词
    a.切面(Aspect):切面是通知和切点的结合
        通知和切点共同定义了切面的全部内容。
        在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
        -----------------------------------------------------------------------------------------------------
        aspect 由 pointcount 和 advice 组成,切面是通知和切点的结合。 它既包含了横切逻辑的定义, 也包括了连接点的定义.
        Spring AOP 就是负责实施切面的框架, 它将切面所定义的横切逻辑编织到切面所指定的连接点中.
        AOP 的工作重心在于如何将增强编织目标对象的连接点上, 这里包含两个工作:
        如何通过 pointcut 和 advice 定位到特定的 joinpoint 上
        如何在 advice 中编写切面代码.
        -----------------------------------------------------------------------------------------------------
        可以简单地认为,使用 @Aspect 注解的类就是切面.
    b.连接点(Join point):指方法
        在Spring AOP中,一个连接点 总是 代表一个方法的执行。
        应用可能有数以千计的时机应用通知。这些时机被称为连接点。
        连接点是在应用执行过程中能够插入切面的一个点。
        这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。
        切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
    c.通知(Advice)
        在AOP术语中,切面的工作被称为通知。
    d.切入点(Pointcut)
        切点的定义会匹配通知所要织入的一个或多个连接点。
        我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
    e.引入(Introduction)
        引入允许我们向现有类添加新方法或属性。
    f.目标对象(Target Object)
        被一个或者多个切面(aspect)所通知(advise)的对象。
        它通常是一个代理对象。也有人把它叫做 被通知(adviced)对象。
        既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied)对象。
    g.织入(Weaving)
        织入是把切面应用到目标对象并创建新的代理对象的过程。在目标对象的生命周期里有多少个点可以进行织入:

1.8 [2]AOP通知:5个

00.AOP通知类型
    前置通知(Before):在目标方法被调用之前调用通知功能;
    后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
    返回通知(After-returning ):在目标方法成功执行之后调用通知;
    异常通知(After-throwing):在目标方法抛出异常后调用通知;
    环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。

01.前置通知(Before)
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class LoggingAspect {

        @Before("execution(* com.example.service.*.*(..))")
        public void beforeAdvice() {
            System.out.println("Before method execution");
        }
    }

02.后置通知(After)
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class LoggingAspect {

        @After("execution(* com.example.service.*.*(..))")
        public void afterAdvice() {
            System.out.println("After method execution");
        }
    }

03.返回通知(After-returning)
    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class LoggingAspect {

        @AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
        public void afterReturningAdvice(Object result) {
            System.out.println("After returning: " + result);
        }
    }

04.异常通知(After-throwing)
    import org.aspectj.lang.annotation.AfterThrowing;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class LoggingAspect {

        @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
        public void afterThrowingAdvice(Throwable error) {
            System.out.println("After throwing: " + error);
        }
    }

05.环绕通知(Around)
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    @Aspect
    @Component
    public class LoggingAspect {

        @Around("execution(* com.example.service.*.*(..))")
        public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("Before method execution");
            Object result = joinPoint.proceed();
            System.out.println("After method execution");
            return result;
        }
    }

1.9 [2]AOP三件套:3个

00.汇总
    AopContext
    AopUtils
    ReflectionUtils

01.AopContext
    a.说明
        AopContext 是 Spring 框架中的一个类,它提供了对当前 AOP 代理对象的访问,以及对目标对象的引用
        AopContext 主要用于获取当前代理对象的相关信息,以及在 AOP 代理中进行一些特定的操作
    b.方法
        getTargetObject(): 获取当前代理的目标对象
        currentProxy(): 获取当前的代理对象
    c.代码
        public void noTransactionTask(String keyword){    // 注意这里 调用了代理类的方法
            ((YourClass) AopContext.currentProxy()).transactionTask(keyword);
        }

        @Transactional
        void transactionTask(String keyword) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {        //logger
                //error tracking
            }
            System.out.println(keyword);
        }
    d.说明
        同一个类中两个方法,noTransactionTask 方法调用 transactionTask 方法,为了使事务注解不失效
        就可以使用 AopContext.currentProxy() 去获取当前代理对象

02.AopUtils
    a.说明
        AopUtils 提供了一些静态方法来处理与 AOP 相关的操作,如获取代理对象、获取目标对象、判断代理类型等
    b.方法
        getTargetObject(): 从代理对象中获取目标对象
        isJdkDynamicProxy(Object obj): 判断是否是 JDK 动态代理
        isCglibProxy(Object obj): 判断是否是 CGLIB 代理
    c.代码
        import org.springframework.aop.framework.AopProxyUtils;
        import org.springframework.aop.support.AopUtils;

        public class AopUtilsExample {
            public static void main(String[] args) {
                MyService myService = ...
                // 假设 myService 已经被代理
                if (AopUtils.isCglibProxy(myService)) {
                    System.out.println("这是一个 CGLIB 代理对象");
                }
            }
        }

03.ReflectionUtils
    a.说明
        ReflectionUtils 提供了一系列反射操作的便捷方法,如设置字段值、获取字段值、调用方法等
        这些方法封装了 Java 反射 API 的复杂性,使得反射操作更加简单和安全
    b.方法
        makeAccessible(Field field): 使私有字段可访问
        getField(Field field, Object target): 获取对象的字段值
        invokeMethod(Method method, Object target, Object... args): 调用对象的方法
    c.代码
        import org.springframework.util.ReflectionUtils;

        import java.lang.reflect.Field;
        import java.util.Map;

        public class ReflectionUtilsExample {
            public static void main(String[] args) throws Exception {
                ExampleBean bean = new ExampleBean();
                bean.setMapAttribute(new HashMap<>());

                Field field = ReflectionUtils.findField(ExampleBean.class, "mapAttribute");
                ReflectionUtils.makeAccessible(field);

                Object value = ReflectionUtils.getField(field, bean);
                System.out.println(value);
            }

            static class ExampleBean {
                private Map<String, String> mapAttribute;

                public void setMapAttribute(Map<String, String> mapAttribute) {
                    this.mapAttribute = mapAttribute;
                }
            }
        }

1.10 [2]AOP工作流程:4步

00.汇总
    定义切面:开发者使用 @Aspect 注解定义切面类,并在其中定义通知和切入点
    创建代理对象:Spring AOP 在应用启动时扫描切面配置,并为目标对象创建代理对象
    方法调用拦截:当代理对象的方法被调用时,Spring AOP 拦截调用,并根据切面配置执行相应的通知
    执行目标方法:在执行通知后,代理对象调用目标方法,并返回结果

01.定义切面
    a.说明
        使用 @Aspect 注解定义切面类,并在其中定义通知和切入点
    b.代码
        package com.example.aspect;

        import org.aspectj.lang.annotation.Aspect;
        import org.aspectj.lang.annotation.Before;
        import org.springframework.stereotype.Component;

        @Aspect
        @Component
        public class LoggingAspect {

            // 定义切入点和前置通知
            @Before("execution(* com.example.service.UserService.getUserById(..))")
            public void beforeAdvice() {
                System.out.println("Before method execution");
            }
        }

02.创建代理对象
    a.说明
        Spring AOP 在应用启动时扫描切面配置,并为目标对象创建代理对象
        这个过程是自动完成的,开发者不需要手动创建代理对象
    b.代码
        package com.example.service;

        import org.springframework.stereotype.Service;

        @Service
        public class UserService {

            public String getUserById(Long id) {
                System.out.println("Executing getUserById method");
                return "User" + id;
            }
        }

03.方法调用拦截
    a.说明
        当代理对象的方法被调用时,Spring AOP 拦截调用,并根据切面配置执行相应的通知
    b.代码
        package com.example.controller;

        import com.example.service.UserService;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.web.bind.annotation.GetMapping;
        import org.springframework.web.bind.annotation.PathVariable;
        import org.springframework.web.bind.annotation.RestController;

        @RestController
        public class UserController {

            @Autowired
            private UserService userService;

            @GetMapping("/user/{id}")
            public String getUser(@PathVariable Long id) {
                return userService.getUserById(id);
            }
        }

04.执行目标方法
    a.说明
        在执行通知后,代理对象调用目标方法,并返回结果
    b.代码
        当你启动应用并访问 /user/1,控制台输出将显示:
        Before method execution
        Executing getUserById method

1.11 [2]AOP实现方式:2步

01.AOP两步过程
    a.第1步:动态生成代理类
        一个类被 AOP 织入增强后,就产生了一个结果类,它融合了原类和增强逻辑的代理类 。
        根据不同的代理方式,代理类可能是与原类具有相同接口的类,也可能是原类的子类。
    b.第2步:织入
        织入是将增强添加到目标的具体连接点上的过程 。
        编译期织入【特殊的Java编译器】
        类装载期织入【特殊的类装载器】
        动态代理织入【在运行期为目标类添加增强生成子类的方式】
    d.总结
        第1步:动态生成代理类,创建一个新的代理类,这个代理类包含了原类的功能和增强的逻辑。
        第2步:织入,可以通过注解方式来实现,@Aspect、@Before、@AfterReturning、@AfterThrowing、@After、@Around

02.Spring如何使用AOP切面 / Spring AOP的实现方式?
    第一种:实现接口+XML
    第二种:普通类+XML
    第三种:注解
            @Aspect           表示当前类是个切面类
            @Before           前置通知
            @AfterReturning   返回通知
            @AfterThrowing    异常通知
            @After            后置通知
            @Around           环绕通知

1.12 [2]SpringAOP动态代理

00.总结
    a.两种方式
        第1种:JDK动态代理
        第2种:CGLIB动态代理
    b.选择使用哪种代理方式
        JDK动态代理:【实现了接口的类】,基于接口实现。
        CGLIB动态代理:【没有实现接口的类】,基于子类实现。
        -----------------------------------------------------------------------------------------------------
        Spring AOP会根据目标类的情况自动选择合适的代理方式,开发者也可以通过配置强制使用某种代理方式。

01.JDK动态代理
    a.介绍
        JDK动态代理是基于Java的反射机制实现的,它只能代理实现了接口的类。
        JDK动态代理通过`java.lang.reflect.Proxy`类和`java.lang.reflect.InvocationHandler`接口来创建代理对象。
    b.特点
        只能代理实现了接口的类。
        代理类和目标类都实现相同的接口。
        代理类在运行时动态生成。
    c.示例
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        import java.lang.reflect.Proxy;

        public class JdkDynamicProxyExample {
            public interface Service {
                void perform();
            }

            public static class ServiceImpl implements Service {
                @Override
                public void perform() {
                    System.out.println("Service is performing...");
                }
            }

            public static class ServiceInvocationHandler implements InvocationHandler {
                private final Object target;

                public ServiceInvocationHandler(Object target) {
                    this.target = target;
                }

                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("Before method call");
                    Object result = method.invoke(target, args);
                    System.out.println("After method call");
                    return result;
                }
            }

            public static void main(String[] args) {
                Service target = new ServiceImpl();
                Service proxy = (Service) Proxy.newProxyInstance(
                        target.getClass().getClassLoader(),
                        target.getClass().getInterfaces(),
                        new ServiceInvocationHandler(target)
                );

                proxy.perform();
            }
        }

02.CGLIB动态代理
    a.介绍
        CGLIB(Code Generation Library)动态代理是通过生成目标类的子类来实现的,它可以代理没有实现接口的类。
        CGLIB动态代理通过继承目标类并覆盖其方法来创建代理对象
    b.特点
        可以代理没有实现接口的类。
        代理类是目标类的子类。
        代理类在运行时动态生成。
    c.示例
        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 CglibDynamicProxyExample {
            public static class Service {
                public void perform() {
                    System.out.println("Service is performing...");
                }
            }

            public static class ServiceMethodInterceptor implements MethodInterceptor {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    System.out.println("Before method call");
                    Object result = proxy.invokeSuper(obj, args);
                    System.out.println("After method call");
                    return result;
                }
            }

            public static void main(String[] args) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(Service.class);
                enhancer.setCallback(new ServiceMethodInterceptor());

                Service proxy = (Service) enhancer.create();
                proxy.perform();
            }
        }

1.13 [2]SpringAOP与AspectJAOP区别

01.Spring AOP、AspectJ AOP有什么区别
    a.介绍
        AOP实现的关键在于【代理模式】
        AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ;动态代理则以Spring AOP为代表。
    b.Spring AOP
        Spring AOP使用的【动态代理】,
        所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,
        这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。
    c.AspectJ AOP
        AspectJ是【静态代理的增强】,
        所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,
        因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

1.14 [3]Bean定义

01.Bean定义
    由Spring IOC容器管理的对象称为 Bean
    Bean 由 Spring IoC 容器实例化,配置,装配和管理。
    我们可以把 Spring IoC 容器看作是一个大工厂,Bean 相当于工厂的产品。

1.15 [3]Bean作用域:5个

01.Bean范围/作用域
    singleton(单例作用域):唯一 bean 实例,Spring 中的 bean 默认都是单例的。
    prototype(原型作用域):每次请求都会创建一个新的 bean 实例。
    request(请求作用域):每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP request 内有效。
    session(会话作用域):每一次 HTTP 请求都会产生一个新的 bean,该 bean 仅在当前 HTTP session 内有效。
    global-session(全局会话作用域):仅仅在基于 Portlet 的 web 应用中才有意义,Spring5 已经没有了。Portlet 是能够生成语义代码(例如:HTML)片段的小型 Java Web 插件。它们基于 portlet 容器,可以像 servlet 一样处理 HTTP 请求。但是,与 servlet 不同,每个 portlet 都有不同的会话。

1.16 [3]Bean扩展点:7个

00.汇总
    扩展点                                 执行时机                       适用场景
    BeanPostProcessor                      初始化前后                     代理、属性修改
    InitializingBean                       属性填充后                     初始化逻辑
    @Postconstruct                         属性填充后                     简洁初始化
    init-method                            属性填充后                     配置式初始化
    SmartInitializingSingleton             所有单例初始化后               全局逻辑
    ApplicationContextAware                属性填充时                     容器操作
    BeanDefinitionRegistryPostProcessor    Bean定义加载后                 动态定义修改

01.BeanPostProcessor 接口
    a.内容
        BeanPostProcessor 是 Spring 提供的最强大、最常用的扩展点之一
        它允许在 Bean 实例化后、初始化前后插入自定义逻辑
    b.方法
        a.postProcessBeforeInitialization
            在初始化方法(如 afterPropertiesSet 或 init-method)之前调用
        b.postProcessAfterInitialization
            在初始化方法之后调用
    c.使用场景
        修改 Bean 的属性
        代理 Bean(如 AOP 代理)
    d.示例
        import org.springframework.beans.factory.config.BeanPostProcessor;
        import org.springframework.stereotype.Component;

        @Component
        public class CustomBeanPostProcessor implements BeanPostProcessor {
            @Override
            public Object postProcessBeforeInitialization(Object bean, String beanName) {
                System.out.println("Before Initialization: " + beanName);
                return bean;
            }

            @Override
            public Object postProcessAfterInitialization(Object bean, String beanName) {
                System.out.println("After Initialization: " + beanName);
                return bean;
            }
        }
    e.注意
        返回的 Bean 可以是原始 Bean,也可以是包装后的代理对象

02.InitializingBean 接口
    a.内容
        InitializingBean 接口允许 Bean 在属性填充完成后执行自定义初始化逻辑
    b.方法
        a.afterPropertiesSet
            在依赖注入完成后调用
    c.使用场景
        初始化资源(如数据库连接)
        验证注入的属性
    d.示例
        import org.springframework.beans.factory.InitializingBean;
        import org.springframework.stereotype.Component;

        @Component
        public class MyBean implements InitializingBean {
            private String name;

            public void setName(String name) {
                this.name = name;
            }

            @Override
            public void afterPropertiesSet() throws Exception {
                if (name == null) {
                    throw new IllegalStateException("Name must be set");
                }
                System.out.println("InitializingBean: " + name);
            }
        }

03.@PostConstruct 注解
    a.内容
        @PostConstruct 是 Java EE 提供的注解,被 Spring 集成用于标记初始化方法
        它在依赖注入完成后、Bean 初始化阶段执行
    b.使用场景
        替代 InitializingBean,代码更简洁
        初始化资源或执行启动逻辑
    c.示例
        import jakarta.annotation.PostConstruct;
        import org.springframework.stereotype.Component;

        @Component
        public class MyService {
            @PostConstruct
            public void init() {
                System.out.println("PostConstruct: Bean initialized");
            }
        }
    d.注意
        与 InitializingBean 相比,@PostConstruct 无需实现接口,更灵活

04.init-method 属性
    a.内容
        通过 XML 配置或 @Bean 注解,可以为 Bean 指定一个自定义的初始化方法
    b.使用场景
        在不修改 Bean 类源码的情况下定义初始化逻辑
        适用于遗留代码集成
    c.示例(XML 配置)
        <bean id="myBean" class="com.example.MyBean" init-method="customInit">
            <property name="name" value="Spring"/>
        </bean>
        -----------------------------------------------------------------------------------------------------
        public class MyBean {
            private String name;

            public void setName(String name) {
                this.name = name;
            }

            public void customInit() {
                System.out.println("init-method: " + name);
            }
        }
    d.示例(Java 配置)
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class AppConfig {
            @Bean(initMethod = "customInit")
            public MyBean myBean() {
                MyBean bean = new MyBean();
                bean.setName("Spring");
                return bean;
            }
        }

05.SmartInitializingSingleton 接口
    a.内容
        SmartInitializingSingleton 适用于所有单例 Bean 初始化完成后执行全局逻辑
    b.方法
        a.afterSingletonsInstantiated
            在所有单例 Bean 初始化完成后调用
    c.使用场景
        检查所有 Bean 的状态
        执行全局启动任务
    d.示例
        import org.springframework.beans.factory.SmartInitializingSingleton;
        import org.springframework.stereotype.Component;

        @Component
        public class GlobalInitializer implements SmartInitializingSingleton {
            @Override
            public void afterSingletonsInstantiated() {
                System.out.println("All singletons initialized");
            }
        }

06.ApplicationContextAware 和 BeanFactoryAware
    a.内容
        这些接口允许 Bean 获取 Spring 容器上下文,从而在初始化时执行与容器相关的操作
    b.方法
        a.setApplicationContext
            获取 ApplicationContext
        b.setBeanFactory
            获取 BeanFactory
    c.使用场景
        动态获取其他 Bean
        操作容器资源
    d.示例
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.ApplicationContextAware;
        import org.springframework.stereotype.Component;

        @Component
        public class ContextAwareBean implements ApplicationContextAware {
            private ApplicationContext context;

            @Override
            public void setApplicationContext(ApplicationContext context) {
                this.context = context;
                System.out.println("ApplicationContext set: " + context.getId());
            }
        }

07.BeanDefinitionRegistryPostProcessor
    a.内容
        BeanDefinitionRegistryPostProcessor 允许在 Bean 定义加载后、实例化之前修改 Bean 定义
    b.方法
        a.postProcessBeanDefinitionRegistry
            修改 Bean 定义
        b.postProcessBeanFactory
            修改 Bean 工厂
    c.使用场景
        动态注册 Bean
        修改现有 Bean 定义
    d.示例
        import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
        import org.springframework.beans.factory.support.BeanDefinitionRegistry;
        import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
        import org.springframework.stereotype.Component;

        @Component
        public class CustomBeanDefinitionProcessor implements BeanDefinitionRegistryPostProcessor {
            @Override
            public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
                System.out.println("Processing Bean Definitions");
            }

            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
                System.out.println("Processing Bean Factory");
            }
        }

1.17 [3]Bean生命周期:5个

01.生命周期
    a.实例化
        创建 Bean 实例,通过构造方法或工厂方法
    b.属性填充
        注入依赖,如 @Autowired 字段
    c.初始化
        执行初始化逻辑,如调用 afterPropertiesSet 或自定义 init 方法
    d.使用
        Bean 投入使用
    e.销毁
        容器关闭时销毁 Bean

02.工作原理
    1.解析类得到 BeanDefinition
    2.如果有多个构造方法,则推断构造方法
    3.确定好构造方法后,进行实例化得到一个对象
    4.对对象中加了 @Autowired 注解的属性进行属性填充
    5.回调 Aware 方法,如 BeanNameAware,BeanFactoryAware
    6.调用 BeanPostProcessor 的初始化前的方法
    7.调用初始化方法
    8.调用 BeanPostProcessor 的初始化后的方法,在这里进行 AOP
    9.如果当前创建的 Bean 是单例的,则会把 Bean 放入单例池里
    10.使用 Bean
    11.Spring 容器关闭时调用 DisposableBean 中的 destroy() 方法

1.18 [3]Bean排序机制:4个

00.汇总
    a.使用 Ordered 接口
        如果你希望控制某个 Bean 的排序,可以让它实现 Ordered 接口,并返回一个整数值来表示优先级
        值越小,优先级越高
    b.使用 PriorityOrdered 接口
        当你需要更高优先级的 Bean 时,可以使用 PriorityOrdered 接口
        这些 Bean 会被认为比实现 Ordered 接口的 Bean 更优先
    c.使用 @Order 注解
        如果不想手动实现 Ordered 接口,@Order 注解是一个很好的替代方案
        尤其是当你配置 @Configuration 类或使用 Bean 注解时
    d.使用 @Priority 注解
        当你需要定义更高优先级的 Bean 时,可以使用 @Priority 注解,它等同于实现 PriorityOrdered 接口

01.Ordered 接口
    a.说明
        Ordered 接口是 Spring 中最基础的排序接口,它用于定义 Bean 的排序优先级
        当一个 Bean 实现了 Ordered 接口时,Spring 容器将根据该 Bean 的 getOrder() 方法返回值进行排序
        返回值越小,优先级越高
    b.示例
        import org.springframework.core.Ordered;

        public class MyBean implements Ordered {
            @Override
            public int getOrder() {
                return 1; // 越小,优先级越高
            }
        }
    c.说明
        在上述示例中,MyBean 实现了 Ordered 接口,并返回了 1,这意味着它的优先级很高

02.PriorityOrdered 接口
    a.说明
        PriorityOrdered 是 Ordered 接口的扩展,用于定义更高的优先级
        实现 PriorityOrdered 接口的 Bean 会被认为具有更高的优先级,即使其 getOrder() 返回的值较大
        也会被容器优先初始化
    b.示例
        import org.springframework.core.PriorityOrdered;

        public class MyPriorityBean implements PriorityOrdered {
            @Override
            public int getOrder() {
                return 10; // 即使返回较大的值,它的优先级会更高
            }
        }
    c.说明
        PriorityOrdered 接口的设计是为了在需要比 Ordered 更高的优先级时使用
        例如,当某些 Bean 需要在所有普通 Bean 加载之前初始化时,使用 PriorityOrdered 可以确保其优先加载

03.@Order 注解
    a.说明
        @Order 注解是 Ordered 接口的注解版,它可以直接在类或方法上使用,用来指定 Bean 的加载优先级
        这个注解是 Ordered 接口的简便形式,适用于在 Spring 容器中配置类、Bean、AOP 切面等
    b.示例
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.core.annotation.Order;

        @Configuration
        public class Config {
            @Bean
            @Order(1)
            public MyBean myBean() {
                return new MyBean();
            }
        }
    c.说明
        在上述代码中,@Order(1) 注解指定了 myBean 的优先级为 1,意味着它会被优先加载

04.@Priority 注解
    a.说明
        @Priority 注解与 @Order 注解类似,但它用于指定更高的优先级,等效于实现 PriorityOrdered 接口
        这个注解通常用于表示 Bean 应该具有比 @Order 注解更高的优先级
    b.示例
        import javax.annotation.Priority;

        @Priority(1)
        public class MyPriorityBean {
            // Bean 实现
        }
    c.说明
        在这个例子中,@Priority(1) 注解表示 MyPriorityBean 的优先级为 1,从而确保它在其他普通 Bean 之前被加载

1.19 [3]Bean并发问题:单例Bean线程安全

01.如何保证【单例Bean】的线程安全?
    a.无状态设计
        尽量设计成无状态的 Bean,即不包含可变的实例变量
        如果单例Bean不包含可变状态,或者仅包含只读状态,通常不会有并发安全问题。
        例如,所有方法都是无状态的纯函数,或者使用的是本地变量。
    b.使用线程安全的数据结构
        如果必须包含状态,使用线程安全的数据结构(如 ConcurrentHashMap、CopyOnWriteArrayList 等)
    c.同步
        使用同步块或同步方法来保护共享资源
    d.使用 @Scope 注解
        根据需要调整 Bean 的作用域,例如使用 @Scope("prototype") 来避免线程安全问题。
        这样每次请求该Bean时,Spring容器都会返回一个新的实例。
    e.使用 ThreadLocal
        使用 ThreadLocal 来确保每个线程都有自己的实例变量。

1.20 [3]Bean并发问题:单例Bean不一定不安全

00.汇总
    a.无状态 Bean(线程安全)
        Bean 没有成员变量,或多线程只会对 Bean 成员变量进行查询操作,不会修改操作
    b.有状态 Bean(非线程安全)
        Bean 有成员变量,并且并发线程会对成员变量进行修改操作
    c.总结
        有状态的单例 Bean 是非线程安全的,而无状态的 Bean 是线程安全的
        但在程序中,只要有一种情况会出现线程安全问题,那么它的整体就是非线程安全的
        所以总的来说,单例 Bean 还是非线程安全的

01.无状态的Bean
    a.定义
        无状态的 Bean 指的是不存在成员变量,或只有查询操作,没有修改操作
    b.代码
        import org.springframework.stereotype.Service;

        @Service
        public class StatelessService {
            public void doSomeTask() {
                // 执行任务
            }
        }

02.有状态的Bean
    a.定义
        有成员变量,并且存在对成员变量的修改操作
    b.代码
        import org.springframework.stereotype.Service;

        @Service
        public class UserService {
            private int count = 0;
            public void incrementCount() {
                count++; // 非原子操作,并发存在线程安全问题
            }
            public int getCount() {
                return count;
            }
        }

1.21 [3]Bean并发问题:单例/原型/请求/会话/全局会话

01.SpringBean的并发安全性取决于其【作用域】和【具体实现】
    a.单例作用域(Singleton Scope)
        Spring 中默认的 Bean 作用域是单例(Singleton),即在 Spring 容器中每个 Bean 只有一个实例。
        单例 Bean 在多线程环境下会被多个线程共享,因此需要考虑线程安全问题。
        如果单例 Bean 中包含可变状态(例如实例变量),并且这些状态会被多个线程修改,那么就需要采取同步措施或使用线程安全的数据结构来保证线程安全。
        -----------------------------------------------------------------------------------------------------
        并发安全问题:【有】
        原因:单例作用域的 Bean 在整个 Spring 容器中只有一个实例,并且会被多个线程共享。如果这个 Bean 中包含可变状态(例如实例变量),并且这些状态会被多个线程修改,那么就会产生并发安全问题。
        解决方法:需要采取同步措施、使用线程安全的数据结构,或者设计成无状态的 Bean。
    b.原型作用域(Prototype Scope)
        每次请求都会创建一个新的 Bean 实例,因此每个线程都会有自己的 Bean 实例。
        原型作用域的 Bean 通常不需要考虑线程安全问题,因为它们不会被多个线程共享。
        -----------------------------------------------------------------------------------------------------
        并发安全问题:通常没有
        原因:每次请求都会创建一个新的 Bean 实例,因此每个线程都会有自己的 Bean 实例,不会被多个线程共享。
        注意事项:虽然原型作用域的 Bean 通常不需要考虑线程安全问题,但如果这些 Bean 被注入到单例 Bean 中,仍然可能会引发线程安全问题。
    c.请求作用域(Request Scope)
        每个 HTTP 请求都会创建一个新的 Bean 实例,适用于 Web 应用程序。
        在同一个请求内,Bean 是线程安全的,但不同请求之间的 Bean 实例是独立的。
        -----------------------------------------------------------------------------------------------------
        并发安全问题:通常没有
        原因:每个 HTTP 请求都会创建一个新的 Bean 实例,在同一个请求内,Bean 是线程安全的,不同请求之间的 Bean 实例是独立的。
        注意事项:在同一个请求内,如果多个线程访问同一个 Bean 实例,仍然需要考虑线程安全问题。
    d.会话作用域(Session Scope)
        每个 HTTP 会话会创建一个新的 Bean 实例,适用于 Web 应用程序。
        在同一个会话内,Bean 是线程安全的,但不同会话之间的 Bean 实例是独立的。
        -----------------------------------------------------------------------------------------------------
        并发安全问题:【可能有】
        原因:每个 HTTP 会话会创建一个新的 Bean 实例,在同一个会话内,Bean 是线程安全的,但不同会话之间的 Bean 实例是独立的。然而,在同一个会话内,如果多个线程访问同一个 Bean 实例,可能会产生并发安全问题。
        解决方法:需要采取同步措施或使用线程安全的数据结构。
    e.全局会话作用域(Global Session Scope)
        主要用于基于 Portlet 的 Web 应用程序,每个全局会话会创建一个新的 Bean 实例。
        -----------------------------------------------------------------------------------------------------
        并发安全问题:【可能有】
        原因:主要用于基于 Portlet 的 Web 应用程序,每个全局会话会创建一个新的 Bean 实例。在同一个全局会话内,如果多个线程访问同一个 Bean 实例,可能会产生并发安全问题。
        解决方法:需要采取同步措施或使用线程安全的数据结构。

1.22 [3]BeanFactory与FactoryBean区别

01.BeanFactory与FactoryBean区别
    a.区别
        BeanFactory是Spring框架的核心接口,用于管理Bean的实例化和配置
        FactoryBean是一个Bean,它实现了BeanFactory接口,并且用于创建其他Bean的工厂
    b.总结
        BeanFactory主要负责【实例化和管理Bean】,而FactoryBean提供了【更多对Bean创建过程的控制】

01.BeanFactory
    a.定义
        BeanFactory 是 Spring 框架的核心接口,用于管理 Bean 的实例化、配置和生命周期。它是 IoC 容器的基础
    b.原理
        BeanFactory 通过配置文件或注解定义 Bean,并在应用运行时根据需要实例化和配置这些 Bean
        它采用延迟加载的方式,只有在需要时才实例化 Bean
    c.常用 API
        getBean(String name):根据 Bean 名称获取 Bean 实例
        getBean(Class<T> requiredType):根据 Bean 类型获取 Bean 实例
        containsBean(String name):检查容器中是否包含指定名称的 Bean
    d.使用步骤
        1.定义 Bean 配置文件或使用注解
        2.使用 ApplicationContext 或 BeanFactory 加载配置
        3.使用 getBean 方法获取 Bean 实例
    e.示例代码
        import org.springframework.beans.factory.BeanFactory;
        import org.springframework.context.support.ClassPathXmlApplicationContext;

        public class BeanFactoryExample {
            public static void main(String[] args) {
                BeanFactory factory = new ClassPathXmlApplicationContext("beans.xml");
                MyBean myBean = factory.getBean(MyBean.class);
                System.out.println(myBean);
            }
        }

02.FactoryBean
    a.定义
        FactoryBean 是一个接口,允许开发者定制 Bean 的创建过程。它本身也是一个 Bean,可以用于创建其他 Bean
    b.原理
        FactoryBean 通过实现 FactoryBean 接口,提供对 Bean 创建过程的更多控制
        它可以在创建 Bean 时执行复杂的逻辑,如动态代理、条件创建等
    c.常用 API
        getObject():返回由 FactoryBean 创建的 Bean 实例
        getObjectType():返回创建的 Bean 的类型
        isSingleton():指示创建的 Bean 是否为单例
    d.使用步骤
        1.实现 FactoryBean 接口,定义 getObject 方法
        2.在配置文件或注解中定义 FactoryBean
        3.使用 getBean 方法获取由 FactoryBean 创建的 Bean 实例
    e.示例代码
        import org.springframework.beans.factory.FactoryBean;

        public class MyFactoryBean implements FactoryBean<MyBean> {

            @Override
            public MyBean getObject() throws Exception {
                // 创建并返回 MyBean 实例
                return new MyBean();
            }

            @Override
            public Class<?> getObjectType() {
                return MyBean.class;
            }

            @Override
            public boolean isSingleton() {
                return true;
            }
        }

1.23 [重]Spring扩展点:11个

00.汇总
    01.自定义拦截器             HandlerInterceptor:preHandle、postHandle、afterCompletion
    02.获取Spring容器对象       BeanFactoryAware接口、ApplicationContextAware接口
    03.全局异常处理             RestControllerAdvice
    04.类型转换器               Converter、ConverterFactory、GenericConverter
    05.@Import导入的类          普通类、配置类、ImportSelector、ImportBeanDefinitionRegistrar
    06.项目启动时               CommandLineRunner、ApplicationRunner
    07.修改BeanDefinition       BeanFactoryPostProcessor接口
    08.初始化Bean前后           BeanPostProcessor接口
    09.初始化方法               使用@PostConstruct注解、实现InitializingBean接口
    10.关闭容器前               DisposableBean接口,并且重写destroy方法
    11.自定义作用域             实现Scope接口

01.自定义拦截器
    a.内容
        spring mvc拦截器根spring拦截器相比,它里面能够获取HttpServletRequest和HttpServletResponse等web对象实例
        spring mvc拦截器的顶层接口是:HandlerInterceptor,包含三个方法:
        preHandle 目标方法执行前执行
        postHandle 目标方法执行后执行
        afterCompletion 请求完成时执行
        -----------------------------------------------------------------------------------------------------
        为了方便我们一般情况会用HandlerInterceptor接口的实现类HandlerInterceptorAdapter类
        假如有权限认证、日志、统计的场景,可以使用该拦截器
    b.第一步,继承HandlerInterceptorAdapter类定义拦截器
        public class AuthInterceptor extends HandlerInterceptorAdapter {

            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                    throws Exception {
                String requestUrl = request.getRequestURI();
                if (checkAuth(requestUrl)) {
                    return true;
                }

                return false;
            }

            private boolean checkAuth(String requestUrl) {
                System.out.println("===权限校验===");
                return true;
            }
        }
    c.第二步,将该拦截器注册到spring容器
        @Configuration
        public class WebAuthConfig extends WebMvcConfigurerAdapter {

            @Bean
            public AuthInterceptor getAuthInterceptor() {
                return new AuthInterceptor();
            }

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(new AuthInterceptor());
            }
        }
    d.第三步,在请求接口时spring mvc通过该拦截器,能够自动拦截该接口,并且校验权限

02.获取Spring容器对象
    a.BeanFactoryAware接口
        @Service
        public class PersonService implements BeanFactoryAware {
            private BeanFactory beanFactory;

            @Override
            public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
                this.beanFactory = beanFactory;
            }

            public void add() {
                Person person = (Person) beanFactory.getBean("person");
            }
        }
        -----------------------------------------------------------------------------------------------------
        实现BeanFactoryAware接口,然后重写setBeanFactory方法,就能从该方法中获取到spring容器对象
    c.ApplicationContextAware接口
        @Service
        public class PersonService2 implements ApplicationContextAware {
            private ApplicationContext applicationContext;

            @Override
            public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
                this.applicationContext = applicationContext;
            }

            public void add() {
                Person person = (Person) applicationContext.getBean("person");
            }
        }
        -----------------------------------------------------------------------------------------------------
        实现ApplicationContextAware接口,然后重写setApplicationContext方法,也能从该方法中获取到spring容器对象
    d.ApplicationListener接口
        @Service
        public class PersonService3 implements ApplicationListener<ContextRefreshedEvent> {
            private ApplicationContext applicationContext;
            @Override
            public void onApplicationEvent(ContextRefreshedEvent event) {
                applicationContext = event.getApplicationContext();
            }

            public void add() {
                Person person = (Person) applicationContext.getBean("person");
            }
        }

03.全局异常处理
    a.背景
        a.说明
            以前我们在开发接口时,如果出现异常,为了给用户一个更友好的提示
        b.代码
            @RequestMapping("/test")
            @RestController
            public class TestController {

                @GetMapping("/add")
                public String add() {
                    int a = 10 / 0;
                    return "成功";
                }
            }
        c.说明
            如果不做任何处理请求add接口结果直接报错:
            what?用户能直接看到错误信息?这种交互方式给用户的体验非常差
    b.通常会在接口中捕获异常
        a.异常处理
            @GetMapping("/add")
            public String add() {
                String result = "成功";
                try {
                    int a = 10 / 0;
                } catch (Exception e) {
                    result = "数据异常";
                }
                return result;
            }
        b.接口变多,使用全局异常RestControllerAdvice
            @RestControllerAdvice
            public class GlobalExceptionHandler {

                @ExceptionHandler(Exception.class)
                public String handleException(Exception e) {
                    if (e instanceof ArithmeticException) {
                        return "数据异常";
                    }
                    if (e instanceof Exception) {
                        return "服务器内部异常";
                    }
                    return null;
                }
            }
        c.说明
            只需在handleException方法中处理异常情况,业务接口中可以放心使用,不再需要捕获异常

04.类型转换器
    a.spring目前支持3中类型转换器
        Converter<S,T>:将 S 类型对象转为 T 类型对象
        ConverterFactory<S, R>:将 S 类型对象转为 R 类型及子类对象
        GenericConverter:它支持多个source和目标类型的转化,同时还提供了source和目标类型的上下文,这个上下文能让你实现基于属性上的注解或信息来进行类型转换
    b.第一步,定义一个实体User
        @Data
        public class User {

            private Long id;
            private String name;
            private Date registerDate;
        }
    c.第二步,实现Converter接口
        public class DateConverter implements Converter<String, Date> {

            private SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

            @Override
            public Date convert(String source) {
                if (source != null && !"".equals(source)) {
                    try {
                        simpleDateFormat.parse(source);
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
                return null;
            }
        }
    d.第三步,将新定义的类型转换器注入到spring容器中
        @Configuration
        public class WebConfig extends WebMvcConfigurerAdapter {

            @Override
            public void addFormatters(FormatterRegistry registry) {
                registry.addConverter(new DateConverter());
            }
        }
    e.第四步,调用接口
        @RequestMapping("/user")
        @RestController
        public class UserController {

            @RequestMapping("/save")
            public String save(@RequestBody User user) {
                return "success";
            }
        }
    f.说明
        请求接口时User对象中registerDate字段会被自动转换成Date类型

05.@Import导入的类
    a.内容
        有时我们需要在某个配置类中引入另外一些类,被引入的类也加到spring容器中,可以使用@Import注解完成这个功能
        如果你看过它的源码会发现,引入的类支持三种不同类型
    b.普通类
        a.说明
            这种引入方式是最简单的,被引入的类会被实例化bean对象
        b.代码
            public class A {
            }

            @Import(A.class)
            @Configuration
            public class TestConfiguration {
            }
        c.说明
            通过@Import注解引入A类,spring就能自动实例化A对象,然后在需要使用的地方通过@Autowired注解注入即可
        d.代码
            @Autowired
            private A a;
        e.说明
            是不是挺让人意外的?不用加@Bean注解也能实例化bean
    c.配置类
        a.说明
            这种引入方式是最复杂的,因为@Configuration注解还支持多种组合注解,比如:
            @Import
            @ImportResource
            @PropertySource等
        b.代码
            public class A {
            }

            public class B {
            }

            @Import(B.class)
            @Configuration
            public class AConfiguration {

                @Bean
                public A a() {
                    return new A();
                }
            }

            @Import(AConfiguration.class)
            @Configuration
            public class TestConfiguration {
            }
        c.说明
            通过@Import注解引入@Configuration注解的配置类
            会把该配置类相关@Import、@ImportResource、@PropertySource等注解引入的类进行递归,一次性全部引入
    d.ImportSelector
        a.说明
            这种引入方式需要实现ImportSelector接口
        b.代码
            public class AImportSelector implements ImportSelector {

            private static final String CLASS_NAME = "com.sue.cache.service.test13.A";

             public String[] selectImports(AnnotationMetadata importingClassMetadata) {
                    return new String[]{CLASS_NAME};
                }
            }

            @Import(AImportSelector.class)
            @Configuration
            public class TestConfiguration {
            }
        c.说明
            这种方式的好处是selectImports方法返回的是数组,意味着可以同时引入多个类,还是非常方便的
    e.ImportBeanDefinitionRegistrar
        a.说明
            这种引入方式需要实现ImportBeanDefinitionRegistrar接口
        b.代码
            public class AImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
                @Override
                public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
                    RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(A.class);
                    registry.registerBeanDefinition("a", rootBeanDefinition);
                }
            }

            @Import(AImportBeanDefinitionRegistrar.class)
            @Configuration
            public class TestConfiguration {
            }
        c.说明
            这种方式是最灵活的,能在registerBeanDefinitions方法中获取到BeanDefinitionRegistry容器注册对象
            可以手动控制BeanDefinition的创建和注册

06.项目启动时
    a.内容
        a.说明
            有时候我们需要在项目启动时定制化一些附加功能
            比如:加载一些系统参数、完成初始化、预热本地缓存等,该怎么办呢?
        b.springboot提供
            CommandLineRunner
            ApplicationRunner
    b.以ApplicationRunner接口为例
        a.代码
            @Component
            public class TestRunner implements ApplicationRunner {

                @Autowired
                private LoadDataService loadDataService;

                public void run(ApplicationArguments args) throws Exception {
                    loadDataService.load();
                }
            }
        b.说明
            实现ApplicationRunner接口,重写run方法,在该方法中实现自己定制化需求
            如果项目中有多个类实现了ApplicationRunner接口,他们的执行顺序要怎么指定呢?
            答案是使用@Order(n)注解,n的值越小越先执行。当然也可以通过@Priority注解指定顺序

07.修改BeanDefinition
    a.内容
        a.说明
            Spring IOC在实例化Bean对象之前,需要先读取Bean的相关属性
            保存到BeanDefinition对象中,然后通过BeanDefinition对象,实例化Bean对象
        b.如果想修改BeanDefinition对象中的属性,该怎么办呢?
            实现BeanFactoryPostProcessor接口
    b.代码示例
        a.代码
            @Component
            public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

                @Override
                public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
                    DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
                    BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
                    beanDefinitionBuilder.addPropertyValue("id", 123);
                    beanDefinitionBuilder.addPropertyValue("name", "苏三说技术");
                    defaultListableBeanFactory.registerBeanDefinition("user", beanDefinitionBuilder.getBeanDefinition());
                }
            }
        b.说明
            在postProcessBeanFactory方法中,可以获取BeanDefinition的相关对象,并且修改该对象的属性

08.初始化Bean前后
    a.内容
        a.说明
            有时,你想在初始化Bean前后,实现一些自己的逻辑
            这时可以实现:BeanPostProcessor接口
        b.该接口目前有两个方法
            postProcessBeforeInitialization 该在初始化方法之前调用
            postProcessAfterInitialization 该方法再初始化方法之后调用
    b.代码示例
        a.代码
            @Component
            public class MyBeanPostProcessor implements BeanPostProcessor {

                @Override
                public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                    if (bean instanceof User) {
                        ((User) bean).setUserName("苏三说技术");
                    }
                    return bean;
                }
            }
        b.说明
            如果spring中存在User对象,则将它的userName设置成:苏三说技术
            其实,我们经常使用的注解,比如:@Autowired、@Value、@Resource、@PostConstruct等
            是通过AutowiredAnnotationBeanPostProcessor和CommonAnnotationBeanPostProcessor实现的

09.初始化方法
    a.目前spring中使用比较多的初始化bean的方法
        使用@PostConstruct注解
        实现InitializingBean接口
    b.使用@PostConstruct注解
        a.代码
            @Service
            public class AService {
                @PostConstruct
                public void init() {
                    System.out.println("===初始化===");
                }
            }
        b.说明
            在需要初始化的方法上增加@PostConstruct注解,这样就有初始化的能力
    c.实现InitializingBean接口
        a.代码
            @Service
            public class BService implements InitializingBean {

                @Override
                public void afterPropertiesSet() throws Exception {
                    System.out.println("===初始化===");
                }
            }
        b.说明
            实现InitializingBean接口,重写afterPropertiesSet方法,该方法中可以完成初始化功能

10.关闭容器前
    a.内容
        有时候,我们需要在关闭spring容器前,做一些额外的工作,比如:关闭资源文件等
        这时可以实现DisposableBean接口,并且重写它的destroy方法
    b.代码示例
        a.代码
            @Service
            public class DService implements InitializingBean, DisposableBean {

                @Override
                public void destroy() throws Exception {
                    System.out.println("DisposableBean destroy");
                }

                @Override
                public void afterPropertiesSet() throws Exception {
                    System.out.println("InitializingBean afterPropertiesSet");
                }
            }
        b.说明
            这样spring容器销毁前,会调用该destroy方法,做一些额外的工作
            通常情况下,我们会同时实现InitializingBean和DisposableBean接口,重写初始化方法和销毁方法

11.自定义作用域
    a.内容
        a.spring默认支持的Scope只有两种
            singleton 单例,每次从spring容器中获取到的bean都是同一个对象
            prototype 多例,每次从spring容器中获取到的bean都是不同的对象
        b.spring web又对Scope进行了扩展,增加了
            RequestScope 同一次请求从spring容器中获取到的bean都是同一个对象
            SessionScope 同一个会话从spring容器中获取到的bean都是同一个对象
        c.说明
            即便如此,有些场景还是无法满足我们的要求
            比如,我们想在同一个线程中从spring容器获取到的bean都是同一个对象,该怎么办?
            这就需要自定义Scope了
    b.第一步,实现Scope接口
        public class ThreadLocalScope implements Scope {
            private static final ThreadLocal THREAD_LOCAL_SCOPE = new ThreadLocal();

            @Override
            public Object get(String name, ObjectFactory<?> objectFactory) {
                Object value = THREAD_LOCAL_SCOPE.get();
                if (value != null) {
                    return value;
                }

                Object object = objectFactory.getObject();
                THREAD_LOCAL_SCOPE.set(object);
                return object;
            }

            @Override
            public Object remove(String name) {
                THREAD_LOCAL_SCOPE.remove();
                return null;
            }

            @Override
            public void registerDestructionCallback(String name, Runnable callback) {
            }

            @Override
            public Object resolveContextualObject(String key) {
                return null;
            }

            @Override
            public String getConversationId() {
                return null;
            }
        }
    c.第二步,将新定义的Scope注入到spring容器中
        @Component
        public class ThreadLocalBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
            @Override
            public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
                beanFactory.registerScope("threadLocalScope", new ThreadLocalScope());
            }
        }
    d.第三步,使用新定义的Scope
        @Scope("threadLocalScope")
        @Service
        public class CService {
            public void add() {
            }
        }

1.24 [重]Spring循环依赖:yml

00.汇总
    方式     依赖情况     注入方式                                                            能否解决循环依赖
    情况一   AB相互依赖   均采用 Setter 方式                                                   能
    情况二   AB相互依赖   均采用构造器方式                                                     不能
    情况三   AB相互依赖   A 中注入 B 采用 Setter,B 中注入 A 采用构造器                         能
    情况四   AB相互依赖   A 中注入 B 采用构造器,B 中注入 A 采用 Setter                         不能
    情况五   AB相互依赖   A 中注入 B 采用 @Autowired,B 中注入 A 采用 @PostConstruct + Setter   能
    情况六   AB相互依赖   A 中注入 B 采用 @PostConstruct + Setter,B 中注入 A 采用 @Autowired   能

01.概述
    a.回答
        Spring设计了三级缓存来解决循环依赖问题
    b.核心思想
        把Bean的实例化和Bean里面的依赖注入进行分离
        一级缓存:储存完整的并实例
        二级缓存:存储不完整的并实例,通过不完整的Bean实力作为突破口解决循环依赖问题
        第三级缓存,主要是解决代理对象的循环依赖问题
    c.工作原理
        a.第一级缓存
            第一级缓存里面缓存完整的Bean实例,这些实例是可以直接被使用的
        b.第二级缓存
            第二级缓存里面存储的实例化以后,但是还没有设置属性值的Bean实例,也就是Bean里面的依赖注入还没有做
        c.第三级缓存
            第三级缓存是用来存放ObjectFactory对象,它主要用来生成原始Bean对象,并且放到第二个缓存里面

02.解决循环依赖的方法
    a.重新设计
        a.描述
            当出现循环依赖时,通常意味着设计上存在问题。应尽量重新设计组件,使其层次分明,避免循环依赖
        b.适用场景
            有时间和资源进行重构时
    b.@Lazy注解
        a.描述
            使用 @Lazy 注解延迟加载某个 Bean,使其在首次使用时才被完全初始化
        b.代码示例
            @Service
            public class ServiceAImpl implements ServiceA {
                private ServiceB serviceB;

                public ServiceAImpl(@Lazy ServiceB serviceB) {
                    this.serviceB = serviceB;
                }
            }
        c.适用场景
            需要快速解决循环依赖,且不影响其他逻辑时
    c.Setter/Field注入
        a.描述
            使用 Setter 注入或字段注入,而不是构造函数注入。这样在创建 Bean 时,依赖并没有被立即注入
        b.代码示例
            @Service
            public class ServiceAImpl implements ServiceA {
                private ServiceB serviceB;

                @Autowired
                public void setServiceB(ServiceB serviceB) {
                    this.serviceB = serviceB;
                }
            }
        c.适用场景
            可以修改注入方式时
    d.@PostConstruct
        a.描述
            在属性上使用 @Autowired,并在 @PostConstruct 方法中设置其他依赖
        b.代码示例
            @Service
            public class ServiceAImpl implements ServiceA {
                @Autowired
                private ServiceB serviceB;

                @PostConstruct
                public void init() {
                    serviceB.setServiceA(this);
                }
            }
        c.适用场景
            需要在 Bean 初始化后进行额外配置时
    e.Spring Boot 2.6.x 配置
        a.描述
            Spring Boot 2.6.x 默认不允许循环引用,可以通过配置文件允许循环引用
        b.配置
            spring:
              main:
                # 属性默认值为false,显示声明为true
                allow-circular-references: true

1.25 [重]Spring的Bean存取:存3、取7

01.存Bean(IOC容器):将Bean定义和其依赖关系注册到Spring容器中
    a.第一种:
        XML配置文件:使用XML文件定义Bean及其依赖关系
    b.第二种:
        注解方式:使用注解将类标识为Spring管理的Bean
        @Component、@Service、@Repository、@Controller:用于标识不同类型的组件
        @Configuration:用于定义配置类,其中包含@Bean注解的方法
    c.第三种
        Java配置类(@Configuration + @Bean):使用Java配置类和@Bean注解定义Bean

02.取Bean(DI依赖注入):从Spring容器中获取Bean实例,并将其注入到需要的地方
    a.基本方法
        通过【ApplicationContext】或【BeanFactory】获取Bean实例
    b.注解方法
        1.构造函数注入(官方推荐)
        2.Setter方法注入(官方推荐)
        3.属性注入:@Autowired
        4.接口注入
        5.方法注入
        6.基于XML配置的注入
        7.基于Java配置的注入
        -----------------------------------------------------------------------------------------------------
        注:@Autowired可以用于【属性注入】、【setter注入】、【构造方法注入】的依赖注入形式

1.26 [重]Spring的Bean选择:5种

00.汇总
    0.场景模拟
    1.@Qualifier
    2.@Resource
    3.@Primary
    4.@Conditional,推荐
    5.自定义@Conditional,强烈推荐

00.背景
    在当今的软件开发中,服务接口通常需要对应多个实现类,以满足不同的需求和场景
    举例来说,假设我们是一家2B公司,公司的产品具备对象存储服务的能力
    然而,在不同的合作机构部署时,发现每家公司底层的对象存储服务都不相同,比如机构A使用阿里云,机构B使用AWS S3等
    针对这种情况,公司应用底层需要支持多种云存储平台,如阿里云、AWS S3等

01.场景模拟
    a.内容
        a.说明
            在应用中新建一个ObjectStorageService存储接口,代码如下
        b.代码
            import java.io.File;

            public interface ObjectStorageService {
                /**
                 * 上传文件到对象存储
                 * @param file 文件
                 * @param bucketName 存储桶名称
                 * @param objectKey 对象键(文件名)
                 * @return 文件在对象存储中的URL
                 */
                String uploadObject(File file, String bucketName, String objectKey);

                /**
                 * 从对象存储下载文件
                 * @param bucketName 存储桶名称
                 * @param objectKey 对象键(文件名)
                 * @return 文件
                 */
                File downloadObject(String bucketName, String objectKey);
            }
    b.实现类
        a.说明
            接下来,我们创建了三个通过@Service注入的实现类
            首先是默认实现类DefaultObjectStorageServiceImpl
            其次是阿里云存储服务的实现类AliyunObjectStorageServiceImpl
            最后是S3存储服务的实现类S3ObjectStorageServiceImpl
        b.DefaultObjectStorageServiceImpl
            @Slf4j
            @Service
            public class DefaultObjectStorageServiceImpl implements ObjectStorageService {
                @Override
                public String uploadObject(File file, String bucketName, String objectKey) {
                    // 默认实现上传逻辑
                    return "Default implementation: Upload successful";
                }

                @Override
                public File downloadObject(String bucketName, String objectKey) {
                    // 默认实现下载逻辑
                    return new File("default-file.txt");
                }
            }
        b.AliyunObjectStorageServiceImpl
            @Slf4j
            @Service
            public class AliyunObjectStorageServiceImpl implements ObjectStorageService {
                @Override
                public String uploadObject(File file, String bucketName, String objectKey) {
                    // 阿里云实现上传逻辑
                    return "Aliyun implementation: Upload successful";
                }

                @Override
                public File downloadObject(String bucketName, String objectKey) {
                    // 阿里云实现下载逻辑
                    return new File("aliyun-file.txt");
                }
            }
        c.S3ObjectStorageServiceImpl
            @Slf4j
            @Service
            public class S3ObjectStorageServiceImpl implements ObjectStorageService {
                @Override
                public String uploadObject(File file, String bucketName, String objectKey) {
                    // S3实现上传逻辑
                    return "S3 implementation: Upload successful";
                }

                @Override
                public File downloadObject(String bucketName, String objectKey) {
                    // S3实现下载逻辑
                    return new File("s3-file.txt");
                }
            }
    c.Controller类
        a.说明
            最后再创建一个Controller类通过@Autowired注解注入ObjectStorageService,并对外开放接口
        b.代码
            @Slf4j
            @RestController
            public class StorageController {
                @Autowired
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
    d.错误信息
        a.此时运行应用报错信息如下
            ***************************
            APPLICATION FAILED TO START
            ***************************
            Description:
            Field objectStorageService in org.example.inject.web.controller.StorageController required a single bean, but 3 were found:
                - aliyunObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\AliyunObjectStorageServiceImpl.class]
                - defaultObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\DefaultObjectStorageServiceImpl.class]
                - s3ObjectStorageServiceImpl: defined in file [D:\IdeaProjects\inject-examples\inject-condition\target\classes\org\example\inject\web\service\impl\S3ObjectStorageServiceImpl.class]
            Action:
            Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
        b.说明
            错误提示StorageController需要一个objectStorageService bean,但是却找到了3个可用的bean:aliyunObjectStorageServiceImpl、defaultObjectStorageServiceImpl和s3ObjectStorageServiceImpl。spring也提示了解决方案
            在其中一个实现类上添加@Primary注解,指示Spring优先选择这个bean
            修改StorageController以接受多个objectStorageService,或者使用@Qualifier注解指定要注入的特定bean

02.@Qualifier
    a.内容
        a.说明
            @Autowired是Spring2.5 引入的注解,@Autowired 注解只根据类型进行注入,不会根据名称匹配
            当类型无法辨别注入对象时,可以使用 @Qualifier 或 @Primary 注解来修饰
        b.代码
            @Slf4j
            @RestController
            public class StorageController {
                @Autowired
                @Qualifier("aliyunObjectStorageServiceImpl")
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
        c.说明
            @Qualifier注解中的参数是BeanID,即@Service注解所注入的实现类的名称
    b.运行结果
        a.说明
            运行应用后一切正常,命令行输入: curl http://localhost:8080/example,日志打印:注入成功
        b.结果
            objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@6f2aa58b
    c.注意
        a.说明
            遗憾的是,@Qualifier注解并不支持变量赋值,只能通过硬编码的方式指定具体的实现类
        b.代码
            @Slf4j
            @RestController
            public class StorageController {

                @Value("${storage.provider}")
                private String storageProvider;

                @Autowired
                @Qualifier("${storageProvider}")
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
        c.说明
            虽然我们希望通过配置变量的方式来指定具体的实现类,但是由于@Qualifier注解的限制,这种方案并不可行
            因此不推荐使用

03.@Resource
    a.内容
        a.说明
            在Spring Boot应用中,除了@Autowired,还可以使用@Resource来进行依赖注入
        b.代码
            import javax.annotation.Resource;

            @Slf4j
            @RestController
            public class StorageController {

            //  @Autowired
                @Resource
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
    b.区别
        a.说明
            @Resource 是 JDK 原生的注解,而 @Autowired 是 Spring 2.5 引入的注解
        b.说明
            @Resource 注解有两个属性:name 和 type
            Spring 将 @Resource 注解的 name 属性解析为 bean 的名称,而 type 属性则解析为 bean 的类型
            因此,如果使用 name 属性,则采用 byName 的自动注入策略;如果使用 type 属性
            则采用 byType 的自动注入策略。如果既不指定 name 也不指定 type 属性
            则将通过反射机制使用 byName 自动注入策略
        c.说明
            @Autowired 注解只根据类型进行注入,不会根据名称匹配
            当类型无法辨别注入对象时,可以使用 @Qualifier 或 @Primary 注解来修饰
    c.代码示例
        a.说明
            我们可以通过@Resource注解指定name属性从而实现指定实现类注入
        b.代码
            @Slf4j
            @RestController
            public class StorageController {

            //  @Autowired
                @Resource(name = "aliyunObjectStorageServiceImpl")
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
    d.运行结果
        a.说明
            运行应用后一切正常,命令行输入: curl http://localhost:8080/example,日志打印:注入成功
        b.结果
            objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@6f2aa58b
    e.注意
        遗憾的是,@Resource注解也不支持变量赋值,只能通过硬编码的方式指定具体的实现类,因此不推荐使用

04.@Primary
    a.内容
        @Primary 是一个 Spring 框架中的注解,用于解决多个 Bean 实例同一类型的自动装配问题
        当一个接口或者类有多个实现时,Spring 在自动装配时可能会出现歧义,不知道选择哪个 Bean 注入
        这时候,可以使用 @Primary 注解来指定首选的 Bean,这样在自动装配时就会选择这个首选的 Bean
    b.代码示例
        a.将DefaultObjectStorageServiceImpl设置为首选实现类
            import org.springframework.context.annotation.Primary;

            @Slf4j
            @Service
            @Primary
            public class DefaultObjectStorageServiceImpl implements ObjectStorageService {

                @Override
                public String uploadObject(File file, String bucketName, String objectKey) {
                    // 默认实现上传逻辑
                    return "Default implementation: Upload successful";
                }

                @Override
                public File downloadObject(String bucketName, String objectKey) {
                    // 默认实现下载逻辑
                    return new File("default-file.txt");
                }
            }
        b.StorageController控制层恢复为最初形态
            @Slf4j
            @RestController
            public class StorageController {

                @Autowired
                private ObjectStorageService objectStorageService;

                @GetMapping("/example")
                public void example() {
                    log.info("objectStorageService: {}", objectStorageService);
                }
            }
    c.运行结果
        a.说明
            运行应用,命令行输入: curl http://localhost:8080/example,日志打印:默认实现类注入成功
        b.结果
            objectStorageService: org.example.inject.condition.service.impl.DefaultObjectStorageServiceImpl@633df06
    d.注意
        遗憾的是,@Primary注解也是只能通过硬编码的方式指定具体的实现类,因此不推荐使用

05.@Conditional,推荐
    a.内容
        @Conditional 注解是 Spring 框架提供的一种条件化装配的机制
        它可以根据特定的条件来决定是否创建一个 Bean 实例
        通过 @Conditional 注解,可以在 Spring 容器启动时根据一些条件来动态地确定是否创建某个 Bean
        从而实现更灵活的 Bean 装配
    b.内置条件注解
        @ConditionalOnClass:当类路径中存在指定的类时,才创建该 Bean
        @ConditionalOnMissingClass:当类路径中不存在指定的类时,才创建该 Bean
        @ConditionalOnBean:当容器中存在指定的 Bean 时,才创建该 Bean
        @ConditionalOnMissingBean:当容器中不存在指定的 Bean 时,才创建该 Bean
        @ConditionalOnProperty:当指定的配置属性满足一定条件时,才创建该 Bean
        @ConditionalOnExpression:当指定的 SpEL 表达式为 true 时,才创建该 Bean
    c.实现目标
        我们希望达到的效果是通过application.properties或application.yml配置文件的一个配置项就可以指定具体实现类
        而非通过硬编码的形式来实现,所以我们将使用@ConditionalOnProperty配置属性条件注解实现
    d.@ConditionalOnProperty注解
        @Retention(RetentionPolicy.RUNTIME)
        @Target({ElementType.TYPE, ElementType.METHOD})
        @Documented
        @Conditional({OnPropertyCondition.class})
        public @interface ConditionalOnProperty {

            /**
             * 配置文件中 key 的前缀,可与 value 或 name 组合使用。
             */
            String prefix() default "";

            /**
             * 与 value 作用相同,但不能与 value 同时使用。
             */
            String[] name() default {};

            /**
             * 与 value 或 name 组合使用,只有当 value 或 name 对应的值与 havingValue 的值相同时,注入生效。
             */
            String havingValue() default "";

            /**
             * 当该属性为 true 时,配置文件中缺少对应的 value 或 name 的属性值,也会注入成功。
             */
            boolean matchIfMissing() default false;
        }
    e.配置文件,定义配置key
        storage.provider=aliyun
    f.在各个实现类中新增@ConditionalOnProperty注解
        import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

        @Slf4j
        @Service
        //@Primary
        @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "default", matchIfMissing = true)
        public class DefaultObjectStorageServiceImpl implements ObjectStorageService {
            // 省略
        }

        @Slf4j
        @Service
        @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "aliyun")
        public class AliyunObjectStorageServiceImpl implements ObjectStorageService {
            // 省略
        }

        @Slf4j
        @Service
        @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "s3")
        public class S3ObjectStorageServiceImpl implements ObjectStorageService {
            // 省略
        }
    g.运行结果
        a.说明
            运行应用,命令行输入: curl http://localhost:8080/example,日志打印:
        b.结果
            objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@3b46e282
        c.说明
            如果在 application.properties 或 application.yml 配置文件中没有配置 storage.provider 属性
            则会注入 DefaultObjectStorageServiceImpl 实现类
            这是因为 DefaultObjectStorageServiceImpl 实现类的 matchIfMissing = true 属性已经指定了
    h.优化配置
        a.说明
            上述注解的实现方式是配置在每个实现类中,这种方式过于分散
            为了让开发人员更清晰地了解应用的注入关系,我们应该通过 @Configuration 整合所有实现类的配置
        b.以下是新增的 WebConfiguration 配置类的代码
            import org.springframework.context.annotation.Bean;
            import org.springframework.context.annotation.Configuration;

            /**
             * 自动装配类
             */
            @Configuration
            public class WebConfiguration {
                @Bean
                @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "default", matchIfMissing = true)
                public ObjectStorageService defaultObjectStorageServiceImpl() {
                    return new DefaultObjectStorageServiceImpl();
                }

                @Bean
                @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "aliyun")
                public ObjectStorageService aliyunObjectStorageServiceImpl() {
                    return new AliyunObjectStorageServiceImpl();
                }

                @Bean
                @ConditionalOnProperty(prefix = "storage", name = "provider", havingValue = "s3")
                public ObjectStorageService s3ObjectStorageServiceImpl() {
                    return new S3ObjectStorageServiceImpl();
                }
            }
        c.再将各个实现类中的@Service,@ConditionalOnProperty注解去掉,更改后代码如下
            import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;

            @Slf4j
            public class DefaultObjectStorageServiceImpl implements ObjectStorageService {
                // 省略
            }

            @Slf4j
            public class AliyunObjectStorageServiceImpl implements ObjectStorageService {
                // 省略
            }

            @Slf4j
            public class S3ObjectStorageServiceImpl implements ObjectStorageService {
                // 省略
            }
        d.运行结果
            运行应用,命令行输入: curl http://localhost:8080/example,日志打印:
            objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@3b46e282
            通过 @ConditionalOnProperty 注解和 WebConfiguration 统一装配类,我们基本实现了可配置化注入实现类的方案,初步实现了我们的目标

06.自定义@Conditional,强烈推荐
    a.内容
        在上面的示例中,我们是通过在配置文件中定义属性来决定实现类
        这需要在配置文件中定义一份属性,并在各个 @ConditionalOnProperty 注解中配置 prefix 和 name 属性
        以前面的示例为例,就需要进行4次配置
        然而,这种方式容易出错,特别是当服务有多个接口需要配置多个实现类时
        需要配置更多的属性,增加了配置的复杂性和出错的可能性
    b.优化目标
        根据上图中的三个接口,需要配置三个配置项以及7次 @ConditionalOnProperty 注解
        因此,我们需要采用一种简化的方式来减少配置,只需要在配置文件中配置一次即可
        而无需更改@ConditionalOnProperty 注解
    c.配置文件
        要满足上述需求,首先需要重点关注配置文件中的属性
        以上面的对象存储的情景举例,一个重要的配置项是storage.provider=aliyun
        为了更通用地解决所有接口的配置需求,建议统一将配置项命名为接口的全限定名
        这种做法不仅能够确保配置项的唯一性,同时也让人一目了然,清晰明了
        以上面对象存储场景为例,修改后的配置如下所示:
        org.example.inject.condition.service.ObjectStorageService=aliyun
    d.简化注解
        a.说明
            其次希望简化@ConditionalOnProperty注解的编写,不再需要指定prefix = "storage", name = "provider"等属性
            而是根据注解所在位置自动分析当前返回值类的全限定名称,然后直接从配置文件中读取相应的配置项
        b.代码
            @Bean
            @ConditionalOnProperty(name = ObjectStorageService.class, matchIfMissing = true)
            public ObjectStorageService defaultObjectStorageServiceImpl() {
                return new DefaultObjectStorageServiceImpl();
            }

            @Bean
            @ConditionalOnProperty(name = ObjectStorageService.class, havingValue = "aliyun")
            public ObjectStorageService aliyunObjectStorageServiceImpl() {
                return new AliyunObjectStorageServiceImpl();
            }

            @Bean
            @ConditionalOnProperty(name = ObjectStorageService.class, havingValue = "s3")
            public ObjectStorageService s3ObjectStorageServiceImpl() {
                return new S3ObjectStorageServiceImpl();
            }
        c.说明
            可以观察到,除了需要配置havingValue属性外,其他配置项无需手动设置,使得配置变得十分简洁
    e.自定义条件注解
        a.说明
            注意,目前Spring并未提供类似的能力来实现我们需要的条件判断,因此我们需要自定义条件注解
            幸运的是,Spring 提供了条件接口,让我们可以自行创建自定义的条件类来实现所需的条件判断逻辑
            首先,我们创建一个自定义条件类,它继承Condition接口,并编写自定义的条件判断逻辑
        b.代码
            import org.springframework.context.annotation.Condition;
            import org.springframework.context.annotation.ConditionContext;
            import org.springframework.core.type.AnnotatedTypeMetadata;
            import org.springframework.util.StringUtils;

            /**
             * 自定义的条件判断类,用于根据指定类名的配置值判断是否应用某个配置。
             */
            public class ConditionalOnClassNameCustom implements Condition {

                /**
                 * 判断是否满足条件。
                 *
                 * @param context  条件上下文
                 * @param metadata 注解元数据
                 * @return 如果满足条件,则返回true;否则返回false
                 */
                @Override
                public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
                    // 获取ConditionalOnClassName注解的属性值
                    Class<?>[] annotationValues = (Class<?>[]) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("name");
                    String annotationClassName = annotationValues[0].getName(); // 获取类的全限定名
                    String havingValue = (String) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("havingValue");
                    boolean matchIfMissing = (boolean) metadata.getAnnotationAttributes(ConditionalOnClassName.class.getName()).get("matchIfMissing");

                    // 获取配置项对应的配置值
                    String propertyValue = context.getEnvironment().getProperty(annotationClassName);

                    // 检查配置值是否符合预期
                    if (StringUtils.hasText(propertyValue)) {
                        return havingValue.equals(propertyValue);
                    } else {
                        return matchIfMissing;
                    }
                }
            }
    f.全新条件配置注解
        a.说明
            借助这个条件判断逻辑,我们接下来设计一个全新的条件配置注解:ConditionalOnClassName
            它将使用前述的ConditionalOnClassNameCustom实现类
        b.代码
            import org.springframework.context.annotation.Conditional;

            import java.lang.annotation.*;

            /**
             * 定义一个自定义条件注解,用于根据指定类名的配置值判断是否应用某个配置。
             */
            @Target({ ElementType.TYPE, ElementType.METHOD }) // 注解可以应用于类和方法
            @Retention(RetentionPolicy.RUNTIME) // 注解会在运行时保留
            @Documented // 注解会被包含在javadoc中
            @Conditional(ConditionalOnClassNameCustom.class) // 该注解条件受到 ConditionalOnClassNameCustom 类的限制
            public @interface ConditionalOnClassName {
                Class<?>[] value() default {}; // 作为 value 属性的别名,用于更简洁地指定需要检查的类
                Class<?>[] name(); // 需要检查的类的全限定名数组
                String havingValue() default "default"; // 期望的配置值,默认为 "default"
                boolean matchIfMissing() default false; // 如果配置值缺失是否匹配,默认为 false
            }
    g.验证新注解
        a.说明
            完成了上述准备工作后,接下来是验证新创建的注解。我们需要修改WebConfiguration配置类
        b.代码
            /**
             * 自动装配类
             */
            @Configuration
            public class WebConfiguration {
                @Bean
                @ConditionalOnClassName(name = ObjectStorageService.class, matchIfMissing = true)
                public ObjectStorageService defaultObjectStorageServiceImpl() {
                    return new DefaultObjectStorageServiceImpl();
                }

                @Bean
                @ConditionalOnClassName(name = ObjectStorageService.class, havingValue = "aliyun")
                public ObjectStorageService aliyunObjectStorageServiceImpl() {
                    return new AliyunObjectStorageServiceImpl();
                }

                @Bean
                @ConditionalOnClassName(name = ObjectStorageService.class, havingValue = "s3")
                public ObjectStorageService s3ObjectStorageServiceImpl() {
                    return new S3ObjectStorageServiceImpl();
                }
            }
    h.配置文件
        接下来定义配置key,在application.properties或application.yml配置文件新增如下内容
        org.example.inject.condition.service.ObjectStorageService=aliyun
    i.运行结果
        运行应用,命令行输入: curl http://localhost:8080/example,日志打印:
        objectStorageService: org.example.inject.condition.service.impl.AliyunObjectStorageServiceImpl@4cf4e0a
        -----------------------------------------------------------------------------------------------------
        在这个示例中,我们利用自定义条件注解简化了@ConditionalOnProperty注解的配置
        同时统一了配置文件属性命名,实现了一次配置多处使用
        这种优化提高了配置的简洁性和可维护性,同时减少了配置的复杂度和错误可能性

1.27 [重]SpringIOC容器[存]:3种

00.总结
    a.第一种:
        XML配置文件:使用XML文件定义Bean及其依赖关系
    b.第二种:
        注解方式:使用注解将类标识为Spring管理的Bean
        @Component、@Service、@Repository、@Controller:用于标识不同类型的组件
        @Configuration:用于定义配置类,其中包含@Bean注解的方法
    c.第三种
        Java配置类(@Configuration + @Bean):使用Java配置类和@Bean注解定义Bean

01.第1种:XML配置文件
    a.介绍
        使用XML文件定义Bean及其依赖关系。这是Spring早期版本中常用的方式
    b.XML示例
        <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 id="myService" class="com.example.MyService">
                <property name="myRepository" ref="myRepository"/>
            </bean>
            <bean id="myRepository" class="com.example.MyRepository"/>
        </beans>

02.第2种:注解方式
    a.使用注解将类标识为Spring管理的Bean
        @Component:通用组件
        @Service:服务层组件
        @Repository:持久层组件
        @Controller:控制层组件
        @Configuration:配置类,包含@Bean注解的方法
    b.示例
        @Component
        public class MyService {
            // ...
        }
        -----------------------------------------------------------------------------------------------------
        @Configuration
        public class AppConfig {
            @Bean
            public MyRepository myRepository() {
                return new MyRepository();
            }
        }

03.第3种:Java配置类(@Configuration + @Bean)
    a.介绍
        使用Java配置类和@Bean注解定义Bean。这种方式提供了类型安全的配置
    b.示例
        @Configuration
        public class AppConfig {
            @Bean
            public MyService myService(MyRepository myRepository) {
                return new MyService(myRepository);
            }

            @Bean
            public MyRepository myRepository() {
                return new MyRepository();
            }
        }

1.28 [重]Spring依赖注入/自动装配[取]:7种

00.总结
    1.构造函数注入(官方推荐)        属于自动装配,constructor          @Autowired
    2.Setter方法注入(官方推荐)      属于自动装配,byName               @Autowired
    3.属性注入                       属于自动装配,byType               @Autowired
    4.接口注入                      不属于自动装配;接口注入通常需要手动实现接口方法来注入依赖,Spring本身不直接支持这种方式。
    5.方法注入                      属于自动装配
    6.基于XML配置的注入             可以属于自动装配;通过在XML配置文件中使用autowire属性进行自动装配。
    7.基于Java配置的注入            可以属于自动装配。通过在Java配置类中使用@Autowired注解进行自动装配。

01.构造函数注入(官方推荐):constructor
    a.说明
        构造函数注入是通过类的构造函数来注入依赖对象。这种方式在对象创建时就强制依赖注入,
        确保所有依赖在对象创建时就已经满足。构造函数注入有助于实现不可变对象,并且更容易进行单元测试。
        -----------------------------------------------------------------------------------------------------
        通过构造函数进行自动装配。Spring会根据构造函数的参数类型在容器中查找与之匹配的Bean,并自动注入。
        这种方式确保所有依赖在对象创建时就已经满足。
    b.示例(XML配置)
        <bean id="myService" class="com.example.MyService" autowire="constructor"/>
        <bean id="myRepository" class="com.example.MyRepository"/>
    c.示例(Java类)
        @Component
        public class MyService {
            private final MyRepository myRepository;

            @Autowired
            public MyService(MyRepository myRepository) {
                this.myRepository = myRepository;
            }
        }
    d.示例(main函数)
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.annotation.AnnotationConfigApplicationContext;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        import java.util.Arrays;
        import java.util.List;

        /**
         * 基于构造函数的依赖注入
         * @author nine
         * @since 1.0
         */
        public class ConstructorDIDemo {
            public static void main(String[] args) {
                // 创建一个基于 Java Config 的应用上下文
                ApplicationContext context = new AnnotationConfigApplicationContext(ConstructorAppConfig.class);
                // 从上下文中获取名bean,其类型为PetStoreService
                SimpleMovieLister bean = context.getBean(SimpleMovieLister.class);
                // 调用获取的bean的方法
                bean.listMovies();
            }
        }

        /**
         * App配置
         */
        @Configuration
        class ConstructorAppConfig{

            @Bean
            public MovieFinder movieFinder() {
                return new MovieFinder();
            }

            @Bean
            public SimpleMovieLister simpleMovieLister(MovieFinder movieFinder) {
                return new SimpleMovieLister(movieFinder);
            }
        }

        /**
         * 服务代码
         */
        @Slf4j
        class SimpleMovieLister {
            private final MovieFinder movieFinder;
            public SimpleMovieLister(MovieFinder movieFinder) {
                this.movieFinder = movieFinder;
            }
            public void listMovies() {
                log.info("电影列表打印中");
                movieFinder.findMovies().forEach(log::info);
            }
        }
        @Slf4j
        class MovieFinder {
            public List<String> findMovies() {
                return Arrays.asList("电影1", "电影2", "电影3");
            }
        }

02.Setter方法注入(官方推荐):byName
    a.说明
        Setter方法注入是通过类的setter方法来注入依赖对象。
        这种方式允许在对象创建后再进行依赖注入,适用于那些依赖项是可选的或者需要在对象创建后进行某些初始化操作的情况。
        -----------------------------------------------------------------------------------------------------
        通过Bean名称进行自动装配。Spring会根据属性名称在容器中查找与之匹配的Bean,并自动注入。
        如果找不到匹配的Bean,Spring会抛出异常。
    b.示例(XML配置)
        <bean id="myService" class="com.example.MyService" autowire="byName"/>
        <bean id="myRepository" class="com.example.MyRepository"/>
    c.示例(Java类)
        @Component
        public class MyService {
            private MyRepository myRepository;

            @Autowired
            public void setMyRepository(MyRepository myRepository) {
                this.myRepository = myRepository;
            }
        }
    d.示例(main函数)
        import lombok.extern.slf4j.Slf4j;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationContext;
        import org.springframework.context.annotation.AnnotationConfigApplicationContext;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        /**
         * 基于Setter的依赖注入
         * @author nine
         * @since 1.0
         */
        public class SetterDIDemo {
            public static void main(String[] args) {
                // 创建一个基于 Java Config 的应用上下文
                ApplicationContext context = new AnnotationConfigApplicationContext(SetterAppConfig.class);
                // 从上下文中获取名bean,其类型为PetStoreService
                SimpleMovieListerSet bean = context.getBean(SimpleMovieListerSet.class);
                // 调用获取的bean的方法
                bean.listMovies();
            }
        }

        /**
         * App配置
         */
        @Configuration
        class SetterAppConfig{
            @Bean
            public MovieFinder movieFinder() {
                return new MovieFinder();
            }

            @Bean
            public SimpleMovieListerSet simpleMovieLister() {
                return new SimpleMovieListerSet();
            }
        }

        @Slf4j
        class SimpleMovieListerSet {
            private MovieFinder movieFinder;
            @Autowired
            public void setMovieFinder(MovieFinder movieFinder) {
                this.movieFinder = movieFinder;
            }
            public void listMovies() {
                log.info("电影列表打印中");
                movieFinder.findMovies().forEach(log::info);
            }
        }

03.属性注入(@Autowired):byType
    a.说明
        字段注入是通过直接在类的字段上使用@Autowired注解来注入依赖对象。这种方式不需要构造函数或setter方法,
        但它通常被认为是不太好的实践,因为它使得依赖关系不够明显,并且不利于单元测试。
        -----------------------------------------------------------------------------------------------------
        通过Bean类型进行自动装配。Spring会根据属性的类型在容器中查找与之匹配的Bean,并自动注入。
        如果找到多个匹配的Bean,Spring会抛出异常。
    b.示例(XML配置)
        <bean id="myService" class="com.example.MyService" autowire="byType"/>
        <bean id="myRepository" class="com.example.MyRepository"/>
    c.示例(Java类)
        @Component
        public class MyService {
            @Autowired
            private MyRepository myRepository;
        }

04.接口注入
    a.说明
        接口注入是一种较少使用的方式,主要用于某些特定场景。它通过定义一个接口来提供依赖注入的方法,
        然后让需要注入依赖的类实现这个接口。Spring本身不直接支持这种方式,但可以通过一些设计模式来实现。
    b.示例
        public interface MyRepositoryAware {
            void setMyRepository(MyRepository myRepository);
        }

        @Component
        public class MyService implements MyRepositoryAware {
            private MyRepository myRepository;

            @Override
            public void setMyRepository(MyRepository myRepository) {
                this.myRepository = myRepository;
            }
        }

05.方法注入
    a.说明
        方法注入是通过在普通方法上使用@Autowired注解来注入依赖对象。
        这种方式可以用于需要在方法调用时动态注入依赖的情况。
    b.示例
        @Component
        public class MyService {
            private MyRepository myRepository;

            @Autowired
            public void configure(MyRepository myRepository) {
                this.myRepository = myRepository;
            }
        }

06.基于XML配置的注入
    a.说明
        在Spring的早期版本中,依赖注入主要通过XML配置文件来实现。
        虽然现在大多数项目都转向了基于注解的配置,但XML配置仍然是有效的方式,特别是在某些需要明确配置的场景下
    b.示例
        <bean id="myService" class="com.example.MyService">
            <property name="myRepository" ref="myRepository"/>
        </bean>
        <bean id="myRepository" class="com.example.MyRepository"/>

07.基于Java配置的注入
    a.说明
        Spring的Java配置(基于@Configuration和@Bean注解)提供了一种类型安全的方式来配置Spring容器。
        通过Java配置,可以在配置类中定义和注入依赖。
    b.示例
        @Configuration
        public class AppConfig {
            @Bean
            public MyRepository myRepository() {
                return new MyRepository();
            }

            @Bean
            public MyService myService(MyRepository myRepository) {
                return new MyService(myRepository);
            }
        }

1.29 [重]Spring依赖注入:final修饰+构造函数注入

00.汇总
    a.官方推荐构造函数实现注入
        为了防止注入的bean在后面某个环节被更改了,导致不可预料的事情发生
    b.推荐
        final修饰+构造函数注入
    c.若出现循环依赖
        a.解决1
            请使用@Resource,final修饰+构造函数注入 本质是构造器注入,构造器注入不能解决循环依赖问题
        b.解决2
            懒加载
            之前某个教程,就全用构造器注入,后来在写项目时碰到循环依赖,用懒加载也不行,不使用构造器注入就解决了

01.常用方式
    @Resource
    private MeterInfoMapper meterInfoMapper

02.final修饰+构造函数注入
    @AllArgsConstructor:生成该类下全部属性的构造方法
    @RequiredArgsConstructor:lombok,生成该类下被final修饰或者带有@NonNull的构造方法
    ---------------------------------------------------------------------------------------------------------
    0Service
    @RequiredArgsConstructor
    public class TenantInfoServiceImpl extends ServiceImpl<TenantInfoMapper,TenantInfo> implements ITenantInfoService {
        private finaL @NonNuLl MeterInfoMapper meterInfoMapper;
    }

2 SpringMVC

2.1 [1]定义

01.定义
    Spring Web MVC 框架提供”模型-视图-控制器”( Model-View-Controller )架构和随时可用的组件,用于开发灵活且松散耦合的 Web 应用程序。
    MVC 模式有助于分离应用程序的不同方面,如输入逻辑,业务逻辑和 UI 逻辑,同时在所有这些元素之间提供松散耦合

02.特点
    使用真的真的真的非常方便,无论是添加 HTTP 请求方法映射的方法,还是不同数据格式的响应。
    提供拦截器机制,可以方便的对请求进行拦截处理。
    提供异常机制,可以方便的对异常做统一处理。
    可以任意使用各种视图技术,而不仅仅局限于 JSP ,例如 Freemarker、Thymeleaf 等等。
    不依赖于 Servlet API (目标虽是如此,但是在实现的时候确实是依赖于 Servlet 的,当然仅仅依赖 Servlet ,而不依赖 Filter、Listener )。

03.三层职责
    Model:负责对请求进行处理,并将结果返回给 Controller;
    View:负责将请求的处理结果进行渲染,展示在客户端浏览器上;
    Controller:是 Model 和 View 交互的纽带;主要负责接收用户请求,并调用 Model 对请求处理,然后将 Model 的处理结果传递给 View。

04.本质:SpringMVC本质是对Servlet的进一步封装
    其最核心的组件是DispatcherServlet,它是 Spring MVC 的前端控制器,主要负责对请求和响应的统一地处理和分发。
    Controller 接收到的请求其实就是 DispatcherServlet 根据一定的规则分发给它的。

2.2 [1]作用域:3个

00.总结
    request域数据:适用于单次请求的数据传递,使用Model、Map、ModelMap等方式
    session域数据:适用于多个请求之间的数据共享,使用HttpSession
    application域数据:适用于整个应用程序范围的数据共享,使用ServletContext

01.共享request域数据,通常用于在一次请求的处理过程中传递数据
    a.5种方式
        a.通过原生servlet方法
            request.setAttribute("key", "value");
        b.Map集合共享数据
             @GetMapping("/example")
             public String example(Map<String, Object> map) {
                 map.put("key", "value");
                 return "viewName";
             }
        c.Model共享数据(推荐)
            @GetMapping("/example")
             public String example(Model model) {
                 model.addAttribute("key", "value");
                 return "viewName";
             }
        d.ModelMap共享数据(推荐)
            @GetMapping("/example")
            public String example(ModelMap modelMap) {
                modelMap.addAttribute("key", "value");
                return "viewName";
            }
        e.ModelAndView共享数据,用于封装模型数据和视图信息,便于在控制器中返回
            @GetMapping("/example")
            public ModelAndView example() {
                ModelAndView modelAndView = new ModelAndView("viewName");
                modelAndView.addObject("key", "value");
                return modelAndView;
            }
    d.总结
        Model、ModelMap、Map的关系:分析源码和debug发现本质都是最后封装成了一个ModelAndView

02.共享session域数据,通常用于在多个请求之间共享数据
    @GetMapping("/example")
    public String example(HttpSession session) {
        session.setAttribute("key", "value");
        return "viewName";
    }

03.共享application域数据,通常用于在整个应用程序范围内共享数据
    @GetMapping("/example")
    public String example(HttpServletRequest request) {
        ServletContext application = request.getServletContext();
        application.setAttribute("key", "value");
        return "viewName";
    }

2.3 [1]核心组件:9个

01.九大核心组件
    MultipartResolver:处理文件上传请求
    LocaleResolver:解析和设置用户的区域信息(Locale)
    ThemeResolver:解析和设置主题(Theme)
    HandlerMapping:将请求映射到处理器(Handler)
    HandlerAdapter:执行处理器(Handler)
    HandlerExceptionResolver:处理处理器(Handler)抛出的异常
    RequestToViewNameTranslator:将请求转换为视图名称
    ViewResolver:解析视图名称并返回视图对象
    FlashMapManager:管理Flash属性

01.MultipartResolver:处理文件上传请求
    a.说明
        用于解析包含文件上传的多部分请求(multipart request)。
        Spring MVC提供了`CommonsMultipartResolver`和`StandardServletMultipartResolver`两种实现。
    b.示例配置
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"/>

02.LocaleResolver:解析和设置用户的区域信息(Locale)
    a.说明
        用于确定用户的区域信息,以便进行国际化处理。
        Spring MVC提供了AcceptHeaderLocaleResolver、CookieLocaleResolver和SessionLocaleResolver等实现。
    b.示例配置
        <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

03.ThemeResolver:解析和设置主题(Theme)
    a.说明
        用于确定应用程序的主题,以便进行主题切换。
        Spring MVC提供了FixedThemeResolver和CookieThemeResolver等实现。
    b.示例配置
        <bean id="themeResolver" class="org.springframework.web.servlet.theme.CookieThemeResolver"/>

04.HandlerMapping:将请求映射到处理器(Handler)
    a.说明
        用于根据请求URL查找对应的处理器。Spring MVC提供了多种实现,
        如BeanNameUrlHandlerMapping、SimpleUrlHandlerMapping和RequestMappingHandlerMapping等。
    b.示例配置
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping"/>

05.HandlerAdapter:执行处理器(Handler)
    a.说明
        用于适配和执行处理器。Spring MVC提供了多种实现,
        如SimpleControllerHandlerAdapter、HttpRequestHandlerAdapter和RequestMappingHandlerAdapter等。
    b.示例配置
        <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"/>

06.HandlerExceptionResolver:处理处理器(Handler)抛出的异常
    a.说明
        用于处理处理器执行过程中抛出的异常,并将异常转换为适当的响应。
        Spring MVC提供了多种实现,如SimpleMappingExceptionResolver和DefaultHandlerExceptionResolver等。
    b.示例配置
        <bean class="org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver"/>

07.RequestToViewNameTranslator:将请求转换为视图名称
    a.说明
        用于在没有显式指定视图名称时,根据请求URL推断视图名称。
        Spring MVC提供了DefaultRequestToViewNameTranslator实现。
    b.示例配置
        <bean class="org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator"/>

08.ViewResolver:解析视图名称并返回视图对象
    a.说明
        用于将控制器返回的视图名称解析为具体的视图对象。Spring MVC提供了多种实现,
        如InternalResourceViewResolver、BeanNameViewResolver和XmlViewResolver等。
    b.示例配置
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/views/"/>
            <property name="suffix" value=".jsp"/>
        </bean>

09.FlashMapManager:管理Flash属性
    a.说明
        用于在重定向场景下保存和传递临时属性。Spring MVC提供了SessionFlashMapManager实现。
    b.示例配置
        <bean class="org.springframework.web.servlet.support.SessionFlashMapManager"/>

2.4 [1]工作流程:6步

01.图示
    组件                | 提供者      | 描述
    |-------------------|------------|---------------------------------------------------------------------------------------------
    | DispatcherServlet | 框架提供    | 前端控制器,它是整个 Spring MVC 流程控制中心,负责统一处理请求和响应,调用其他组件对用户请求进行处理。
    | HandlerMapping    | 框架提供    | 处理器映射器,根据请求的 url、method 等信息查找相应的 Handler。
    | Handler           | 开发人员提供| 处理器,通常被称为 Controller(控制器)。它可以在 DispatcherServlet 的控制下,对具体的用户请求进行处理。
    | HandlerAdapter    | 框架提供    | 处理器适配器,负责调用具体的控制器方法,对用户发来的请求来进行处理。
    | ViewResolver      | 框架提供    | 视图解析器,其职责是对视图进行解析,得到相应的视图对象。常见的视图解析器有 ThymeleafViewResolver、InternalResourceViewResolver 等。
    | View              | 开发人员提供 | 视图,它作用是将模型(Model)数据通过页面展示给用户。
    ---------------------------------------------------------------------------------------------------------

02.工作流程:6步
    a.第1步:前端控制器,DispatcherServlet
        【不需要程序员开发】,接收请求、响应结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。
    b.第2步:处理器映射器,HandlerMapping
        【不需要程序员开发】,根据请求的URL来查找Handler
    c.第3步:处理器适配器,HandlerAdapter
        【需要程序员开发】,在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以正确的去执行Handler。
    d.第4步:处理器,Handler
        【需要程序员开发】
    e.第5步:视图解析器,ViewResolver
        【不需要程序员开发】,进行视图的解析,根据视图逻辑名解析成真正的视图(view)
    f.第6步:视图,View
        【需要程序员开发】,View是一个接口,它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)
    g.总结
        1.客户端发送请求:客户端发送一个HTTP请求到服务器。
        2.DispatcherServlet接收请求:前端控制器(DispatcherServlet)接收请求。
        3.HandlerMapping查找处理器:DispatcherServlet根据请求的URL,通过HandlerMapping查找对应的处理器(Handler)。
        4.HandlerAdapter执行处理器:DispatcherServlet通过HandlerAdapter执行找到的处理器(Handler)。
        5.处理器处理请求:处理器(Handler)处理具体的业务逻辑,并返回一个ModelAndView对象。
        6.ViewResolver解析视图:DispatcherServlet通过ViewResolver将视图逻辑名解析成具体的视图对象。
        7.视图渲染:视图对象(View)渲染视图,将处理结果展示给用户。
        8.响应客户端:DispatcherServlet将渲染后的视图返回给客户端。

2.5 [1]视图解析器:6个

00.3大主流视图技术
    a.介绍
        视图技术,这是一个抽象概念,或者说是一个概括性概念。
        任何一款视图技术产品,都应该包括:1.模板 2.模板解析引擎
    b.分类
        Jsp:这个很老牌,相信大家一定知道。Jsp 模板是 J2EE 官方指定的,所以其模板引擎可由遵循 J2EE 规范的服务器实现,如 tomcat ;
        Freemarker:Freemarker 曾经因为 Struts MVC 风靡一时,当然,现在也很受欢迎。其模板需要自己提供的模板引擎解析;
        Thymeleaf:Thymeleaf 应该算是后起之秀,因为是后起之秀,会改善前辈的诸多不足,更适应现代化开发理念和习惯。

01.定义
    视图解析器是用来接收经过处理器适配器调用具体的controller后生成的逻辑视图的,
    它接受 DispatcherServlet传过来的ModelAndView,然后将ModelAndView数据填充到相应的视图中,
    然后返回一个带有数据的视图再传给DispatcherServlet.

02.作用
    视图解析器(ViewResolver)它是Spring MVC框架中的一个接口,负责将逻辑视图名称解析为实际的视图对象(如JSP、Thymeleaf、.FreeMarker模板等)
    根据控制器返回的视图名称,找到对应的视图文件,并将模型数据传递给视图,生成最终的TML响应。

03.工作流程
    1.接收视图名称:控制器返回一个逻辑视图名称,,SpringMVC将目标方法返回的String、View、ModelMap或是ModelAndView都转换为一个ModelAndView对象
    2.视图解析:通过视图解析器(ViewResolver)对ModelAndView对象中的View对象进行解析,将该逻辑视图View对象解析为一个物理视图View对象
    3.渲染视图:用物理视图View对象的render()方法进行视图渲染,由视图对象生成最终的HTML响应。

04.返回类型
    视图解析器不仅可以返回物理视图,还可以返回一个模板。
    它会解析逻辑视图配置,返回一种Freemarker模板或者thymelea模板,该模板负责将数据模型中的数据合并到模板中,从而生成标准的输出,可以生成各种文本,包括HTML,XML,java源码等。

05.常见解析器
    a.XmlViewResolver
        接口 ViewResolver 的实现,从 XML 配置文件中查找视图实现(默认 XML 配置文件为 /WEB-INF/views.xml)
    b.ResourceBundleViewResolver
        接口 ViewResolver 的实现,用于从 properties 文件中查找视图。
    c.UrlBasedViewResolver
        接口 ViewResolver 的实现,用于根据请求的 URL 路径返回相应的视图,该视图需为抽象类 AbstractUrlBasedView 的实现,它还有些子类,如 InternalResourceView 和 JstlView 等
    d.InternalResourceViewResolver
        UrlBasedViewResolver 的子类,通常用于查找 JSP(类 InternalResourceView)和 JSTL(类 JstlView,InternalResourceView 的子类)等视图。
    e.VelocityViewResolver /FreeMarkerViewResolver
        UrlBasedViewResolver 的子类分别用于支持 Velocity(类 VelocityView)和 FreeMark 视图(类 FreeMarkerView)。
    f.ContentNegotiatingViewResolver
        接口 ViewResolver 的实现,用于根据请求文件的后缀名或请求的 header 中的 accept 字段查找视图。
    g.总结
        XmlViewResolver:从XML文件中加载视图定义
        ResourceBundleViewResolver:从资源包(ResourceBundle)中加载视图定义
        UrlBasedViewResolver:基于URL的视图解析
        InternalResourceViewResolver:解析JSP视图
        VelocityViewResolver:解析Velocity模板视图
        FreeMarkerViewResolver:解析FreeMarker模板视图
        ContentNegotiatingViewResolver:内容协商视图解析

2.6 [1]返回处理方式:3种

00.汇总
    约定统一的格式
    封装常量的案例
    全局异常处理:@RestControllerAdvice
    ResponseEntity实体:ResponseBodyAdvice
    StreamingResponseBody处理大数据流的接口

01.约定统一的格式
    a.简单的业务状态+详情出参
        a.固定的出参json格式
            {
                "code":2000,
                "message":"成功",
                "data":{}
            }
        b.前端处理逻辑
            code = 2000: 做成功data数据内容的展示
            code = 5000: 做失败的提示语message提示
    b.通过Http状态码判断
        a.2xx(成功状态码)
            200 (OK): 请求成功
            201 (Created): 请求成功,服务器对应资源已创建
            202 (Accepted): 服务器接受请求,但还没处理好
            204 (No Content): 服务器成功处理请求,但没有内容返回
            205 (Reset Content): 服务器成功处理请求,要求客户端重置之前显示内容
            206 (Partial Content): 服务器成功处理部分请求,用于断点续传
        b.3xx(重定向状态码)
            301 (Moved Permanently): 请求资源已经换成新的url,望客户端请求新的地址Location的url
            302 (Found): 请求资源暂时换成新的url
            304 (Not Modified): 服务端判断是否资源没被修改,浏览器使用本地缓存
        c.4xx(客户端错误状态码)
            400 (Bad Request): 客户端请求格式有问题
            401 (Unauthorized): 未认证或者认证失败
            403 (Forbidden): 没有对应的权限
            404 (Not Found): 服务器找不到对应的客户端请求的资源
            405 (Method Not Allowed): 客户端请求方法不被服务端允许要求的
        d.5xx(服务端错误状态码)
            500 (Internal Server Error): 服务器内部程序代码等引起的错误
            502 (Bad Gateway): 作为网关,收到服务器无效的响应
            503 (Service Unavailable): 服务器无法提供服务
            504 (Gateway Time - out): 作为网关,收到服务器响应超时过慢
    c.复杂展示的业务场景
        a.errorDetails: 错误对象具体字段描述
            field: 指定具体错误字段
            errorMsg: 指定具体错误信息
        b.其他可选参数
            timestamp: 记录请求时间
            version: 记录接口版本
            traceId: 记录链路追踪id
        c.示例
            {
                "code":4000,
                "message":"提交格式错误",
                "data":null,
                "errorDetails":[
                    {
                        "field":"username",
                        "errorMsg":"名称必须在6-20个字符内"
                    }
                ]
            }

02.封装常量的案例
    a.请求状态码的枚举值
        @Getter
        public enum ResultEnum {
            SUCCESS("请求成功", 2000),
            ERROR("服务器内部错误", 5000),
            PARAM_ERROR("参数错误", 4001);

            private String msg;
            private Integer code;

            ResultEnum(String msg, Integer code) {
                this.msg = msg;
                this.code = code;
            }
        }
    b.统一出参的格式封装
        @Data
        public class ResultData<T> {
            private static final long serialVersionUID = 1L;
            private Integer code = 2000; //默认成功
            private String msg = "";
            private T data;

            public ResultData(Integer code, String msg) {
                this.code = code;
                this.msg = msg;
            }

            public ResultData(Integer code, String msg, T data) {
                this.code = code;
                this.msg = msg;
                this.data = data;
            }

            public static <T> ResultData<T> error(Integer code, String msg) {
                return new ResultData<T>(code, msg);
            }

            public static <T> ResultData<T> error(String msg) {
                return new ResultData<T>(ResultEnum.ERROR.getCode(), msg);
            }

            public static <T> ResultData<T> success(String msg) {
                return new ResultData<T>(ResultEnum.SUCCESS.getCode(), msg);
            }

            public static <T> ResultData<T> success(String msg, T data) {
                return new ResultData<T>(ResultEnum.SUCCESS.getCode(), msg, data);
            }
        }

03.全局异常处理:@RestControllerAdvice
    a.使用方式
        @RestControllerAdvice
        public class GlobalExceptionHandlerAdvice {
            @ExceptionHandler(MethodArgumentNotValidException.class)
            public ResultData<Object> resolveMethodArgumentNotValidException(MethodArgumentNotValidException e) {
                FieldError fieldError = e.getBindingResult().getFieldError();
                log.error("【参数校验异常】错误提示:{}", fieldError != null ? fieldError.getDefaultMessage() : "");
                return ResultData.error(ResultEnum.PARAM_ERROR.getCode(), e.getBindingResult().getFieldError().getDefaultMessage());
            }
        }
    b.常见的需补充的错误异常
        SQLException、DataAccessException: 与数据库交互过程中产生的异常
        HttpRequestMethodNotSupportedException: 请求方式不支持的异常
        MethodArgumentNotValidException、ConstraintViolationException: 使用@Valid或@Validated时校验参数时可能会遇到的异常
        HttpMessageNotReadableException、HttpMessageNotWritableException: 请求体的数据读写导致的问题
        IllegalArgumentException: 参数格式错误的异常
        CustomeException(自定义异常): 自定义实现标识特定异常错误
        Exception: 最后兜底顶级的异常,保证异常处理最后会走到的地方
    c.ExceptionHandler 处理异常优先级
        处理的优先级,必然是有精确匹配到的异常处理方法先走,如果没有精确匹配的异常处理,会根据子类->父类的顺序来处理
        如有如下三种异常,ExceptionA,ExceptionB,ExceptionC ,分别是ExceptionA 是 ExceptionB的父类,ExceptionB是ExceptionC的父类
        此时对应请求抛出ExceptionC,全局处理异常方法exceptionBSolve会去对应处理它
        -----------------------------------------------------------------------------------------------------
        @ExceptionHandler(ExceptionA.class)
        @ResponseBody
        public ResultData<Object> ExceptionASolve(ExceptionA e) {
        }
        @ExceptionHandler(ExceptionB.class)
        @ResponseBody
        public ResultData<Object> exceptionBSolve(ExceptionB e) {
        }
        @RequestMapping("/hello")
        public String hello() {
            throw new ExceptionC("sss");
        }

04.ResponseEntity实体:ResponseBodyAdvice
    a.说明
        ResponseBodyAdvice是Spring Framework中的一个接口,允许您在将响应写入客户端之前自定义响应
        它通常与@ControllerAdvice注释结合使用,以跨多个控制器将全局更改应用于响应主体
    b.创建ResponseBodyAdvice实现
        创建一个实现ResponseBodyAdvice接口的类。这个接口有两个泛型参数:响应主体的类型和MessageConverter的类型
        -----------------------------------------------------------------------------------------------------
        import org.springframework.core.MethodParameter;
        import org.springframework.http.MediaType;
        import org.springframework.http.server.ServerHttpRequest;
        import org.springframework.http.server.ServerHttpResponse;
        import org.springframework.web.bind.annotation.ControllerAdvice;
        import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;

        @ControllerAdvice
        public class CustomResponseBodyAdvice implements ResponseBodyAdvice<Object> {

            @Override
            public boolean supports(MethodParameter returnType, Class converterType) {
                // This method is called to determine if the advice should be applied
                // based on the return type and converter type.
                // Return true if you want to apply the advice, false otherwise.
                return true;
            }

            @Override
            public Object beforeBodyWrite(
                    Object body,
                    MethodParameter returnType,
                    MediaType selectedContentType,
                    Class selectedConverterType,
                    ServerHttpRequest request,
                    ServerHttpResponse response) {
                // This method is called just before the response body is written to the client.
                // You can modify the body or the response before it's sent to the client.

                // For example, you can wrap the original response in a custom wrapper.
                CustomResponseWrapper wrapper = new CustomResponseWrapper(body);
                return wrapper;
            }
        }
    c.自定义响应
        在beforeBodyWrite方法中,您可以自定义响应主体或响应本身
        例如,您可以将原始响应包装在自定义包装器中,修改内容,添加标题等
        -----------------------------------------------------------------------------------------------------
        public class CustomResponseWrapper {

            private Object data;

            public CustomResponseWrapper(Object data) {
                this.data = data;
            }

            public Object getData() {
                return data;
            }

            // You can add more methods or properties as needed
        }
    d.在控制器中使用自定义响应
        当控制器返回响应时,将调用beforeBodyWrite方法,允许您自定义响应
        -----------------------------------------------------------------------------------------------------
        @RestController
        public class MyController {

            @GetMapping("/api/data")
            public ResponseEntity<String> getData() {
                // Your original response
                String responseData = "Hello, World!";
                return ResponseEntity.ok(responseData);
            }
        }
        -----------------------------------------------------------------------------------------------------
        使用此设置,当调用/api/data端点时,将调用beforeBodyWrite中的CustomResponseBodyAdvice方法
        并且响应主体将在发送到客户端之前包装在您的CustomResponseWrapper中

05.StreamingResponseBody处理大数据流的接口
    a.介绍
        StreamingResponseBody 是 Spring MVC 提供的一种用于处理大数据流的接口
        它允许应用程序以流的方式将数据写入 HTTP 响应,而不是一次性将整个数据加载到内存中
        这对于处理大文件下载或需要实时生成数据的场景非常有用
    b.定义
        StreamingResponseBody 是一个函数式接口,定义如下:
        @FunctionalInterface
        public interface StreamingResponseBody {
            void writeTo(OutputStream outputStream) throws IOException;
        }
        实现该接口的 writeTo 方法时,开发者可以将数据写入提供的 OutputStream
    c.原理
        StreamingResponseBody 的原理是通过流式传输数据,避免将整个响应内容加载到内存中
        Spring MVC 在处理请求时,会在一个独立的线程中调用 writeTo 方法,将数据写入响应的输出流
    d.常用 API
        StreamingResponseBody 本身没有复杂的 API,因为它是一个函数式接口,主要依赖于 writeTo 方法
        常用的相关 API 包括:
        ResponseEntity<StreamingResponseBody>:用于返回 StreamingResponseBody 的响应实体
        OutputStream:用于将数据写入响应
    e.使用步骤
        定义控制器方法:返回类型为 ResponseEntity<StreamingResponseBody>
        实现 StreamingResponseBody:在 writeTo 方法中将数据写入 OutputStream
        设置响应头:根据需要设置响应的内容类型和其他头信息
    f.场景:大文件下载
        @GetMapping("/download")
        public ResponseEntity<StreamingResponseBody> downloadFile() {
            StreamingResponseBody stream = outputStream -> {
                try (InputStream inputStream = new FileInputStream(new File("largefile.zip"))) {
                    byte[] buffer = new byte[4096];
                    int bytesRead;
                    while ((bytesRead = inputStream.read(buffer)) != -1) {
                        outputStream.write(buffer, 0, bytesRead);
                    }
                }
            };

            return ResponseEntity.ok()
                    .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=largefile.zip")
                    .contentType(MediaType.APPLICATION_OCTET_STREAM)
                    .body(stream);
        }
    g.场景:实时生成数据流
        @GetMapping("/stream")
        public ResponseEntity<StreamingResponseBody> streamData() {
            StreamingResponseBody stream = outputStream -> {
                for (int i = 0; i < 100; i++) {
                    String data = "Data chunk " + i + "\n";
                    outputStream.write(data.getBytes());
                    outputStream.flush();
                    Thread.sleep(100); // 模拟数据生成延迟
                }
            };

            return ResponseEntity.ok()
                    .contentType(MediaType.TEXT_PLAIN)
                    .body(stream);
        }

2.7 [1]异常处理方式:3种

00.总结
    a.SpringMVC的异常处理
        可以将异常抛给Spring框架,由Spring框架来处理;我们只需要配置简单的异常处理器,在异常处理器中添视图页面即可。
    b.Spring统一异常处理有3种方式
        1.使用@ExceptionHandler注解:适用于单个控制器的异常处理,便于在控制器内部定义特定的异常处理逻辑
        2.使用@controlleradvice注解:适用于全局异常处理,便于集中管理和处理应用程序中的异常
        3.实现HandlerExceptionResolver接口:提供了更灵活的异常处理机制,适用于需要自定义异常处理逻辑的场景

01.使用@ExceptionHandler注解:适用于单个控制器的异常处理,便于在控制器内部定义特定的异常处理逻辑
    a.说明
        在控制器类中,使用@ExceptionHandler注解标注一个方法,该方法可以处理由控制器方法抛出的特定异常
    b.示例
        @Controller
        public class MyController {

            @GetMapping("/example")
            public String example() {
                // 可能抛出异常的代码
                throw new RuntimeException("Example exception");
            }

            @ExceptionHandler(RuntimeException.class)
            public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Error: " + ex.getMessage());
            }
        }

02.使用@controlleradvice注解:适用于全局异常处理,便于集中管理和处理应用程序中的异常
    a.说明
        使用@ControllerAdvice注解标注一个类,该类可以包含多个@ExceptionHandler方法,用于处理不同类型的异常
    b.示例
        @ControllerAdvice
        public class GlobalExceptionHandler {

            @ExceptionHandler(RuntimeException.class)
            public ResponseEntity<String> handleRuntimeException(RuntimeException ex) {
                return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("Global Error: " + ex.getMessage());
            }

            @ExceptionHandler(Exception.class)
            public ResponseEntity<String> handleException(Exception ex) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("General Error: " + ex.getMessage());
            }
        }

03.实现HandlerExceptionResolver接口:提供了更灵活的异常处理机制,适用于需要自定义异常处理逻辑的场景
    a.说明
        实现HandlerExceptionResolver接口,并在resolveException方法中定义异常处理逻辑
    b.示例
        public class CustomExceptionResolver implements HandlerExceptionResolver {
            @Override
            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
                ModelAndView modelAndView = new ModelAndView("error");
                modelAndView.addObject("message", ex.getMessage());
                return modelAndView;
            }
        }

2.8 [1]请求和响应包装器:2个

01.请求和响应包装器
    a.请求包装器
        ContentCachingRequestWrapper用于缓存请求的输入流,允许多次读取请求体
        这在需要多次处理请求数据(如日志记录和业务处理)时非常有用
    b.响应包装器
        ContentCachingResponseWrapper用于缓存响应的输出流,允许在响应提交给客户端之前修改响应体
        这在需要对响应内容进行后处理(如添加额外的头部信息、修改响应体)时非常有用

02.使用场景
    a.请求日志记录
        记录请求的详细信息,包括请求头、请求参数和请求体
    b.修改请求数据
        在请求到达控制器之前修改请求数据,例如添加或修改请求头
    c.响应内容修改
        在响应发送给客户端之前修改响应内容,例如添加或修改响应头,或者对响应体进行签名
    d.性能测试
        通过缓存请求和响应数据,可以进行性能测试,而不影响实际的网络 I/O 操作

03.具体用法
    a.请求包装器
        import org.springframework.web.filter.OncePerRequestFilter;
        import org.springframework.stereotype.Component;
        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        import org.springframework.web.util.ContentCachingRequestWrapper;

        @Component
        public class RequestWrapperFilter extends OncePerRequestFilter {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                ContentCachingRequestWrapper requestWrapper = new ContentCachingRequestWrapper(request);
                // 可以在这里处理请求数据
                byte[] body = requestWrapper.getContentAsByteArray();
                // 处理body,例如记录日志
                // ...
                filterChain.doFilter(requestWrapper, response);
            }
        }
    b.响应包装器
        import org.springframework.web.filter.OncePerRequestFilter;
        import org.springframework.stereotype.Component;
        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        import org.springframework.web.util.ContentCachingResponseWrapper;

        @Component
        public class ResponseWrapperFilter extends OncePerRequestFilter {
            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                ContentCachingResponseWrapper responseWrapper = new ContentCachingResponseWrapper(response);
                filterChain.doFilter(request, responseWrapper);

                // 可以在这里处理响应数据
                byte[] body = responseWrapper.getContentAsByteArray();
                // 处理body,例如添加签名
                responseWrapper.setHeader("X-Signature", "some-signature");

                // 必须调用此方法以将响应数据发送到客户端
                responseWrapper.copyBodyToResponse();
            }
        }

04.说明
    a.OncePerRequestFilter
        确保过滤器在一次请求的生命周期中只被调用一次,避免在请求转发或包含时重复处理数据
    b.ContentCachingRequestWrapper 和 ContentCachingResponseWrapper
        提供了对请求和响应数据的缓存功能,允许多次读取和修改

2.9 [2]国际化:4步

01.介绍
    国际化(i18)是指在应用程序中提供多种语言和区域设置的支持,使得不同语言的用户都能使用该应用程序。
    (例如一些航空公司的网页或软件,都需要提供国际化的支持)

02.Spring中国际化的主要组件:
    MessageSource:用于加载和管理国际化消息资源
    LocaleResolver:用于解析和确定用户的区域设置(Locale)

03.具体实现步骤
    a.配置MessageSource
        定义消息资源文件(如messages.properties、messages_.en.properties)
    b.注册MessageSource bean
        用于加载这些消息资源文件
    c.配置LocaleResolver
        配置LocaleResolver bean,用于确定用户的区域设置或通过Handlerlnterceptor拦截器获取前端传的lang参数
    d.控制器中的使用
        在控制器中使用Locale和MessageSource获取国际化消息

2.10 [2]表单提交:5步

01.工作流程:5步
    a.定义数据模型
        在Controller中定义一个JavaBean或POO类,其属性与表单字段相对应
    b.接收表单数据
        使用@ModelAttribute注解来接收表单数据,并将其绑定到数据模型的实例上
    c.验证数据(可选)
        使用Spring的验证框架,如@Valid注解和javax.validation规范,来验证用户输入
    d.处理业务逻辑
        在Controller中调用Service层的方法,根据用户输入执行相应的业务逻辑
    e.返回响应
        根据业务逻辑处理结果,Controller可以重定向到新页面,或者返回到同一个表单页面(如果需要显示错误信息)

2.11 [2]重定向、转发

00.汇总
    a.重定向
        客户端会发起两次请求
        浏览器地址栏的URL会改变
        可以重定向到外部URL
    b.转发
        服务器内部处理,客户端只发起一次请求
        浏览器地址栏的URL不会改变
        只能转发到同一应用程序内的资源

01.重定向 (Redirect)
    a.定义
        重定向是指服务器返回一个新的URL给客户端,客户端会重新发起请求到这个新的URL
        重定向会导致浏览器地址栏的URL发生变化
    b.使用方法
        在Spring MVC中,可以通过返回一个以 "redirect:" 开头的字符串来实现重定向
    c.代码
        @Controller
        public class MyController {

            @RequestMapping("/oldUrl")
            public String redirect() {
                // 重定向到新的URL
                return "redirect:/newUrl";
            }
        }
    d.说明
        在这个例子中,当访问 /oldUrl 时,客户端会被重定向到 /newUrl

02.转发 (Forward)
    a.定义
        转发是指服务器内部将请求转发到另一个资源进行处理,客户端并不知道请求被转发过
        转发不会改变浏览器地址栏的URL
    b.使用方法
        在Spring MVC中,可以通过返回一个以 "forward:" 开头的字符串来实现转发
    c.代码
        @Controller
        public class MyController {

            @RequestMapping("/start")
            public String forward() {
                // 转发到另一个URL
                return "forward:/end";
            }
        }
    d.说明
        在这个例子中,当访问 /start 时,服务器会将请求转发到 /end,而客户端并不会察觉到这个过程

2.12 [3]监听器:5种

00.有5种实现
    1.使用@EventListener注解
    2.实现ApplicationListener接口
    3.使用SmartInitializingSingleton接口
    4.使用ApplicationRunner或CommandLineRunner接口
    5.使用@PostConstruct注解

01.使用@EventListener注解
    a.@EventListener注解,可以方便地定义事件监听器
        import org.springframework.context.event.EventListener;
        import org.springframework.stereotype.Component;

        @Component
        public class MyEventListener {

            @EventListener
            public void handleCustomEvent(CustomEvent event) {
                System.out.println("Received custom event - " + event.getMessage());
            }
        }
    b.自定义事件类
        public class CustomEvent extends ApplicationEvent {
            private String message;

            public CustomEvent(Object source, String message) {
                super(source);
                this.message = message;
            }

            public String getMessage() {
                return message;
            }
        }
    c.发布事件
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.ApplicationEventPublisher;
        import org.springframework.stereotype.Component;

        @Component
        public class EventPublisher {

            @Autowired
            private ApplicationEventPublisher applicationEventPublisher;

            public void publishEvent(String message) {
                CustomEvent customEvent = new CustomEvent(this, message);
                applicationEventPublisher.publishEvent(customEvent);
            }
        }

02.实现ApplicationListener接口
    a.示例
        import org.springframework.context.ApplicationListener;
        import org.springframework.stereotype.Component;

        @Component
        public class MyApplicationListener implements ApplicationListener<CustomEvent> {

            @Override
            public void onApplicationEvent(CustomEvent event) {
                System.out.println("Received custom event - " + event.getMessage());
            }
        }

03.使用SmartInitializingSingleton接口
    a.示例
        如果你需要在所有单例bean初始化完成后执行某些操作,可以实现SmartInitializingSingleton接口。
        -----------------------------------------------------------------------------------------------------
        import org.springframework.beans.factory.SmartInitializingSingleton;
        import org.springframework.stereotype.Component;

        @Component
        public class MySmartInitializingSingleton implements SmartInitializingSingleton {

            @Override
            public void afterSingletonsInstantiated() {
                System.out.println("All singletons are instantiated");
            }
        }

04.使用CommandLineRunner接口
    a.示例
        用于在Spring Boot应用启动后执行一些代码
        -----------------------------------------------------------------------------------------------------
        import org.springframework.boot.CommandLineRunner;
        import org.springframework.stereotype.Component;

        @Component
        public class MyCommandLineRunner implements CommandLineRunner {

            @Override
            public void run(String... args) throws Exception {
                System.out.println("Application started with CommandLineRunner");
            }
        }

05.使用ApplicationRunner接口
    a.示例
        用于在Spring Boot应用启动后执行一些代码
        -----------------------------------------------------------------------------------------------------
        import org.springframework.boot.ApplicationRunner;
        import org.springframework.boot.ApplicationArguments;
        import org.springframework.stereotype.Component;

        @Component
        public class MyApplicationRunner implements ApplicationRunner {

            @Override
            public void run(ApplicationArguments args) throws Exception {
                System.out.println("Application started with ApplicationRunner");
            }
        }

06.使用@PostConstruct注解
    a.示例
        @PostConstruct注解用于在bean初始化完成后执行一些操作
        -----------------------------------------------------------------------------------------------------
        import javax.annotation.PostConstruct;
        import org.springframework.stereotype.Component;

        @Component
        public class MyPostConstructListener {

            @PostConstruct
            public void init() {
                System.out.println("Bean is initialized with @PostConstruct");
            }
        }

2.13 [3]过滤器:5种

00.有5种实现
    1.实现javax.servlet.Filter接口
    2.使用@WebFilter注解
    3.使用Spring的OncePerRequestFilter
    4.使用SpringSecurity的过滤器
    5.使用Spring的FilterRegistrationBean进行配置

01.实现javax.servlet.Filter接口
    a.通过实现 javax.servlet.Filter 接口来定义一个过滤器
        import javax.servlet.Filter;
        import javax.servlet.FilterChain;
        import javax.servlet.FilterConfig;
        import javax.servlet.ServletException;
        import javax.servlet.ServletRequest;
        import javax.servlet.ServletResponse;
        import java.io.IOException;

        public class MyFilter implements Filter {

            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                // Initialization code, if needed
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                // Pre-processing
                System.out.println("Request received at MyFilter");

                // Continue the request
                chain.doFilter(request, response);

                // Post-processing
                System.out.println("Response generated at MyFilter");
            }

            @Override
            public void destroy() {
                // Cleanup code, if needed
            }
        }
    b.在Spring Boot应用中注册这个过滤器
        import org.springframework.boot.web.servlet.FilterRegistrationBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class FilterConfig {

            @Bean
            public FilterRegistrationBean<MyFilter> loggingFilter() {
                FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
                registrationBean.setFilter(new MyFilter());
                registrationBean.addUrlPatterns("/api/*"); // Apply filter to specific URL patterns
                return registrationBean;
            }
        }

02.使用@WebFilter注解
    a.使用的是Servlet 3.0或更高版本,可以使用 @WebFilter 注解来定义过滤器
        import javax.servlet.Filter;
        import javax.servlet.FilterChain;
        import javax.servlet.FilterConfig;
        import javax.servlet.ServletException;
        import javax.servlet.ServletRequest;
        import javax.servlet.ServletResponse;
        import javax.servlet.annotation.WebFilter;
        import java.io.IOException;

        @WebFilter(urlPatterns = "/api/*")
        public class MyWebFilter implements Filter {

            @Override
            public void init(FilterConfig filterConfig) throws ServletException {
                // Initialization code, if needed
            }

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                // Pre-processing
                System.out.println("Request received at MyWebFilter");

                // Continue the request
                chain.doFilter(request, response);

                // Post-processing
                System.out.println("Response generated at MyWebFilter");
            }

            @Override
            public void destroy() {
                // Cleanup code, if needed
            }
        }

03.使用Spring的OncePerRequestFilter
    a.说明
        a.单次执行
            OncePerRequestFilter 确保在一次请求的生命周期内
            无论请求如何转发(forwarding)或包含(including)
            过滤器逻辑只执行一次,这对于避免重复处理请求或响应非常有用
        b.内置支持
            它内置了对请求和响应包装器的支持,使得开发者可以方便地对请求和响应进行包装和处理
        c.简化代码
            通过继承 OncePerRequestFilter,开发者可以减少重复代码,因为过滤器的执行逻辑已经由基类管理
        d.易于扩展
            开发者可以通过重写 doFilterInternal 方法来实现自己的过滤逻辑,而不需要关心过滤器的注册和执行次数
    b.场景
        a.请求日志记录
            在请求处理之前和之后记录请求的详细信息,如请求头、请求参数和请求体,而不希望在请求转发时重复记录
        b.请求数据修改
            在请求到达控制器之前,对请求数据进行预处理或修改
            例如添加或修改请求头,而不希望这些修改在请求转发时被重复应用
        c.响应数据修改
            在响应发送给客户端之前,对响应数据进行后处理或修改,例如添加或修改响应头
            而不希望这些修改在请求包含时被重复应用
        d.安全控制
            实现安全控制逻辑,如身份验证、授权检查等,确保这些逻辑在一次请求的生命周期内只执行一次
        e.请求和响应的包装
            使用 ContentCachingRequestWrapper 和 ContentCachingResponseWrapper 等包装器来缓存请求和响应数据
            以便在请求处理过程中多次读取或修改数据
        f.性能监控
            在请求处理前后进行性能监控,如记录处理时间,而不希望这些监控逻辑在请求转发时被重复执行
        g.异常处理
            在请求处理过程中捕获和处理异常,确保异常处理逻辑只执行一次,即使请求被转发到其他处理器
    c.Spring提供了一个方便的抽象类 OncePerRequestFilter,它确保过滤器只会在每个请求中执行一次。
        import org.springframework.web.filter.OncePerRequestFilter;

        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;

        public class MyOncePerRequestFilter extends OncePerRequestFilter {

            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                // Pre-processing
                System.out.println("Request received at MyOncePerRequestFilter");

                // Continue the request
                filterChain.doFilter(request, response);

                // Post-processing
                System.out.println("Response generated at MyOncePerRequestFilter");
            }
        }
    d.在Spring Boot应用中注册这个过滤器
        import org.springframework.boot.web.servlet.FilterRegistrationBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class FilterConfig {

            @Bean
            public FilterRegistrationBean<MyOncePerRequestFilter> loggingFilter() {
                FilterRegistrationBean<MyOncePerRequestFilter> registrationBean = new FilterRegistrationBean<>();
                registrationBean.setFilter(new MyOncePerRequestFilter());
                registrationBean.addUrlPatterns("/api/*"); // Apply filter to specific URL patterns
                return registrationBean;
            }
        }

04.使用Spring Security的过滤器
    a.通过配置Spring Security的过滤器链来定义过滤器
        import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
        import org.springframework.security.config.annotation.web.builders.HttpSecurity;
        import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

        public class SecurityConfig extends WebSecurityConfigurerAdapter {

            @Override
            protected void configure(HttpSecurity http) throws Exception {
                http.addFilterBefore(new MySecurityFilter(), UsernamePasswordAuthenticationFilter.class);
                // Other security configurations
            }
        }
    b.自定义Spring Security过滤器
        import javax.servlet.FilterChain;
        import javax.servlet.ServletException;
        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        import org.springframework.web.filter.OncePerRequestFilter;

        public class MySecurityFilter extends OncePerRequestFilter {

            @Override
            protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
                    throws ServletException, IOException {
                // Pre-processing
                System.out.println("Request received at MySecurityFilter");

                // Continue the request
                filterChain.doFilter(request, response);

                // Post-processing
                System.out.println("Response generated at MySecurityFilter");
            }
        }

05.使用Spring的FilterRegistrationBean进行配置
    a.这种方式可以更灵活地控制过滤器的顺序和应用范围。
        import org.springframework.boot.web.servlet.FilterRegistrationBean;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        @Configuration
        public class FilterConfig {

            @Bean
            public FilterRegistrationBean<MyFilter> loggingFilter() {
                FilterRegistrationBean<MyFilter> registrationBean = new FilterRegistrationBean<>();
                registrationBean.setFilter(new MyFilter());
                registrationBean.addUrlPatterns("/api/*"); // Apply filter to specific URL patterns
                registrationBean.setOrder(1); // Set the order of the filter
                return registrationBean;
            }
        }

2.14 [3]拦截器:3种

00.有3种实现
    1.实现HandlerInterceptor接口,用于拦截HTTP请求
    2.实现WebRequestInterceptor接口,主要用于拦截异步请求
    3.继承AbstractInterceptor抽象类,已过时,是HandlerInterceptor的一个抽象实现,提供了默认实现

01.实现HandlerInterceptor接口,用于拦截HTTP请求
    a.思路
        1)实现Handlerlnterceptor接口:定义一个类实现org.springframework.web.servlet.Handlerlnterceptor接口。
        2)创建拦截方法:实现preHandle方法,在控制器调用之前执行。如果返回false,则中断请求处理。
                       实现postHandle方法,在控制器调用之后执行,但在视图渲染之前。
                       实现afterCompletion方法,在请求完成后执行,用于清理资源。
        3)注册拦截器:在Spring MVC的配置类中注册拦截器,可以使用WebMvcConfigurer接口的addInterceptors方法。
        4)配置拦截器的路径匹配:通过addPathPatterns方法指定拦截器应该应用于哪些请求路径。
        5)配置拦截器的排除路径:使用excludePathPatterns方法指定哪些请求路径应该被拦截器忽略。
        6)配置拦截器的顺序:使用oder方法设置拦截器的顺序,数字越小,优先级越高。
        7)启用MVC配置:确保配置类上使用了@EnableWebMvc注解,以启用Neb MVC配置。
    b.第1步:定义拦截器
        import org.springframework.stereotype.Component;
        import org.springframework.web.servlet.HandlerInterceptor;
        import org.springframework.web.servlet.ModelAndView;

        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;

        @Component
        public class MyHandlerInterceptor implements HandlerInterceptor {

            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("Pre Handle method is Calling");
                return true; // Continue with the next interceptor or the handler itself
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("Post Handle method is Calling");
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception exception) throws Exception {
                System.out.println("Request and Response is completed");
            }
        }
    c.第2步:注册拦截器
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
        import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

        @Configuration
        public class WebConfig implements WebMvcConfigurer {

            @Autowired
            private MyHandlerInterceptor myHandlerInterceptor;

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(myHandlerInterceptor).addPathPatterns("/api/**");
            }
        }

02.实现WebRequestInterceptor接口,主要用于拦截异步请求
    a.第1步:定义拦截器
        import org.springframework.stereotype.Component;
        import org.springframework.web.context.request.WebRequest;
        import org.springframework.web.context.request.WebRequestInterceptor;

        @Component
        public class MyWebRequestInterceptor implements WebRequestInterceptor {

            @Override
            public void preHandle(WebRequest request) throws Exception {
                System.out.println("Pre Handle method is Calling in WebRequestInterceptor");
            }

            @Override
            public void postHandle(WebRequest request, ModelMap model) throws Exception {
                System.out.println("Post Handle method is Calling in WebRequestInterceptor");
            }

            @Override
            public void afterCompletion(WebRequest request, Exception ex) throws Exception {
                System.out.println("Request and Response is completed in WebRequestInterceptor");
            }
        }
    b.第2步:注册拦截器
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.context.request.WebRequestInterceptor;
        import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
        import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

        @Configuration
        public class WebConfig implements WebMvcConfigurer {

            @Autowired
            private WebRequestInterceptor myWebRequestInterceptor;

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addWebRequestInterceptor(myWebRequestInterceptor);
            }
        }

03.继承AbstractInterceptor抽象类,已过时,是HandlerInterceptor的一个抽象实现,提供了默认实现
    a.第1步:定义拦截器
        import org.springframework.stereotype.Component;
        import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

        import javax.servlet.http.HttpServletRequest;
        import javax.servlet.http.HttpServletResponse;

        @Component
        public class MyHandlerInterceptorAdapter extends HandlerInterceptorAdapter {

            @Override
            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
                System.out.println("Pre Handle method is Calling in HandlerInterceptorAdapter");
                return true;
            }

            @Override
            public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
                System.out.println("Post Handle method is Calling in HandlerInterceptorAdapter");
            }

            @Override
            public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
                System.out.println("Request and Response is completed in HandlerInterceptorAdapter");
            }
        }
    b.第2步:注册拦截器
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.context.annotation.Configuration;
        import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
        import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

        @Configuration
        public class WebConfig implements WebMvcConfigurer {

            @Autowired
            private MyHandlerInterceptorAdapter myHandlerInterceptorAdapter;

            @Override
            public void addInterceptors(InterceptorRegistry registry) {
                registry.addInterceptor(myHandlerInterceptorAdapter).addPathPatterns("/api/**");
            }
        }

2.15 [3]监听器、过滤器、拦截器

00.启动顺序
    项目启动时,先启动【1.监听器】,再启动【2.过滤器】,最后启动【3.拦截器】

01.总结
    a.监听器,【始终监控】
        略
    b.过滤器,【全局过滤】
        发起1次请求,过滤1次请求:doFilter
        请求路径:只有登录成功后才可以访问系统中的页面,如果没有登录则跳转到登录页面
    c.拦截器,【某项业务拦截】
        发起1次请求,过滤1次请求:preHandle、afterCompletion、postHandle
        jwt令牌校验

02.拦截器、过滤器
    a.相同点
        本质上都是面向切面编程(AOP)
    b.拦截器
        preHandle方法:在处理请求的方法(控制器方法)之前执行。可以用于做一些前置处理,比如权限验证、日志记录等。如果返回 false,则请求处理到此为止,不会继续调用后续的拦截器和控制器方法
        postHandle方法:在处理请求的方法(控制器方法)之后,但在视图渲染之前执行。可以用于修改视图数据或对响应进行后续处理
        afterCompletion方法:在整个请求完成之后(包括视图渲染完成后)执行。通常用于进行资源清理、记录日志等收尾工作
        -----------------------------------------------------------------------------------------------------
        对用户请求进行统一认证
        对用户的访问请求进行记录和审核
        设置字符编码
        压缩响应信息
    c.过滤器
        void init(FilterConfig filterConfig):容器启动(初始化 Filter)时会被调用,整个程序运行期只会被调用一次。用于实现 Filter 对象的初始化
        void doFilter(ServletRequest request, ServletResponse response,FilterChain chain):具体的过滤功能实现代码,通过此方法对请求进行过滤处理,其中 FilterChain 参数是用来调用下一个过滤器或执行下一个流程
        void destroy():用于 Filter 销毁前完成相关资源的回收工作
        -----------------------------------------------------------------------------------------------------
        登录验证(判断用户是否登录)
        权限验证(判断用户是否有权限访问资源,如校验token. 日志记录)
        日志
    d.总结
        拦截器和过滤器都能实现相应的功能,谁也不比谁强
        【过滤器】通常是用来进行【全局过滤】,而【拦截器】是用来实现【某项业务拦截】
        -----------------------------------------------------------------------------------------------------
        1.拦截器是基于java的反射机制的,而过滤器是基于函数回调
        2.拦截器不依赖与servlet容器,过滤器依赖与servlet容器
        3.拦截器只能对action请求起作用,而过滤器则可以对几乎所有的请求起作用
        4.拦截器可以访问action上下文、值栈里的对象,而过滤器不能访问
        5.在action的生命周期中,拦截器可以多次被调用,而过滤器只能在容器初始化时被调用一次

03.拦截器
    a.拦截器
        在Spring MVC中,拦截器链是通过HandlerInterceptor接口及其实现类来实现的
        DispatcherServlet作为前端控制器(Front Controller),负责协调请求的各个阶段,包括调用拦截器
    b.核心组件
        a.HandlerMapping
            HandlerMapping负责将请求URL映射到具体的处理器(Handler)
            处理器通常是一个控制器(Controller)的方法
            Spring提供了多种HandlerMapping实现,如RequestMappingHandlerMapping,支持基于注解的映射
        b.HandlerAdapter
            HandlerAdapter是负责执行具体处理器的组件
            它知道如何调用特定类型的处理器,并返回一个ModelAndView对象,用于渲染视图
        c.DispatcherServlet
            DispatcherServlet是Spring MVC的核心组件,充当前端控制器
            它接收所有的HTTP请求,协调HandlerMapping、HandlerAdapter和视图解析等组件
            最终将请求分发给合适的处理器进行处理
        d.HandlerInterceptor
            HandlerInterceptor接口定义了拦截器的基本行为。通过实现该接口,可以在请求处理的不同阶段插入自定义逻辑
            如请求前、请求后或完成后的处理
    c.拦截器的工作流程
        a.请求到达 DispatcherServlet
            所有的HTTP请求首先由DispatcherServlet接收
        b.查找 Handler
            DispatcherServlet使用HandlerMapping查找与请求URL匹配的处理器(Handler)
        c.应用拦截器前置
            在调用处理器之前,DispatcherServlet会调用已注册的所有拦截器的preHandle方法
            这些拦截器按照定义的顺序依次执行。如果任意一个拦截器的preHandle返回false
            请求将被终止,后续的拦截器和处理器将不会执行
        d.调用 HandlerAdapter 执行 Handler
            所有前置拦截器的preHandle方法返回true后
            DispatcherServlet会调用HandlerAdapter执行具体的处理器方法(如Controller中的方法)
        e.应用拦截器后置
            处理器执行完成后,DispatcherServlet会调用拦截器的postHandle方法,这些拦截器按照定义的顺序逆序执行
        f.渲染视图
            DispatcherServlet使用视图解析器(ViewResolver)渲染最终的视图,如返回一个HTML页面
        g.完成拦截器
            最后,DispatcherServlet调用拦截器的afterCompletion方法,通知拦截器请求已经完成,同样按逆序执行

3 SpringBoot

3.1 [1]定义

01.定义
    SpringBoot是Spring的子项目,正如其名字,提供Spring的引导(Boot)的功能
    通过SpringBoot ,我们开发者可以快速配置Spring项目,引入各种SpringMVC、SpringTransaction、SpringAOP、MyBatis等框架
    而无需不断重复编写繁重的Spring配置,降低了Spring的使用成本

02.SpringBoot、SpringMVC和Spring有什么区别?
    Spring的完整名字,应该是SpringFramework
    它提供了多个模块,SpringIOC、SpringAOP、SpringMVC等,所以SpringMVC是SpringFramework众多模块中的一个
    Spring Boot是构造在SpringFramework之上的Boot启动器,旨在更容易的配置一个Spring项目

03.SpringBoot核心功能
    a.独立运行Spring项目
        SpringBoot可以以jar包形式独立运行,运行一个SpringBoot项目只需要通过java -jar xx.jar来运行
    b.内嵌Servlet容器
        SpringBoot可以选择内嵌Tomcat、Jetty或者Undertow,这样我们无须以war包形式部署项目
    c.供Starter简化Maven配置
        Spring提供了一系列的starter的pom来简化Maven的依赖加载。例如,当你使用了spring-boot-starter-web,会自动加入如下依赖
    d.自动配置SpringBean
        SpringBoot检测到特定类的存在,就会针对这个应用做一定的配置,进行自动配置Bean,这样会极大地减少我们要使用的配置
        SpringBoot只考虑大多数的开发场景,并不是所有的场景,若在实际开发中我们需要配置Bean,而SpringBoot没有提供支持,则可以自定义自动配置进行解决
    e.准生产的应用监控
        SpringBoot提供基于HTTP、JMX、SSH对运行时的项目进行监控
    f.无代码生成和XML配置
        SpringBoot没有引入任何形式的代码生成,它是使用的Spring4.0的条件@Condition注解以实现根据条件进行配置
        同时使用了Maven/Gradle的依赖传递解析机制来实现Spring应用里面的自动配置

3.2 [1]parent

01.parent作用
    1.定义了Java编译版本
    2.使用UTF-8格式编码
    3.执行打包操作的配置
    4.自动化的资源过滤
    5.自动化的插件配置
    6.针对 application.properties 和 application.yml 的资源过滤,包括通过 profile 定义的不同环境的配置文件,例如 application-dev.properties 和 application-dev.yml

3.3 [1]starter

01.自动化配置类,一般命名为XXXAutoConfiguration
    在这个配置类中通过条件注解来决定一个配置是否生效(条件注解就是 Spring 中原本就有的)
    然后它还会提供一系列的默认配置,也允许开发者根据实际情况自定义相关配置
    然后通过类型安全的属性注入将这些配置属性注入进来,新注入的属性会代替掉默认属性
    正因为如此,很多第三方框架,我们只需要引入依赖就可以直接使用了

02.常用starter
    spring-boot-starter-web :提供 Spring MVC + 内嵌的 Tomcat
    spring-boot-starter-data-jpa :提供 Spring JPA + Hibernate
    spring-boot-starter-data-Redis :提供 Redis
    mybatis-spring-boot-starter :提供 MyBatis

03.制作一个starter
    a.制作
        a.依赖
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-autoconfigure</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-configuration-processor</artifactId>
                <optional>true</optional>
            </dependency>
        b.创建自动配置类:编写一个自动配置类,使用 @Configuration 和 @ConditionalOnMissingBean 等注解来定义自动配置逻辑
            @Configuration
            @ConditionalOnClass(MyService.class)
            @EnableConfigurationProperties(MyProperties.class)
            public class MyAutoConfiguration {

                @Bean
                @ConditionalOnMissingBean
                public MyService myService(MyProperties properties) {
                    return new MyService(properties.getSomeProperty());
                }
            }
        c.定义配置属性:创建一个配置属性类,使用 @ConfigurationProperties 注解来定义可配置的属性
            @ConfigurationProperties(prefix = "my.service")
            public class MyProperties {
                private String someProperty;

                // getters and setters
            }
        d.创建 spring.factories 文件:在 src/main/resources/META-INF 目录下创建一个 spring.factories 文件,声明自动配置类
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
            com.example.MyAutoConfiguration
        e.发布Starter
            将项目打包并发布到 Maven 仓库,以便其他项目可以使用这个 Starter
    b.使用
        a.依赖
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>my-starter</artifactId>
                <version>1.0.0</version>
            </dependency>
        b.示例
            假设我们创建了一个名为 my-starter 的 Starter,用于提供一个简单的服务 MyService
            在使用这个 Starter 的项目中,可以通过配置文件来设置 MyProperties 的属性:
            my:
              service:
                someProperty: "Hello, World!"

04.方法一:基础配置类方式
    a.概述
        这是最简单的starter开发方法,通过创建一个包含@Configuration注解的配置类,使用@Bean方法定义需要注入的组件。
    b.实现步骤
        a.创建Maven项目
            命名遵循xxx-spring-boot-starter格式
        b.添加依赖
            <dependencies>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter</artifactId>
                </dependency>
            </dependencies>
        c.创建配置类
            @Configuration
            public class SimpleServiceAutoConfiguration {

                @Bean
                public SimpleService simpleService() {
                    return new SimpleServiceImpl();
                }
            }
        d.创建自动配置文件
            在resources/META-INF/spring.factories中添加:
            org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
            com.example.SimpleServiceAutoConfiguration
    c.使用方式
        使用时只需要在项目中添加依赖:
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>simple-spring-boot-starter</artifactId>
            <version>1.0.0</version>
        </dependency>
    d.优缺点分析
        a.优点
            实现简单,上手容易
            适合封装简单的功能组件
        b.缺点
            不支持定制化配置
            无法根据条件选择性装配
            功能过于简单,适用场景有限
        c.适用场景
            适合封装简单的工具类或无需外部配置的功能组件。

05.方法二:条件装配方式
    a.概述
        通过SpringBoot的条件装配机制,实现根据特定条件决定是否启用配置的starter。
    b.实现步骤
        a.创建Maven项目
            同上
        b.创建配置类,添加条件注解
            @Configuration
            @ConditionalOnClass(RedisTemplate.class)
            public class RedisServiceAutoConfiguration {

                @Bean
                @ConditionalOnMissingBean
                public RedisService redisService() {
                    return new RedisServiceImpl();
                }

                @Bean
                @ConditionalOnProperty(prefix = "redis.cache", name = "enabled", havingValue = "true")
                public RedisCacheManager redisCacheManager() {
                    return new RedisCacheManager();
                }
            }
        c.配置自动装配文件
            同上
    c.条件注解说明
        SpringBoot提供了丰富的条件注解:
        @ConditionalOnClass:当类路径下有指定类时
        @ConditionalOnMissingBean:当容器中没有指定Bean时
        @ConditionalOnProperty:当配置文件中有指定属性时
        @ConditionalOnWebApplication:当应用是Web应用时
        @ConditionalOnExpression:基于SpEL表达式的条件
    d.优缺点分析
        a.优点
            智能装配,避免无用组件加载
            可以根据环境条件决定是否启用功能
            防止与已有Bean冲突
        b.缺点
            配置逻辑较复杂
            调试排错难度增加
            需要考虑条件之间的优先级和冲突
        c.适用场景
            适合需要根据环境条件选择性启用的功能组件,如根据是否是Web环境决定是否启用Web相关功能。

06.方法三:属性绑定方式
    a.概述
        通过@ConfigurationProperties实现自定义配置的starter,支持从配置文件中读取参数。
    b.实现步骤
        a.创建属性类
            @ConfigurationProperties(prefix = "example.service")
            @Data
            public class ServiceProperties {
                /**
                 * 是否启用服务
                 */
                private boolean enabled = true;

                /**
                 * 服务URL
                 */
                private String url = "http://localhost:8080";

                /**
                 * 连接超时时间
                 */
                private int timeout = 3000;
            }
        b.创建自动配置类
            @Configuration
            @EnableConfigurationProperties(ServiceProperties.class)
            @ConditionalOnProperty(prefix = "example.service", name = "enabled", havingValue = "true", matchIfMissing = true)
            public class ExampleServiceAutoConfiguration {

                @Autowired
                private ServiceProperties properties;

                @Bean
                @ConditionalOnMissingBean
                public ExampleService exampleService() {
                    return new ExampleServiceImpl(properties.getUrl(), properties.getTimeout());
                }
            }
        c.配置元数据提示
            创建META-INF/spring-configuration-metadata.json
            {
              "properties": [
                {
                  "name": "example.service.enabled",
                  "type": "java.lang.Boolean",
                  "description": "Whether to enable example service.",
                  "defaultValue": true
                },
                {
                  "name": "example.service.url",
                  "type": "java.lang.String",
                  "description": "Service URL.",
                  "defaultValue": "http://localhost:8080"
                },
                {
                  "name": "example.service.timeout",
                  "type": "java.lang.Integer",
                  "description": "Connection timeout in milliseconds.",
                  "defaultValue": 3000
                }
              ]
            }
    c.优缺点分析
        a.优点
            支持从配置文件读取参数,实现灵活配置
            配置项有元数据提示,用户体验好
            支持配置校验和默认值
        b.缺点
            开发工作量增加
            需要维护配置元数据
            配置项过多时管理复杂
        c.适用场景
            适合需要通过外部配置定制化的功能组件,如各种客户端和连接池配置。

07.方法四:完全自动配置方式
    a.概述
        结合前面方法,实现一个完整的自动配置starter,包含条件装配、属性绑定和多组件配置。
    b.实现步骤
        a.创建多个组件
            // 核心服务接口
            public interface ComplexService {
                String process(String input);
            }

            // 实现类
            public class ComplexServiceImpl implements ComplexService {
                private final String endpoint;
                private final int timeout;

                public ComplexServiceImpl(String endpoint, int timeout) {
                    this.endpoint = endpoint;
                    this.timeout = timeout;
                }

                @Override
                public String process(String input) {
                    // 实现逻辑
                    return "Processed: " + input;
                }
            }

            // 辅助组件
            public class ServiceHelper {
                public void assist() {
                    // 辅助功能实现
                }
            }
        b.创建属性类
            @ConfigurationProperties(prefix = "complex.service")
            @Data
            public class ComplexServiceProperties {
                private boolean enabled = true;
                private String endpoint = "http://api.example.com";
                private int timeout = 5000;
                private AdvancedConfig advanced = new AdvancedConfig();

                @Data
                public static class AdvancedConfig {
                    private boolean cacheEnabled = false;
                    private int cacheSize = 100;
                }
            }
        c.创建自动配置类
            @Configuration
            @EnableConfigurationProperties(ComplexServiceProperties.class)
            @ConditionalOnProperty(prefix = "complex.service", name = "enabled", havingValue = "true", matchIfMissing = true)
            public class ComplexServiceAutoConfiguration {

                @Autowired
                private ComplexServiceProperties properties;

                @Bean
                @ConditionalOnMissingBean
                public ComplexService complexService() {
                    return new ComplexServiceImpl(
                        properties.getEndpoint(),
                        properties.getTimeout()
                    );
                }

                @Bean
                @ConditionalOnProperty(prefix = "complex.service.advanced", name = "cache-enabled", havingValue = "true")
                public CacheManager cacheManager() {
                    return new SimpleCacheManager(properties.getAdvanced().getCacheSize());
                }

                @Bean
                public ServiceHelper serviceHelper() {
                    return new ServiceHelper();
                }
        d.添加自动装配文件
            在META-INF/spring.factories中添加配置
    c.优缺点分析
        a.优点
            功能完整,支持复杂场景
            组件化设计,支持条件装配
            灵活的配置选项
        b.缺点
            实现复杂度高
            需要考虑多组件间的依赖关系
        c.适用场景
            适合复杂的企业级功能组件,如分布式事务、安全认证等需要多组件协同工作的场景。

08.方法五:Enable模式方式
    a.概述
        通过自定义@Enable注解,允许用户主动启用特定功能。
    b.实现步骤
        a.创建功能接口和实现类
            public interface FeatureService {
                void execute();
            }

            public class FeatureServiceImpl implements FeatureService {
                @Override
                public void execute() {
                    // 实现逻辑
                }
            }
        b.创建@Enable注解
            @Retention(RetentionPolicy.RUNTIME)
            @Target(ElementType.TYPE)
            @Import(FeatureConfiguration.class)
            public @interface EnableFeature {
                /**
                 * 模式设置
                 */
                Mode mode() default Mode.SIMPLE;

                enum Mode {
                    SIMPLE, ADVANCED
                }
            }
        c.创建配置类
            @Configuration
            public class FeatureConfiguration implements ImportAware {

                private EnableFeature.Mode mode;

                @Override
                public void setImportMetadata(AnnotationMetadata importMetadata) {
                    Map<String, Object> attributes = importMetadata.getAnnotationAttributes(
                            EnableFeature.class.getName());
                    this.mode = (EnableFeature.Mode) attributes.get("mode");
                }

                @Bean
                public FeatureService featureService() {
                    if (mode == EnableFeature.Mode.SIMPLE) {
                        return new SimpleFeatureServiceImpl();
                    } else {
                        return new AdvancedFeatureServiceImpl();
                    }
                }
            }
    c.使用方式
        在应用主类中使用@Enable注解启用功能:
        @SpringBootApplication
        @EnableFeature(mode = EnableFeature.Mode.ADVANCED)
        public class MyApplication {
            public static void main(String[] args) {
                SpringApplication.run(MyApplication.class, args);
            }
        }
    d.优缺点分析
        a.优点
            显式启用功能,使用意图明确
            支持通过注解参数定制功能
            可以与自动配置结合使用
        b.缺点
            需要用户主动添加注解
            不完全符合SpringBoot开箱即用的理念
            增加用户的使用负担
        c.适用场景
            适合可选功能或有多种使用模式的功能组件,如特定的集成方案或可选功能增强。

09.方法六:模块化与组合式starter
    a.概述
        通过拆分功能模块,实现可组合的starter体系,用户可以按需引入所需功能。
    b.实现步骤
        a.创建基础模块
            myproject-spring-boot-starter (父模块)
            ├── myproject-core-spring-boot-starter (核心功能)
            ├── myproject-web-spring-boot-starter (Web功能)
            ├── myproject-cache-spring-boot-starter (缓存功能)
            └── myproject-security-spring-boot-starter (安全功能)
        b.核心模块实现
            // 在core模块中
            @Configuration
            @ConditionalOnClass(CoreService.class)
            public class CoreAutoConfiguration {

                @Bean
                @ConditionalOnMissingBean
                public CoreService coreService() {
                    return new CoreServiceImpl();
                }
            }
        c.功能模块实现
            // 在web模块中
            @Configuration
            @ConditionalOnWebApplication
            @ConditionalOnClass(CoreService.class)
            public class WebAutoConfiguration {

                @Autowired
                private CoreService coreService;

                @Bean
                public WebService webService() {
                    return new WebServiceImpl(coreService);
                }
            }
        d.依赖管理
            <dependency>
                <groupId>com.example</groupId>
                <artifactId>myproject-core-spring-boot-starter</artifactId>
                <version>${project.version}</version>
            </dependency>
    c.优缺点分析
        a.优点
            功能模块化,按需引入
            减少不必要的依赖
            便于团队协作开发
            符合单一职责原则
        b.缺点
            模块间依赖关系管理复杂
            版本一致性维护困难
            开发和测试工作量增加
        c.适用场景
            适合大型项目或平台型应用,需要根据不同业务场景选择不同功能组合的情况。

3.4 [2]自动装配:注解

01.@Import
    a.说明
        将指定的配置类或组件类导入到当前的 Spring 应用上下文中,从而将这些类中定义的 bean 注册到 IOC 容器中
    b.导入配置类
        可以将一个或多个 @Configuration 配置类导入到当前上下文中
        导入后,这些配置类中定义的 @Bean 方法会被执行,生成的 bean 会被注册到 IOC 容器中
    c.导入普通类
        可以导入普通的 Java 类(非 @Configuration 类),Spring 会将这些类作为 bean 注册到容器中
    d.导入 ImportSelector 实现类
        支持动态导入配置类或 bean 定义,适合需要根据条件动态加载配置的场景

02.SpringBoot通过@SpringBootApplication核心注解进行启动
    a.组合
        @SpringBootApplication = @SpringBootConfiguration + @ComponentScan + @EnableAutoConfiguration
    b.说明
        @SpringBootConfiguration:标识启动类是一个配置类
        @ComponentScan:自动扫描并注册Spring组件
        @EnableAutoConfiguration:启用自动配置功能,使用@Import(AutoConfigurationImportSelector.class) 导入自动配置选择器
                                  如关闭数据源配置,@SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })
    b.示例
        @Slf4j
        @EnableScheduling
        @ComponentScan(basePackages = {"cn.jggroup"})
        @SpringBootApplication(scanBasePackages = {"cn.jggroup"})
        public class SystemDependApplication extends SpringBootServletInitializer{

            @Override
            protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
                return application.sources(SystemDependApplication.class);
            }

            public static void main(String[] args) throws UnknownHostException {
                SpringApplication.run(SystemDependApplication.class, args);
            }
        }

3.5 [2]自动装配:步骤、原理

01.自动装配的步骤:6步
    a.第1步:开启自动配置
        @EnableAutoConfiguration:在Spring Boot应用的主类上使用@EnableAutoConfiguration注解,开启自动配置功能。
    b.第2步:扫描spring.factories文件
        spring.factories配置文件:Spring Boot扫描各个jar包下的META-INF/spring.factories文件,加载其中注册的AutoConfiguration类。
    c.第3步:加载AutoConfiguration类
        AutoConfiguration自动配置类:Spring Boot加载spring.factories文件中定义的自动配置类,这些类通常以xxAutoConfiguration命名。
    d.第4步:检查条件注解
        @Conditional条件注解:在每个AutoConfiguration类上,Spring Boot检查@Conditional及其衍生注解,判断是否满足条件以决定是否实例化该类。
    e.第5步:实例化并注入Bean
        实例化Bean:如果条件满足,Spring Boot实例化AutoConfiguration类中定义的Bean,并将其注入到Spring容器中。
    f.第6步:使用Starters
        Starters三方组件:Spring Boot的Starters提供了一组预置的组件和配置,简化了依赖管理和配置过程。

02.自动装配的原理:4步
    a.扫描和加载配置类
        @EnableAutoConfiguration 通过 AutoConfigurationImportSelector 加载自动配置类
        AutoConfigurationImportSelector 使用 SpringFactoriesLoader 从 META-INF/spring.factories文件中加载自动配置类的全限定名
    b.条件化注解
        自动配置类通常使用条件化注解(如 @ConditionalOnClass、@ConditionalOnMissingBean、@ConditionalOnProperty)来决定是否生效
        这些注解确保只有在满足特定条件时,相关的配置类或 Bean 才会被加载和创建
    c.属性配置
        自动配置通常依赖于属性文件(如 application.properties 或 application.yml)中的配置
        通过 @ConfigurationProperties 注解,可以将属性文件中的配置绑定到 Bean 中
    d.加载顺序
        自动配置类的加载顺序由 spring.factories 文件中的定义顺序决定
        可以通过 @AutoConfigureBefore、@AutoConfigureAfter 和 @AutoConfigureOrder 注解来显式控制自动配置类的加载顺序

3.6 [2]自动装配:spring.factories

00.汇总
    a.spring boot 2.7前
        META-INF/spring.factories
        多个配置之间用,\来换行
    b.spring boot 2.7后
        /META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
        多个配置之间也不用再用,\来换行,直接换行

01.spring boot 2.7前
    a.创建配置类
        @Configuration(proxyBeanMethods = false)
        public class NebulaDistributedLockAutoConfiguration {

            @Bean
            public RedissonDistributedLockTemplate redissonDistributedLockTemplate(RedissonClient redissonClient) {
                RedissonDistributedLockTemplate template = new RedissonDistributedLockTemplate(redissonClient);
                return template;
            }
        }
    b.自动装配:在resources目录下创建META-INF/spring.factories文件,并添加如下内容
        org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
          com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration1,\
          com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration2,\

02.spring boot 2.7后
    a.说明
        spring boot 2.7之后不再推荐使用spring.factories自动装配
        推荐使用META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports来自动装配
    b.创建配置类
        @Configuration(proxyBeanMethods = false)
        public class NebulaDistributedLockAutoConfiguration {

            @Bean
            public RedissonDistributedLockTemplate redissonDistributedLockTemplate(RedissonClient redissonClient) {
                RedissonDistributedLockTemplate template = new RedissonDistributedLockTemplate(redissonClient);
                return template;
            }
        }
    c.自动装配:在resources新建/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件
        com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration1
        com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration2
        com.nebula.distribute.lock.autoconfigure.NebulaDistributedLockAutoConfiguration3

3.7 [2]启动流程:spring

01.spring启动流程:8步
    a.加载配置文件
        Spring 启动时会加载应用程序的配置文件,通常是 applicationContext.xml 或者通过 Java Config 配置类
        这些配置文件包含了 Spring Bean 的定义、依赖关系、AOP 设置、事务管理器等信息
    b.创建并初始化 Spring 容器
        一旦配置文件加载完成,Spring 将会创建并初始化一个 Spring 容器(ApplicationContext)
        Spring 容器负责管理应用程序中的所有 Bean,以及它们之间的依赖关系
        容器的初始化包括实例化 Bean、注入依赖、应用 AOP 等
    c.扫描组件
        Spring 容器会扫描配置文件中指定的包,查找带有特定注解的组件类
        如 @Component、@Service、@Repository 和 @Controller
        一旦找到这些组件类,Spring 将会实例化它们并将它们纳入容器管理
    d.实例化 Bean
        Spring 容器会根据配置文件中的定义,实例化所有的 Bean
        这些 Bean 可能是普通的 POJO 类、数据访问对象(DAO)、服务类等
    e.注入依赖
        在实例化 Bean 的过程中,Spring 容器会解析 Bean 之间的依赖关系,并将依赖的 Bean 注入到需要它们的 Bean 中
        这个过程可以通过构造函数注入、Setter 方法注入或者字段注入来完成
    f.应用 AOP
        如果应用程序中使用了 AOP(面向切面编程),Spring 将会应用切面逻辑,为 Bean 动态生成代理
        并将切面逻辑织入到相应的 Bean 中。这个过程包括创建代理对象、将切面逻辑织入到代理对象中等
    g.触发生命周期回调
        Spring 容器会在 Bean 实例化、依赖注入完成以及其他初始化工作完成后,触发相应 Bean 的生命周期回调方法
        如 InitializingBean 接口的 afterPropertiesSet() 方法、@PostConstruct 注解标注的方法等
    h.完成启动
        一旦所有的 Bean 实例化、依赖注入和初始化工作完成,Spring 容器就会完成启动过程
        应用程序就可以开始处理请求和响应了

3.8 [2]启动流程:springboot

00.springboot启动流程:8步
    a.第1步:启动引导类
        启动主类:执行包含@SpringBootApplication注解的主类的main方法
    b.第2步:创建SpringApplication实例
        SpringApplication:创建SpringApplication对象,初始化应用程序的上下文环境
    c.第3步:准备环境
        Environment准备:配置应用程序的环境,包括读取application.properties或application.yml等配置文件
    d.第4步:创建应用上下文
        ApplicationContext:根据应用类型(如Servlet、Reactive)创建相应的ApplicationContext
    e.第5步:准备上下文
        上下文准备:加载所有的ApplicationContextInitializer,并调用其initialize方法
    f.第6步:刷新上下文
        刷新上下文:加载所有的Bean定义,完成Bean的创建和依赖注入
    g.第7步:调用应用程序事件监听器
        事件监听器:调用所有的ApplicationListener,处理应用程序事件
    h.第8步:运行应用程序
        启动完成:调用CommandLineRunner和ApplicationRunner接口的实现类,执行应用程序的启动逻辑

01.SpringBoot启动流程
    a.创建启动容器
        创建DefaultBootstrapContext,用于引导IOC 容器启动
    b.启动监听器
        监听应用启动时发生的相关事件
    c.准备应用环境
        配置应用所需的环境
    d.创建应用上下文
        创建核心IOC容器
    e.准备应用上下文
        加载BeanDefinition
    f.刷新应用上下文
        创建Bean
    g.调用Runner
        执行用户自定义的ApplicationRunnerCommandLineRunner,用于扩展

02.SpringBoot生命周期
    a.starting(启动)
        发布 ApplicationStartingEvent事件
    b.environmentPrepared(环境准备完成)
        发布 ApplicationEnvironmentPreparedEvent事件
    c.contextPrepared(上下文准备完成)
        发布 ApplicationContextInitializedEvent事件
    d.contextLoad(上下文加载)
        发布 ApplicationPreparedEvent事件
    e.started(启动完成)
        发布 ApplicationStartedEvent事件
    f.ready(就绪)
        发布 ApplicationReadyEvent 事件

03.Bean生产过程
    1.BeanDefinitionLoader 加载 XML 文件或注解中定义的内容
    2.BeanDefinitionLoader 将加载的内容转换成 BeanDefinition
    3.BeanDefinitionRegistry 将 BeanDefinition 注册到 BeanFactory 中
    4.BeanFactory 负责将 BeanDefinition 生成 Bean

04.BeanDefinitionLoader依靠以下几个类完成加载并解析BeanDefinition
    1.AnnotatedBeanDefinitionReader:负责解析注解
    2.AbstractBeanDefinitionReader:负责解析 XML 文件
    3.BeanDefinitionReader:负责解析 Groovy 文件
    4.ClassPathBeanDefinitionScanner:扫描指定类路径

05.Bean生命周期的5个阶段
    1.实例化阶段:实例化 Bean
    2.填充属性阶段(可能发生循环依赖):对 Bean 的实例化对象进行属性填充
    3.初始化阶段(核心操作):负责初始化 Bean,执行一些预定义的方法
    4.使用阶段:开发者使用
    5.销毁阶段:执行一些预定义的销毁方法

06.在 Spring 中,若创建 Bean 发生解决循环依赖会通过三级缓存解决
    1.singletonObjects(一级缓存):存放 完整 的 Bean 对象
    2.earlysingletonObjects(二级缓存):存放 Bean 的 早期(early)对象
    3.singletonFactories(三级缓存):存放 Bean 的 工厂(Factory)对象

3.9 [3]多数据源:3种

00.总结
    a.3种方式
        方式1:手动方式来设置数据源
        方式2:利用切面代理类设置数据源(配置文件都是写死的)
        方式3:利用切面代理类设置数据源(动态的加载数据源)
    b.本次以常用的MyBatis使用方法为例
        1.首先要先引入你要使用的数据源依赖。
        2.通过配置application.yml/application.properties来配置数据源的地址、密码等信息
        3.通过配置数据源DataSource进行Bean注入,指定每个数据源的配置信息
        4.配置不同数据源的SqlSessionFactory,后面即可根据不同包下面的mapper来指定不同的数据源啦

01.数据库准备
    创建两个库,分别是db_test_1和db_test_2
    db_test_1数据库中创建一张用户表
    db_test_2数据库中创建另一张账户表

02.环境准备
    <!--spring boot核心-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!--spring boot 测试-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!--mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <!-- druid -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid-spring-boot-starter</artifactId>
        <version>1.1.10</version>
    </dependency>
    <!--mybatis-->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.0.0</version>
    </dependency>
    <!--aspectj 注解代理-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
    </dependency>

03.方式1:手动方式来设置数据源
    a.说明
        SpringBoot支持根据一定的规则来动态选择数据源,用户可以通过继承AbstractRoutingDataSource抽象类
        并重写determineCurrentLookupKey方法来完成数据源的切换。
        在每次执行数据库操作之前,它会先调用determineCurrentLookupKey()抽象方法,
        根据初始化时设置的数据源集合,通过其中的key来决定使用哪个数据源
    b.创建动态数据源服务类
        首先,创建一个DynamicDataSource类,并继承AbstractRoutingDataSource抽象类,
        同时重写determineCurrentLookupKey()方法,代码示例如下:
    c.创建动态数据源缓存类
        创建一个DataSourceContextHolder类,用于缓存数据源,同时需要确保线程环境下安全
        -------------------------------------------------------------------------------------------------
        package com.example.dynamic.datasource.config;

        publicclass DataSourceContextHolder {

            /**
             * 设置线程独立变量,用于存储数据源唯一标记
             */
            privatestaticfinal ThreadLocal<String> DATASOURCE_HOLDER = new ThreadLocal<>();

            /**
             * 设置数据源
             * @param dataSourceName 数据源名称
             */
            public static void set(String dataSourceName){
                DATASOURCE_HOLDER.set(dataSourceName);
            }

            /**
             * 获取当前线程的数据源
             * @return 数据源名称
             */
            public static String get(){
                return DATASOURCE_HOLDER.get();
            }

            /**
             * 删除当前数据源
             */
            public static void remove(){
                DATASOURCE_HOLDER.remove();
            }
        }
    d.创建动态数据源配置类
        接着,创建一个DataSourceConfig配置类,设置动态数据源相关的参数,并注入到 Bean 工厂
        package com.example.dynamic.datasource.config;

        @Configuration
        publicclass DataSourceConfig {

            @Bean(name = "db1")
            @ConfigurationProperties(prefix = "spring.datasource.db1.druid")
            public DataSource db1(){
                return DruidDataSourceBuilder.create().build();
            }

            @Bean(name = "db2")
            @ConfigurationProperties(prefix = "spring.datasource.db2.druid")
            public DataSource db2(){
                return DruidDataSourceBuilder.create().build();
            }

            @Bean
            @Primary
            public DynamicDataSource createDynamicDataSource(){
                // 配置数据源集合,其中key代表数据源名称,DataSourceContextHolder中缓存的就是这个key
                Map<Object,Object> dataSourceMap = new HashMap<>();
                dataSourceMap.put("db1",db1());
                dataSourceMap.put("db2",db2());

                // 注入动态数据源到bean工厂
                DynamicDataSource dynamicDataSource = new DynamicDataSource();
                // 设置默认数据源
                dynamicDataSource.setDefaultTargetDataSource(db1());
                // 设置动态数据源集
                dynamicDataSource.setTargetDataSources(dataSourceMap);
                return dynamicDataSource;
            }
        }
    e.编写相关配置变量
        根据上面的配置变量,我们还需要在application.properties文件中添加相关的数据源变量,内容如下
    f.排除自动装配数据源
        需要在注解@SpringBootApplication类上排除自动装配数据源配置

04.方式2:利用切面代理类设置数据源(配置文件都是写死的)
    a.创建数据源注解
        首先,定义一个数据源注解来实现数据源的切换,同时配置一个默认的数据源名称
        package com.example.dynamic.datasource.config.aop;

        @Target({ElementType.METHOD, ElementType.TYPE})
        @Retention(RetentionPolicy.RUNTIME)
        @Documented
        public @interface DbSource {

            /**
             * 数据源key值
             * @return
             */
            String value() default "db1";
        }
    b.编写数据源代理类
        接着,基于@DbSource注解,创建一个 AOP 代理类,所有配置该注解的方法都会被前后拦截
        package com.example.dynamic.datasource.config.aop;

        @Order(1)
        @Aspect
        @Component
        publicclass DbSourceAspect {

            privatestaticfinal Logger LOGGER = LoggerFactory.getLogger(DbSourceAspect.class);


            @Pointcut("@annotation(com.example.dynamic.datasource.config.aop.DbSource)")
            public void dynamicDataSource(){}

            @Around("dynamicDataSource()")
            public Object datasourceAround(ProceedingJoinPoint point) throws Throwable {
                // 获取要切换的数据源名称
                MethodSignature methodSignature = (MethodSignature)point.getSignature();
                Method method = methodSignature.getMethod();
                DbSource dbSource = method.getAnnotation(DbSource.class);
                LOGGER.info("select dataSource:" + dbSource.value());
                DataSourceContextHolder.set(dbSource.value());
                try {
                    return point.proceed();
                } finally {
                    DataSourceContextHolder.remove();
                }
            }
        }
    c.使用注解切换数据源
        最后,在需要的方法上配置相关的数据源注解即可。
        -------------------------------------------------------------------------------------------------
        @Service
        public class UserInfoService {

            @Autowired
            private UserInfoMapper userInfoMapper;

            @Transactional
            @DbSource(value = "db1")
            public void add(UserInfo entity){
                userInfoMapper.insert(entity);
            }
        }
        -------------------------------------------------------------------------------------------------
        账户服务类,代码示例如下:
        @Service
        public class AccountInfoService {

            @Autowired
            private AccountInfoMapper accountInfoMapper;

            @Transactional
            @DbSource(value = "db2")
            public void add(AccountInfo entity){
                accountInfoMapper.insert(entity);
            }
        }
        -------------------------------------------------------------------------------------------------
        采用 aop 代理的方式来切换数据源,业务实现上会更加的灵活。
        在上文中,我们介绍了多数据源的配置实现方式,这种配置方式有一个不好的地方在于:配置文件都是写死的。

05.方式3:利用切面代理类设置数据源(动态的加载数据源)
    a.数据库准备
        首先,我们需要准备一张数据源配置表。新建一个test_db数据库,然后在数据库中创建一张数据源配置表,脚本如下:
        CREATE TABLE`tb_db_info` (
        `id`int(11) unsignedNOTNULL AUTO_INCREMENT,
        `db_name`varchar(50) DEFAULTNULL,
        `db_url`varchar(200) DEFAULTNULL,
        `driver_class_name`varchar(100) DEFAULTNULL,
        `username`varchar(80) DEFAULTNULL,
        `password`varchar(80) DEFAULTNULL,
          PRIMARY KEY (`id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=3DEFAULTCHARSET=utf8mb4;
        -------------------------------------------------------------------------------------------------
        最后,初始化两条数据,方便后续数据源的查询。
        INSERT INTO `tb_db_info` (`id`, `db_name`, `db_url`, `driver_class_name`, `username`, `password`)
        VALUES
          (1, 'db1', 'jdbc:mysql://localhost:3306/db_test_1', 'com.mysql.jdbc.Driver', 'root', 'root'),
          (2, 'db2', 'jdbc:mysql://localhost:3306/db_test_2', 'com.mysql.jdbc.Driver', 'root', 'root');
    b.修改全局配置文件
        我们还是以上文介绍的工程为例,把之前自定义的配置参数删除掉,重新基于 Spring Boot 约定的配置方式,添加相关的数据源参数,内容如下:
        # 配置默认数据源
        spring.datasource.url=jdbc:mysql://localhost:3306/test
        spring.datasource.username=root
        spring.datasource.password=root
        spring.datasource.driver-class-name=com.mysql.jdbc.Driver
    c.编写相关的服务类
        基于数据库中tb_db_info表,编写相关的查询逻辑,代码示例如下:
        package com.example.dynamic.datasource.entity;

        publicclass DbInfo {

            /**
             * 主键ID
             */
            private Integer id;

            /**
             * 数据库key,即保存Map中的key
             */
            private String dbName;

            /**
             * 数据库地址
             */
            private String dbUrl;

            /**
             * 数据库驱动
             */
            private String driverClassName;

            /**
             * 数据库用户名
             */
            private String username;

            /**
             * 数据库密码
             */
            private String password;

            // set、get方法等...
        }
        -------------------------------------------------------------------------------------------------
        public interface DbInfoMapper {

            List<DbInfo> findAll();
        }
        -------------------------------------------------------------------------------------------------
        <?xml version="1.0" encoding="UTF-8" ?>
        <!DOCTYPE mapper
                PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
                "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
        <mapper namespace="com.example.dynamic.datasource.mapper.DbInfoMapper">

            <select id="findAll" resultType="com.example.dynamic.datasource.entity.DbInfo">
                select
                id
                ,db_name as dbName
                ,db_url as dbUrl
                ,driver_class_name as driverClassName
                ,username
                ,password
                from tb_db_info
                order by id
            </select>
        </mapper>
    d.修改动态数据源服务类
        对DynamicDataSource类进行一些调整,代码如下:
        -------------------------------------------------------------------------------------------------
        public class DynamicDataSource extends AbstractRoutingDataSource {

            @Override
            protected Object determineCurrentLookupKey() {
                return DataSourceContextHolder.get();
            }

            /**
             * 重新加载数据源集合
             * @param dbList
             */
            public void loadDataSources(List<DbInfo> dbList){
                try {
                    Map<Object, Object> targetDataSourceMap = new HashMap<>();
                    for (DbInfo source : dbList) {
                        // 初始化数据源
                        DruidDataSource dataSource = new DruidDataSource();
                        dataSource.setDriverClassName(source.getDriverClassName());
                        dataSource.setUrl(source.getDbUrl());
                        dataSource.setUsername(source.getUsername());
                        dataSource.setPassword(source.getPassword());
                        dataSource.setInitialSize(1);
                        dataSource.setMinIdle(1);
                        dataSource.setMaxActive(5);
                        dataSource.setTestWhileIdle(true);
                        dataSource.setTestOnBorrow(true);
                        dataSource.setValidationQuery("select 1 ");
                        dataSource.init();
                        targetDataSourceMap.put(source.getDbName(), dataSource);
                    }
                    super.setTargetDataSources(targetDataSourceMap);
                    // 重新初始化resolvedDataSources对象
                    super.afterPropertiesSet();
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
    e.修改动态数据源配置类
        对DataSourceConfig类也需要进行一些调整,通过 Spring Boot 默认的数据源配置类初始化一个数据源实例对象
        @Configuration
        publicclass DataSourceConfig {

            @Autowired
            private DataSourceProperties basicProperties;

            /**
             * 注入动态数据源
             * @param dataSource
             * @return
             */
            @Bean
            @Primary
            public DynamicDataSource dynamicDataSource(){
                // 获取初始数据源
                DataSource defaultDataSource = basicProperties.initializeDataSourceBuilder().build();

                Map<Object,Object> targetDataSources = new HashMap<>();
                targetDataSources.put("defaultDataSource", defaultDataSource);
                // 注入动态数据源
                DynamicDataSource dynamicDataSource = new DynamicDataSource();
                // 设置默认数据源
                dynamicDataSource.setDefaultTargetDataSource(defaultDataSource);
                // 设置动态数据源集
                dynamicDataSource.setTargetDataSources(targetDataSources);
                return dynamicDataSource;
            }
        }
    f.配置启动时加载数据源服务类
        以上的配置调整完成之后,我们还需要配置一个服务启动监听类,将从数据库中查询到的数据配置信息加载到DynamicDataSource对象中
    g.调整 SpringBootApplication 注解配置
        以上的实现方式,因为启动的时候,采用的是 Spring Boot 默认的数据源配置实现,因此无需排除DataSourceAutoConfiguration类,可以将相关参数移除掉。

3.10 [3]配置文件:3种

01.方式1:标准方式
    SpringBoot配置加载顺序
    properties文件 -> YAML文件 -> 系统环境变量 -> 命令行参数 - XML配置(推荐JAVA配置,可以使用 @ImportResource 引入XML配置)

02.方式2:@PropertySource,加载非默认配置文件的数据
    @PropertySource:默认会加载application.properties / application.yml文件中的数据
    例如,@PropertySource(value=('classpath:conf.properties'"),来指定加载conf.properties文件中的数据
    但是,唯一遗憾“@PropertySource.只能加载properties,不能加载yml”
    ---------------------------------------------------------------------------------------------------------
    @PropertySource(value {"classpath:conf.properties"})
    @PropertySource(value ={"classpath:conf.yml"})
    public class Student {
        ...
    }

03.方式3:@ImportResource,识别spring.xml配置文件
    @ImportResource(locations {"classpath:spring.xml"})
    @SpringBootApplication
    public class HelloWorldApplication {
        public static void main(String[] args) {
            SpringApplication.run(HelloWorldApplication.class, args);
        }
    }

3.11 [3]注值方式:6种

00.总结
    @Value                                 获取Spring默认的全局配置文件                       推荐使用
    @ConfigurationProperties               获取Spring默认的全局配置文件                       推荐使用
    Environment                            获取Spring默认的全局配置文件
    @PropertySources                       获取自定义配置文件,此方法只能读取properties文件
    PropertySourcesPlaceholderConfigurer   获取自定义配置文件,此方法只能读取yaml文件
    原生输入流                              获取自定义配置文件

01.@Value(推荐使用)(获取Spring默认的全局配置文件)
    a.介绍
        通过@Value注解绑定配置文件的某一个key,就可以直接读取配置,并给变量赋值。
    b.yaml文件
        value:
          name: 小陈Coding
    c.JavaBean
        @SpringBootTest
        public class ValuesTest {

            @Value("${value.name}")
            private String name;

            @Test
            void test1(){
                System.out.println("@Value:" + name);
            }
        }
        -----------------------------------------------------------------------------------------------------
        注意:Java类必须是Java Bean且属性不能被static和final修饰,
        不然@Value会失效,yaml文件的配置必须存在,不存在会报错,
        为了增加容错,可以给@Value添加默认值
        -----------------------------------------------------------------------------------------------------
        @SpringBootTest
        public class ValuesTest {
            @Value("${value.name}:小陈Coding")
            private String name;

            @Test
            void test1() {
                System.out.println("@Value:" + name);
            }
        }
        如果yaml配置存在且默认值存在则会追加

02.@ConfigurationProperties(推荐使用)(获取Spring默认的全局配置文件)
    a.介绍
        只需指定某个key的前缀,前缀下的key将自动绑定到类当中的同名的属性成员变量中。(适用批量绑定,比@Value更加高效)
    b.yaml文件
        value:
          id: 1
          name: 小陈Coding
    c.JavaBean类
        @Component
        @Data
        @ConfigurationProperties(prefix = "value")
        public class ValueTest1 {

            private String id;

            private String name;
        }
        -----------------------------------------------------------------------------------------------------
        注意:有时会报未通过 @EnableConfigurationProperties 注册、标记为Spring 组件或通过@ConfigurationPropertiesScan 扫描
        只需在JavaBean上添加@EnableConfigurationProperties注解即可

03.Environment(获取Spring默认的全局配置文件)
    a.介绍
        无需自定义JavaBean配置,直接注入Environment或者实现EnvironmentAware接口即可
    b.Test类1
        @SpringBootTest
        public class ValuesTest {

            @Autowired
            private Environment env;

            @Test
            void test1() {
                System.out.println("Environment:" + env.getProperty("value.id"));
                System.out.println("Environment:" + env.getProperty("value.name"));
            }
        }
    c.Test类2
        @SpringBootTest
        public class ValuesTest implements EnvironmentAware {

            private Environment env;

            @Override
            public void setEnvironment(Environment environment) {
                this.env=environment;
            }

            @Test
            void test1() {
                System.out.println("Environment:" + env.getProperty("value.id"));
                System.out.println("Environment:" + env.getProperty("value.name"));
            }
        }

04.@PropertySources(获取自定义配置文件,此方法只能读取properties文件)
    a.介绍
        通过@PropertySources指定配置类,通过@PropertySource指定配置文件的类路径,通过@Value绑定属性变量的值
    b.properties配置文件
        value.id=1
        value.name=小陈Coding
    c.JavaBean类
        @Configuration
        @Data
        @PropertySources({
                @PropertySource(value = "classpath:xiaoChenCoding.properties",encoding = "utf-8")
        })
        public class ValueTest1 {

            @Value("${value.id}")
            private String id;

            @Value("${value.name}")
            private String name;
        }

05.PropertySourcesPlaceholderConfigurer(获取自定义配置文件,此方法只能读取yaml文件)
    a.介绍
        通过注册PropertySourcesPlaceholderConfigurerBean配合@Value获取自定义yaml文件
        PropertySourcesPlaceholderConfigurerBean
    b.yaml文件
        value:
          id: 1
          name: 小陈Coding
    c.JavaBean类
        @Component
        @Data
        public class ValueTest1 {

            @Value("${value.id}")
            private String id;

            @Value("${value.name}")
            private String name;
        }

06.原生输入流(获取自定义配置文件)
    a.介绍
        通过输入流读取配置文件
        注意:只能读取properties文件
    b.Test类
        @SpringBootTest
        public class ValuesTest{

            @Test
            void test1() {
                Properties properties = new Properties();
                try{
                    InputStreamReader inputStreamReader = new InputStreamReader(
                            this.getClass().getClassLoader().getResourceAsStream("xiaoChenCoding.properties")
                    );
                    properties.load(inputStreamReader);
                }catch (Exception e){
                    System.out.println(e);
                }
                System.out.println("输入流:" + properties.getProperty("value.id"));
                System.out.println("输入流:" + properties.getProperty("value.name"));
            }
        }

3.12 [3]默认配置:11条

01.Tomcat连接池
    a.说明
        SpringBoot默认使用Tomcat作为Web容器,但默认的连接池配置在高并发场景下会成为瓶颈。
        默认配置下,Tomcat的最大连接数只有200,最大线程数也只有200。这意味着当并发请求超过200时,后续请求就会排队等待。在生产环境中,这个配置明显不够用。
    b.代码
        server:
          tomcat:
            max-connections: 10000  # 最大连接数
            threads:
              max: 800              # 最大工作线程数
              min-spare: 100        # 最小空闲线程数
            accept-count: 100       # 等待队列长度
            connection-timeout: 20000
    c.说明
        更坑的是,SpringBoot的默认超时时间是无限长。这会导致连接一直占用,直到客户端主动断开。
        在网络不稳定的环境下,大量连接会一直挂着不释放,最终耗尽服务器资源。

02.数据库连接池
    a.说明
        SpringBoot默认使用HikariCP作为数据库连接池,但默认的连接池配置在生产环境下会成为瓶颈。默认最大连接数只有10个,对于稍微复杂一点的应用来说根本不够用。
    b.代码
        spring:
          datasource:
            hikari:
              maximum-pool-size: 50
              minimum-idle: 10
              connection-timeout: 30000
              idle-timeout: 600000
              max-lifetime: 1800000
              leak-detection-threshold: 60000
    c.说明
        特别要注意leak-detection-threshold这个配置。默认情况下这个检测是关闭的,如果代码中存在连接泄漏问题,根本发现不了。
        开启后,HikariCP会监控连接的使用时间,超过阈值就会打印警告日志。

03.JPA懒加载
    a.说明
        SpringBoot集成JPA时,默认开启了懒加载。这个设计初衷是好的,但在实际使用中经常会导致N+1查询问题。
    b.代码
        @Entity
        public class User {
            @Id
            private Long id;

            @OneToMany(fetch = FetchType.LAZY)  // 默认就是LAZY
            private List<Order> orders;
        }
    c.说明
        当查询用户列表时,每访问一次orders属性,就会触发一次数据库查询。
        如果有100个用户,就会执行101次SQL。
        这种情况下,要么使用@EntityGraph指定加载策略,要么在Repository中使用JOIN FETCH。
        @Query("SELECT u FROM User u LEFT JOIN FETCH u.orders")
        List<User> findAllWithOrders();

04.Jackson时区序列化
    a.说明
        SpringBoot默认使用Jackson处理JSON序列化,但时区处理经常出问题。
        默认情况下,Jackson会使用系统时区,这在分布式部署时会导致不一致的问题。
    b.代码
        spring:
          jackson:
            time-zone: GMT+8
            date-format: yyyy-MM-dd HH:mm:ss
            serialization:
              write-dates-as-timestamps: false
    c.说明
        更要命的是,如果你的应用部署在不同时区的服务器上,同样的时间可能会被序列化成不同的值。
        这个问题在国际化应用中特别突出。

05.日志配置
    a.说明
        SpringBoot默认使用Logback,但默认配置下没有对日志文件进行滚动和清理。
        长时间运行的应用会产生巨大的日志文件,最终占满磁盘空间。
    b.代码
        logging:
          file:
            name: app.log
          logback:
            rollingpolicy:
              max-file-size: 100MB
              max-history: 30
              total-size-cap: 3GB
    c.说明
        另外,默认的日志级别是INFO,在生产环境中会产生大量不必要的日志。
        合理设置日志级别可以显著提升性能。

06.缓存配置
    a.说明
        SpringBoot的@Cacheable注解默认使用ConcurrentHashMap作为缓存实现,
        但这个实现没有过期机制,也没有大小限制。在高并发场景下,缓存会无限增长,最终导致内存溢出。
    b.代码
        spring:
          cache:
            type: caffeine
            caffeine:
              spec: maximumSize=10000,expireAfterWrite=600s
    c.说明
        可以考虑使用Caffeine替代默认实现,可以提供更好的性能和内存管理能力。

07.监控端点
    a.说明
        SpringBoot Actuator默认暴露了很多监控端点,包括健康检查、配置信息、环境变量等。
        这些信息在开发环境中很有用,但在生产环境中可能泄漏敏感信息。
    b.代码
        management:
          endpoints:
            web:
              exposure:
                include: health,info,metrics
          endpoint:
            health:
              show-details: when-authorized
    c.说明
        只暴露必要的端点,并且配置适当的安全策略,避免信息泄漏。

08.文件上传大小限制
    a.说明
        SpringBoot默认的文件上传限制非常小,单个文件只能上传1MB,整个请求大小限制10MB。
        在实际业务中,这个限制经常不够用,用户上传稍大一点的文件就会报错。
    b.代码
        spring:
          servlet:
            multipart:
              max-file-size: 100MB
              max-request-size: 100MB
              file-size-threshold: 2KB
              location: /tmp
              resolve-lazily: false
    c.说明
        file-size-threshold 这个参数也很重要,它决定了多大的文件会直接写入内存。
        如果设置过大,大量并发上传会占用过多内存;设置过小,小文件也要写磁盘,影响性能。一般设置为几KB比较合适。

09.异步线程池配置
    a.说明
        使用@Async注解时,SpringBoot默认使用SimpleAsyncTaskExecutor,这个执行器每次都会创建新线程,
        没有线程池复用机制。高并发情况下会创建大量线程,最终导致系统资源耗尽。
    b.代码
        spring:
          task:
            execution:
              pool:
                core-size: 8
                max-size: 16
                queue-capacity: 100
                keep-alive: 60s
              thread-name-prefix: async-task-
            scheduling:
              pool:
                size: 4
              thread-name-prefix: scheduling-
    c.说明
        线程池大小的设置也有讲究。
        如果是CPU密集型任务,线程数设置为CPU核心数就够了;如果是IO密集型任务,可以设置为CPU核心数的2-3倍。
        queue-capacity设置了任务队列长度,当线程池满了之后,新任务会放到队列里等待执行。

10.静态资源缓存策略
    a.说明
        SpringBoot默认不为静态资源设置HTTP缓存头,这意味着浏览器每次都会重新请求CSS、JS、图片等静态文件,严重影响页面加载性能。
    b.代码
        spring:
          web:
            resources:
              cache:
                cachecontrol:
                  max-age: 365d
                  cache-public: true
              chain:
                strategy:
                  content:
                    enabled: true
                    paths: /**
                cache: true
              static-locations: classpath:/static/
    c.说明
        开启内容版本化策略后,SpringBoot会根据文件内容生成MD5哈希值作为版本号,文件名变成style-abc123.css这样的格式。
        当文件内容发生变化时,哈希值也会变化,浏览器会认为这是新文件重新下载;如果文件没变化,浏览器就直接使用缓存,有效提升页面加载速度。

11.数据库事务超时
    a.说明
        @Transactional注解默认没有设置超时时间,长时间运行的事务会一直持有数据库锁,影响其他操作的执行。特别是在批量数据处理时,很容易出现锁表问题。
    b.代码
        @Transactional(timeout = 30, rollbackFor = Exception.class)
        public void batchProcess(List<Data> dataList) {
            // 分批处理,避免长事务
            int batchSize = 100;
            for (int i = 0; i < dataList.size(); i += batchSize) {
                List<Data> batch = dataList.subList(i, 
                    Math.min(i + batchSize, dataList.size()));
                processBatch(batch);
            }
        }
    c.说明
        对于大批量数据处理,建议分成多个小事务,每个事务处理少量数据。这样即使某个小事务失败,也不会影响整体进度,而且可以及时释放数据库锁,提高系统并发性能。
        同时再加上rollbackFor = Exception.class确保所有异常都会触发回滚,避免数据不一致。

3.13 [4]CORS跨域:3种

00.汇总
    通过WebMvcConfigurer全局配置
    通过@CrossOrigin注解
    使用过滤器统一处理

00.跨越
    a.定义
        跨域问题主要出现在前端开发中,特别是涉及到前后端分离的项目
        当一个网页的JavaScript代码试图通过Ajax等方式访问不同源(包括协议、域名、端口号之一不同)的API接口时
        就会遇到跨域问题,这就是浏览器的同源策略
        浏览器为了安全性考虑,默认阻止了这种跨域请求,除非服务器明确允许
    b.同源策略:跨域问题的根源
        a.定义
            同源策略(Same-Origin Policy)要求以下三要素完全一致:
            1.协议(如 HTTP/HTTPS)
            2.域名(如 example.com)
            3.端口(如 80/443)
        b.说明
            同源策略(Same-Origin Policy)是一种重要的安全机制,用于控制不同源(origin)之间的交互
            这里的“源”由协议、域名和端口号三部分组成
            当两个URL的这三个部分完全相同时,则它们属于同一源
            只要有任何一部分不同,它们就被认为是不同源
        c.示例
            http://www.example.com:80/a.html 与 https://www.example.com/b.html 不同源(协议不同)
            http://shop.example.com 与 http://pay.example.com 不同源(子域名不同)
    c.跨域的特性与矛盾
        a.安全性保障
            1.数据安全:防止恶意网站窃取用户敏感数据
            2.服务器防护:减少 XSS、CSRF 等攻击风险
            3.资源隔离:避免第三方脚本随意操作 DOM
        b.开发痛点
            1.前后端分离架构下,本地开发常需处理跨域
            2.第三方 API 集成时需协调跨域策略
            3.多子域系统(如微服务)需跨域通信

01.在配置类中配置全局CORS规则
    @Configuration
    public class WebConfig implements WebMvcConfigurer {

        @Override
        public void addCorsMappings(CorsRegistry registry) {
            registry.addMapping("/**")
                    .allowedOrigins("http://example.com") // 允许的域名
                    .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
                    .allowedHeaders("*")
                    .allowCredentials(true)
                    .maxAge(3600);
        }
    }

02.使用控制器级别的CORS配置
    public class GoodsController {
    @CrossOrigin(origins = "http://localhost:4000")
    @GetMapping("goods-url")
    public Response queryGoodsWithGoodsUrl(@RequestParam String goodsUrl) throws Exception {}

    }

03.全局CORS过滤器
    @WebFilter(filterName = "CorsFilter ")
    @Configuration
    public class CorsFilter implements Filter {
        @Override
        public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin","*");
            response.setHeader("Access-Control-Allow-Credentials", "true");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
            response.setHeader("Access-Control-Max-Age", "3600");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
            chain.doFilter(req, res);
        }
    }

3.14 [4]日志级别:7级

01.3种
    a.Log4j
        简介:Log4j 是 Apache 的一个开源项目,是最流行的 Java 日志记录工具之一。它提供了灵活的配置和强大的功能,如日志级别控制、日志旋转、异步日志记录等
        版本:Log4j 1.x 是最早的版本,Log4j 2.x 是重写后的版本,提供了更好的性能和更多的特性
        相关框架:Log4j 2.x 与 SLF4J(Simple Logging Facade for Java)紧密集成,可以通过 SLF4J 与其它日志框架桥接
    b.Logback
        简介:Logback 是由 Log4j 原班人马开发的一个日志框架,它旨在成为 Log4j 的替代品。Logback 与 Log4j 1.x 兼容,但提供了更好的性能和更灵活的配置
        版本:主要版本是 Logback 1.x
        相关框架:同样与 SLF4J 紧密集成
    c.Log4j2
        简介:Log4j2 是 Log4j 的后续版本,提供了比 Log4j 更好的性能和更多的特性,如异步日志记录、插件系统等
        版本:主要版本是 Log4j 2.x
        相关框架:Log4j2 可以直接使用,也可以通过 SLF4J 与其它日志框架桥接
    d.SLF4J
        第三方开发
        通过桥接器调用具体框架
        适配器可以支持让 jcl 也使用 SLFJ 门面进行日志输出

02.SpringBoot自带的实现SJF4J+ logback
    a.日志级别(7级)
        TRACE < DEBUG < INFO(默认,只打印INFO后的级别信息) < WARN < ERROR < FATAL < OFF
    b.自定义日志级别
        logging.level.org.myslayers.HelloWorld=warn

03.Log4j2配置全流程
    a.去除默认logback
        a.注释掉整个 spring-boot-starter-logging
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
              <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
               </exclusions>
            </dependency>
        b.注释logback的实现logback-classi
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-freemarker</artifactId>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <exclusions>
                  <exclusion>
                    <groupId>ch.qos.logback</groupId>
                    <artifactId>logback-classic</artifactId>
                  </exclusion>
                </exclusions>
            </dependency>
    b.指定 log4j2 配置文件,并编写 log4j2.xml 文件
        a.application.yml
            #如下是指定 log4j2 配置文件的路径
            logging:
              config: classpath:log4j2.xml

            #以下是指定 mybatis 或mybatis-plus 日志的实现
            #mybatis
            mybatis:
              configuration:
                log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl

            #mybatis-plus配置
            mybatis-plus:
              configuration:
                log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
        b.log4j2.xml
            <?xml version="1.0" encoding="UTF-8"?>

            <configuration debug="false">
                <!-- 控制台输出 -->
                <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
                    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                    </encoder>
                </appender>

                <!-- 按照每天生成日志文件 -->
                <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
                    <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
                        <!--日志文件输出的文件名-->
                        <FileNamePattern>./logs/%d{yyyy-MM}/log.%d{yyyy-MM-dd}.log</FileNamePattern>
                        <!--日志文件保留天数-->
                        <MaxHistory>30</MaxHistory>
                    </rollingPolicy>
                    <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
                        <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
                        <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
                    </encoder>
                    <!--日志文件最大的大小-->
                    <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
                        <MaxFileSize>100MB</MaxFileSize>
                    </triggeringPolicy>
                </appender>

                <!--mybatis log configure-->
                <logger name="com.apache.ibatis" level="debug">
                    <appender-ref ref="STDOUT"/>
                    <appender-ref ref="FILE"/>
                </logger>
                <logger name="com.yinhaoinfo.wenhaoRecruit.mapper" level="debug">
                    <appender-ref ref="STDOUT"/>
                    <appender-ref ref="FILE"/>
                </logger>
                <logger name="java.sql.Connection" level="DEBUG"/>
                <logger name="java.sql.Statement" level="DEBUG"/>
                <logger name="java.sql.PreparedStatement" level="DEBUG"/>

                <!-- 日志输出级别,logback日志级别包括五个:TRACE < DEBUG < INFO < WARN < ERROR -->
                <root level="Info">
                    <appender-ref ref="STDOUT"/>
                    <appender-ref ref="FILE"/>
                </root>
            </configuration>

3.15 [4]多环境切换:active

01.propertiest切换环境
    spring.profiles.active=dev

02.yml切换环境
    spring:
        profiles:
        active:
        - dev

3.16 [4]API版本控制:6种

00.汇总
    01.URL路径版本控制
    02.请求参数版本控制
    03.HTTP Header版本控制
    04.Accept Header版本控制(媒体类型版本控制)
    05.自定义注解版本控制
    06.面向接口的API版本控制

01.URL路径版本控制
    a.实现方式
        @RestController
        @RequestMapping("/api/v1/users")
        public class UserControllerV1 {
            
            @GetMapping("/{id}")
            public UserV1DTO getUser(@PathVariable Long id) {
                // 返回v1版本的用户信息
                return userService.getUserV1(id);
            }
        }
        @RestController
        @RequestMapping("/api/v2/users")
        public class UserControllerV2 {
            
            @GetMapping("/{id}")
            public UserV2DTO getUser(@PathVariable Long id) {
                // 返回v2版本的用户信息,可能包含更多字段
                return userService.getUserV2(id);
            }
        }
    b.优缺点
        a.优点
            简单直观,客户端调用明确
            完全隔离不同版本的API
            便于API网关路由和文档管理
        b.缺点
            可能导致代码重复
            维护多个版本的控制器类

02.请求参数版本控制
    a.实现方式
        @RestController
        @RequestMapping("/api/users")
        public class UserController {
            
            @GetMapping("/{id}")
            public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "1") int version) {
                switch (version) {
                    case 1:
                        return userService.getUserV1(id);
                    case 2:
                        return userService.getUserV2(id);
                    default:
                        throw new IllegalArgumentException("Unsupported API version: " + version);
                }
            }
        }
        @RestController
        @RequestMapping("/api/users")
        public class UserController {
            
            @GetMapping(value = "/{id}", params = "version=1")
            public UserV1DTO getUserV1(@PathVariable Long id) {
                return userService.getUserV1(id);
            }
            
            @GetMapping(value = "/{id}", params = "version=2")
            public UserV2DTO getUserV2(@PathVariable Long id) {
                return userService.getUserV2(id);
            }
        }
    b.优缺点
        a.优点
            保持URL资源定位的语义性
            实现相对简单
            客户端可以通过查询参数轻松切换版本
        b.缺点
            可能与业务查询参数混淆
            不便于缓存(相同URL不同版本)
            不如URL路径版本那样明显

03.HTTP Header版本控制
    a.实现方式
        @RestController
        @RequestMapping("/api/users")
        public class UserController {
            
            @GetMapping(value = "/{id}", headers = "X-API-Version=1")
            public UserV1DTO getUserV1(@PathVariable Long id) {
                return userService.getUserV1(id);
            }
            
            @GetMapping(value = "/{id}", headers = "X-API-Version=2")
            public UserV2DTO getUserV2(@PathVariable Long id) {
                return userService.getUserV2(id);
            }
        }
    b.优缺点
        a.优点
            URL保持干净,符合RESTful理念
            版本信息与业务参数完全分离
            可以携带更丰富的版本信息
        b.缺点
            不易于在浏览器中测试
            对API文档要求更高
            客户端需要特殊处理头信息

04.Accept Header版本控制(媒体类型版本控制)
    a.实现方式
        @RestController
        @RequestMapping("/api/users")
        public class UserController {
            
            @GetMapping(value = "/{id}", produces = "application/vnd.company.app-v1+json")
            public UserV1DTO getUserV1(@PathVariable Long id) {
                return userService.getUserV1(id);
            }
            
            @GetMapping(value = "/{id}", produces = "application/vnd.company.app-v2+json")
            public UserV2DTO getUserV2(@PathVariable Long id) {
                return userService.getUserV2(id);
            }
        }
        -----------------------------------------------------------------------------------------------------
        客户端请求时需要设置Accept头:
        Accept: application/vnd.company.app-v2+json
    b.优缺点
        a.优点
            最符合HTTP规范
            利用了内容协商的既有机制
            URL保持干净和语义化
        b.缺点
            客户端使用门槛较高
            不直观,调试不便
            可能需要自定义MediaType解析

05.自定义注解版本控制
    a.实现方式
        a.首先定义版本注解
            @Target({ElementType.TYPE, ElementType.METHOD})
            @Retention(RetentionPolicy.RUNTIME)
            public @interface ApiVersion {
                int value() default 1;
            }
        b.创建版本匹配的请求映射处理器
            @Component
            public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

                @Override
                protected RequestCondition<?> getCustomTypeCondition(Class<?> handlerType) {
                    ApiVersion apiVersion = handlerType.getAnnotation(ApiVersion.class);
                    return createCondition(apiVersion);
                }

                @Override
                protected RequestCondition<?> getCustomMethodCondition(Method method) {
                    ApiVersion apiVersion = method.getAnnotation(ApiVersion.class);
                    return createCondition(apiVersion);
                }

                private ApiVersionCondition createCondition(ApiVersion apiVersion) {
                    return apiVersion == null ? new ApiVersionCondition(1) : new ApiVersionCondition(apiVersion.value());
                }
            }

            public class ApiVersionCondition implements RequestCondition<ApiVersionCondition> {

                private final int apiVersion;

                public ApiVersionCondition(int apiVersion) {
                    this.apiVersion = apiVersion;
                }

                @Override
                public ApiVersionCondition combine(ApiVersionCondition other) {
                    // 采用最高版本
                    return new ApiVersionCondition(Math.max(this.apiVersion, other.apiVersion));
                }

                @Override
                public ApiVersionCondition getMatchingCondition(HttpServletRequest request) {
                    String version = request.getHeader("X-API-Version");
                    if (version == null) {
                        version = request.getParameter("version");
                    }

                    int requestedVersion = version == null ? 1 : Integer.parseInt(version);
                    return requestedVersion >= apiVersion ? this : null;
                }

                @Override
                public int compareTo(ApiVersionCondition other, HttpServletRequest request) {
                    // 优先匹配高版本
                    return other.apiVersion - this.apiVersion;
                }
            }
        c.配置WebMvc使用自定义的映射处理器
            @Configuration
            public class WebConfig implements WebMvcConfigurer {

                @Bean
                public RequestMappingHandlerMapping requestMappingHandlerMapping() {
                    return new ApiVersionRequestMappingHandlerMapping();
                }
            }
        d.使用自定义注解
            @RestController
            @RequestMapping("/api/users")
            public class UserController {

                @ApiVersion(1)
                @GetMapping("/{id}")
                public UserV1DTO getUserV1(@PathVariable Long id) {
                    return userService.getUserV1(id);
                }

                @ApiVersion(2)
                @GetMapping("/{id}")
                public UserV2DTO getUserV2(@PathVariable Long id) {
                    return userService.getUserV2(id);
                }
            }
    b.优缺点
        a.优点
            高度灵活和可定制
            可以结合多种版本控制策略
            代码组织更清晰
        b.缺点
            实现较为复杂
            需要自定义Spring组件

06.面向接口的API版本控制
    a.实现方式
        a.首先定义API接口
            public interface UserApi {
                Object getUser(Long id);
            }

            @Service
            @Primary
            public class UserApiV2Impl implements UserApi {
                // 最新版本实现
                @Override
                public UserV2DTO getUser(Long id) {
                    // 返回V2版本数据
                    return new UserV2DTO();
                }
            }

            @Service
            @Qualifier("v1")
            public class UserApiV1Impl implements UserApi {
                // 旧版本实现
                @Override
                public UserV1DTO getUser(Long id) {
                    // 返回V1版本数据
                    return new UserV1DTO();
                }
            }
        b.控制器层根据版本动态选择实现
            @RestController
            @RequestMapping("/api/users")
            public class UserController {

                private final Map<Integer, UserApi> apiVersions;

                // 通过构造注入收集所有实现
                public UserController(List<UserApi> apis) {
                    // 简化示例,实际应通过某种方式标记每个实现的版本
                    this.apiVersions = Map.of(
                        1, apis.stream().filter(api -> api instanceof UserApiV1Impl).findFirst().orElseThrow(),
                        2, apis.stream().filter(api -> api instanceof UserApiV2Impl).findFirst().orElseThrow()
                    );
                }

                @GetMapping("/{id}")
                public Object getUser(@PathVariable Long id, @RequestParam(defaultValue = "2") int version) {
                    UserApi api = apiVersions.getOrDefault(version, apiVersions.get(2)); // 默认使用最新版本
                    return api.getUser(id);
                }
            }
        c.可以自己实现一个版本委托器来简化版本选择
            // 自定义API版本委托器
            public class ApiVersionDelegator<T> {

                private final Class<T> apiInterface;
                private final Map<String, T> versionedImpls = new HashMap<>();
                private final Function<HttpServletRequest, String> versionExtractor;
                private final String defaultVersion;

                public ApiVersionDelegator(Class<T> apiInterface, 
                                      Function<HttpServletRequest, String> versionExtractor,
                                      String defaultVersion,
                                      ApplicationContext context) {
                    this.apiInterface = apiInterface;
                    this.versionExtractor = versionExtractor;
                    this.defaultVersion = defaultVersion;

                    // 从Spring上下文中查找所有实现了该接口的bean
                    Map<String, T> impls = context.getBeansOfType(apiInterface);
                    for (Map.Entry<String, T> entry : impls.entrySet()) {
                        ApiVersion apiVersion = entry.getValue().getClass().getAnnotation(ApiVersion.class);
                        if (apiVersion != null) {
                            versionedImpls.put(String.valueOf(apiVersion.value()), entry.getValue());
                        }
                    }
                }

                public T getApi(HttpServletRequest request) {
                    String version = versionExtractor.apply(request);
                    return versionedImpls.getOrDefault(version, versionedImpls.get(defaultVersion));
                }

                // 构建器模式简化创建过程
                public static <T> Builder<T> builder() {
                    return new Builder<>();
                }

                public static class Builder<T> {
                    private Class<T> apiInterface;
                    private Function<HttpServletRequest, String> versionExtractor;
                    private String defaultVersion;
                    private ApplicationContext applicationContext;

                    public Builder<T> apiInterface(Class<T> apiInterface) {
                        this.apiInterface = apiInterface;
                        return this;
                    }

                    public Builder<T> versionExtractor(Function<HttpServletRequest, String> versionExtractor) {
                        this.versionExtractor = versionExtractor;
                        return this;
                    }

                    public Builder<T> defaultVersion(String defaultVersion) {
                        this.defaultVersion = defaultVersion;
                        return this;
                    }

                    public Builder<T> applicationContext(ApplicationContext applicationContext) {
                        this.applicationContext = applicationContext;
                        return this;
                    }

                    public ApiVersionDelegator<T> build() {
                        return new ApiVersionDelegator<>(apiInterface, versionExtractor, defaultVersion, applicationContext);
                    }
                }
            }
        d.配置和使用委托器
            @Configuration
            public class ApiConfiguration {

                @Bean
                public ApiVersionDelegator<UserApi> userApiDelegator(ApplicationContext context) {
                    return ApiVersionDelegator.<UserApi>builder()
                        .apiInterface(UserApi.class)
                        .versionExtractor(request -> {
                            String version = request.getHeader("X-API-Version");
                            return version == null ? "2" : version;
                        })
                        .defaultVersion("2")
                        .applicationContext(context)
                        .build();
                }
            }

            @RestController
            @RequestMapping("/api/users")
            public class UserController {

                private final ApiVersionDelegator<UserApi> apiDelegator;

                public UserController(ApiVersionDelegator<UserApi> apiDelegator) {
                    this.apiDelegator = apiDelegator;
                }

                @GetMapping("/{id}")
                public Object getUser(@PathVariable Long id, HttpServletRequest request) {
                    UserApi api = apiDelegator.getApi(request);
                    return api.getUser(id);
                }
            }
    b.优缺点
        a.优点
            实现真正的关注点分离
            遵循开闭原则,新版本只需添加新实现
            业务逻辑与版本控制解耦
        b.缺点
            需要设计良好的接口层次
            可能需要额外的适配层处理返回类型差异
            初始设置较复杂

3.17 [5]优雅关闭:mq

00.汇总
    确保内存里面的mq消息被消费完

01.禁用kill -9
    a.为什么不使用kill -9
        a.数据丢失
            kill -9会立即终止进程,不会给应用程序任何机会去保存数据或完成正在进行的操作
        b.资源泄漏
            进程被强制终止后,可能无法正确释放内存、文件句柄或网络连接等资源
        c.不执行清理逻辑
            应用程序通常在关闭时执行一些清理逻辑(如关闭数据库连接、写入日志等),kill -9会跳过这些步骤
    b.推荐命令
        a.kill -2 pid
            向指定 pid 发送 SIGINT 中断信号,等同于 ctrl+c
            也就说,不仅当前进程会收到该信号,而且它的子进程也会收到终止的命令
        b.kill -9 pid
            向指定 pid 发送 SIGKILL 立即终止信号
            程序不能捕获该信号,最粗暴最快速结束程序的方法。
        c.kill -15 pid
            向指定 pid 发送 SIGTERM 终止信号
            信号会被当前进程接收到,但它的子进程不会收到
            如果当前进程被 kill 掉,它的的子进程的父进程将变成 init 进程 (init 进程是那个 pid 为 1 的进程)
        d.kill pid
            等同于 kill 15 pid
    c.实战命令
        a.查看jvm进程pid
            jps
        b.列出所有信号名称
            kill -l
            -------------------------------------------------------------------------------------------------
            # Windows下信号常量值
            # 简称  全称    数值
            # INT   SIGINT     2       Ctrl+C中断
            # ILL   SIGILL     4       非法指令
            # FPE   SIGFPE     8       floating point exception(浮点异常)
            # SEGV  SIGSEGV    11      segment violation(段错误)
            # TERM  SIGTERM    5       Software termination signal from kill(Kill发出的软件终止)
            # BREAK SIGBREAK   21      Ctrl-Break sequence(Ctrl+Break中断)
            # ABRT  SIGABRT    22      abnormal termination triggered by abort call(Abort)
            -------------------------------------------------------------------------------------------------
            # linux信号常量值
            # 简称  全称  数值
            # HUP   SIGHUP      1    终端断线
            # INT   SIGINT      2    中断(同 Ctrl + C)
            # QUIT  SIGQUIT     3    退出(同 Ctrl + \)
            # KILL  SIGKILL     9    强制终止
            # TERM  SIGTERM     15    终止
            # CONT  SIGCONT     18    继续(与STOP相反, fg/bg命令)
            # STOP  SIGSTOP     19    暂停(同 Ctrl + Z)
        c.推荐命令
            # 可以理解为操作系统从内核级别强行杀死某个进程
            kill -9 pid
            # 理解为发送一个通知,等待应用主动关闭
            kill -15 pid
            # 也支持信号常量值全称或简写(就是去掉SIG后)
            kill -l KILL

02.SpringBoot 2.3.0后
    a.方式1
        a.说明
            在 Spring Boot 2.3.0 之后,可以通过配置设置开启 Spring Boot 的优雅停机功能
        b.配置
            # 开启优雅停机,默认值:immediate 为立即关闭
            server.shutdown=graceful
            # 设置缓冲期,最大等待时间,默认:30秒
            spring.lifecycle.timeout-per-shutdown-phase=60s
        c.说明
            此时,应用在关闭时,Web 服务器将不再接受新请求,并等待正在进行的请求完成的缓冲时间
    b.方式2
        a.依赖
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-actuator</artifactId>
            </dependency>
        b.启用shutdown端点
            a.说明
                默认情况下,Spring Boot 的 shutdown 端点是禁用的
                我们需要在 application.properties 或 application.yml 中显式启用它
            b.application.properties
                management.endpoint.shutdown.enabled=true
                management.endpoints.web.exposure.include=shutdown
            c.application.yml
                management:
                  endpoint:
                    shutdown:
                      enabled: true
                  endpoints:
                    web:
                      exposure:
                        include: "shutdown"
        c.触发优雅停机
            a.请求
                curl -X POST http://localhost:8080/actuator/shutdown
            b.说明
                当你调用这个端点时,Spring Boot 应用会停止接收新的请求
                继续处理已经收到的请求,直到所有请求处理完毕后,应用才会退出

03.SpringBoot 2.3.0前
    a.说明
        如果是 Spring Boot 2.3.0 之前,就需要自行扩展(线程池)来实现优雅停机了
        它的核心实现实现是在系统关闭时会调用 ShutdownHook,然后在 ShutdownHook 中阻塞 Web 容器的线程池
        直到所有请求都处理完毕再关闭程序,这样就实现自定义优雅线下了
        但是,不同的 Web 容器(Tomcat、Jetty、Undertow)有不同的自定义优雅停机的方法
    b.说明
        以 Tomcat 为例,它的自定义优雅停机实现如下
    c.Tomcat容器关闭代码
        public class TomcatGracefulShutdown implements TomcatConnectorCustomizer, ApplicationListener<ContextClosedEvent> {
            private volatile Connector connector;

            public void customize(Connector connector) {
                this.connector = connector;
            }

            public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
                this.connector.pause();
                Executor executor = this.connector.getProtocolHandler().getExecutor();
                if (executor instanceof ThreadPoolExecutor) {
                    try {
                        log.info("Start to shutdown tomcat thread pool");
                        ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) executor;
                        threadPoolExecutor.shutdown();
                        if (!threadPoolExecutor.awaitTermination(20, TimeUnit.SECONDS)) {
                            log.warn("Tomcat thread pool did not shutdown gracefully within 20 seconds. ");
                        }
                    } catch (InterruptedException e) {
                        log.warn("Fail to shut down tomcat thread pool ", e);
                    }
                }
            }
        }
    d.设置 Tomcat 自动装配
        @Configuration
        @ConditionalOnClass({Servlet.class, Tomcat.class})
        public static class TomcatConfiguration {
            @Bean
            public TomcatGracefulShutdown tomcatGracefulShutdown() {
                return new TomcatGracefulShutdown();
            }

            @Bean
            public EmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory(TomcatGracefulShutdown gracefulShutdown) {
                TomcatEmbeddedServletContainerFactory tomcatFactory = new TomcatEmbeddedServletContainerFactory();
                tomcatFactory.addConnectorCustomizers(gracefulShutdown);
                return tomcatFactory;
            }
        }

03.actuator优雅停机
    a.依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>
    b.放开shutdown接口
        management:
          endpoints:
            web:
              exposure:
                include: "*"
          endpoint:
            shutdown:
              enabled: true
        server:
          port: 8088
    c.说明
        然后post http://127.0.0.1:8088/actuator/shutdown 实现优雅停机
        但是spring boot 2.3以下,停止后不能停止api继续对外
        我们可以使用过滤器来禁止api对外提供服务,手动设置HttpServletResponse.SC_SERVICE_UNAVAILABLE
    d.代码
        package com.et.disruptor.config;

        import org.springframework.stereotype.Component;

        import javax.servlet.*;
        import javax.servlet.http.HttpServletResponse;
        import java.io.IOException;
        import java.util.concurrent.atomic.AtomicBoolean;

        @Component
        public class GracefulShutdownFilter implements Filter {

            private final AtomicBoolean shuttingDown = new AtomicBoolean(false);

            @Override
            public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                    throws IOException, ServletException {
                if (shuttingDown.get()) {
                    ((HttpServletResponse) response).setStatus(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
                    return;
                }
                chain.doFilter(request, response);
            }

            public void startShutdown() {
                shuttingDown.set(true);
            }
        }
    e.说明
        DisposableBean是Spring框架中的一个接口,用于在Spring容器销毁Bean时执行一些自定义的清理逻辑
        实现这个接口的Bean会在容器关闭时自动调用其destroy()方法
        这对于需要在应用程序关闭时释放资源或执行其他清理操作的Bean非常有用
    f.代码
        package com.et.disruptor.config;

        import org.springframework.beans.factory.DisposableBean;
        import org.springframework.beans.factory.annotation.Autowired;
        import org.springframework.stereotype.Component;

        @Component
        public class GracefulShutdownManager implements DisposableBean {
            @Autowired
            private GracefulShutdownFilter shutdownFilter;
            @Autowired
            MqManager mqManager;
            @Override
            public void destroy() throws Exception {
                // reject  new  requests
                shutdownFilter.startShutdown();

                //graceful shutdown Disruptor
                mqManager.shutdownDisruptor(); // wait all events to complete

                // wait all  your self-definite task finish
                waitForTasksToComplete();
            }

            private void waitForTasksToComplete() throws InterruptedException {
                System.out.println("Waiting for tasks to complete...");
                // use CountDownLatch or other
                //mock task process
                Thread.sleep(100000);
            }
        }

04.disruptor优雅关闭
    a.说明
        如果不想显示的调用shutdown 也可以用注解@PreDestroy
    b.代码
        package com.et.disruptor.config;

        import com.et.disruptor.event.HelloEventFactory;
        import com.et.disruptor.event.HelloEventHandler;
        import com.et.disruptor.model.MessageModel;
        import com.lmax.disruptor.BlockingWaitStrategy;
        import com.lmax.disruptor.RingBuffer;
        import com.lmax.disruptor.dsl.Disruptor;
        import com.lmax.disruptor.dsl.ProducerType;
        import org.springframework.context.annotation.Bean;
        import org.springframework.context.annotation.Configuration;

        import java.util.concurrent.ExecutorService;
        import java.util.concurrent.Executors;

        @Configuration
        public class MqManager {
            private static Disruptor<MessageModel> disruptor;
            @Bean("ringBuffer")
            public RingBuffer<MessageModel> messageModelRingBuffer() {
                //define the thread pool for consumer message hander, Disruptor touch the consumer event to process by java.util.concurrent.ExecutorSerivce
                ExecutorService executor = Executors.newFixedThreadPool(2);
                //define Event Factory
                HelloEventFactory factory = new HelloEventFactory();
                //ringbuffer byte size
                int bufferSize = 1024 * 256;

                disruptor = new Disruptor<>(factory, bufferSize, executor, ProducerType.SINGLE, new BlockingWaitStrategy());
                //set consumer event
                disruptor.handleEventsWith(new HelloEventHandler());
                //start disruptor thread
                disruptor.start();
                //gain ringbuffer ring,to product event
                RingBuffer<MessageModel> ringBuffer = disruptor.getRingBuffer();
                return ringBuffer;
            }
            //@PreDestroy
            public void shutdownDisruptor() {
                if (disruptor != null) {
                    System.out.println("close Disruptor...");
                    disruptor.shutdown(); //cl0se Disruptor
                }
            }
        }

3.18 [5]缓存预热:4种

00.总结
    如果需要处理命令行参数:使用 CommandLineRunner 或 ApplicationRunner
    如果需要监听容器事件:使用 @EventListener 或 ApplicationListener
    如果只需简单初始化:使用 @PostConstruct 或 InitializingBean
    如果需要复杂启动控制:结合 ApplicationContextInitializer 或 AOP

01.实现 CommandLineRunner 接口
    a.说明
        适合需要在应用程序启动后执行一些初始化逻辑
    b.代码
        import org.springframework.boot.CommandLineRunner;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupCommandLineRunner implements CommandLineRunner {

            @Override
            public void run(String... args) throws Exception {
                System.out.println("Spring Boot 启动完成,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

02.实现 ApplicationRunner 接口
    a.说明
        类似于 CommandLineRunner,但 ApplicationRunner 提供了对参数的更高级抽象
    b.代码
        import org.springframework.boot.ApplicationRunner;
        import org.springframework.boot.ApplicationArguments;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupApplicationRunner implements ApplicationRunner {

            @Override
            public void run(ApplicationArguments args) throws Exception {
                System.out.println("Spring Boot 启动完成,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

03.使用 @EventListener 注解
    a.说明
        监听 Spring 的 ApplicationReadyEvent,这是 Spring Boot 应用完全启动并准备就绪的事件
    b.代码
        import org.springframework.context.event.EventListener;
        import org.springframework.boot.context.event.ApplicationReadyEvent;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupEventListener {

            @EventListener(ApplicationReadyEvent.class)
            public void onApplicationReady() {
                System.out.println("Spring Boot 启动完成,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

04.实现 ApplicationListener 接口
    a.说明
        通过监听 Spring Boot 的生命周期事件
    b.代码
        import org.springframework.context.ApplicationListener;
        import org.springframework.boot.context.event.ApplicationReadyEvent;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupApplicationListener implements ApplicationListener<ApplicationReadyEvent> {

            @Override
            public void onApplicationEvent(ApplicationReadyEvent event) {
                System.out.println("Spring Boot 启动完成,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

05.使用 @PostConstruct 注解
    a.说明
        在 @Component 或其他托管 Bean 中定义一个方法,在容器启动时调用
    b.代码
        import jakarta.annotation.PostConstruct;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupPostConstruct {

            @PostConstruct
            public void init() {
                System.out.println("Spring Boot 容器启动后,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

06.使用 InitializingBean 接口
    a.说明
        适合需要在 Bean 初始化完成后执行额外逻辑的场景
    b.代码
        import org.springframework.beans.factory.InitializingBean;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupInitializingBean implements InitializingBean {

            @Override
            public void afterPropertiesSet() throws Exception {
                System.out.println("Spring Boot Bean 初始化完成,执行额外操作...");
                // 这里添加你的启动逻辑
            }
        }

07.使用 SpringApplication.addListeners
    a.说明
        在 SpringApplication 的启动类中添加自定义的事件监听器
    b.代码
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.context.ApplicationListener;
        import org.springframework.boot.context.event.ApplicationReadyEvent;

        @SpringBootApplication
        public class DemoApplication {

            public static void main(String[] args) {
                SpringApplication app = new SpringApplication(DemoApplication.class);
                app.addListeners((ApplicationListener<ApplicationReadyEvent>) event -> {
                    System.out.println("Spring Boot 启动完成,执行额外操作...");
                    // 这里添加你的启动逻辑
                });
                app.run(args);
            }
        }

08.结合 AOP (面向切面编程)
    a.说明
        使用 AOP 在特定的启动时机执行操作
    b.代码
        import org.aspectj.lang.annotation.After;
        import org.aspectj.lang.annotation.Aspect;
        import org.springframework.stereotype.Component;

        @Aspect
        @Component
        public class StartupAspect {

            @After("execution(* org.springframework.boot.SpringApplication.run(..))")
            public void afterSpringApplicationRun() {
                System.out.println("Spring Boot 启动完成,执行额外操作(通过 AOP)...");
                // 这里添加你的启动逻辑
            }
        }

09.通过 ApplicationContextInitializer
    a.说明
        在应用启动阶段对 ApplicationContext 进行自定义操作
    b.代码
        import org.springframework.context.ApplicationContextInitializer;
        import org.springframework.context.ConfigurableApplicationContext;
        import org.springframework.stereotype.Component;

        @Component
        public class StartupContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {

            @Override
            public void initialize(ConfigurableApplicationContext applicationContext) {
                System.out.println("Spring Boot ApplicationContext 初始化...");
                // 这里添加你的启动逻辑
            }
        }

10.通过 SpringBootApplication 注解的主类
    a.说明
        直接在启动类中定义一个 @Bean,如使用 CommandLineRunner
    b.代码
        import org.springframework.boot.CommandLineRunner;
        import org.springframework.boot.SpringApplication;
        import org.springframework.boot.autoconfigure.SpringBootApplication;
        import org.springframework.context.annotation.Bean;

        @SpringBootApplication
        public class DemoApplication {

            public static void main(String[] args) {
                SpringApplication.run(DemoApplication.class, args);
            }

            @Bean
            public CommandLineRunner runAtStartup() {
                return args -> {
                    System.out.println("Spring Boot 启动完成,执行额外操作...");
                    // 这里添加你的启动逻辑
                };
            }
        }

3.19 [5]缓存预热:示例

00.汇总
    SpringBoot首次启动时数据初始化
    1.SpringBoot的ApplicationRunner
    2.Flyway的回调:让初始化任务只执行一次

01.SpringBoot的ApplicationRunner
    a.说明
        允许我们在应用启动完成后执行一些自定义任务
        比如,我们可以用它来检查数据库是否正常、初始化一些默认数据,或者做一些必要的配置。
    b.设计
        假设我们有一个模板服务,需要在应用启动时检查数据库里是否存在某些默认模板。如果不存在,就自动创建它们
    c.代码
        @Slf4j
        @Component
        @Order(1)
        public class InitDataRunner implements ApplicationRunner {

            @Resource
            private TemplateService templateService;

            @Override
            public void run(ApplicationArguments args) {
                // 初始化模板
                initTemplate("Template", "模板",
                    template -> template.setFlag(1),
                    "初始化模板 失败{}"
                );
                addTemplateIfNotExists("xxx", this::initTemplate, "新增模板", "新增模板失败{}");

            }
            /**
             * 如果没有找到对应的模板,执行updater逻辑,创建新的模板并保存到数据库中。
             * 如果有多个模板匹配,选择更新第一条模板。
             */
            private void initTemplate(String templateCode, String logMessage, Consumer<Template> updater, String errorLog) {
                try {
                    log.info("初始化模板:{}", logMessage);
                    List<Template> templates = templateService.lambdaQuery()
                            .eq(Template::getTemplateCode, templateCode)
                            // 根据需求调整查询条件
                            .list();
                    if (templates.isEmpty()) {
                        log.info("未找到符合条件的模板");
                    } else if (templates.size() == 1) {
                        updater.accept(templates.get(0));
                        templateService.updateById(templates.get(0));
                    } else {
                        log.warn("发现多条符合条件的模板,数量:{}", templates.size());
                        // 更新第一条数据
                        updater.accept(templates.get(0));
                        templateService.updateById(templates.get(0));
                    }
                } catch (Exception e) {
                    log.error(errorLog, e);
                }
            }
            /**
             * 如果模板不存在,则新增模板
             *
             * @param templateCode 模板代码
             * @param initMethod   初始化方法
             * @param successLog   成功日志信息
             * @param errorLog     错误日志信息
             */
            private void addTemplateIfNotExists(String templateCode, Runnable initMethod, String successLog, String errorLog) {
                try {
                    Template template = templateService.lambdaQuery()
                    .eq(Template::getTemplateCode, templateCode)
                    .one();
                    if (ObjectUtils.isEmpty(template)) {
                        initMethod.run();
                        log.info(successLog);
                    }
                } catch (Exception e) {
                    log.error(errorLog, e);
                }
            }
            /**
             * 初始化模板
             */
            private void initTemplate() {
                try {
                    Template template = createTemplate(xxx);
                    templateService.save(template);
                } catch (Exception e) {
                    log.error("保存失败: {}", e.getMessage());
                }
            }

02.Flyway的回调:让初始化任务只执行一次
    a.说明
        如果我们直接用Flyway脚本来插入数据,每次应用重启时都会重复执行这些脚本,这显然不是我们想要的
        那么,怎么才能确保某些初始化任务只在应用首次启动时执行一次呢?
        答案就是Flyway的回调机制。通过实现Flyway的Callback接口,我们可以监听数据库迁移的事件
        并在特定时刻执行自定义逻辑。比如,我们可以监听AFTER_MIGRATE事件
        在所有迁移脚本执行完毕后,插入一些默认数据
    b.设计
        通过监听BEFORE_MIGRATE和AFTER_MIGRATE事件,确保初始化任务只在应用首次启动时执行一次
        isFirst这个布尔变量就是用来标记是否是第一次启动的
    c.代码
        /**
         * 只在部署之后执行一次
         */
        @Slf4j
        @Configuration
        public class InitOutWorkProductData {

            @Resource
            private OAuthClientDetailsService oauthClientDetailsService;
            @Bean
            public Callback callback () {

                return new  Callback () {
                    // 判断是否初次启动
                    private boolean isFirst = true;

                    @Override
                    public boolean supports(Event event, Context context) {
                        return event == Event.BEFORE_MIGRATE || event == Event.AFTER_MIGRATE;
                    }

                    @Override
                    public boolean canHandleInTransaction(Event event, Context context) {
                        return false;
                    }

                    @Override
                    public void handle(Event event, Context context) {
                        if (!isFirst) {
                            return;
                        }
                        // 判断flyway记录表是否存在  Event.BEFORE_MIGRATE  flyway的前置事件
                        if (event == Event.BEFORE_MIGRATE) {
                            try (Database database = DatabaseFactory.createDatabase(context.getConfiguration(), false)) {
                                Schema currentSchema = database.getMainConnection().getCurrentSchema();
                                Table table = currentSchema.getTable(context.getConfiguration().getTable());
                                isFirst = !table.exists();
                            }
                        }
                        // Event.BEFORE_MIGRATE  flyway的后置事件
                        if (event == Event.AFTER_MIGRATE) {
                            isFirst = false;
                            // flyway完成之后执行一次
                            try {
                                //具体逻辑
                            }
                        }

                    }
                };
            }
        }

3.20 [5]处理请求数:8192连接、100等待

01.Web三大容器
    Web 容器目前也是三分天下,市面上最常见的三种 Web 容器分别是:Tomcat、Undertow 和 Jetty,其中 Tomcat 为 Spring Boot 框架默认的 Web 容器。
    它们三者的区别如下:
    Tomcat 是 Apache 软件基金会下的开源项目,是最广泛使用的 Servlet 容器之一,完全实现了 Java Servlet 和 JavaServer Pages(JSP)规范。它不仅是一个 Servlet 容器,也是一个轻量级的应用服务器,尽管相比其他轻量级服务器,Tomcat 被认为是稍微重一些的。Tomcat 支持众多的企业级特性,如 SSL、连接池等,适合运行大型的、复杂的企业级应用。它的稳定性和成熟度经过了多年的企业级应用验证,因此在很多企业中作为首选的 Web 容器。
    Undertow 是 Red Hat(红帽公司)开发的一个灵活的、高性能的 Web 服务器和反向代理服务器,它是 WildFly 应用服务器的默认 Web 容器。Undertow 设计上注重低内存占用和高并发处理能力,尤其擅长处理大量的短连接场景,比如 RESTful API 服务。Undertow 支持 Servlet 3.1、WebSocket以及非阻塞 IO(NIO),并且是支持 HTTP/2 协议的现代服务器之一。它的设计理念在于提供一个模块化、可嵌入式的解决方案,易于集成到现有的系统中,同时也适合微服务架构。
    Jetty 是一个开源的、轻量级的 Web 服务器和 Servlet 容器,由 Eclipse 基金会维护。它以其可嵌入式、高度可配置性著称,常用于需要快速启动和轻量级部署的场景,比如开发阶段、测试环境或轻量级应用。Jetty 也支持 Servlet 规范和 WebSocket,且同样基于 NIO,使得它在处理大量并发连接时表现出色。Jetty 设计上强调灵活性和可扩展性,易于通过 API 定制以满足特定需求,因此在云环境、持续集成、DevOps 等领域很受欢迎。

02.最大连接数(8192个)和最大等待数(100个)
    虽然 Tomcat 可以允许最大的连接数是 8192,但是 Tomcat 还有一个最大等待数,
    也就是说,如果达到了 8192 之后,还有一个等待队列可以存放请求的连接,
    所以,Spring Boot 可以同时处理多少个连接,等于 Tomcat 的最大连接数加 Tomcat 的最大等待数。
    ---------------------------------------------------------------------------------------------------------
    那么,最大等待数是多少呢?
    我们继续在 spring-configuration-metadata.json 文件中,搜索“server.tomcat.accept-count”(Tomcat 最大等待数)
    也就是说,默认情况下,Tomcat 最大等待数为 100 个。

03.默认情况下 Spring Boot 能够同时处理的请求数=最大连接数(8192)+最大等待数(100),结果为 8292 个
    当然,这两个值是可以在 Spring Boot 配置文件中修改的,如下配置所示:
    server:
      tomcat:
        max-connections: 2000 # 最大连接数
        accept-count: 200 # 最大等待数