spring的bean创建流程源码解析

文章目录

    • IOC 和 DI
    • BeanFactory
    • ApplicationContext
      • 实现的接口
        • 1、BeanFactory接口
        • 2、MessageSource 国际化接口
        • 3、ResourcePatternResolver,资源解析接口
        • 4、EnvironmentCapable接口,用于获取环境变量,配置信息
        • 5、ApplicationEventPublisher 事件发布接口
      • Application 的继承体系
    • Bean 的创建执行流程
    • Spring 给开发者留下的扩展点
    • 结合代码描述
    • Bean 的创建流程总结:
    • 面试中说的三级缓存是什么?
      • 为什么是三级缓存而不是二级缓存?

本文基于 Spring 5.0.2.RELEASE

spring 是 Java Web 开发中,非常常用的框架。它的基础作用是为我们生成对象。这里涉及到 2 个概念。IOC 和 DI。

IOC 和 DI

IOC:Inversion of Control,控制反转。强调的是原来在程序中创建Bean的权利反转给第三方,这里的第三方,其实就是 spring 框架。

DI:依赖注入。比如 UserService类,依赖 UserDao,但是 UserDao 是由 BeanFactory 注入给 UserService 的。

BeanFactory

1、BeanFactory是Spring的早期接口,称为Spring的Bean工厂.它提供了一系列的方法,用于获取 Bean
在这里插入图片描述
BeanFactory 的默认实现类是 DefaultListableBeanFactory,DefaultListableBeanFactory主要是管理 BeanDefinition 对象,并根据BeanDefinition 对象创建 Bean 对象。
在这里插入图片描述
在DefaultListableBeanFactory中,实现了BeanDefinitionRegistry接口,这个接口定义了关于 BeanDefinition 的注册、移除、查询等一系列的操作。

DefaultListableBeanFactory还继承了DefaultSingletonBeanRegistry,这个类中,实现了三级缓存,创建单例 Bean 的操作。????BeanDefinition对象,怎么传给DefaultSingletonBeanRegistry对象的????

DefaultListableBeanFactory总结起来,就是可以增删改查BeanDefinition定义对象,并通过父类方法,生成 Bean 对象。但是它并没有把 xml 文件,或者注解,解析成BeanDefinition对象的功能。这个功能是在ApplicationContext实现的。

ApplicationContext

实现的接口

1、BeanFactory接口

ApplicationContext 继承了很多接口,每增加一个接口,都增加了对应的功能。
在这里插入图片描述
但是,ApplicationContext 中,并不是继承了 DefaultListableBeanFactory,而是实现了 BeanFactory接口,并引入了 DefaultListableBeanFactory 作为属性,由 DefaultListableBeanFactory 实现 BeanFactory 相关的功能。(组合优于继承)

在这里插入图片描述

注意看,AnnotationConfigApplicationContext中包含 beanFactory 属性,在它的父类 AbstractApplicationContext中,实现了 getBean 等 方法,都是调用 beanFactory 来实现的。

@Override
public Object getBean(String name) throws BeansException {
  assertBeanFactoryActive();
  return getBeanFactory().getBean(name);
}

ApplicationContext 相比 BeanFactory,还多继承了四个接口,功能如下:

2、MessageSource 国际化接口
public static void main(String[] args) {
  ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
  System.out.println(context.getMessage("hi", null, Locale.CHINA));
  System.out.println(context.getMessage("hi", null, Locale.ENGLISH));
}

在 resources 目录下创建三个配置文件,注意设置 idea 的 properties 编码格式是 utf-8
messages.properties 空内容
messages_en.properties 填写内容 hi=hello
messages_zh.properties 填写内容 hi=你好

运行代码,控制台打印如下,说明国际化生效

你好
hello
3、ResourcePatternResolver,资源解析接口

可以用于读取配置文件

