Spring高手之路6——Bean生命周期的扩展点:BeanPostProcessor

news2024/11/16 6:24:58

文章目录

  • 1. 探索Spring的后置处理器(BeanPostProcessor)
    • 1.1 BeanPostProcessor的设计理念
    • 1.2 BeanPostProcessor的文档说明
  • 2. BeanPostProcessor的使用
    • 2.1 BeanPostProcessor的基础使用示例
    • 2.2 利用BeanPostProcessor修改Bean的初始化结果的返回值
    • 2.3 通过BeanPostProcessor实现Bean属性的动态修改
  • 3. 深度剖析BeanPostProcessor的执行时机
    • 3.1 后置处理器在Bean生命周期中的作用及执行时机
    • 3.2 图解:Bean生命周期与后置处理器的交互时序

在前一篇讲解生命周期的时候就可以讲解后置处理器了,但是内容比较多,还是分开来讲解。

1. 探索Spring的后置处理器(BeanPostProcessor)

1.1 BeanPostProcessor的设计理念

  BeanPostProcessor的设计目标主要是提供一种扩展机制,让开发者可以在Spring Bean的初始化阶段进行自定义操作。这种设计理念主要体现了Spring的一种重要原则,即“开放封闭原则”。开放封闭原则强调软件实体(类、模块、函数等等)应该对于扩展是开放的,对于修改是封闭的。在这里,Spring容器对于Bean的创建、初始化、销毁等生命周期进行了管理,但同时开放了BeanPostProcessor这种扩展点,让开发者可以在不修改Spring源码的情况下,实现对Spring Bean生命周期的自定义操作,这种设计理念大大提升了Spring的灵活性和可扩展性。

  BeanPostProcessor不是Spring Bean生命周期的一部分,但它是在Spring Bean生命周期中起重要作用的组件

1.2 BeanPostProcessor的文档说明

  我们来看看这个方法的文档注释,从图中可以看到,BeanPostProcessor 接口定义了两个方法,postProcessBeforeInitializationpostProcessAfterInitialization

在这里插入图片描述

  postProcessBeforeInitialization方法会在任何bean初始化回调(如InitializingBeanafterPropertiesSet方法或者自定义的init-method之前被调用。也就是说,这个方法会在bean的属性已经设置完毕,但还未进行初始化时被调用。

  postProcessAfterInitialization方法在任何bean初始化回调(比如InitializingBeanafterPropertiesSet或者自定义的初始化方法)之后被调用。这个时候,bean的属性值已经被填充完毕。返回的bean实例可能是原始bean的一个包装。

在这里插入图片描述


2. BeanPostProcessor的使用

2.1 BeanPostProcessor的基础使用示例

全部代码如下:

首先定义两个简单的BeanLionElephant

Lion.java

package com.example.demo.bean;

public class Lion {
    private String name;

    public String getName() {
        return name;
    }

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

Elephant.java

package com.example.demo.bean;

public class Elephant {
    private String name;

    public String getName() {
        return name;
    }

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

然后定义一个简单的BeanPostProcessor,它只是打印出被处理的Bean的名字:

package com.example.demo.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before initialization: " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After initialization: " + beanName);
        return bean;
    }
}

接着我们定义一个配置类,其中包含对LionElephant类和MyBeanPostProcessor 类的Bean定义:

package com.example.demo.configuration;

import com.example.demo.bean.Elephant;
import com.example.demo.bean.Lion;
import com.example.demo.processor.MyBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class AnimalConfig {
    @Bean
    public Lion lion() {
        return new Lion();
    }

    @Bean
    public Elephant elephant() {
        return new Elephant();
    }

    @Bean
    public MyBeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}

最后,我们在主程序中创建ApplicationContext对象:

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class Main {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
        ((AnnotationConfigApplicationContext)context).close();
    }
}

运行结果:

