Spring源码之PostProcessor解析

news2024/9/25 11:15:56

系列文章目录


文章目录

  • 系列文章目录
  • 前言
  • 一、PostProcessor是什么
  • 二、PostProcessor的作用
  • 三、Spring框架中有哪些PostProcessor呢
    • BeanPostProcessor
    • BeanFactoryPostProcessor
    • InstantiationAwareBeanPostProcessor
    • DestructionAwareBeanPostProcessor
    • MergedBeanDefinitionPostProcessor
  • 总结


前言

  在阅读Spring源码的过程中,会发现Spring框架中有许多不同类型的PostProcessor,今天整理一下Spring框架中有哪些PostProcessor,又分别起到了什么作用?


一、PostProcessor是什么

  简单的来说,PostProcessor就是一种特殊的bean,它可以拦截bean的创建过程,以提供一些额外的处理。PostProcessor的主要作用是在Spring容器初始化bean时,允许我们介入bean的创建过程,以实现一些自定义的逻辑。

二、PostProcessor的作用

具体来说,PostProcessor可以在以下几个方面发挥作用:

  1. 修改bean的定义:通过实现BeanFactoryPostProcessor接口,我们可以在Spring容器加载bean定义之后,但在实例化bean之前,对bean定义进行修改。例如,我们可以修改bean的作用域,或者添加新的属性值。
  2. 修改bean的实例:通过实现BeanPostProcessor接口,我们可以在Spring容器实例化bean之后,但在返回bean之前,对bean进行额外的处理。例如,我们可以修改bean的属性,或者返回一个完全不同的bean实例。
  3. 处理特定类型的bean:通过实现BeanPostProcessor的子接口,例如InstantiationAwareBeanPostProcessorDestructionAwareBeanPostProcessor,我们可以在bean实例化之前、之后或销毁之前,对特定类型的bean进行更详细的处理。
  4. 处理占位符:通过使用PropertyPlaceholderConfigurerPropertySourcesPlaceholderConfigurer,我们可以在bean定义中使用占位符,并在实例化bean时,用实际的值替换这些占位符。

总的来说,PostProcessor提供了一种强大的机制,使我们可以在Spring容器管理bean的生命周期的不同阶段,进行自定义的处理。这对于实现一些高级的功能,例如AOP(面向切面编程),事务管理,安全性等非常有用。接下来也会慢慢用实例讲解PostProcessor的作用。

三、Spring框架中有哪些PostProcessor呢

  Spring框架中有许多不同类型的PostProcessor,它们在Spring容器初始化bean时提供了一种机制,允许我们介入bean的创建过程。以下是一些常见的PostProcessor:

BeanPostProcessor

这是最常见的PostProcessor,它允许我们在Spring容器实例化bean之后,但在返回bean之前,对bean进行额外的处理。例如,我们可以修改bean的属性,或者返回一个完全不同的bean实例。先来看看源码:

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

这个接口只有两个默认的方法,通过方法的名字也可以很容易猜到这两个方法分别是在Bean的初始化前初始化后调用。再看看它的实现也是特别的多。
在这里插入图片描述
现在用一个例子看看,我写了一个MyBeanPostProcessor类实现了BeanPostProcessor接口,要注意给这个类添加@Component注解,才会被扫描成为一个Bean。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("初始化之前");
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("初始化之后");
		return bean;
	}
}

给这两个方法打上断点,看看这两个方法分别是什么时候调用的?

来看看postProcessBeforeInitialization初始化之前和postProcessAfterInitialization初始化之后这两个方法:
在这里插入图片描述

在这里插入图片描述
可以看到初始化之前和初始化之后都是是在doCreateBean方法里面,并且是在populateBean方法之后,这个时候Bean已经实例化完成,并完成好了属性填充也就是依赖注入。

在这里插入图片描述
那这两个方法又有什么用呢?

  1. postProcessBeforeInitialization:这个方法在Bean的初始化方法(例如,标记为@PostConstruct的方法,或者实现InitializingBean接口的afterPropertiesSet方法)被调用之前执行。你可以在这个方法中进行一些预处理操作,例如修改Bean的属性,或者对Bean进行一些验证。
  2. postProcessAfterInitialization:这个方法在Bean的初始化方法被调用之后执行。你可以在这个方法中进行一些后处理操作,例如包装Bean实例,或者返回一个完全不同的Bean实例。例如,你可以创建一个BeanPostProcessor,在postProcessAfterInitialization方法中,返回一个代理对象。这个代理对象可以在调用原始Bean的方法前后,执行一些额外的操作。这是实现AOP(面向切面编程)的一种常见方式。