public static void main(String[] args) throws IOException {
  ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
  //读取类类路径下文件
  Resource[] resources1 = context.getResources("classpath:application.properties");
  //读取jar 包中的类路径文件,注意带个 * 号
  Resource[] resources2 = context.getResources("classpath*:META-INF/spring.factories");
  //读取文件目录下的文件
  Resource[] resources3 = context.getResources("file:/tmp/test.log");
}
4、EnvironmentCapable接口,用于获取环境变量,配置信息
  public static void main(String[] args) throws IOException {
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
    //读取application.properties 中的内容
    System.out.println(context.getEnvironment().getProperty("server.port"));
    //读取系统环境变量
    System.out.println(context.getEnvironment().getProperty("JAVA_HOME"));
  }
5、ApplicationEventPublisher 事件发布接口
// 自定义事件
public class UserLoginEvent extends ApplicationEvent {
  public UserLoginEvent(Object source) {
    super(source);
  }
}
//发布事件
  public static void main(String[] args) throws IOException {
    ConfigurableApplicationContext context = SpringApplication.run(Application.class, args);
    context.publishEvent(new UserLoginEvent("username"));
  }
//接收事件
@Component
public class UserMessageService {

  @EventListener
  public void sendMessage(UserLoginEvent event){
    Object source = event.getSource();
    System.out.println("欢迎用户:"+source);
  }
}

当调用publishEvent发布事件时,会调用到 sendMessage 方法。(观察者模式)

总结:
1、BeanFactory 类,用于生成 Bean。
2、ApplicationContext,继承了 BeanFactory 接口,引入了 DefaultListableBeanFactory对象,体现了组合优于继承的原则。
3、ApplicationContext,实现了其他接口,增加了国际化,事件发布,资源解析,读取配置等功能。

Application 的继承体系

在这里插入图片描述

常用的 ApplicationContext 有 3 个,分别是
AnnotationConfigApplicationContext,用于加载注解配置类的ApplicationContext
FileSystemXmlApplicationContext,用于加载磁盘路径下的xml配置的ApplicationContext
ClassPathXmlApplicationContext,用于加载类路径下的xml配置的ApplicationContext
如果 spring 中加入了 Web 相关的组件,那么使用的 ApplicationContext 是
XmlWebApplicationContext,用于web环境下,加载类路径下的xml配置的ApplicationContext
AnnotationConfigWebApplicationContext,用于web环境下,加载磁盘路径下的xml配置的ApplicationContext

Bean 的创建执行流程

以解析 xml 文件为例,使用ClassPathXmlApplicationContext,流程如下:

1、加载xml配置文件,解析获取配置中的每个的信息,封装成一个个的BeanDefinition对象;
2、将BeanDefinition存储在一个名为beanDefinitionMap的Map<String,BeanDefinition>中;
3、ApplicationContext底层遍历beanDefinitionMap,创建Bean实例对象;
4、创建好的Bean实例对象,被存储到一个名为singletonObjects的Map<String,Object>中;
5、当执行applicationContext.getBean(beanName)时,从singletonObjects去匹配Bean实例返回。

在这里插入图片描述

Spring 给开发者留下的扩展点

主要有两个地方,
1 是对 beanDefinitionMap 进行添加,修改,只要向 beanDefinitionMap 中添加了 BeanDefinition,就会被 Spring 生成 Bean。
2 是当 Bean 创建完成后,可以对 Bean 对象进行修改,比如 AOP,注解,都是生成代理 Bean,替换原有的 Bean。

流程如下:

结合代码描述

代码入口:

ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:application-single.xml");

UserService userService = context.getBean("userServiceBeanId",UserService.class);

System.out.println(userService.getName());

进入ClassPathXmlApplicationContext

	public ClassPathXmlApplicationContext(
			String[] configLocations, boolean refresh, @Nullable ApplicationContext parent)
			throws BeansException {

		//1、返回一个classloader
		//2、返回一个解析器
		super(parent);  // ===> 1
		// 1、获取环境(系统环境、jvm环境)
		// 2、设置Placeholder占位符解析器,${xxx}.xml解析为完整文件名
		// 2、将xml的路径解析完存储到数组
		setConfigLocations(configLocations); //  ===>
		//默认为true
		if (refresh) {
			//核心方法,入口
			refresh();  // ===>  magic happens here!
		}
	}