在这里插入图片描述

  以上代码在执行时,将先创建LionElephant对象,然后在初始化过程中和初始化后调用postProcessBeforeInitializationpostProcessAfterInitialization方法,打印出被处理的Bean的名字。

细心的小伙伴可能观察到这里有红色日志
信息: Bean 'animalConfig' of type [com.example.demo.configuration.AnimalConfig$$EnhancerBySpringCGLIB$$ee4adc7e] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

  这个日志信息告诉我们,BeanPostProcessor 并没有应用到所有的 bean 上,具体来说,没有应用到 ‘animalConfig’ 这个 bean。这个信息的出现是因为在 Spring 的初始化过程中,BeanPostProcessor 们会被提前实例化,并处理其他的 bean,但是这个过程需要注意,如果在 BeanPostProcessor 实例化过程中需要用到其他 bean,那么这些 bean 不会被所有的 BeanPostProcessor 处理,只会被当前已经实例化的 BeanPostProcessor 处理。

  以这个例子来说,AnimalConfig bean 是一个配置类,它是在初始化 MyBeanPostProcessor 之前就被实例化的,因此它没有被所有的 BeanPostProcessor 处理,也就是说 MyBeanPostProcessor 没有对这个配置类进行处理。我们可以不把MyBeanPostProcessor实例化放在配置类,直接加上@Component,这个红色提示日志就会消失。

  如果这些话不理解,咱们先看完后面的内容,再回头看看这句话就简单多了。

2.2 利用BeanPostProcessor修改Bean的初始化结果的返回值

还是上面的例子,我们只修改一下MyBeanPostProcessor 类的方法后再次运行

package com.example.demo.processor;

import com.example.demo.bean.Elephant;
import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before initialization: " + bean);
        if (bean instanceof Lion) {
            return new Elephant();
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After initialization: " + bean);
        return bean;
    }
}

运行结果:

在这里插入图片描述

  BeanPostProcessor的两个方法都可以返回任意的Object,这意味着我们可以在这两个方法中更改返回的bean。例如,如果我们让postProcessBeforeInitialization方法在接收到Lion实例时返回一个新的Elephant实例,那么我们将会看到Lion实例变成了Elephant实例。

  那既然BeanPostProcessor的两个方法都可以返回任意的Object,那我搞点破坏返回null会怎么样,会不会因为初始化beannull而导致异常呢?

  答案是不会的,我们来看一下:

package com.example.demo.processor;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Before initialization: " + bean);
        return null;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("After initialization: " + bean);
        return bean;
    }
}

我们运行看结果

在这里插入图片描述

结果发现还是正常初始化的bean类型,不会有任何改变,我们继续调试看看是为什么

在这里插入图片描述

我们通过堆栈帧看到调用postProcessBeforeInitialization方法的上一个方法是applyBeanPostProcessorsBeforeInitialization,双击点开看一看这个方法

在这里插入图片描述

  从我这个调试图中可以看到,如果postProcessBeforeInitialization返回nullSpring仍然用原始的bean进行后续的处理,同样的逻辑在postProcessAfterInitialization也是一样。这就是为什么我们在BeanPostProcessor类的方法中返回null,原始bean实例还是存在的原因。

2.3 通过BeanPostProcessor实现Bean属性的动态修改

来看看是怎么拦截 bean 的初始化的

全部代码如下:

首先,我们定义一个Lion类:

public class Lion {
    private String name;

    public Lion() {
        this.name = "Default Lion";
    }

    public Lion(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public String toString() {
        return "Lion{" + "name='" + name + '\'' + '}';
    }
}

接下来,我们定义一个BeanPostProcessor,我们称之为MyBeanPostProcessor

package com.example.demo.processor;

import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean的初始化之前:" + bean);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean的初始化之后:" + bean);
        if (bean instanceof Lion) {
            ((Lion) bean).setName("Simba");
        }
        return bean;
    }
}