下面写了两个方法的基本使用

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if(bean instanceof UserService){
			((UserService) bean).name = "Echo";
		}
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
//		return Proxy.newProxyInstance(bean.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
//			@Override
//			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//				System.out.println("Before method:"+method.getName());
//				Object result = method.invoke(bean,args);
//				System.out.println("After method:"+method.getName());
//				return result;
//			}
//		});
		if(bean instanceof User){
			Enhancer enhancer = new Enhancer();
			enhancer.setSuperclass(bean.getClass());
			enhancer.setCallback((MethodInterceptor)(obj, method, args, proxy)->{
				return proxy.invokeSuper(obj,args);
			});
			return enhancer.create();
		}
		return bean;
	}
}

这里要注意如果使用JDK动态代理的话,被代理的对象必须要实现了接口。如果想要被代理对象没有实现接口的话,可以改成后面的CGLIB代理。

BeanFactoryPostProcessor

  这个PostProcessor允许我们在Spring容器实例化任何其他bean之前自定义应用程序上下文的bean定义。这是一个非常强大的特性,因为它允许我们在Spring框架的bean实例化阶段之前修改应用程序上下文。

  BeanFactoryPostProcessor的方法在Spring IoC容器的启动过程中被调用。具体来说,它们在所有的bean定义被加载到容器中之后,但在任何bean实例被创建之前被调用。这使得BeanFactoryPostProcessor可以对bean定义进行修改,例如改变bean的属性或是改变bean的依赖。

以下是BeanFactoryPostProcessor的主要方法:

  • postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory):
    这是BeanFactoryPostProcessor接口的唯一方法,它允许我们在Spring容器实例化任何其他bean之前自定义应用程序上下文的bean定义。

在Spring的生命周期中,BeanFactoryPostProcessor的方法运行的顺序如下:

  1. 所有的bean定义加载到容器中
  2. 实例化BeanFactoryPostProcessor
  3. Spring IoC容器调用BeanFactoryPostProcessorpostProcessBeanFactory方法。
  4. 实例化其他所有的bean。

因此,可以看出BeanFactoryPostProcessor的方法在Spring IoC容器的启动过程中非常早就被调用,这使得它可以对bean定义进行大量的自定义操作。

示例

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition bd = beanFactory.getBeanDefinition("userService");
		bd.getPropertyValues().addPropertyValue("name","keaizp");
	}
}

这里要注意,当调用postProcessBeanFactory方法,只是对BeanDefinition进行修改,Bean还没有进行实例化,真正的赋值是在属性填充的最后一步。
在这里插入图片描述
并且使用addPropertyValue方法,必须该属性要有setter方法,不然会报错。它正是利用setter方法赋值的。
看到这里也会想到上面说的BeanPostProcessor也有一个postProcessBeforeInitialization方法同样可以给属性赋值。不过一个是给Bean对象的属性赋值,一个是给BeanDefinition的属性赋值,如果两个方法都给同一个属性赋值会怎么样呢?通过它们在Spring生命周期的运行顺序可以很容易知道,只有属性会是最后哪个给Bean对象的属性赋的值,也就是postProcessBeforeInitialization方法赋的值。

InstantiationAwareBeanPostProcessor

这是BeanPostProcessor的一个子接口,它提供了更多的回调方法,允许我们在bean实例化之前和之后,对bean进行更详细的处理。
先来看看它的源码:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
		return true;
	}

	@Nullable
	default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
			throws BeansException {

		return null;
	}

	@Deprecated
	@Nullable
	default PropertyValues postProcessPropertyValues(
			PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {

		return pvs;
	}

}

