130.【Spring注解】

news2025/4/8 5:03:26

Spring 注解

  • (一)、AOP功能测试
    • 1.AOP 使用步骤
        • (1).导入AOP对应的依赖
        • (2).编写业务逻辑类
        • (3).编写切面类
        • (4).编写配置类
        • (5). 编写测试类
  • (二)、AOP 原理
    • 1.@EnableAspectJAutoProxy
        • (1).@EnableAspectJAutoProxy源码
        • (2).AspectJAutoProxyRegistrar 自定义注册bean源码
        • (3).打断点进行Debug测试
    • 2.AnnotationAwareAspectJAutoProxyCreator 分析
        • (1).继承树
        • (2).AbstractAutoProxyCreator
        • (3).AbstractAdvisorAutoProxyCreator
    • 3.注册 AnnotationAwareAspectJAutoProxyCreator
        • (1).打断点进行Debug调试
        • (2).运行流程 (创建和注册AnnotationAwareAspectJAutoProxyCreator)
    • 4.AnnotationAwareAspectJAutoProxyCreator 执行时机
    • 5.创建AOP代理对象
    • 6.AOP原理的总结 ⭐
  • (三)、声明式事务
    • 1.声明式事务 - 环境搭建
        • (1).导入依赖
        • (2).实体类和业务层
    • 2.声明式事务 - 测试成功
        • (1).遇见问题
        • (2).解决问题 -> 事务型(成功全成功_失败全失败)
    • 3.声明式事务原理
  • (四)、扩展原理
    • 1.BeanFactoryPostProcessor ->来定制和修改beanFactory的内容
    • 2.BeanDefinitionRegistryPostProcessor -> 额外添加组件
    • 3.ApplicationListener -> 应用监听器
        • (1).ApplicationListener 的应用
    • 4.@EventListener 监听注解
        • (1).@EventListener 注解监听方法事件
  • (五)、Spring源码总结
    • 1.Spring源码总结
  • (六)、WEB 注解
    • 1.简介与测试
        • (1).创建一个Maven的web项目
        • (2).创建页面支持
        • (3).编写后端
    • 2.ServletContainerInitalizer
        • (1).创建我们的指定扫描的文件并编辑
        • (2).编写被扫描的文件信息
        • (3).将要被扫描的类全限定名写入文件中
        • (4).运行结果
    • 3.ServletContext 注册三大组件
    • 4.SpringMVC整合分析
    • 5.Servlet 异步
        • (1).AsyncContext 实现异步处理
        • (2).Callable 实现异步处理
        • (3).DeferredResult 实现异步

在这里插入图片描述

(一)、AOP功能测试

AOP是指在程序的运行期间动态地将某段代码切入到指定方法、指定位置进行运行的编程方式。AOP的底层是使用动态代理实现的。

1.AOP 使用步骤

 * AOP【动态代理】 : 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
 *                  1.如何使用?
 *                      (1).导入AOP的依赖。
 *                      (2).定义一个业务逻辑类(MathCalculator)。在业务逻辑运行的时候将日志进行打印(方法之前,方法结束,方法异常)
 *                      (3).定义一个日志切面类(LogAspect)。切面类里面的方法需要动态的感知 业务逻辑类(MathCalculator)的运行情况
 *                              (3.1).通知方法: 前置通知@Before(在目标方法运行之前运行)*                                              后置通知@After(在目标方法运行之后运行,不在意正常结束还是异常结束)*                                              返回通知@AfterReturning(在目标方法正常返回之后运行)*                                              异常通知@AfterThrowing(在目标方法发生异常之后运行)*                                              环绕通知@Round(动态代理,手动推进目标方法运行 joinPoint.)
 *                      (4).给切面类(LogAspect)的目标方法标注何时何处地运行 (通知注解)
 *                      (5).将切面类和业务逻辑类(目标方法所在类) 都加入到容器中
 *                      (6).必须告诉SpringIOC容器哪个类是切面类(我们需要给切面类添加一个注解 @Aspect)
 *                      (7).在配置类上开启 注解版的切面功能 ( <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  @EnableAspectJAutoProxy)
 *                      (8).测试,这个逻辑类一定要使用IOC容器的类,不能使用new

(1).导入AOP对应的依赖

        <!--    Spring切面的包    -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

(2).编写业务逻辑类

package com.jsxs.aop;

/**
 * @Author Jsxs
 * @Date 2023/8/19 11:49
 * @PackageName:com.jsxs.aop
 * @ClassName: MathCalculator
 * @Description: TODO
 * @Version 1.0
 */
public class MathCalculator {

    public int div(int x,int y){
        return x/y;
    }
}

(3).编写切面类

  1. 使用非公共切入点 @Pointcut

标注是切面的注解,且使用四个通知方法和公共切点注解

package com.jsxs.aop;

import org.aspectj.lang.annotation.*;

/**
 * @Author Jsxs
 * @Date 2023/8/19 11:52
 * @PackageName:com.jsxs.aop
 * @ClassName: LogAspect
 * @Description: TODO
 * @Version 1.0
 */

@Aspect // 告诉IOC容器这是一个切面类 ⭐
public class LogAspect {

    // 1.抽取公共地切入点表达式  ⭐⭐

    @Pointcut("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void  pointCut(){}

    // 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int))
    // *号代表这个类地所有方法, 一个.代表一个变量
    @Before("pointCut()")  ⭐⭐⭐
    public void logStart(){
        System.out.println("除法运行..... 参数列表是:{}");
    }
    // 3.在目标方法之后进行切入, 使用公共地切入点表达式
    @After("pointCut()")  ⭐⭐⭐⭐
    public void endStart(){
        System.out.println("除法运行结束....");
    }

    // 4.在目标方法返回正确地话
    @AfterReturning("pointCut()")  ⭐⭐⭐⭐⭐
    public void success(){
        System.out.println("除法正常运行... 运行结果:{}");
    }

    // 5.在目标方法出现异常地话
    @AfterThrowing("pointCut()")  ⭐⭐⭐⭐⭐⭐
    public void logError(){
        System.out.println("除法异常运行...");
    }
}
  1. 不使用公共切入点

标注是切面的注解,且使用四个通知方法

package com.jsxs.aop;

import org.aspectj.lang.annotation.*;

/**
 * @Author Jsxs
 * @Date 2023/8/19 11:52
 * @PackageName:com.jsxs.aop
 * @ClassName: LogAspect
 * @Description: TODO
 * @Version 1.0
 */

@Aspect // 告诉IOC容器这是一个切面类  ⭐
public class LogAspect {

    // 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int)) 
    // *号代表这个类地所有方法, 一个.代表一个变量  ⭐⭐
    @Before("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void logStart(){
        System.out.println("除法运行..... 参数列表是:{}");
    }
    // 3.在目标方法之后进行切入, 使用公共地切入点表达式 ⭐⭐⭐
    @After("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void endStart(){
        System.out.println("除法运行结束....");
    }

    // 4.在目标方法返回正确地话  ⭐⭐⭐⭐
    @AfterReturning("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void success(){
        System.out.println("除法正常运行... 运行结果:{}");
    }

    // 5.在目标方法出现异常地话 ⭐⭐⭐⭐⭐
    @AfterThrowing("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void logError(){
        System.out.println("除法异常运行...");
    }
}

(4).编写配置类