然后我们定义一个配置类,其中包含对Lion类的Bean定义和对MyBeanPostProcessor 类的Bean定义:

package com.example.demo.configuration;

import com.example.demo.bean.Lion;
import com.example.demo.processor.MyBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class AnimalConfig {
    
    @Bean
    public Lion lion() {
        return new Lion();
    }

    @Bean
    public MyBeanPostProcessor myBeanPostProcessor() {
        return new MyBeanPostProcessor();
    }
}

最后,我们在主程序中创建ApplicationContext对象,并获取Lion对象:

package com.example.demo;

import com.example.demo.bean.Lion;
import com.example.demo.configuration.AnimalConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class DemoApplication {
    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext(AnimalConfig.class);
        Lion lion = context.getBean("lion", Lion.class);
        System.out.println(lion);
        ((AnnotationConfigApplicationContext)context).close();
    }
}

运行结果:

在这里插入图片描述

  上面代码在执行时,先创建一个Lion对象,然后在初始化过程中和初始化后调用postProcessBeforeInitializationpostProcessAfterInitialization方法,修改Lion的名字为"Simba",最后在主程序中输出Lion对象,显示其名字为"Simba"


3. 深度剖析BeanPostProcessor的执行时机

3.1 后置处理器在Bean生命周期中的作用及执行时机

  在这个例子中,我们将创建一个名为LionElephantBean,它会展示属性赋值和生命周期的各个步骤的执行顺序。同时,我们还将创建一个BeanPostProcessor来打印消息并显示它的执行时机。

全部代码如下:

首先,我们定义我们的Lion

package com.example.demo.bean;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

public class Lion implements InitializingBean, DisposableBean {

    private String name;

    private Elephant elephant;

    public Lion() {
        System.out.println("1. Bean Constructor Method Invoked!");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
        System.out.println("2. Bean Setter Method Invoked! name: " + name);
    }

    /**
     * setter注入
     * @param elephant
     */
    @Resource
    public void setElephant(Elephant elephant) {
        this.elephant = elephant;
        System.out.println("2. Bean Setter Method Invoked! elephant: " + elephant);
    }

    @PostConstruct
    public void postConstruct() {
        System.out.println("4. @PostConstruct Method Invoked!");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("5. afterPropertiesSet Method Invoked!");
    }

    public void customInitMethod() {
        System.out.println("6. customInitMethod Method Invoked!");
    }

    @PreDestroy
    public void preDestroy() {
        System.out.println("8. @PreDestroy Method Invoked!");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("9. destroy Method Invoked!");
    }

    public void customDestroyMethod() {
        System.out.println("10. customDestroyMethod Method Invoked!");
    }
}

创建Lion所依赖的Elephant

package com.example.demo.bean;

import org.springframework.stereotype.Component;

@Component
public class Elephant {
    private String name;

    public String getName() {
        return name;
    }

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

然后,我们定义一个简单的BeanPostProcessor

package com.example.demo.processor;

import com.example.demo.bean.Lion;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Lion) {
            System.out.println("3. postProcessBeforeInitialization Method Invoked!");
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof Lion) {
            System.out.println("7. postProcessAfterInitialization Method Invoked!");
        }
        return bean;
    }
}

创建一个配置类AnimalConfig

package com.example.demo.configuration;

import com.example.demo.bean.Lion;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;


@Configuration
public class AnimalConfig {

    @Bean(initMethod = "customInitMethod", destroyMethod = "customDestroyMethod")
    public Lion lion() {
        Lion lion = new Lion();
        lion.setName("my lion");
        return lion;
    }
}

主程序:

package com.example.demo;

import com.example.demo.bean.Lion;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class DemoApplication {
    public static void main(String[] args) {
        System.out.println("容器初始化之前...");
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example");
        System.out.println("容器初始化完成");
        Lion bean = context.getBean(Lion.class);
        bean.setName("oh!!! My Bean set new name");
        System.out.println("容器准备关闭...");
        context.close();
        System.out.println("容器已经关闭");
    }
}