可以看到这个接口是继承了上面说到的BeanPostProcessor接口的,并且它是多了三个方法,分别是postProcessBeforeInstantiationpostProcessAfterInstantiationpostProcessPropertyValues。前两个方法可以从名字很容易得知,分别是实例化之前和实例化之后运行。先来看看它们的什么时候调用。
先来看看postProcessBeforeInstantiation方法的调用时间。
在这里插入图片描述
从这个图片可以看到这个是在createBean方法中,并且是在doCreateBean方法前,此时还没有实例化Bean。那这个方法有什么用处呢,能做些什么呢?

  可以在这个方法中改变要实例化的 Bean 类,或者直接返回一个已经准备好的 Bean 实例,这样 Spring 容器就不会再去创建这个 Bean 了。如果这个方法返回非 null 的对象,那么后续的 BeanPostProcessor 将不会调用 postProcessBeforeInstantiation 方法,所以这里返回的对象,没办法在实例化后方法进行处理。并且从上面的源码可以看到,如果实例化前返回了一个非null对象,就不会执行后面的doCreateBean方法了,会直接返回bean。所以这里可以返回一个代理对象来替代原来要实例化的Bean。但是如果这个方法返回非 null 的对象,会直接执行初始化后的方法

在这里插入图片描述
实例化之前和初始化之后这两个方法都能对bean进行代理,而且都会执行,那这两个方法都代理同一个对象会发生什么呢?

答案是会报错,而不是初始化之后的代理对象覆盖实例化前的代理对象,因为相当于初始化后将实例化之前的代理对象再进行代理一遍。

那实例化前和初始化后这两个方法进行代理有什么区别呢?

  1. 在实例化之前创建代理对象:在这种情况下,代理对象是在 Bean 实例创建之前就已经创建好了。这意味着代理对象不能访问到 Bean的任何状态和信息,因为 Bean 实例此时还没有创建。这种方式主要用于改变 Bean 的实例化逻辑,例如创建一个完全不同的 Bean实例,或者创建一个代理对象来替代原始的 Bean 实例。
  2. 在初始化之后创建代理对象:在这种情况下,代理对象是在 Bean 实例已经创建并初始化完成后才创建的。这意味着代理对象可以访问到 Bean 的所有状态和信息,包括属性值,依赖关系等。这种方式主要用于在不改变原始 Bean 实例的基础上添加额外的行为,例如添加日志,事务管理,安全检查等。

再来看看postProcessAfterInstantiation方法是什么时候调用的
在这里插入图片描述

在这里插入图片描述
从这里可以看出postProcessAfterInstantiation方法调用是在populateBean方法中,此时刚进这个方法,还没进行属性填充,所以这个方法是在Bean实例化之后,但是是在属性填充之前。从代码也可以看出,如果实例化之后这个方法返回的是false,会直接return,就不会执行后面的填充方法了。所以postProcessAfterInstantiation方法可以用来检查Bean的状态,如果不满足条件,可以阻止属性填充。

最后再来看postProcessPropertyValues方法的调用
在这里插入图片描述
在这里插入图片描述
可以看到这个方法也是在populateBean方法中,并且这个方法是在实例化之后的。这个方法正是用来给属性赋值的。
这个接口还有一个重要的实现类AutowiredAnnotationBeanPostProcessor正是实现依赖注入的关键类,它实现了postProcessProperties方法。

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
		// 找注入点(所有被@Autowired注解了的Field或Method)
		InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
		try {
			metadata.inject(bean, beanName, pvs);
		}
		catch (BeanCreationException ex) {
			throw ex;
		}
		catch (Throwable ex) {
			throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
		}
		return pvs;
	}

Spring会执行所有InstantiationAwareBeanPostProcessorpostProcessProperties方法,这个时候有一个问题,如果自定义了一个InstantiationAwareBeanPostProcessor实现类,并使用postProcessProperties方法给pvs赋值,同时依赖注入也会给属性赋值,那该Bean的属性最后得到的是哪一个值呢?
在这里插入图片描述
这是populateBean方法的最后的代码,可以看到最后pvs的值还是会覆盖依赖注入的值。

DestructionAwareBeanPostProcessor

这是BeanPostProcessor的一个子接口,它提供了一个回调方法,允许我们在bean销毁之前,进行一些清理工作。
同样先看看源码:

public interface DestructionAwareBeanPostProcessor extends BeanPostProcessor {

	void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;

	default boolean requiresDestruction(Object bean) {
		return true;
	}

}

可以看到这个接口同样也是继承了BeanPostProcessor接口的,并且多了两个方法postProcessBeforeDestructionrequiresDestruction,并且requiresDestruction是有自己的默认实现的。

  1. postProcessBeforeDestruction(Object bean, String beanName): 这个方法在Spring容器销毁Bean之前被调用。你可以在这个方法中执行任何必要的清理工作,比如关闭网络连接,释放资源等。这个方法接收两个参数,第一个参数是要被销毁的Bean,第二个参数是该Bean的名称。
  2. requiresDestruction(Object bean): 这个方法用于判断一个Bean是否需要执行销毁回调。如果这个方法返回true,那么postProcessBeforeDestruction方法将会被调用。这个方法接收一个参数,即要被判断的Bean。