进入 Refresh 方法

	@Override
	public void refresh() throws BeansException, IllegalStateException {
		// synchronized块锁(monitorenter --monitorexit)
		// 不然 refresh() 还没结束,又来个启动或销毁容器的操作
		//	 startupShutdownMonitor就是个空对象,锁
		synchronized (this.startupShutdownMonitor) {
			//1、【准备刷新】,设置了几个变量,也是准备工作
			prepareRefresh();   //  ===>
			// 2、【获得新的bean工厂】关键步骤,重点!
			//2.1、关闭旧的 BeanFactory
			//2.2、创建新的 BeanFactory(DefaluListbaleBeanFactory)
			//2.3、解析xml/加载 Bean 定义、注册 Bean定义到beanFactory(不初始化)
			//2.4、返回全新的工厂
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			//3、【bean工厂前置操作 】为BeanFactory配置容器特性
			// 例如类加载器、表达式解析器、注册默认环境bean、后置管理器
			prepareBeanFactory(beanFactory);// ===>
			try {
				// 4、【bean工厂后置操作】此处为空方法,如果子类需要,自己去实现
				postProcessBeanFactory(beanFactory);  // ===> 空的!

				//5、【调用bean工厂后置处理器】,开始调用我们自己实现的接口
				//调用顺序一:先执行BeanDefinitionRegistryPostProcessor接口的方法,它也继承了BeanFactoryPostProcessor
				//调用顺序二:后执行BeanFactoryPostProcessor接口的方法,用于修改 beanDefinition
				invokeBeanFactoryPostProcessors(beanFactory);  // ===>  重头戏

				//6、【注册bean后置处理器】只是注册,但是还不会调用
				//逻辑:找出所有实现BeanPostProcessor接口的类,分类、排序、注册
				registerBeanPostProcessors(beanFactory);  // ===>  关键点
				// Initialize message source for this context.
				//7、【初始化消息源】国际化问题i18n,参照https://nacos.io/
				initMessageSource(); // ===> 就是往factory加了个single bean
				// Initialize event multicaster for this context.
				//8、【初始化事件广播器】初始化自定义的事件监听多路广播器
				// 如果需要发布事件,就调它的multicastEvent方法
				// 把事件广播给listeners,其实就是起一个线程来处理,把Event扔给listener处理
				// (可以通过 SimpleApplicationEventMulticaster的代码来验证)
				initApplicationEventMulticaster(); // ===> 同样,加了个bean
				// 9、【刷新】这是个protected空方法,交给具体的子类来实现
				//  可以在这里初始化一些特殊的 Bean
				// (在初始化 singleton beans 之前)
				onRefresh();  // ===> 空的!一般没人管它
				//10、【注册监听器】,监听器需要实现 ApplicationListener 接口
				// 也就是扫描这些实现了接口的类,给他放进广播器的列表中
				// 其实就是个观察者模式,广播器接到事件的调用时,去循环listeners列表,
				// 挨个调它们的onApplicationEvent方法,把event扔给它们。
				registerListeners();  // ===> 观察者模式
				//11、 【结束bean工厂初始化操作】
				//1、初始化所有的 singleton beans,反射生成对象/填充
				//2、 调用Bean的前置处理器和后置处理器
				// 关键点:getBean方法里完成
				finishBeanFactoryInitialization(beanFactory);  // ===>  关键点!
				// 12、结束refresh操作
				// 发布事件与清除上下文环境
				finishRefresh();
			} catch (BeansException ex) {

			} finally {
			}
		}
	}

Bean 的创建流程总结:

在这里插入图片描述

面试中说的三级缓存是什么?

三级缓存是为了解决循环依赖,也是为了存储单例对象

Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题

在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:

public class DefaultSingletonBeanRegistry ... { 
  //1、最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
  Map<String, Object> singletonObjects = new ConcurrentHashMap(256); 
  //2、早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存" 
  Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16); 
  //3、单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存" 
  Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
}

三级缓存怎么解决循环依赖问题?核心就是先把半成品的 Bean 放入一个缓存中