  1. 注解版开启aop切面
package com.jsxs.config;

import com.jsxs.aop.LogAspect;
import com.jsxs.aop.MathCalculator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

/**
 * @Author Jsxs
 * @Date 2023/8/19 11:41
 * @PackageName:com.jsxs.config
 * @ClassName: MainConfigOfAOP
 * @Description: TODO  AOP【动态代理】 : 指在程序运行期间动态的将某段代码切入到指定方法指定位置进行运行的编程方式;
 *                  1.如何使用?
 *                      (1).导入AOP的依赖。
 *                      (2).定义一个业务逻辑类(MathCalculator)。在业务逻辑运行的时候将日志进行打印(方法之前,方法结束,方法异常)
 *                      (3).定义一个日志切面类(LogAspect)。切面类里面的方法需要动态的感知 业务逻辑类(MathCalculator)的运行情况
 *                              (3.1).通知方法:  前置通知@Before(在目标方法运行之前运行),
 *                                              后置通知@After(在目标方法运行之后运行,不在意正常结束还是异常结束),
 *                                              返回通知@AfterReturning(在目标方法正常返回之后运行),
 *                                              异常通知@AfterThrowing(在目标方法发生异常之后运行),
 *                                              环绕通知@Round(动态代理,手动推进目标方法运行 joinPoint.)
 *                      (4).给切面类的目标方法标注何时何处地运行 (通知注解)⭐⭐
 *                      (5).将切面类和业务逻辑类(目标方法所在类) 都加入到容器中 ⭐⭐⭐
 *                      (6).必须告诉Spring的IOC容器哪个类是切面类(我们需要给切面类添加一个注解 @Aspect)  ⭐⭐⭐⭐
 *                      (7).在配置类上开启 注解版的切面功能 ( <aop:aspectj-autoproxy></aop:aspectj-autoproxy>  @EnableAspectJAutoProxy)
 *                      (8).测试,这个逻辑类一定要使用IOC容器的类,不能使用new
 * @Version 1.0
 */

@Configuration  //⭐
@EnableAspectJAutoProxy //⭐⭐ 开启注解版本的依赖
public class MainConfigOfAOP {


    // 业务逻辑类  ⭐⭐⭐⭐
    @Bean
    public MathCalculator mathCalculator(){
        return new MathCalculator();
    }

    // 切面类 ⭐⭐⭐⭐⭐
    @Bean
    public LogAspect logAspect(){
        return new LogAspect();
    }

}

  1. 非注解版开启aop依赖

在这里插入图片描述

(5). 编写测试类

package com.jsxs.Test;

import com.jsxs.aop.MathCalculator;
import com.jsxs.config.MainConfigOfAOP;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/19 12:47
 * @PackageName:com.jsxs.Test
 * @ClassName: AOP_Test
 * @Description: TODO
 * @Version 1.0
 */
public class AOP_Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        
        // 1.这个对象我们不能使用new创建,我们需要使用IOC容器的组件进行调用 ⭐
        MathCalculator calculator = applicationContext.getBean(MathCalculator.class);
        calculator.div(1,3);
    }
}

  1. 测试结果 不写类名 和 结果

在这里插入图片描述

  1. 测试结果 写类名 和 结果

1.我们需要在切面类进行更改
JoinPoint 这个类如果要使用一定要写在参数列表的第一位,否则可能不生效

package com.jsxs.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;

import java.util.Arrays;

/**
 * @Author Jsxs
 * @Date 2023/8/19 11:52
 * @PackageName:com.jsxs.aop
 * @ClassName: LogAspect
 * @Description: TODO
 * @Version 1.0
 */

@Aspect // 告诉IOC容器这是一个切面类  ⭐
public class LogAspect {


    // 2.在目标方法之前运行切入:切入点表达式( public int com.jsxs.aop.MathCalculator.div(int,int))
    // *号代表这个类地所有方法, 一个.代表一个变量
    @Before("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void logStart(JoinPoint joinPoint){  //⭐⭐ 切入点类可以获取名字和参数列表
        System.out.println(joinPoint.getSignature().getName()+"除法运行..... 参数列表是:{"+ Arrays.asList(joinPoint.getArgs())+"}");
    }
    // 3.在目标方法之后进行切入, 使用公共地切入点表达式
    @After("execution(public int com.jsxs.aop.MathCalculator.*(..))")
    public void endStart(){
        System.out.println("除法运行结束....");
    }

    // 4.在目标方法返回正确地话  ⭐⭐⭐ 获取返回结果,注解里面的参数名要和方法里面的参数名一致
    @AfterReturning(value = "execution(public int com.jsxs.aop.MathCalculator.*(..))",returning = "result")
    public void success(JoinPoint joinPoint,Object result){
        System.out.println(joinPoint.getSignature().getName()+"除法正常运行... 运行结果:{"+result+"}");
    }

    // 5.在目标方法出现异常地话 ⭐⭐⭐⭐ 获取异常信息
    @AfterThrowing(value = "execution(public int com.jsxs.aop.MathCalculator.*(..))",throwing = "exception")
    public void logError(Exception exception){
        System.out.println("除法异常运行..."+exception);
    }
}

在这里插入图片描述

(二)、AOP 原理

  1. 看给容器中注册了什么组件?
  2. 这个组件什么时候工作?
  3. 这个组件工作时候的功能?

1.@EnableAspectJAutoProxy

(1).@EnableAspectJAutoProxy源码

 *                 AOP【原理】
 *                  1.@EnableAspectJAutoProxy ->核心注解
 *                      (1).@Import(AspectJAutoProxyRegistrar.class) -> 给容器中导入AspectJAutoProxyRegistrar这个组件
 *                          internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator
 *                          给容器中注入了一个AnnotationAwareAspectJAutoProxyCreator

@EnableAspectJAutoProxy ->核心注解 给容器中导入AspectJAutoProxyRegistrar这个组件

在这里插入图片描述

(2).AspectJAutoProxyRegistrar 自定义注册bean源码

通过这个类进行创建自定义组件
在这里插入图片描述

(3).打断点进行Debug测试

  1. registerBeanDefinitions 方法处打一个断点

在这里插入图片描述

  1. registerOrEscalateApcAsRequired()

在这里插入图片描述

  1. 给容器中注入AnnotationAwareAspectJAutoProxyCreator

在这里插入图片描述

2.AnnotationAwareAspectJAutoProxyCreator 分析

(1).继承树

CTRL+H 打开目录树

在这里插入图片描述

 * AOP【原理】
 *                  1.@EnableAspectJAutoProxy ->核心注解
 *                      (1).@Import(AspectJAutoProxyRegistrar.class) -> 给容器中导入AspectJAutoProxyRegistrar这个组件
 *                          internalAutoProxyCreator=AnnotationAwareAspectJAutoProxyCreator
 *                          给容器中注入了一个AnnotationAwareAspectJAutoProxyCreator
 *                  2. AnnotationAwareAspectJAutoProxyCreator
 *                      AnnotationAwareAspectJAutoProxyCreator
 *                          ->(继承) AspectJAwareAdvisorAutoProxyCreator
 *                              ->(继承) AbstractAutoProxyCreator implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware
 *                                       关注后置处理器(在bean初始化前后) 和 自动装配BeanFactory
 *                                       (2.1). setBeanFactory
 *                                  ->(继承) AbstractAdvisorAutoProxyCreator
 *                                      ->(继承) ProxyConfig
 *                                          ->(继承) Object       

(2).AbstractAutoProxyCreator

AbstractAutoProxyCreator类 具有后置处理器的逻辑

在这里插入图片描述
具有setBeanFactry()的功能
在这里插入图片描述

(3).AbstractAdvisorAutoProxyCreator

这里初始化了 initBeanFactory()
在这里插入图片描述

3.注册 AnnotationAwareAspectJAutoProxyCreator

(1).打断点进行Debug调试

1.打上第一个断点
在这里插入图片描述
2.配置类上打上两个断点
在这里插入图片描述