接下来先来看看requiresDestruction的调用堆栈
在这里插入图片描述

在这里插入图片描述
可以看到这个方法是在doCreateBean方法的最后调用,结合上面的实例化前方法如果返回非null的对象,后面的doCreateBean方法不会再执行,可以知道:如果在实例化前方法里面代理一个Bean,将不会调用这个方法,无法判断这个Bean是否需要执行销毁回调,那这个Bean在销毁的时候也不会调用postProcessBeforeDestruction方法。

再来看看postProcessBeforeDestruction的调用堆栈
在这里插入图片描述

在这里插入图片描述
这个方法的调用时机是在Spring容器关闭阶段,当容器关闭时,首先会检查是否存在DestructionAwareBeanPostProcessor,如果存在,就会调用其postProcessBeforeDestruction方法。然后,如果bean实现了DisposableBean接口,就会调用其destroy方法。

MergedBeanDefinitionPostProcessor

这是BeanFactoryPostProcessor的一个子接口,它提供了一个回调方法,允许我们在bean定义合并之后,对合并的bean定义进行处理。
先来看看这个接口的源码:

public interface MergedBeanDefinitionPostProcessor extends BeanPostProcessor {

	void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName);

	default void resetBeanDefinition(String beanName) {
	}

}

可以看到这个接口同样是实现了BeanPostProcessor接口,有两个自己的方法postProcessMergedBeanDefinitionresetBeanDefinition,并且resetBeanDefinition是有着一个空实现的。

再来看看postProcessMergedBeanDefinition方法的调用堆栈
在这里插入图片描述

在这里插入图片描述
可以看到这个方法是在doCreateBean方法中调用的,这个时候已经创建了Bean实例了,但是还没有进行属性填充,是在populateBean方法之前的。这个方法可以用于修改合并后的 bean 定义,例如可以修改 bean 的属性值或者其他元数据。

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		if(beanType == UserService.class){
			beanDefinition.getPropertyValues().add("name","zengpei");
		}
	}

这个接口还有一个重要的实现AutowiredAnnotationBeanPostProcessor,这个实现在上面说过,也就是说InstantiationAwareBeanPostProcessor的时候,它实现了InstantiationAwareBeanPostProcessorMergedBeanDefinitionPostProcessor
两个接口。先看看它的postProcessMergedBeanDefinition方法。

public class AutowiredAnnotationBeanPostProcessor implements SmartInstantiationAwareBeanPostProcessor,
		MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {

	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
		metadata.checkConfigMembers(beanDefinition);
	}

}

从这里可以看到这里寻找@Autowire注入点的过程。这个方法会在属性填充之前执行,也就是还没属性填充之前,就会先找到注入点并缓存起来。


总结

最后写一个实例,展示一下前面的顺序,这里写了三个PostProcessor,都是用来给userService的name赋值,最后得到的结果是什么呢?

BeanFactoryPostProcessor

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		BeanDefinition bd = beanFactory.getBeanDefinition("userService");
		bd.getPropertyValues().addPropertyValue("name","keaizp");
	}
}

BeanPostProcessor

@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		System.out.println("初始化之前");
		if(bean instanceof UserService){
			((UserService) bean).name = "Echo";
		}
		return bean;
	}
}

InstantiationAwareBeanPostProcessor

@Component
public class MyInstantiationPostProcessor implements InstantiationAwareBeanPostProcessor {

	@Override
	public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) throws BeansException {
		System.out.println("postProcessProperties");

		if(bean instanceof UserService){
			MutablePropertyValues propertyValues;
			if(pvs instanceof MutablePropertyValues){
				propertyValues = (MutablePropertyValues) pvs;
			}else {
				propertyValues = new MutablePropertyValues(pvs);
			}
			if(propertyValues.contains("name")){
				String name = (String)pvs.getPropertyValue("name").getValue();
				propertyValues.addPropertyValue("name",name.toUpperCase());
			}
			return propertyValues;

		}
		return null;
	}
}

MergedBeanDefinitionPostProcessor