控制台上看到所有的方法调用都按照预期的顺序进行,这可以更好地理解Bean属性赋值和生命周期以及BeanPostProcessor的作用。

在这里插入图片描述

根据打印日志我们可以分析出

  1. 首先,Bean Constructor Method Invoked! 表明 Lion 的构造器被调用,创建了一个新的 Lion 实例。

  2. 接着,Bean Setter Method Invoked! name: my lionBean Setter Method Invoked! elephant: com.example.demo.bean.Elephant@7364985f 说明 SpringLion 实例的依赖注入。在这一步,Spring 调用了 Lionsetter 方法,为 name 属性设置了值 “my lion”,同时为 elephant 属性注入了一个 Elephant 实例。

  3. 然后,postProcessBeforeInitialization Method Invoked! 说明 MyBeanPostProcessorpostProcessBeforeInitialization 方法被调用,这是在初始化 Lion 实例之前。

  4. @PostConstruct Method Invoked! 说明 @PostConstruct 注解的方法被调用,这是在 Bean 初始化之后,但是在 Spring 执行任何进一步初始化之前。

  5. afterPropertiesSet Method Invoked! 说明 Spring 调用了 InitializingBeanafterPropertiesSet 方法

  6. customInitMethod Method Invoked! 表示调用了 Lion 实例的 init-method 方法。

  7. postProcessAfterInitialization Method Invoked! 说明 MyBeanPostProcessorpostProcessAfterInitialization 方法被调用,这是在初始化 Lion 实例之后。

然后 Spring 完成了整个初始化过程。

  1. 主程序中手动调用了 Lion 实例的 setter 方法,因此在 Bean Setter Method Invoked! name: oh!!! My Bean set new name 可见,name 属性被设置了新的值 "oh!!! My Bean set new name"

当容器准备关闭时:

  1. @PreDestroy Method Invoked! 说明 @PreDestroy 注解的方法被调用,这是在 Bean 销毁之前。

  2. destroy Method Invoked! 表示 Lion 实例开始销毁。在这一步,Spring 调用了 DisposableBeandestroy 方法。

  3. customDestroyMethod Method Invoked! 表示 Lion 实例开始销毁,调用了Lion 实例的 destroy-method 方法。

最后,Spring 完成了整个销毁过程,容器关闭。

  这个日志提供了 Spring Bean 生命周期的完整视图,显示了从创建到销毁过程中的所有步骤。

  注意:DisposableBeandestroy 方法和 destroy-method 方法调用,这个销毁过程不意味着bean实例就被立即从内存中删除了,Java的垃圾收集机制决定了对象什么时候被从内存中删除。Spring容器无法强制进行这个操作,比如解除bean之间的关联和清理缓存,这并不是Spring在销毁bean时会做的,而是由Java的垃圾回收器在一个对象不再被引用时做的事情。

  BeanPostProcessor 的执行顺序是在 Spring Bean 的生命周期中非常重要的一部分。例如,如果一个 Bean 实现了 InitializingBean 接口,那么 afterPropertiesSet 方法会在所有的 BeanPostProcessorpostProcessBeforeInitialization 方法之后调用,以确保所有的前置处理都完成了。同样,BeanPostProcessorpostProcessAfterInitialization 方法会在所有的初始化回调方法之后调用,以确保 Bean 已经完全初始化了。

  我们可以注册多个 BeanPostProcessor。在这种情况下,Spring 会按照它们的 Ordered 接口或者 @Order 注解指定的顺序来调用这些后置处理器。如果没有指定顺序,那么它们的执行顺序是不确定的。

3.2 图解:Bean生命周期与后置处理器的交互时序

  综合上面的执行结果,我们来总结一下,下面是Spring Bean生命周期的时序图,它详细地描绘了Spring Bean从实例化到准备使用的整个过程,包括Bean的实例化、属性赋值、生命周期方法的执行和后置处理器的调用。