假设 Bean1 和 Bean2 循环依赖,那么创建过程如下:
1、Bean1 实例化对象,但尚未初始化,将 Bean1 存储到三级缓存;
2、Bean1 属性注入,需要 Bean2,从缓存中获取,没有 Bean2;
3、Bean2 实例化对象,但尚未初始化,将 Bean2 存储到到三级缓存;
4、Bean2 属性注入,需要 Bean1,从三级缓存获取 Bean1,Bean1 从三级缓存移入二级缓存;
5、Bean2 执行其他生命周期过程,最终成为一个完成Bean,存储到一级缓存,删除二三级缓存;
6、Bean1 注入 Bean2;
7、Bean1 执行其他生命周期过程,最终成为一个完整 Bean,存储到一级缓存,删除二三级缓存。

示例如下:

public static void main(String[] args) {
    GenericApplicationContext context = new GenericApplicationContext();
    context.registerBean(ConfigurationClassPostProcessor.class);
    context.registerBean(Config.class);
    context.refresh();
    context.close();
}

@Configuration
static class Config {
    @Bean // 解析 @Aspect、产生代理
    public AnnotationAwareAspectJAutoProxyCreator annotationAwareAspectJAutoProxyCreator() {
        return new AnnotationAwareAspectJAutoProxyCreator();
    }

    @Bean // 解析 @Autowired
    public AutowiredAnnotationBeanPostProcessor autowiredAnnotationBeanPostProcessor() {
        return new AutowiredAnnotationBeanPostProcessor();
    }

    @Bean // 解析 @PostConstruct
    public CommonAnnotationBeanPostProcessor commonAnnotationBeanPostProcessor() {
        return new CommonAnnotationBeanPostProcessor();
    }

    @Bean
    public Advisor advisor(MethodInterceptor advice) {
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution(* foo())");
        return new DefaultPointcutAdvisor(pointcut, advice);
    }

    @Bean
    public MethodInterceptor advice() {
        return (MethodInvocation invocation) -> {
            System.out.println("before...");
            return invocation.proceed();
        };
    }

    @Bean
    public Bean1 bean1() {
        return new Bean1();
    }

    @Bean
    public Bean2 bean2() {
        return new Bean2();
    }
}

static class Bean1 {
    public Bean1() {
        System.out.println("Bean1()");
    }
    @Autowired public void setBean2(Bean2 bean2) {
        System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
    }
    @PostConstruct public void init() {
        System.out.println("Bean1 init()");
    }
}

static class Bean2 {
    public Bean2() {
        System.out.println("Bean2()");
    }
    @Autowired public void setBean1(Bean1 bean1) {
        System.out.println("Bean2 setBean1(bean1) class is: " + bean1.getClass());
    }
    @PostConstruct public void init() {
        System.out.println("Bean2 init()");
    }
}

执行 main 方法后,打印如下:

Bean1()
Bean2()
Bean2 setBean1(bean1) class is: class com.spring.test.A17_1$Bean1
Bean2 init()
Bean1 setBean2(bean2) class is: class com.spring.test.A17_1$Bean2
Bean1 init()

为什么是三级缓存而不是二级缓存?

如果只是为了解决循环依赖问题,那么二级缓存就够了。为什么要设计三级缓存?为了解决循环依赖时,某个 Bean 需要被 AOP 增强,生成代理对象。

在Bean 的创建流程总结中,注意看图,Bean 的初始化方法执行完成后,才执行 BeanPostProcess的后处理方法,如果这个 Bean 被 AOP 增强了,会生成代理对象。

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean方法

// 方法一:实例化! Bean的构造函数在这里被调用!
instanceWrapper = createBeanInstance(beanName, mbd, args); 
//方法二:注入相关的属性!【关键点】
populateBean(beanName, mbd, instanceWrapper);
//方法三:初始化方法,这里会调用 BeanPostProcess
exposedObject = initializeBean(beanName, exposedObject, mbd);

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#initializeBean(java.lang.String, java.lang.Object, org.springframework.beans.factory.support.RootBeanDefinition) 方法

//调用Bean的前置处理器!我们自己实现的bean的processer在这里!
wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);

// 1、调用初始化方法, 处理 bean 中定义的 init-method,
//2、如果 bean 实现了 InitializingBean 接口,调用 afterPropertiesSet() 方法
invokeInitMethods(beanName, wrappedBean, mbd); 

// 调用Bean的后置管理器!用于生成代理aop也在这里偷偷做了手脚……注意aop调试时,看这句后wrappedBean的变化!
wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); 