@Component
public class MyMergedBeanDefinitionPostProcessor implements MergedBeanDefinitionPostProcessor {
	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		System.out.println("postProcessMergedBeanDefinition");
		if(beanType == UserService.class){
			beanDefinition.getPropertyValues().add("name","zengpei");
		}

	}
}

如果前面搞懂了,就会知道了应该是EchopostProcessBeforeInitialization是初始化之前的方法,应该是最后执行的,执行的顺序依次是:postProcessBeanFactorypostProcessMergedBeanDefinitionpostProcessPropertiespostProcessBeforeInitialization
所以name变化的顺序应该是:"keaizp" -> "zengpei" -> "ZENGPEI" -> "Echo",当然前面三个改变的都是pvs,只有最后的实例化之前,改变的是Bean对象的值。

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

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

相关文章

Pinia 和 Vuex ,理解这两个 Vue 状态管理模式

Pinia和Vuex一样都是是vue的全局状态管理器。其实Pinia就是Vuex5&#xff0c;只不过为了尊重原作者的贡献就沿用了这个看起来很甜的名字Pinia。 本文通过Vue3的形式对两者的不同实现方式进行对比&#xff0c;让你在以后工作中无论使用到Pinia还是Vuex的时候都能够游刃有余。 …

Linux下在日志中打印时间戳

1、背景介绍&#xff1a;在实验过程中需要记录任务运行情况&#xff0c;为此需要在日志中增加时间戳打印信息&#xff0c;方便事后查看。 2、实现方法 示例如下&#xff1a; #include <stdio.h> #include <time.h> #include<string.h>void print_debug_me…

如何在iPhone上用ChatGPT替换Siri

To use ChatGPT with Siri on an iPhone or iPad, get an OpenAI API key and download the ChatGPT Siri shortcut. Enter your API key in the shortcut setup and select the GPT model you want to use, then hit “Add Shortcut.” Trigger the shortcut manually first t…

FreeRTOS实时操作系统(二)系统文件代码学习

文章目录 前言系统配置任务创建任务创建删除实践 前言 接着学习正点原子的FreeRTOS教程&#xff0c;涉及到一些详细的系统内文件代码 系统配置 可以通过各种的宏定义来实现我们自己的RTOS配置&#xff08;在FreeRTOSconfig.h&#xff09; “INCLUDE”&#xff1a;配置API函数…

【Java】catch里面抛出了异常finally里面的事务会提交吗?

文章目录 背景目前的代码直接实战演示单元测试总结 背景 我们公司的系统中有一个业务场景&#xff0c;需要第三方的账户数据同步到我们系统。 同步账号的同时&#xff0c;会将所有同步数据和是否成功记录到一张同步日志表中&#xff0c;方便排查问题和记录。 好了&#xff0c;…

window11系统CUDA、cuDNN 安装以及环境变量配置

文章目录 一&#xff0c;说明二&#xff0c;cuda的下载以及安装1. 确定自己电脑设备哪个版本cudaa. 点击左下角b. 点击左下角c.接着点击 组件 2. cuda的下载3. cuda的安装1. 双击 点击 ok2. 同意即可3. 这个随意哪个都行4.选择安装位置 接着下一步 三&#xff0c;cuda环境变量设…

Oracle安装时先决条件检查失败和[INS-35180] 无法检查可用内存问题解决

Oracle安装时先决条件检查失败和[INS-35180] 无法检查可用内存问题解决 问题&#xff1a; [INS-13001] 此操作系统不支持 Oracle 数据库问题原因解决方案 问题2&#xff1a;[INS-35180] 无法检查可用内存问题原因解决方案 问题&#xff1a; [INS-13001] 此操作系统不支持 Oracl…

Python面向对象编程-构建游戏和GUI 手把手项目教学(1.1)

总项目目标&#xff1a;设计一个简单的纸牌游戏程序&#xff0c;称为"Higher or Lower"&#xff08;高还是低&#xff09;。游戏中&#xff0c;玩家需要猜测接下来的一张牌是比当前牌高还是低。根据猜测的准确性&#xff0c;玩家可以得到或失去相应的积分。 项目1.1…

循环码的编码、译码与循环冗余校验

本专栏包含信息论与编码的核心知识&#xff0c;按知识点组织&#xff0c;可作为教学或学习的参考。markdown版本已归档至【Github仓库&#xff1a;https://github.com/timerring/information-theory 】或者公众号【AIShareLab】回复 信息论 获取。 文章目录 循环码的编码循环码…