Spring Bean生命周期

欢迎一键三连~

有问题请留言,大家一起探讨学习

----------------------Talk is cheap, show me the code-----------------------

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

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

相关文章

【Linux工具】yum指令、vim的使用和修改信任白名单

【Linux工具】yum指令、vim的使用和修改信任白名单 目录 【Linux工具】yum指令、vim的使用和修改信任白名单软件包rzsz查看软件包 安装软件卸载软件vim的使用vim的三种模式vim的基本操作vim命令模式命令集vim末行模式命令集vim操作总结 简单vim配置修改信任白名单 作者&#xf…

第四章 进程同步

目录 一、进程同步、进程互斥 1.1 进程同步 1.2 进程互斥 二、信号量机制 2.1 整型信号量 2.2 记录型信号量 三、用信号量实现进程互斥、同步、前驱关系 3.1 信号量机制实现进程互斥 3.2 信号量机制实现进程同步 3.3 信号量机制实现前驱关系 四、生产者-消费者问题…

【MySQL】· 一文了解四大子查询

前言 ✨欢迎来到小K的MySQL专栏,本节将为大家带来MySQL标量/单行子查询、列子/表子查询的讲解✨ 目录 前言一、子查询概念二、标量/单行子查询、列子/表子查询三、总结 一、子查询概念 子查询指一个查询语句嵌套在另一个查询语句内部的查询,这个特性从My…

MySQL - 第7节 - MySQL内置函数

1.日期函数 1.1.常用的日期函数 常用的日期函数如下: 1.2.current_date函数 current_date函数用于获取当前的日期。如下: 1.3.current_time函数 current_time函数用于获取当前的时间。如下: 1.4.current_timestamp函数 current_timestamp函数…

计组:各硬件工作原理