(2).运行流程 (创建和注册AnnotationAwareAspectJAutoProxyCreator)

 *  3.流程:
 *                      (1).传入配置类,创建IOC容器 -> new AnnotationConfigApplicationContext(MainConfigOfAOP.class);
 *                      (2).注册配置类register(annotatedClasses),重启IOC容器refresh()
 *                      (3).registerBeanPostProcessors(beanFactory); 注册bean的后置处理器来方便拦截bean的创建
 *                          (3.1).先获取IOC容器已经定义了的需要创建对象的所有 BeanPostProcessor
 *                          (3.2).给容器中添加别的BeanPostProcessor
 *                          (3.3).优先注册了priorityOrderedPostProcessors接口的BeanPostProcessor
 *                          (3.4).在给容器中注册实现了Ordered接口的BeanPostProcessor
 *                          (3.5).注册没实现优先级接口的 BeanPostProcessors
 *                          (3.6).注册BeanPostProcessors,实际上就是创建BeanPostProcessors对象,保存在容器中;
 *                              创建internalAutoProxyCreator的BeanPostProcessorsAnnotationAwareAspectJAutoProxyCreator*                                  (1).创建Bean的实列
 *                                  (2).populateBean:给bean的各种属性赋值
 *                                  (3).initializeBean: 初始化bean
 *                                      (1).invokeAwareMethods() :处理Aware接口的方法回调
 *                                      (2).applyBeanPostProcessorsBeforeInitialization() 应用后置处理器
 *                                      (3).invokeInitMethods()执行自定义的初始方法
 *                                      (4).applyBeanPostProcessorsAfterInitialization() 执行后置处理器的postProcessor
 *                                  (4).BeanPostProcessor(AnnotationAwareAspectJAutoProxyCreator) 创建成功
 *3.7.BeanPostProcessor注册到BeanFactory中 beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));

1.创建IOC容器
在这里插入图片描述

2.发现refresh()方法中具有 后置处理器的功能 注册配置类register(annotatedClasses),重启IOC容器refresh()
在这里插入图片描述
3.registerBeanPostProcessors(beanFactory); 注册bean的后置处理器来方便拦截bean的创建

3.1 先获取IOC容器已经定义了的需要创建对象的所有 BeanPostProcessor
在这里插入图片描述
3.2 给容器中添加别的BeanPostProcessor
在这里插入图片描述
3.3 优先注册了priorityOrderedPostProcessors接口的BeanPostProcessor
在这里插入图片描述
3.4 在给容器中注册实现了Ordered接口的BeanPostProcessor
在这里插入图片描述
3.5注册没实现优先级接口的 BeanPostProcessors
在这里插入图片描述
3.6 注册BeanPostProcessors,实际上就是创建BeanPostProcessors对象,保存在容器中;
在这里插入图片描述

4.AnnotationAwareAspectJAutoProxyCreator 执行时机

 * 4.执行时机
 *                          1.遍历容器中所有的Bean,依次创建对象getBean(beanName)
 *                              getBean->doGetBean()->getSingleton->
 *                          2.创建bean
 *                              (1).先从缓存中获取当前bean,如果能获取到,说明bean是之前被创建过的,直接使用,否则再创建
 *                                  只要创建好的Bean都会被缓存起来。
 *
 *                              (2).CreateBean() 创建bean
 *                                  (2.1).resolveBeforeForeInstantion(beanName,mbdToUse) --> 希望后置处理器在此能返回一个代理对象,如果能返回代理对象就使用,如果不能就继续
 *                                  (2.2).doCreateBean() 真正的去创建一个bean实列
 *                           3.  AnnotationAwareAspectJAutoProxyCreator 在所有的bean创建之前会有一个拦截,InstantionAnwareBeanPostProcessor,会调用PostProcessBeforeInstantiation
 *

5.创建AOP代理对象

 *  5.AOP原理
 *          1.每一个bean创建之前,调用postProcessBeforeInstantiation();
 *                              关心 普通组件 和 切面组件 的创建
 *                                  (1).判断当前bean是否在advisedBeans中(保存了所需要增强的bean)
 *                                  (2).判断是否是切面
 *                                  (3).是否需要跳过
 *                                      (3.1).获取候选的增强器(切面里面的通知方法) 每一个封装的通知方法都是一个增强器 ,返回true
 *                                      (3.2). 返回false -> 永远不跳过
 *          2.每一个bean创建之后,调用postProcessAfterInstantiation();
 *                              (1).获取当前bean的所有增强器(增强方法)
 *                                  (1.1).找到的候选的所有增强器(找那些通知方法是需要切入当前bean方法的)
 *                                  (1.2).获取能在当前bean使用的增强器
 *                                  (1.3).给增强器进行排序
 *                              (2).保存当前bean在adviseBeans中
 *                              (3).如果当前bean需要增强,创建当前bean的代理对象。
 *                                  (3.1).获取所有增强器(通知方法)
 *                                  (3.2).保存到proxyFactory
 *                                  (3.3).创建代理对象 ⭐ (两种)
 *                                      (3.3.1).JDKDynamicAopProxy(config)  ->jdk创建代理模式(有接口使用jdk)
 *                                      (3.3.2).ObjenesisCglibAopProxy(config)  ->cglib创建代理模式(无接口使用cglib)
 *(3.4).IOC容器返回当前组件使用cglib增强了的代理对象
 *(3.5).以后容器中获取到的就是这个组件的代理对象,执行目标方法的时候,代理对象就会执行通知方法的流程

6.AOP原理的总结 ⭐

  1. @EnableAspectJAutoProxy 作用: 开启AOP注解功能
  2. @EnableAspectJAutoProxy 作用: 会给容器注册一个组件(AnnotationAwareAspectJAutoProxyCreator)
  3. AnnotationAwareAspectJAutoProxyCreator 是一个后置处理器
  4. 容器的创建流程
    • registerBeanPostProcessors(注册后置处理器),作用:创建registerBeanPostProcessors
    • finishBeanFactoryInitialization(beanFactory); 作用:初始化剩下的单实列bean
      • 创建业务逻辑组件和切面组件
      • AnnotationAwareAspectJAutoProxyCreator 拦截组件的创建过程
      • 组件创建完之后,判断组件是否需要增强
        • 是: 切面的通知方法,包装成增强器(Adviros); 业务逻辑组件创建一个代理对象
  5. 执行目标方法
    • 代理对象执行目标方法
    • CglibAopProxy.intercept();
      • 得到目标方法的拦截器链 (增强器包装成拦截器MethodInterceptor)
      • 利用拦截器的链式机制,依次进入每一个拦截器进行执行
      • 效果:
        • 正常执行: 前置通知 -> 目标方法 -> 后置通知 -> 返回通知
        • 出现异常: 前置通知 ->目标方法 -> 后置通知 -> 异常通知

(三)、声明式事务

1.声明式事务 - 环境搭建

(1).导入依赖

        <!--    Spring包  ->IOC  -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
                <!--     C3P0数据包 ->数据源  -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.2</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--    Spring切面的包   ->aop -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>
        <!--    Spring-JDBC -> jdbcTemplate   -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.3.12.RELEASE</version>
        </dependency>

(2).实体类和业务层

1.实体类

package com.jsxs.bean;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:27
 * @PackageName:com.jsxs.bean
 * @ClassName: Admin
 * @Description: TODO
 * @Version 1.0
 */
public class Admin {
    private int id;
    private String name;
    private String password;

    public Admin() {
    }

    public Admin(int id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

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

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "Admin{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

2.Dao层:

package com.jsxs.mapper;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:28
 * @PackageName:com.jsxs.mapper
 * @ClassName: AdminMapper
 * @Description: TODO
 * @Version 1.0
 */
@Repository
public class AdminMapper {

    @Resource
    private JdbcTemplate jdbcTemplate;

    public void add(){
        String sql="insert into admin(name,password) values(?,?)";
        int i = jdbcTemplate.update(sql,222,222);  // 后面的是参数也就是占位符,占位符有几个,参数也就有几个
        if (i>0){
            System.out.println("添加数据成功!!");
        }else {
            System.out.println("添加数据失败!!");
        }
    }

}

3.业务层

package com.jsxs.service;

import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:30
 * @PackageName:com.jsxs.service
 * @ClassName: AdminServer
 * @Description: TODO
 * @Version 1.0
 */

@Service
public class AdminServer {

    @Resource
    AdminMapper adminMapper;

    public void insert(){
        adminMapper.add();
    }
}

4.配置类

package com.jsxs.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:06
 * @PackageName:com.jsxs.tx
 * @ClassName: TxConfig
 * @Description: TODO  声明式事务
 * 1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)
 * 2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库
 * @Version 1.0
 */

@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
public class TxConfig {