实现 strStr

在一个串中查找是否出现过另一个串&#xff0c;这是KMP的看家本领。 28. 实现 strStr() 力扣题目链接 实现 strStr() 函数。 给定一个 haystack 字符串和一个 needle 字符串&#xff0c;在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在&…

七、docker-compose方式运行Jenkins,更新Jenkins版本,添加npm node环境

docker-compose方式运行Jenkins&#xff0c;更新Jenkins版本&#xff0c;添加npm node环境 一、docker-compose方式安装运行Jenkins 中发现Jenkins版本有点老&#xff0c;没有node环境&#xff0c;本节来说下更新jenkins 及添加构建前端的node环境。 1. 准备好docker-compose…

算法刷题-双指针-二分法

27. 移除元素 力扣题目链接 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素&#xff0c;并返回移除后数组的新长度。 不要使用额外的数组空间&#xff0c;你必须仅使用 O(1) 额外空间并原地修改输入数组。 元素的顺序可以改变。你不需…

XSS数据接收平台——蓝莲花(BlueLotus)

文章目录 一、前言二、安装三、使用1、我的JS创建一个模板2、使用创建的模板攻击3、打开攻击的目标&#xff08;这里选择pikachu靶场的存储型XSS模块测试&#xff09;4、查看返回的数据 一、前言 蓝莲花平台是清华大学曾经的蓝莲花战队搭建的平台&#xff0c;该平台用于接收xs…

【QQ界面展示-通知的发布和监听 Objective-C语言】

一、来,看看,我们先给大家介绍一下通知 1.那么,这个通知,我们就是要给大家介绍三个东西 1)一个是通知的发布:如何发布通知 2)一个是通知的监听:发布以后,如何监听通知 3)一个是通知的移除:注意,通知一定要怎么样,最后,移除, 2.当你监听了一个通知以后,当你…

【Proteus仿真】51单片机+8255A IO扩展例程

【Proteus仿真】51单片机+8255A IO扩展例程 📍相关参考:51单片机8255A扩展IO口🎬Proteus仿真演示: 📓8255A与51单片机连接 🌿51单片机的P0口作为数据总线使用,与8255A的D7~D0数据信号线进行连接,当P00 - P07不作为8255A 的A、B、C端口地址使用时,可以不接上拉电阻…

3.部署glance服务(镜像获取组件)

身份认证服务部署完毕之后&#xff0c;部署 glance 映像服务&#xff0c;映像服务可以帮助用户发现、注册、检索虚拟机镜像&#xff0c;就是说 启动实例的镜像是放在这里的 。 默认镜像存储目录为&#xff1a; /var/lib/glance/images/ controller节点 在安装和配置 glance …

lua的元表与元方法理解

元表 在 Lua 中&#xff0c;元表&#xff08;metatable&#xff09;是一种特殊的表&#xff0c;用于定义另一个表的行为。每个表都有一个关联的元表&#xff0c;通过元表可以重载表的各种操作&#xff0c;例如索引、新索引、相加等。在 Lua 中&#xff0c;元表的使用非常灵活&…

【Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读】

Soft-prompt Tuning for Large Language Models to Evaluate Bias 论文略读 INFORMATIONAbstract1 Introduction2 Related work3 Methodology3.1 Experimental setup 4 Results5 Discussion & Conclusion总结A Fairness metricsB Hyperparmeter DetailsC DatasetsD Prompt …

Intellij IDEA设置“选中变量或方法”的背景颜色、字体颜色(Mark Occurrences)

背景 IDEA 中选中一个变量就会将所有的变量相关变量标出来&#xff0c;这样就很方便知道这个变量出现的地方。Eclipse里头把这个功能叫做 Mark Occurrences&#xff0c;IDEA 里不知道怎么称呼。 我们要解决的痛点就是提示不明显&#xff0c;如下图所示&#xff0c;Macbook这么…

RocketMQ一条消息从生产者到消费者的流程

目录 1. rocketmq 中的角色介绍 2. 一条消息从生产者到消费者的所有流程&#xff08;简版&#xff09; 3. 一条消息从生产者到消费者的所有流程 1. rocketmq 中的角色介绍 生产者 producer 生产、创造消息&#xff0c;会把消息发送到 broker 中消息代理服务 broker 负责消息…