通过上述源码,可以看到,代理对象 生成,是在初始化方法执行后,才生成代理对象。

这个功能,在循环依赖场景下,就会存在问题。

刚才的 Bean1 和 Bean2 循环依赖时,打印如下:

1、Bean1()
2、Bean2()
3、Bean2 setBean1(bean1) class is: class com.spring.test.A17_1$Bean1
4、Bean2 init()
5、Bean1 setBean2(bean2) class is: class com.spring.test.A17_1$Bean2
6、Bean1 init()

如果是在初始化后,才对 Bean1 进行增强生成代理对象,那么是在第 6 步完成后,才生成代理对象。这样Bean2 setBean1(bean1) 注入的就是原始对象,不是代理对象。不符合预期。

所以 Spring 打了一个补丁,提供一个三级缓存,三级缓存中存储的不是未完成的对象,而是 Bean 的创建工厂ObjectFactory。当需要Bean1 对象时,执行的是 ObjectFactory 的 getObject 方法,这个方法中,也会进行是否 AOP 的判断,如果是,就生成代理对象。

源码如下:

org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#doCreateBean

// () -> getEarlyBeanReference(beanName, mbd, bean) 是 ObjectFactory 匿名实现类 的简写
addSingletonFactory(beanName, () -> getEarlyBeanReference(beanName, mbd, bean));

getEarlyBeanReference 方法

	protected Object getEarlyBeanReference(String beanName, RootBeanDefinition mbd, Object bean) {
		Object exposedObject = bean;
		//是否存在实现InstantiationAwareBeanPostProcessors接口的类
		if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
			//循环所有Bean后置处理器
			for (BeanPostProcessor bp : getBeanPostProcessors()) {
				if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
					SmartInstantiationAwareBeanPostProcessor ibp = (SmartInstantiationAwareBeanPostProcessor) bp;
					//重点:开始创建AOP代理,
					exposedObject = ibp.getEarlyBeanReference(exposedObject, beanName);
				}
			}
		}
		//总结
		//1、如果不调用后置,返回的bean和三级缓存一样
		//2、如果调用AOP后置,返回的就是代理对象
		//3、这就是三级缓存设计的巧妙之处!!!!Map<String, ObjectFactory<?>>
		// 结论:虽然二级缓存能解决循环依赖,但是使用不了aop了,也就是扩展点没有了
		return exposedObject;
	}

示例如下:修改 Bean1 代码增加一个 foo 方法,让 Bean1 被 AOP 增强

    static class Bean1 {
        public void foo() {

        }
        public Bean1() {
            System.out.println("Bean1()");
        }
        @Autowired public void setBean2(Bean2 bean2) {
            System.out.println("Bean1 setBean2(bean2) class is: " + bean2.getClass());
        }
        @PostConstruct public void init() {
            System.out.println("Bean1 init()");
        }
    }

再次执行,观察打印效果:

Bean1()
Bean2()
Bean2 setBean1(bean1) class is: class com.spring.test.A17_1$Bean1$$EnhancerBySpringCGLIB$$28831f11
Bean2 init()
Bean1 setBean2(bean2) class is: class com.spring.test.A17_1$Bean2
Bean1 init()

注意看,Bean1 对象,很早就变成了代理对象,说明是三级缓存生效了。注意:虽然很早就变成代理对象了,但是初始化方法还没执行。

总结下:

AOP 代理对象生成的时机?

1、如果存在循环依赖,是在三级缓存中生成代理对象

2、如果不存在循环依赖,是在 Bean 初始化方法执行后生成。

为什么需要三级缓存?

为了打补丁,让循环依赖的 AOP 的 Bean,能正常注入到其他 Bean 中。

1,2,3 三级缓存的作用是什么?

一级缓存存储已经完全创建好的单例对象

二级缓存存储未完成的,还被其他 Bean 引用的对象

三级缓存,用于存储未完成的对象工厂,且没被其他对象引用过。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java文件流练习

1 扫描指定目录&#xff0c;并找到名称中包含指定字符的所有普通文件&#xff08;不包含目录&#xff09;&#xff0c;并且后续询问用户是否要删除该文件 import java.io.File; import java.util.Scanner;public class Main {public static void main(String[] args) {Scanne…