    // 1.配置我们的数据源

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        // 1.1.c3p0数据源
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");
        dataSource.setUser("root");
        dataSource.setPassword("121788");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    // 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

5.测试

package com.jsxs.Test;

import com.jsxs.service.AdminServer;
import com.jsxs.tx.TxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:45
 * @PackageName:com.jsxs.Test
 * @ClassName: AOP_TX
 * @Description: TODO
 * @Version 1.0
 */
public class AOP_TX {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        AdminServer server = applicationContext.getBean(AdminServer.class);
        server.insert();
    }
}

在这里插入图片描述

2.声明式事务 - 测试成功

(1).遇见问题

 *                      1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)
 *                      2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库
 *3.给方法上标注@Transactional 表示当前方法是一个事务方法.
 *4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务
 *5.添加PlatformTransactionManager这个组件

我们在插入的方法中故意设置一个异常的操作,然后我们执行这个方法但是我们发现我们并不会发生事务回滚(即 失败都失败,成功都成功!);
在这里插入图片描述
在这里插入图片描述

(2).解决问题 -> 事务型(成功全成功_失败全失败)

package com.jsxs.service;

import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:30
 * @PackageName:com.jsxs.service
 * @ClassName: AdminServer
 * @Description: TODO
 * @Version 1.0
 */

@Service
public class AdminServer {

    @Resource
    AdminMapper adminMapper;

    @Transactional  // 声明标注事务的注解⭐
    public void insert(){
        adminMapper.add();
        // 2.故意制造异常 ⭐⭐
        System.out.println(1/0);
    }
}

  1. 基于XML文件的

在这里插入图片描述

  1. 基于注解

1.业务层添加上事务注解

package com.jsxs.service;

import com.jsxs.mapper.AdminMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:30
 * @PackageName:com.jsxs.service
 * @ClassName: AdminServer
 * @Description: TODO
 * @Version 1.0
 */

@Service
public class AdminServer {

    @Resource
    AdminMapper adminMapper;

    @Transactional
    public void insert(){
        adminMapper.add();
        System.out.println(1/0);
    }
}

2.需要在配置类上开启注解支持

package com.jsxs.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:06
 * @PackageName:com.jsxs.tx
 * @ClassName: TxConfig
 * @Description: TODO  声明式事务
 *                      1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)
 *                      2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库
 *                      3.给方法上标注@Transactional 表示当前方法是一个事务方法.
 *                      4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务
 * @Version 1.0
 */

@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
@EnableTransactionManagementpublic class TxConfig {

    // 1.配置我们的数据源

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        // 1.1.c3p0数据源
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");
        dataSource.setUser("root");
        dataSource.setPassword("121788");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    // 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
}

3.进行测试的操作

package com.jsxs.Test;

import com.jsxs.service.AdminServer;
import com.jsxs.tx.TxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:45
 * @PackageName:com.jsxs.Test
 * @ClassName: AOP_TX
 * @Description: TODO
 * @Version 1.0
 */
public class AOP_TX {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(TxConfig.class);
        for (String beanDefinitionName : applicationContext.getBeanDefinitionNames()) {
            System.out.println(beanDefinitionName);
        }
        AdminServer server = applicationContext.getBean(AdminServer.class);
        server.insert();
    }
}

4.结果我们发现缺少一个组件 PlatformTransactionManager
在这里插入图片描述
5. 给IOC容器中添加这个组件

package com.jsxs.tx;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jca.cci.connection.CciLocalTransactionManager;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * @Author Jsxs
 * @Date 2023/8/22 14:06
 * @PackageName:com.jsxs.tx
 * @ClassName: TxConfig
 * @Description: TODO  声明式事务
 *                      1. 导入相关依赖 : 数据源、数据库驱动、Spring-JDBC->(提供了JDBCTemplate)
 *                      2.配置数据源,JDBCTemplate(Spring提供的简化数据库操作的工具)操作数据库
 *                      3.给方法上标注@Transactional 表示当前方法是一个事务方法.
 *                      4.@EnableTransactionManagement 需要放在配置类进行开启注解的事务
 *                      5.添加PlatformTransactionManager这个组件
 * @Version 1.0
 */

@Configuration
@ComponentScan(value = "com.jsxs")
@PropertySource(value = "classpath:db.properties")
@EnableTransactionManagement  // ⭐
public class TxConfig {

    // 1.配置我们的数据源

    @Bean
    public DataSource dataSource() throws PropertyVetoException {
        // 1.1.c3p0数据源
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setJdbcUrl("jdbc:mysql://127.0.0.1:3306/demo1");
        dataSource.setUser("root");
        dataSource.setPassword("121788");
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        return dataSource;
    }

    // 2.我们设置JDBC的模板,又因为这个模板需要数据源,所以我们要存放我们的数据源。又因为在配置类中的参数是先从IOC容器中拿取的,所以我们直接设置成参数即可。

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean  // 在配置类中的@Bean注解的方法参数,参数默认是从IOC容器中赋值的 ⭐⭐
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

在这里插入图片描述

3.声明式事务原理

 *   2.事务原理
 *                      (1).@EnableTransactionManagement
 *                              利用 TransactionManagementConfigurationSelector 给容器中会导入批量组件
 *                              导入两个组件
 *                                  (1).AutoProxyRegistrar (2).ProxyTransactionManagementConfiguration
 *                      (2).细聊 AutoProxyRegistrar
 *                              给容器中注入一个组件,利用后置处理器机制在对象创建以后,包装对象,返回一个代理对象(增强器),代理方法执行方法利用拦截器链进行调用
 *
 *                      (3).细聊 ProxyTransactionManagementConfiguration
 *                              1.给事务注册一个事务增强器->事务增强器要用事务注解的信息
 *                              2.事务拦截器
 *                                  (1).先获取事务相关的属性
 *                                  (2).在获取PlatformTransactionManager,如果事务先没有添加指定任何
 *                                      最终会从容器中按照类型获取一个PlateFormTransactionManager;
 *                                  (3).执行目标方法
 *                                      如果异常,获取到事务管理器,利用事务管理回滚操作
 *                                      如果正常,利用事务管理器,提交事务控制

(四)、扩展原理

1.BeanFactoryPostProcessor ->来定制和修改beanFactory的内容

在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建

1.判断无参构造函数创建实列是否在BeanFactory之前还是之后?

1.BeanFactory的后置处理器MyBeanFactoryPostProcessor

package com.jsxs.etx;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:44
 * @PackageName:com.jsxs.etx
 * @ClassName: MyBeanFactoryPostProcessor
 * @Description: TODO
 * @Version 1.0
 */
@Component  ⭐注册组件
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { ⭐⭐ 继承接口
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanFactoryPostProcessor->执行力postProcessBeanFactory方法");
        // 组件的数量
        int count = beanFactory.getBeanDefinitionCount();
        // 组件的名字
        String[] names = beanFactory.getBeanDefinitionNames();
        System.out.println("当前beanFactory中有"+count+"个Bean");
        System.out.println(Arrays.asList(names));
    }
}

2.实体类

package com.jsxs.bean;

/**
 * @Author Jsxs
 * @Date 2023/8/13 21:11
 * @PackageName:com.jsxs.bean
 * @ClassName: Yellow
 * @Description: TODO
 * @Version 1.0
 */
public class Yellow {

    public Yellow() {  // ⭐构造函数
        System.out.println("Yellow无参构造创建实列");
    }
}

3.配置类

package com.jsxs.etx;

import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:38
 * @PackageName:com.jsxs.etx
 * @ClassName: EtxConfig
 * @Description: TODO   BeanFactoryPostProcessor : beanFactory的后置处理器 ⭐
 *                          在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建
 * 							在初始化创建其他组件前面执行
 * @Version 1.0
 */

@ComponentScan("com.jsxs.etx")  ⭐扫面组件
@Configuration
public class EtxConfig {

    @Bean  ⭐⭐ 将组件注册进来
    public Yellow yellow(){
        return new Yellow();
    }
}

4.测试

package com.jsxs.Test;