目录 ​编辑程序指令执行流程 程序执行指令前 执行各指令的顺序 程序(每一条)指令执行具体步骤 以第一步为例: 取指令(#1~#4) 初:(PC)0,指向第一条指令的存储地址 #1&#x…

计算机组成原理学习笔记(学习中)

计算机系统概论 1.1计算机基本组成 冯诺依曼计算机特点: 计算机由五大部件组成:控制器,运算器,存储器,输入设备,输出设备 指令和数据以同等地位存于存储器,可按地址寻访 指令和数据用二进制…

一文读懂CAN总线及通信协议

CAN总线的汽车 CAN概念 CAN是控制器域网 (Controller Area Network, CAN) 的简称,是由研发和生产汽车电子产品著称的德国BOSCH公司开发了的,并最终成为国际标准(ISO11898),是ISO国际标准化的串行通信协议。是国际上应…

Anchor-free应用一览:目标检测、实例分割、多目标跟踪

作者|杨阳知乎 来源|https://zhuanlan.zhihu.com/p/163266388 本文整理了与Anchor free相关的一些工作。一方面是分享近期在目标检测领域中一些工作,另一方面,和大家一起梳理一下非常火热的网络模型CenterNet、FCOS,当…

Linux网络-数据链路层,MAC帧解析

目录 数据链路层VS网络层 以太网概念 以太网的帧格式(报文格式)(也可以称之为MAC帧) MAC地址的概念 MAC帧格式 局域网通信原理 MTU MTU说明 MTU对IP协议的影响 MTU对UDP协议的影响 MTU对TCP协议的影响 数据链路层VS网…

【Linux】应用层协议:HTTP和HTTPS

每个人都可以很喜欢每个人,但喜欢治不了病,喜欢买不了东西,喜欢不能当饭吃,喜欢很廉价… 文章目录 一、HTTP协议1.URL1.1 URL的组成1.2 urlencode && urldecode 2.HTTP协议格式2.1 http请求和响应的格式2.2 通过代码来进行…

[Eigen中文文档] 求解稀疏线性系统

文档总目录 本文目录 稀疏求解器列表内置直接求解器内置迭代求解器外部求解器的包装器 稀疏求解器概念计算步骤基准测试例程 英文原文(Solving Sparse Linear Systems) 在Eigen中,有多种方法可用于求解稀疏系数矩阵的线性系统。由于此类矩阵的特殊表示&#xff0c…

零基础速成simulink代码生成——结合CANOE的DBC文件CAN报文代码生成 移植到硬件4

零基础速成simulink代码生成——结合CANOE的DBC文件CAN报文代码生成 移植到硬件4 本次我们将讲解如何将代码放到嵌入式硬件上运行,本次例子将dbc文件导入simulink中,生成代码,不需要我们自己实现数据库的内容。 导入DBC文件 新建一个simulink模型 MCU_CAN.slx simulink具…

开源计算机视觉库OpenCV详解

目录 1、概述 2、OpenCV详细介绍 2.1、OpenCV的起源 2.2、OpenCV开发语言 2.3、OpenCV的应用领域 3、OpenCV模块划分 4、OpenCV源码文件结构 4.1、根目录介绍 4.2、常用模块介绍 4.3、CUDA加速模块 5、OpenCV配置以及Visual Studio使用OpenCV 6、OpenCV和OpenGL的区…

SpringBoot常用操作

SpringBoot常用操作 SpringBoot启动过程 1. 生成一个SpringApplication的对象1. webApplicationType 推测web应用类型(NONE、REACTIVE、SERVLET)2. 从spring.factories中获取BootstrapRegistryInitializer对象3. initializers 从spring.factories中获…

A brief taste of JIFA

JIFA 是阿里贡献给 Eclipse 的一个适用于 Java 应用的问题诊断应用。 它以图形化的方式展示 Heap Dump AnalysisGC Log AnalysisThread Dump Analysis 因为云环境/生产环境的一些限制,应用的问题可能不能被就地分析,所以Jifa provides a web solution…

关于运动模糊问题的分析及处理方法

1、问题背景 前段时间有做一个化妆镜项目,就是一面镜子上装有一个摄像头,用户对着镜子化妆时,可同时用来采集人脸信息,分析人脸用的。客户反馈抓拍静止的人脸图像时,画面正常,而当人脸稍微运动时&#xff…

java springboot整合MyBatis实现分页查询以及带条件的分页查询

之前的文章 java springboot整合MyBatis做数据库查询操作操作了springboot整合MyBatis,然后简单做了个按id查询的操作 那么 我们按上文搭建起的环境继续 我们直接在staffDao接口中声明一个分页函数 Select("select * from staff limit #{page},#{pageSize}&q…

【Vue】Vite 组件化开发

文章目录 组件化开发一、组件化开发思想二、Vue 组件的构成2.1 组件组成结构2.2 组件 template 节点2.2.1 在 template 中使用指令2.2.2 在 template 中定义根节点 2.3 组件的 script 模板 三、组件的基本使用3.1 组件的注册3.1.1 全局组件注册3.1.2 局部组件注册 3.2 组件样式…

ffmpeg命令参数

主要参数 -i 设定输入流 -f 设定输出格式(format) -ss 开始时间 -t 时间长度视频参数 -vframes 设置要输出的视频帧数 -b 设定视频码率 -b:v 视频码率 -r 设定帧率 -s 设定画面的宽与高 -vn 不处理视频 -aspect aspect 设置横纵比4:3 或16:9 或1.333或…

软件项目管理 第三章软件项目的启动过程课后习题参考答案——主编:李冰、张桥珍、刘玉娥

第三章 软件项目的启动过程 课后习题参考答案 1.选择题 (1)乙方在项目初始阶段的主要任务不包含以下哪一项(D)。 A. 项目分析 B. 竞标 C. 合同签署 D. 合同管理 (2)项目章程中不…