Windows 10 安装配置WSL2(Ubuntu 20.04)教程

Windows 10 安装配置WSL2&#xff08;Ubuntu 20.04&#xff09;教程 一、WSL简介 WSL&#xff08;Windows Subsystem for Linux&#xff09;是一个兼容层&#xff0c;允许在Windows 10上原生运行Linux二进制可执行文件。 二、安装WSL2 3.1 传统手动安装 更新系统&#xff…

技术速递|Java on Azure Tooling 3月更新 - Java on Azure 开发工具未来六个月路线图发布

作者&#xff1a;Jialuo Gan - Program Manager, Developer Division At Microsoft 排版&#xff1a;Alan Wang 大家好&#xff0c;欢迎阅读 Java on Azure 工具的三月更新。在本次更新中&#xff0c;我们将分享未来几个月对 Java on Azure 开发工具的投资。此外&#xff0c;我…

无限多交换机串联,可以将网线无限延长吗?

网线使用时为了网络质量一般不超过100m&#xff0c;那我每隔100m接一个交换机是不是就可以无限延长了&#xff1f; 完全没有问题。 但是慎用无限、永远、永恒这些字眼&#xff0c;“爱你到永远”这句山盟海誓&#xff0c;看看现在的离婚率就知道有多么不靠谱。 但是&#xff…

MySQL数据库精讲001——概述

MySQL数据库精讲001——概述 文章目录 MySQL数据库精讲001——概述1.1 安装1.1.1 版本1.1.2 安装一、下载二、解压三、配置1. 添加环境变量2. 初始化MySQL3. 注册MySQL服务4. 启动MySQL服务5. 修改默认账户密码 四、登录MySQL五、卸载MySQL 1.1.3 连接1.1.4 企业使用方式(了解)…

共享单车(二):项目日志

stdin, stdout, stderr Linux系统下&#xff0c;当一个用户进程被创建时&#xff0c;与之对应的三个数据流&#xff08;stdin&#xff0c;stdout和stderr&#xff0c;即三个文件&#xff09;也会被创建。 stdin&#xff0c;标准输入文件&#xff0c;通常对应着终端的键盘。 s…

SpringBoot内容协商机制(就是接受数据的类型如json,xml)

目录 一、基于请求头的内容协商机制 二、基于请求参数的内容协商机制 一、基于请求头的内容协商机制 如果我们的Java服务为浏览器和安卓手机同时提供服务&#xff0c;浏览器期望接受的请求是JSON格式&#xff0c;安卓客户端期望接收的请求是XML格式&#xff0c;这个时候是否需…

Linux Shell字符串截取#与%使用

背景Jenkins需要解析gerrit的commit message中特殊字段的值&#xff0c;比如Depend-On&#xff1a;字段的值 比如commit msg内容如下&#xff1a;用变量msg表示 1. # 号截取, 截取指定字符保留右边的字符串&#xff0c;删除左边的部分。分为#和##两种 1.1 # 号截取&#xff0c…

Linux文件系统/企业文件系统选型/企业常规服务应用建议/软件及软件安装包管理,rpm,yum系列知识--12272字详谈

这里先补充一下上一节的命令&#xff1a; tune2fs 调整或查看ext2/ext3/ext4文件系统的参数&#xff08;关闭ext4日志功能&#xff09; 现在已经被淘汰但是企业笔试或者认证考试会存在 dumpe2fs 用于导出ext2&#xff0c;ext4&#xff0c;ext3文件系统信息&#xff08;文件系统…

自己写的爬虫小案例

网址&#xff1a;aHR0cDovL2pzc2NqZ3B0Lmp4d3JkLmdvdi5jbi8/dXJsPS92aWV3L3dvcmtpbmdVbml0L3dvcmtpbmdVbml0Lmh0bWw 这串代码能够爬取勘察单位企业的详细信息。 import requests import time import csv f open(勘察单位公司信息.csv,w,encodingutf-8,newline) csv_writer …

时序分析基础(6)——input delay时序分析