import com.jsxs.etx.EtxConfig;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:48
 * @PackageName:com.jsxs.Test
 * @ClassName: IOC_ext
 * @Description: TODO
 * @Version 1.0
 */
public class IOC_ext {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);

        applicationContext.close();
    }
}

所有的bean已经保存到beanFactory工厂中,但是实列依然还没有创建。在初始化创建其他组件前面执行
在这里插入图片描述

2.BeanDefinitionRegistryPostProcessor -> 额外添加组件

1.注册后置处理器

package com.jsxs.etx;

import com.jsxs.bean.Yellow;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.stereotype.Component;

/**
 * @Author Jsxs
 * @Date 2023/8/24 11:09
 * @PackageName:com.jsxs.etx
 * @ClassName: MyBeanDefinitionRegistryPostProcessor
 * @Description: TODO
 * @Version 1.0
 */
@Componentpublic class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    // BeanDefinitionRegistry Bean定义信息的保存中心,以后BeanFactory就是按照BeanDefinitionRegistry里面保存的每一个bean定义信息创建bean实列 ⭐⭐
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor的数量是 "+registry.getBeanDefinitionCount()+"Registry ");

        // 创建一个组件 ⭐⭐⭐
        RootBeanDefinition beanDefinition = new RootBeanDefinition(Yellow.class);
        // 将刚才创建的组件注册进IOC中,且名字叫做hello ⭐⭐⭐⭐
        registry.registerBeanDefinition("hello",beanDefinition);
    }


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        System.out.println("MyBeanDefinitionRegistryPostProcessor的数量是:"+beanFactory.getBeanDefinitionCount()+"   BeanFactory");

    }
}

2. 配置类 EtxConfig.java MyBeanFactoryPostProcessor.java Yellow.java IOC_ext.java 和上面的类是一样的

package com.jsxs.etx;

import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:38
 * @PackageName:com.jsxs.etx
 * @ClassName: EtxConfig
 * @Description: TODO  1. BeanFactoryPostProcessor : beanFactory的后置处理器
 *                          在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建
 *                          在初始化创建其他组件之前运行
 *                          (1).原理
 *                              (1.1).创建IOC容器
 *
 *                     2.BeanDefinitionRegistryPostProcessor
 *                          在所有的bean定义信息将要被加载,bean实列还未创建的时候执行。
 *                          优先于BeanFactoryPostProcessor执行,利用BeanDefinitionRegistryPostProcessor给容器中再额外的添加一些组件
 *                          (2).原理
 *                              (2.1).创建IOC容器
 *                              (2.2).refresh() 刷新容器 ->invokeBeanFactoryPostProcessors(beanFactory);.
 *                              
 *
 *
 * @Version 1.0
 */

@ComponentScan("com.jsxs.etx")
@Configuration
public class EtxConfig {

    @Bean
    public Yellow yellow(){
        return new Yellow();
    }
}


3.ApplicationListener -> 应用监听器

(1).ApplicationListener 的应用

1.事件-接口

package com.jsxs.etx;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;

/**
 * @Author Jsxs
 * @Date 2023/8/24 11:59
 * @PackageName:com.jsxs.etx
 * @ClassName: MyApplicationListener
 * @Description: TODO
 * @Version 1.0
 */
@Componentpublic class MyApplicationListener implements ApplicationListener<ApplicationEvent> {

    // 当容器中发布此事以后,方法触发  ⭐⭐
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        System.out.println("收到事件"+event);
    }
}

2.自定义事件

package com.jsxs.Test;

import com.jsxs.etx.EtxConfig;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:48
 * @PackageName:com.jsxs.Test
 * @ClassName: IOC_ext
 * @Description: TODO
 * @Version 1.0
 */
public class IOC_ext {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);

        // 1.自定义发布一个事件 ⭐⭐⭐
        applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {

        });
        applicationContext.close();
    }
}

3.配置类等其他的都没有变化

package com.jsxs.etx;

import com.jsxs.bean.Yellow;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:38
 * @PackageName:com.jsxs.etx
 * @ClassName: EtxConfig
 * @Description: TODO  1. BeanFactoryPostProcessor : beanFactory的后置处理器
 *                          在BeanFactory标准初始化之后调用,所有的bean定义已经保存加载到beanFactory,但是bean的实列还未创建
 *                          在初始化创建其他组件之前运行
 *                          (1).原理
 *                              (1.1).创建IOC容器
 *
 *                     2.BeanDefinitionRegistryPostProcessor
 *                          在所有的bean定义信息将要被加载,bean实列还未创建的时候执行。
 *                          优先于BeanFactoryPostProcessor执行,利用BeanDefinitionRegistryPostProcessor给容器中再额外的添加一些组件
 *                          (2).原理
 *                              (2.1).创建IOC容器
 *                              (2.2).refresh() 刷新容器 ->invokeBeanFactoryPostProcessors(beanFactory);.
 *                     3.ApplicationListener 监听容器中发布的事件。事件驱动模型开发
 *                          (1).监听ApplicationEvent及其下面的子事件
 *
 *                          步骤:
 *                              (1).写一个监听器来监听某个事件(ApplicationEvent及其子类)
 *                              (2).把监听器加入到容器中
 *                              (3).只要容器中有相关事件的发布,我们就能监听到这个事件
 *                                      ContextRefreshedEvent ->刷新事件 (所有的bean都已经创建)
 *                                      ContextClosedEvent -> 关闭事件
 *                              (4).自定义事件 ⭐⭐⭐⭐
 *                                  applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {});
 *
 *
 *
 *
 * @Version 1.0
 */

@ComponentScan("com.jsxs.etx")
@Configuration
public class EtxConfig {

    @Bean
    public Yellow yellow(){
        return new Yellow();
    }
}

在这里插入图片描述

4.@EventListener 监听注解

(1).@EventListener 注解监听方法事件

@EventListener: 这个注解可以标注在方法上,然后包扫描到我们这个包就可以实现我们的注解监听。

1.UserService.java

package com.jsxs.service;

import org.springframework.context.ApplicationEvent;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Service;

/**
 * @Author Jsxs
 * @Date 2023/8/27 9:02
 * @PackageName:com.jsxs.service
 * @ClassName: UserService
 * @Description: TODO
 * @Version 1.0
 */
@Service
public class UserService {

    // 设置监听的注解,里面的类是被监听的类。这里我们监听 ApplicationEvent 这个类
    @EventListener(classes = {ApplicationEvent.class})
    public void Listener(ApplicationEvent applicationEvent){ //这里是拿到我们监听的事件,这个是固定的,
        System.out.println("UserService 监听事件为...."+applicationEvent);
    }
}

2.配置类什么的都和上面的一样

package com.jsxs.Test;

import com.jsxs.etx.EtxConfig;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

/**
 * @Author Jsxs
 * @Date 2023/8/24 10:48
 * @PackageName:com.jsxs.Test
 * @ClassName: IOC_ext
 * @Description: TODO
 * @Version 1.0
 */
public class IOC_ext {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(EtxConfig.class);

        // 1.自定义发布一个事件
        applicationContext.publishEvent(new ApplicationEvent(new String("我发布了一个事件")) {

        });
        applicationContext.close();
    }
}

在这里插入图片描述

(五)、Spring源码总结

1.Spring源码总结

1. Spring容器在启动的时候,先保存所有注册进来的Bean定义信息
   (1).xml注册bean <bean>
   (2).注解: @Service ....
2.Spring容器保存定义信息之后,将会根据保存的信息在合适的时机创建Bean的实列。
	(1).用到Bean的时候创建组件的实列,利用getBean的方法创建Bean。创建好的Bean实列将会保存在Spring容器中。
	(2).统一创建剩下所有Bean的时候。
3.后置处理器
	(1).每一个Bean创建完成,都会使用各种后置处理器进行处理,来增强我们的Bean功能,后置处理器在bean创建的各个环节中。比如 自动注入功能,Aop代理功能	 
4.事件驱动模型
	(1).ApplicationListener 事件监听
	(2).ApplicationEventMulticaster 事件派发	  

(六)、WEB 注解

注意事项: servlet3.0的版本要求要使用tomcat7.0+

1.简介与测试

(1).创建一个Maven的web项目

在这里插入图片描述

(2).创建页面支持

index.jsp

<html>
<body>
<a href="Hello">点击我跳转</a>
</body>
</html>

(3).编写后端

  1. 注解实现映射关系

使用@WebServlet注解之后,我们可以省略我们的web.xml的映射关系
HelloServlet .java

package com.jsxs.servlet;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:01
 * @PackageName:com.jsxs.servlet
 * @ClassName: HelloServlet
 * @Description: TODO
 * @Version 1.0
 */


@WebServlet("/Hello")  // 1.设置拦截的URL,请求过来就会调用这个类的方法 ⭐

public class HelloServlet extends HttpServlet {  // 2.继承Servlet 并重写两个方法 ⭐⭐

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("hello..."); ⭐⭐⭐
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        super.doPost(req, resp);
    }
}

  1. 使用web.xml 映射关系
<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app>
  <display-name>Archetype Created Web Application</display-name>
<!--  具体处理器: 具体的业务逻辑类-->
  <servlet>
    <servlet-name>hello</servlet-name>
    <servlet-class>com.jsxs.servlet.HelloServlet</servlet-class>
  </servlet>
<!--  处理器映射器: 主要根据处理器名字和路径找具体的处理器-->
  <servlet-mapping>
    <servlet-name>hello</servlet-name>
    <url-pattern>/hello</url-pattern>
  </servlet-mapping>
</web-app>

在这里插入图片描述

2.ServletContainerInitalizer

容器在启动应用的时候,会扫描当前应用每一个jar包里面的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer指定实现的类,启动并运行这个实现类的方法。

(1).创建我们的指定扫描的文件并编辑

在这里插入图片描述

(2).编写被扫描的文件信息

package com.jsxs.servlet;

import com.jsxs.services.HelloService;

import javax.servlet.ServletContainerInitializer;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.annotation.HandlesTypes;
import java.util.Set;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口,抽象类)传递过来

@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐

    // 2.应用启动的时候会调用这个方法

    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }
    }
}

1.抽象类

package com.jsxs.services;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:54
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceAbart
 * @Description: TODO  父接口的抽象子类
 * @Version 1.0
 */
public abstract class HelloServiceAbart implements HelloService{
}

2.子接口: 接口继承接口

package com.jsxs.services;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:53
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceExt
 * @Description: TODO  父接口的子接口
 * @Version 1.0
 */
public interface HelloServiceExt extends HelloService{
}

3.实现类

package com.jsxs.services;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:56
 * @PackageName:com.jsxs.services
 * @ClassName: HelloServiceImpi
 * @Description: TODO  普通类继承接口
 * @Version 1.0
 */
public class HelloServiceImpi implements HelloService{
}

(3).将要被扫描的类全限定名写入文件中

com.jsxs.servlet.MyServletContainerInitializer

在这里插入图片描述

(4).运行结果

我们发现不管是子类 子接口 还是抽象类 实现类都被打印出全限定名了。

3.ServletContext 注册三大组件

过滤器 + 监听器 + Servlet

1.要注册的过滤器

package com.jsxs.servlet;

import javax.servlet.*;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/8/27 12:28
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserFilter
 * @Description: TODO  过滤器: 使用的是Servlet的包
 * @Version 1.0
 */
public class UserFilter implements Filter {

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }
        // 过滤请求,假如为false那么就拦截,true就放行
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        // 过滤请求
        System.out.println("UserFilter...");
        // 放行
        filterChain.doFilter(servletRequest,servletResponse);
    }

    @Override
    public void destroy() {

    }
}

2.要注册的监听器

package com.jsxs.servlet;

import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

/**
 * @Author Jsxs
 * @Date 2023/8/27 12:34
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserListener
 * @Description: TODO  监听项目的启动和停止
 * @Version 1.0
 */
public class UserListener implements ServletContextListener {

    // 监听项目的开始和初始化
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("UserListener---项目初始化成功....");
    }

    // 监听销毁
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("UserListener---项目关闭成功....");
    }
}

3. 要注册的Servlet

package com.jsxs.servlet;

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

/**
 * @Author Jsxs
 * @Date 2023/8/27 12:39
 * @PackageName:com.jsxs.servlet
 * @ClassName: UserServlet
 * @Description: TODO
 * @Version 1.0
 */
public class UserServlet extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        resp.getWriter().write("tomcate...");
    }
}

4.进行注册的操作

package com.jsxs.servlet;

import com.jsxs.services.HelloService;

import javax.servlet.*;
import javax.servlet.annotation.HandlesTypes;
import java.util.EnumSet;
import java.util.Set;

/**
 * @Author Jsxs
 * @Date 2023/8/27 10:41
 * @PackageName:com.jsxs.servlet
 * @ClassName: MyServletContainerInitalizer
 * @Description: TODO
 * @Version 1.0
 */
// 容器启动的时候会将@HandlesTypes指定的这个类型下面的子类(实现类,子接口)传递过来

@HandlesTypes(value = {HelloService.class})  // 传入我们感兴趣的类型并赋值给set 🚹
public class MyServletContainerInitializer implements ServletContainerInitializer {  // 1.继承接口并实现方法 ⭐

    // 2.应用启动的时候会调用这个方法

    /**
     * @param set : 负责接受我们感兴趣类型的所有子类型 即HelloService🚹
     * @param servletContext: 代表当前web应用的ServletContext,一个web应用相当于一个ServletContext
     * @throws ServletException
     */
    @Override
    public void onStartup(Set<Class<?>> set, ServletContext servletContext) throws ServletException {
        System.out.println("感兴趣的类型"+set);
        for (Class<?> aClass : set) {
            System.out.println(aClass);
        }

        // 这里我们向容器中注册监听组件和过滤组件 ⭐⭐
            // (1).添加Servlet组件 -> 具体的映射请求 ⭐⭐⭐
        ServletRegistration.Dynamic userServlet = servletContext.addServlet("UserServlet", new UserServlet());
            // 配置要映射请求的路径
        userServlet.addMapping("/user");
            // (2).过滤器⭐⭐⭐
        FilterRegistration.Dynamic userFilter = servletContext.addFilter("UserFilter", UserFilter.class);
            // 配置映射的信息
        userFilter.addMappingForUrlPatterns(EnumSet.of(DispatcherType.REQUEST),true,"/*"); // 请求方式 是否为true 映射路径
            // (3).添加监听器⭐⭐⭐
        servletContext.addListener(UserListener.class);
    }
}

4.SpringMVC整合分析

1.web容器在启动的时候,会扫描每个jar包下的src/main/webapp/META-INF/services/javax.servlet.ServletContainerInitializer的全限定名
2.根据全限定名加载这个文件指定的类
3.Spring的应用一启动会加载感兴趣的WebApplicationInitializer下的所有组件
4.并且为WebApplicationInitializer组件创建对象(组件不是接口,不是抽象类)
	(1).AbstractContextLoaderInitializer 创建跟容器,createRootApplicationContext();
	(2).AbstractDispatcherServletInitializer 
		1.创建一个web的ioc容器 createServletApplicationContext()
		2.创建一个DispatchServlet  createDispatchServlet()
		3.将创建的DispatchServlet添加到ServletContext(3). AbstractAnnotationConfigDispatcherServletInitializer注解方式配置的DispatcherServlet初始化器 
		1.创建更容器: createRootApplicationContext()
			getRootConfigClasses(); 传入一个配置类
		2.创建web的ioc容器 createServletApplicationContext()
总结:
	以注解的方式启动SpringMvc,继承AbstractAnnotationConfigDispatcherServletInitializer实现抽象发发指定DipatcherServlet的配置信息

5.Servlet 异步

(1).AsyncContext 实现异步处理

有时候我们在处理一个数据的时候,会有相对应的时间限制。在实际的用户体验中可能会产生很糟糕的效应。所以我们要使用异步的方式来减少时间差。

package com.jsxs.servlet;