1 简介 FPGA对于外部的时钟以及数据的延时信息是不知道的&#xff0c;在低速时钟且时钟发射沿在数据正中心的时候&#xff0c;一般可以不做约束来直接使用。但是到了高速时钟或者双沿采样或者发射沿和数据对齐的情况下&#xff0c;这时候就需要告诉VIVADO外部的时钟与数据情况来…

[Meachines][Medium]IClean

Main $ nmap -p- -sC -sV 10.10.11.12 -Pn --min-rate 1000 $ echo "10.10.11.12 capiclean.htb">>/etc/hosts 这题可能和python的SSTI有关 $ gobuster dir --url "http://capiclean.htb" --wordlist /usr/share/seclists/Discovery/Web-Content/c…

授权协议OAuth 2.0之通过OIDC实现SSO

写在前面 本文来一起看下OIDC&#xff08;openid connect&#xff09;相关内容。 1&#xff1a;什么是OIDC OIDC的全称是openid connect&#xff0c;和OAuth2.0一样&#xff0c;也是属于协议和规范的范畴。OAuth2.0是一种授权协议&#xff0c;即规定了what you can do的内容…

2024 证券从业资格证考试备考资料分享

2024 证券从业资格证考试备考资料分享 2024 年 06月1、2日 证券从业资格考试全国统一考试&#xff08;统考&#xff09;&#xff0c;预计将于5月初&#xff08;考前一个月&#xff09;左右开启报名 有没有小伙伴在准备备考的&#xff0c;不知道大家都准备怎么学习呢&#xff…

前端css中keyframes(关键帧)的简单使用

前端css中keyframes的使用 一、前言二、例子&#xff08;一&#xff09;、例子源码1&#xff08;二&#xff09;、源码1运行效果1.视频效果2.截图效果 三、结语四、定位日期 一、前言 关键帧keyframes是css动画的一种&#xff0c;主要用于定义动画过程中某一阶段的样式变化&am…

【小白误闯】这可能是对 Tomcat 工作原理解释最详细的文章

脑子一闪而过&#xff0c;当年 V 哥在面试 Java 开发时&#xff0c;被问到让你写一个 Tomcat 服务器&#xff0c;你有什么想法&#xff1f;尼码&#xff0c;面试官摆明是在压工资了&#xff0c;你得逞了&#xff0c;我回答不上来&#xff0c;当时也没研究过 Tomcat 的源码&…

Codeforces Round 940 E. Carousel of Combinations 【威尔逊定理】

题意 给定一个正整数 n n n&#xff0c;定义 C ( i , j ) C(i, j) C(i,j) 为&#xff1a;从 ( 1 , 2 , 3 , . . . , i ) (1,2,3,...,i) (1,2,3,...,i) 中选出 j j j 个不同的数&#xff0c;构成一个圆排列的不同的方案数 求出&#xff1a; ∑ i 1 n ∑ j 1 i ( C ( i ,…

STM32的GPIO控制寄存器开发

寄存器GPIO控制 寄存器地址 寄存器地址计算 某个寄存器地址&#xff0c;由三个参数决定&#xff1a;1、总线基地址&#xff08;BUS_BASE_ADDR&#xff09;&#xff1b;2&#xff0c;外设基于总线基地址的偏移量&#xff08;PERIPH_OFFSET&#xff09;&#xff1b;3&#xff…

Linux系统CPU持续飙高,如何排查

若一台服务器CPU使用率持续处于一个高峰值&#xff0c;可能导致如&#xff1a;无法ssh链接、操作卡顿、用户访问超时等问题 1.查看CPU使用情况 top命令常用于分析内存指标使用情况 htop命令更直观于top 当CPU达到70%-80%以上时&#xff0c;使用率已过高需要处理 2.找出CPU占…

C++ Qt QMainWindow实现无边框窗口自定义标题栏可拖拽移动拉伸改变窗口大小

本篇博客介绍C Qt QMainWindow实现无边框窗口&#xff0c;适用于win10/win11系统。 QMainWindow相对于QWidget多了dockedwidget功能&#xff0c;跟多人可能更喜欢用QMainWindow做主窗口&#xff0c;如果不需要dockedwidget功能&#xff0c;QMainWindow与QWidget做主窗口基本无…