import javax.servlet.AsyncContext;
import javax.servlet.ServletException;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @Author Jsxs
 * @Date 2023/8/27 16:10
 * @PackageName:com.jsxs.servlet
 * @ClassName: HelloAsyncServlet
 * @Description: TODO
 * @Version 1.0
 */

@WebServlet(value = "/async",asyncSupported = true) // 设置映射的的路径和支持异步 ⭐
public class HelloAsyncServlet extends HttpServlet {
    @Override
    protected void doGet(final HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        final AsyncContext asyncContext = req.startAsync(); // 2.开启异步模式 ⭐⭐
        System.out.println("主线程----------------------");
        // 3.通过使用线程的方式进行异步处理 ⭐⭐⭐
        asyncContext.start(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello-------------------------------------");
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 4. 通知异步处理完毕 ⭐⭐⭐⭐
                asyncContext.complete();
                // 5.获取到异步的上下文 ⭐⭐⭐⭐⭐
                AsyncContext asyncContext1 = req.getAsyncContext();
                // 6.获取响应 ⭐⭐⭐⭐⭐⭐
                ServletResponse response = asyncContext1.getResponse();
                try {
                    response.getWriter().write("hello async...");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

在这里插入图片描述

(2).Callable 实现异步处理

package com.jsxs.controller;



import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.concurrent.Callable;

/**
 * @Author Jsxs
 * @Date 2023/8/27 16:37
 * @PackageName:com.jsxs.controller
 * @ClassName: AsyncController
 * @Description: TODO
 * @Version 1.0
 */
@Controller
public class AsyncController {

    @RequestMapping("/async")
    @ResponseBody
    public Callable<String> async01(){
        System.out.println("主线程开始"+System.currentTimeMillis());
        Callable<String> callable=new Callable<String>(){
            @Override
            public String call() throws Exception {
                System.out.println("副线程开始A"+System.currentTimeMillis());
                Thread.sleep(2000);
                System.out.println("副线程结束B"+System.currentTimeMillis());
                return "Callabled";
            }
        };
        System.out.println("主线程结束"+System.currentTimeMillis());
        return callable;
    }
}

(3).DeferredResult 实现异步

在这里插入图片描述
我们以创建订单为例,创建订单的请求一进来,应用1就要启动一个线程,来帮我们处理这个请求。如果假设应用1并不能创建订单,创建订单需要应用2来完成,那么此时应该怎么办呢?应用1可以把创建订单的消息存放在消息中间件中,比如RabbitMQ、Kafka等等,而应用2就来负责监听这些消息中间件里面的消息,一旦它发现有创建订单这个消息,那么它就进行相应处理,然后将处理完成后的结果,比如订单的订单号等等,再次存放在消息中间件中,接着应用1再启动一个线程,例如线程2,来监听消息中间件中的返回结果,只要订单创建完毕,它就会拿到返回的结果(即订单的订单号),最后将其响应给客户端。

1.创建订单

package com.meimeixia.controller;

import java.util.concurrent.Callable;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

@Controller
public class AsyncController {
	
	@ResponseBody
	@RequestMapping("/createOrder")
	public DeferredResult<Object> createOrder() {
	    /*
	     * 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
	     * 
	     * 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
	     * 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
	     */
		DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");return deferredResult;
	}

	@ResponseBody
	@RequestMapping("/async01")
	public Callable<String> async01() {
		System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
		Callable<String> callable = new Callable<String>() {

			@Override
			public String call() throws Exception {
				System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
				Thread.sleep(2000); // 我们来睡上2秒 
				System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
				// 响应给客户端一串字符串,即"Callable<String> async01()"
				return "Callable<String> async01()";
			}
			
		};
		System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
		return callable;
	}
	
}

在这里插入图片描述

很明显这是超出时间限制了,应该是相当于创建订单失败了吧!那接下来,我们应该怎么办呢?真令人头疼😭

OK,我们不妨来模拟一个队列。首先新建一个类,例如DeferredResultQueue,如下所示。
2.模拟队列

package com.meimeixia.service;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.springframework.web.context.request.async.DeferredResult;

public class DeferredResultQueue {
	
	// DeferredResult对象临时保存的地方
	private static Queue<DeferredResult<Object>> queue = new ConcurrentLinkedQueue<DeferredResult<Object>>();// 临时保存DeferredResult对象的方法 ⭐⭐
	public static void save(DeferredResult<Object> deferredResult) {
		queue.add(deferredResult); 
	}
	
	// 获取DeferredResult对象的方法 ⭐⭐⭐
	public static DeferredResult<Object> get() {
		/*
		 * poll():检索并且移除,移除的是队列头部的元素
		 */
		return queue.poll();
	}
	
}

然后,修改一下AsyncController中的createOrder方法。上面我也已经说过了,该方法并不能真正地来处理创建订单的请求。即使如此,那也没关系,因为我们可以在该方法中先把new出来的DeferredResult对象临时保存起来。

3.将正在创建中的订单临时保存起来

package com.meimeixia.controller;

import java.util.concurrent.Callable;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;

import com.meimeixia.service.DeferredResultQueue;

@Controller
public class AsyncController {
	
	@ResponseBody
	@RequestMapping("/createOrder")
	public DeferredResult<Object> createOrder() {
		/*
		* 在创建DeferredResult对象时,可以像下面这样传入一些参数哟!
		* 
		* 第一个参数(timeout): 超时时间。限定(请求?)必须在该时间内执行完,如果超出时间限制,那么就会返回一段错误的提示信息(timeoutResult)
		* 第二个参数(timeoutResult):超出时间限制之后,返回的错误提示信息
		*/
		DeferredResult<Object> deferredResult = new DeferredResult<>((long)3000, "create fail...");
		DeferredResultQueue.save(deferredResult);// 保存起来
		return deferredResult;
	}

	@ResponseBody
	@RequestMapping("/async01")
	public Callable<String> async01() {
		System.out.println("主线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
		Callable<String> callable = new Callable<String>() {

			@Override
			public String call() throws Exception {
				System.out.println("副线程开始..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
				Thread.sleep(2000); // 我们来睡上2秒 
				System.out.println("副线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
				// 响应给客户端一串字符串,即"Callable<String> async01()"
				return "Callable<String> async01()";
			}
			
		};
		System.out.println("主线程结束..." + Thread.currentThread() + "==>" + System.currentTimeMillis());
		return callable;
	}
	
}

当然了,实际开发中应该有一个别的线程来专门监听这个事,啥事?我猜应该是在其他的地方临时保存DeferredResult对象这件事吧!不过,我们在这儿并不会这样做,而是再在AsyncController中编写一个方法,例如create,该方法才是真正来处理创建订单的请求的。

4.真正创建订单号(监听)

@ResponseBody
@RequestMapping("/create")
public String create() {
    // 在这模拟创建订单
    String order = UUID.randomUUID().toString();
    /*
     * 如果我们想在上一个请求(即createOrder)中使用订单,那么该怎么办呢?从临时保存DeferredResult对象的地方获取
     * 到刚才保存的DeferredResult对象,然后调用其setResult方法设置结果,例如设置订单的订单号
     */
    DeferredResult<Object> deferredResult = DeferredResultQueue.get();  // ⭐
    deferredResult.setResult(order); // 设置结果
    // 这儿给客户端直接响应"success===>订单号"这样的字符串,不要再跳转页面了
    return "success===>" + order;
}

至此,可以看到,只要客户端发送一个createOrder请求进来创建订单,那么服务端就会先将new出来的DeferredResult对象临时保存起来。等到create方法触发并把订单号设置进去之后,在createOrder方法中就会立即得到返回结果,即订单号。

最后,我们来重启项目进行测试。重启成功之后,先来访问createOrder请求,以便来创建订单,但是订单必须得在3秒内创建完,所以一旦访问了createOrder请求后,你必须立即访问create请求来真正创建订单,而且至少得在3秒内完成。

这时,你会看到什么结果呢?可以看到访问create请求之后,直接给浏览器页面响应了一个success===>4e7e3e4c-27d2-4989-87c1-00d545e05feb这样的字符串,其中4e7e3e4c-27d2-4989-87c1-00d545e05feb就是所创建订单的订单号,如下图所示。

在这里插入图片描述

切到访问createOrder请求的浏览器窗口之后,你也可以在浏览器页面中看到所创建订单的订单号,即4e7e3e4c-27d2-4989-87c1-00d545e05feb,如下图所示。
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/936881.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Harbor 私有仓库迁移博客

文章目录 Harbor 私有仓库迁移一.私有仓库迁移的介绍1.为何要对Harbor 私有仓库的迁移2.Harbor 私有仓库的迁移特点3. Harbor 私有仓库的迁移注意要点 二.私有仓库迁移配置1.源Harbor配置&#xff08;192.168.198.11&#xff09;&#xff08;1&#xff09;接着以下操作查看容器…

软考:中级软件设计师:信息系统的安全属性,对称加密和非对称加密,信息摘要,数字签名技术,数字信封与PGP

软考&#xff1a;中级软件设计师:信息系统的安全属性 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都是需要细心准…

零售再增长,直播登“C位”,美团稳稳交出成绩单

8月24日&#xff0c;美团发布2023年中期业绩和二季报&#xff0c;财报显示其二季度实现营收680亿元&#xff0c;同比增长33.4%&#xff1b;实现净利润47.13亿元&#xff0c;同比扭亏为盈&#xff0c;调整后净利润达历史最高水平。其中&#xff0c;与消费市场走势息息相关的美团…

腾讯云服务器建站教程_新手站长搭建网站全流程

使用腾讯云服务器搭建网站全流程&#xff0c;包括轻量应用服务器和云服务器CVM建站教程&#xff0c;轻量可以使用应用镜像一键建站&#xff0c;云服务器CVM可以通过安装宝塔面板的方式来搭建网站&#xff0c;腾讯云服务器网txyfwq.com分享使用腾讯云服务器建站教程&#xff0c;…

字节律动之*你太美, emm 其实是个字符画雪花视频-哈哈哈-将视频转成一张张字符画图片

效果 整体效果 局部图片放大效果 视频转换后带雪花特效,凑合看吧, 视频地址 准备工作 安装FFmpeg 电脑上安装ffpeg处理视频并设置环境变量, windows可以参考FFmpeg的安装教程这篇博客安装 mac可以直接执行brew install ffmpeg安装 安装python依赖包 执行pip3 install -…

一篇掌握BFD技术(三):单臂回声配置

1. 实验目的 熟悉单臂回声的应用场景掌握单臂回声的配置方法 2. 实验拓扑 想要华为数通配套实验拓扑和配置笔记的朋友们点赞关注&#xff0c;评论区留下邮箱发给你 3. 实验步骤 1&#xff09;配置IP地址 AR1的配置 <Huawei>system-v…

【Unity笔记】TimeLine的详细使用介绍

文章目录 前言素材一、timeline基础介绍1. 打开timeline轨道面板2. 创建TimeLine轨道3. Timeline常用轨道4. 修改Timeline单位5. 锁定界面 二、timeline的通用轨道使用三、Cinemeachine虚拟相机结合Timeline实现场景移动四、DialogueTrack&#xff1a;自定义的对话轨道(自己编写…

JVM第三篇 运行时数据区-虚拟机栈和PC程序计数器

目录 1. JAVA中的线程 2. 栈区 2.1 栈帧 2.2 栈可能出现的异常 2.3 设置栈大小 3.程序计数器&#xff08;PC&#xff09; 4. PC和栈发挥的作用 5. 关于栈的常见面试题 虚拟机包含三大部分&#xff0c;类加载子系统&#xff0c;运行时数据区&#xff0c;执行引擎。运行时…

基于亚马逊云科技无服务器服务快速搭建电商平台——性能篇

使用 Serverless 构建独立站的优势 在传统架构模式下&#xff0c;如果需要进行电商大促需要提前预置计算资源以支撑高并发访问&#xff0c;会造成计算资源浪费并且增加运维工作量。本文介绍一种新的部署方式&#xff0c;将 WordPress 和 WooCommerce 部署在 Amazon Lambda 中。…

C++信息学奥赛1149:最长单词2

#include <iostream> #include <string> using namespace std; int main() {string str1;// 输入一行字符串getline(cin,str1);int n0;string MaxArr"";string MinArrstr1;string arr"";for(int i0;i<str1.length();i){if(str1[i] or str1…

电阻器件的分类

电阻器的种类碳膜电阻膜式电阻器中的一种。气态碳氢化合物在高温和真空中分解&#xff0c;碳沉积在瓷棒或者瓷管上&#xff0c;形成一层结晶碳膜。改变碳膜厚度和用刻槽的方式变更碳膜的长度可以得到不同的阻值。碳膜电阻成本较低&#xff0c;电性能和稳定性较差&#xff0c;一…

Win 11 电脑的 Win + E 快捷键失效

报的错误信息如下&#xff1a; 该文件没有与之关联的应用来执行该操作。请安装应用&#xff0c;若已经安装应用&#xff0c;请在”默认应用设置"页面中创建关联。 报错原因&#xff1a;系统注册表被改写了导致的出错 解决办法&#xff1a; 1、首先&#xff0c;按键盘上…

【Linux】以太网协议以及MTU

以太网协议 数据链路层的功能以太网的数据格式MTUMTU对IP协议的影响MTU对UDP协议的影响MTU对TCP协议的影响 数据链路层的功能 数据链路层的主要功能是&#xff1a;控制链路。包括数据链路的建立、链路的维护和释放。MAC寻址也是它的功能&#xff0c;寻址是指计算机网卡的MAC地…

算法通过村第四关-栈青铜笔记|手写栈操作

文章目录 前言1. 栈的基础概要1.1 栈的特征1.2 栈的操作1.3 Java中的栈 2. 栈的实现&#xff08;手写栈&#xff09;2.1 基于数组实现2.2 基于链表实现2.3 基于LinkedList实现 总结 前言 提示&#xff1a;我自己一个人的感觉很好 我并不想要拥有你 除非你比我的独处更加宜人 --…

低代码与低代码平台的概念解析

随着数字化转型和软件需求的不断增长&#xff0c;传统的手写代码开发方式已经无法满足迅速推出应用程序的需求。为了加快软件开发的速度并降低技术门槛&#xff0c;低代码开发模式应运而生。本文将介绍低代码的概念&#xff0c;探讨什么是低代码什么是低代码平台&#xff1f; 一…

JAVA中如何知道某项目使用什么数据库类型

文章目录 1. 前言2. 先说结论3. 代码案例 1. 前言 最近在写第三方工具的时候&#xff0c;需要判断使用方项目具体使用了什么数据库类型&#xff0c;从而进行不一样的实现逻辑。当然可以让用户在YML中配置说明项目用什么数据库&#xff0c;但用户不一定填写正确。因此需要想其他…

华为OD机试 - 求满足条件的最长子串的长度 - 双指针(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

day-03 基于TCP的服务器端/客户端

一.理解TCP和UDP TCP&#xff08;Transmission Control Protocol&#xff09;和UDP&#xff08;User Datagram Protocol&#xff09;是两种常见的传输层协议&#xff0c;用于在计算机网络中提供可靠的数据传输。 1.TCP&#xff1a; 连接导向&#xff1a;TCP是一种面向连接的…

麦淘商城系统 商城系统源码 电商系统源码 分类商城网站系统源码

麦淘商城系统 商城系统源码 电商系统源码 分类商城网站系统源码 麦淘商城系统源码 | 提供完整可定制的电商解决方案 电商系统源码 商城网站系统源码

【C++笔记】C++之类与对象(下)

【C笔记】C之类与对象(下&#xff09; 1、再看构造函数1.1、构造函数的初始化列表1.2、C支持单参数的构造函数的隐式类型转换1.3、匿名对象 2、Static成员2.1、为什么要有静态成员变量&#xff1f;2.2、一个类的静态成员变量属于这个类的所有对象2.3、静态成员函数 3、友元3.1、…