Spring 源码解析 - BeanPostProcessor 扩展接口

一、BeanPostProcessor 扩展接口

BeanPostProcessorSpring中的一个扩展接口,它可以在Spring容器实例化bean之后,在执行 bean的初始化方法前后,允许我们自定义修改新的 bean实例。比如修改 bean 的属性,将 bean 替换为动态代理等。其中 AOP 的底层创建代理实例,就是通过实现 BeanPostProcessor 来实现的。

BeanPostProcessor 包含两个方法:

public interface BeanPostProcessor {
	/**
	 *  实例化及依赖注入完成后、bean 初始化方法触发之前执行
	 */
	Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;

	/**
	 *  bean 初始化方法触发后执行
	 */
	Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;

}

如果在 Bean 实例化、配置和其他初始化过程中有任何错误,由 Spring 抛出 BeansException 异常。由于 BeanPostProcessor 可以干预 Bean 实例化、配置和初始化的每个阶段,因此实现过程需要非常谨慎。

BeanPostProcessor 是怎么干预 Bean 实例化的呢?可以通过下面这个案例感受一下:

这里我们创建一个测试 bean

@Component
public class Test {
    public void test() {
        System.out.println("test...");
    }
}

下面创建一个类实现 BeanPostProcessor 接口,在后通知中使用 CGLIB 创建一个动态代理对象:

@Component
public class TestBeanPostProcessor implements BeanPostProcessor {

    /**
     * 实例化及依赖注入完成后、bean 初始化方法触发之前执行
     */
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
       return bean;
    }

    /**
     * bean 初始化方法触发后执行
     */
    public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
        Class beanClass = bean.getClass();
        if (beanClass == Test.class) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(beanClass);
            enhancer.setCallback((MethodInterceptor) (o, method, objects, methodProxy) -> {
                System.out.println("<目标方法之前开始执行....>");
                Object result = methodProxy.invokeSuper(o, objects);
                System.out.println("<目标方法之后开始执行....>");
                return result;
            });
            return enhancer.create();
        }
        return bean;
    }
}

下面测试下效果:

public class App {

    public static void main(String[] args) {
        ApplicationContext context = new AnnotationConfigApplicationContext("com.example.demo.beanpost");
        Test hello = (Test) context.getBean("test");
        hello.test();
    }
}

在这里插入图片描述

这样是不是就感受到 BeanPostProcessor 扩展接口的强大之处了吧,可以看出它可以干预 Bean 的实例化,因此使用的话需要非常谨慎。

下面从源码角度分析下,在 Spring 中是如何使用 BeanPostProcessor 进行扩展的。

二、BeanPostProcessor 的注册

从上面的实例中可以感觉出来,我们并没有对 BeanPostProcessor 进行特殊处理,仅仅将其加入 Spring 容器中即会生效,那 Spring 是如何读取BeanPostProcessor 呢?

在本专栏的其他 Spring 源码分析的文章中,分析的入口都是基于 AbstractApplicationContext 中的 refresh() 方法,那现在也是从这里入手,主要逻辑在该方法下触发的 registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) 方法:

在这里插入图片描述

主要逻辑则在 PostProcessorRegistrationDelegate 下的 registerBeanPostProcessors 方法,源码如下:

public static void registerBeanPostProcessors(
		ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {

	// 从bean工厂中获取到所有实现了 BeanPostProcessor 接口的 beanName
	String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);

	// Register BeanPostProcessorChecker that logs an info message when
	// a bean is created during BeanPostProcessor instantiation, i.e. when
	// a bean is not eligible for getting processed by all BeanPostProcessors.
	int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
	// 往bean工厂中添加一个BeanPostProcessor -> BeanPostProcessorChecker
	// BeanPostProcessorChecker 是一个在创建bean期间记录信息消息的 BeanPostProcessor
	beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

	// Separate between BeanPostProcessors that implement PriorityOrdered,
	// Ordered, and the rest.
	// 存放实现了PriorityOrdered接口的BeanPostProcessor
	List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
	// 存放实现了MergedBeanDefinitionPostProcessor接口的BeanPostProcessor
	List<BeanPostProcessor> internalPostProcessors = new ArrayList<BeanPostProcessor>();
	// 存放实现了Ordered接口的BeanPostProcessor
	List<String> orderedPostProcessorNames = new ArrayList<String>();
	// 存放普通的BeanPostProcessor
	List<String> nonOrderedPostProcessorNames = new ArrayList<String>();
	for (String ppName : postProcessorNames) {
		// 如果实现了PriorityOrdered接口
		if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
			// 从工厂中获取对应的 Bean实例
			BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
			priorityOrderedPostProcessors.add(pp);

			// 如果同时实现了MergedBeanDefinitionPostProcessor接口
			if (pp instanceof MergedBeanDefinitionPostProcessor) {
				internalPostProcessors.add(pp);
			}
		}
		// 如果实现了Ordered接口
		else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
			orderedPostProcessorNames.add(ppName);
		}
		// 普通的 BeanPostProcessor
		else {
			nonOrderedPostProcessorNames.add(ppName);
		}
	}

	// 排序
	sortPostProcessors(beanFactory, priorityOrderedPostProcessors);
	// 注册实现了PriorityOrdered 接口的 BeanPostProcessor
	registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);

	// Next, register the BeanPostProcessors that implement Ordered.
	List<BeanPostProcessor> orderedPostProcessors = new ArrayList<BeanPostProcessor>();
	for (String ppName : orderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		orderedPostProcessors.add(pp);
		// 如果同时实现了MergedBeanDefinitionPostProcessor接口
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	// 排序
	sortPostProcessors(beanFactory, orderedPostProcessors);
	//  注册实现了Ordered接口的BeanPostProcessor
	registerBeanPostProcessors(beanFactory, orderedPostProcessors);

	// Now, register all regular BeanPostProcessors.
	// 注册其他普通的BeanPostProcessor
	List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<BeanPostProcessor>();
	for (String ppName : nonOrderedPostProcessorNames) {
		BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
		nonOrderedPostProcessors.add(pp);
		// 如果实现了 MergedBeanDefinitionPostProcessor 接口
		if (pp instanceof MergedBeanDefinitionPostProcessor) {
			internalPostProcessors.add(pp);
		}
	}
	// 通过 beanFactory.addBeanPostProcessor(postProcessor) 添加 BeanPostProcessor
	registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);

	// Finally, re-register all internal BeanPostProcessors.
	// 排序
	sortPostProcessors(beanFactory, internalPostProcessors);
	// 通过beanFactory.addBeanPostProcessor(postProcessor)添加BeanPostProcessor
	// 注册所有 BeanPostProcessor
	registerBeanPostProcessors(beanFactory, internalPostProcessors);

	// Re-register post-processor for detecting inner beans as ApplicationListeners,
	// moving it to the end of the processor chain (for picking up proxies etc).
	// 往bean工厂中添加一个BeanPostProcessor -> ApplicationListenerDetector
	// ApplicationListenerDetector主要是检测bean是否实现了ApplicationListener接口
	beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

三、BeanPostProcessor 触发点

BeanPostProcessorbean 的实例化息息相关,因此可以从 bean 实例化的入口 AbstractAutowireCapableBeanFactory 类的 createBean 方法入手:

3.1 resolveBeforeInstantiation

首先是 createBean 方法下触发的 resolveBeforeInstantiation 方法:

在这里插入图片描述

在这会尝试通过 BeanPostProcessors 获取一个对象,这个可能是代理实例也可能是其他实例:

在这里插入图片描述

在这里插入图片描述

这里判断如果有 InstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor ,则尝试使用 postProcessBeforeInstantiation 方法获取一个代理实例。

3.2 getEarlyBeanReference

如果上面没有代理实例的话,在回到 createBean 方法中,在 doCreateBean 方法中,创建实例后,会放入三级缓存中:

在这里插入图片描述

getEarlyBeanReference 方法中,会尝试使用 SmartInstantiationAwareBeanPostProcessor 类型的 BeanPostProcessor 创建一个早期的实例,虽说 SmartInstantiationAwareBeanPostProcessor 没有直接实现 BeanPostProcessor 但其父类 InstantiationAwareBeanPostProcessor 实现了 BeanPostProcessor

在这里插入图片描述

Spring AOP 代理对象就是在此处获取,因此三级缓存存入ObjectFactory<?>而不是具体的 bean 的意义在此。

3.3 initializeBean

上面都是对特殊类型的 BeanPostProcessor 进行触发,在 doCreateBeaninitializeBean 中则触发了全部的BeanPostProcessor

在这里插入图片描述

这里在 invokeInitMethods 方法前后触发了 BeanPostProcessorpostProcessBeforeInitializationpostProcessAfterInitialization

在这里插入图片描述

在这里插入图片描述

四、Spring 中的 BeanPostProcessor 常用子类

从上面的触发点源码中,可以看出有些地方是触发的特定类型的 BeanPostProcessor 子类,而这些子类又有着不同的用途。

4.1 InstantiationAwareBeanPostProcessor

可以在Bean生命周期实例化Bean之前和实例化Bean之后对Bean进行操作,比如在上面源码中 createBean 方法下触发的 resolveBeforeInstantiation 方法。

InstantiationAwareBeanPostProcessor 类型的子类,会在目标Bean实例化之前尝试使用该类型的实例生成一个代理对象,如果方法返回的是一个非空对象, 将会跳过后续 Spring 默认的创建流程:

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

	// 在目标Bean实例化之前执行尝试使用该类型的生成一个代理对象,如果方法返回的是一个非空对象, 将会跳过后续 Spring 默认的创建流程
	@Nullable
	default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	// 在目标Bean实例化之后、属性填充前执行,如果方法返回false,将会跳过后续的属性填充过程,通常情况下返回true
	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;
	}

	// 对属性值进行修改,通过基于原始的PropertyValues创建一个新的MutablePropertyValues实例,添加或删除特定的值
	// 已标记过期,官方推荐使用 postProcessProperties()方法
	@Deprecated
	@Nullable
	default PropertyValues postProcessPropertyValues(
            PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
		return pvs;
	}
}

4.2 SmartInstantiationAwareBeanPostProcessor

SmartInstantiationAwareBeanPostProcessor 也是一个接口继承了上面的 InstantiationAwareBeanPostProcessor 因此拥有 InstantiationAwareBeanPostProcessor 的特征,在此之外又提供了获取早期实例的方法。

Spring 的三级缓存中则会尝试使用 getEarlyBeanReference 获取一个早期实例。

public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {

	//在 Bean 实例化前预测最终返回的 Class 类型,触发时机是在InstantiationAwareBeanPostProcessor的 postProcessBeforeInstantiation()之前
	@Nullable
	default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
		return null;
	}

	//决定使用哪个构造器构造Bean,如果不指定,默认为null,即bean的无参构造方法;
	@Nullable
	default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
			throws BeansException {

		return null;
	}

	//获得提前暴露的bean引用,主要用于 Spring 循环依赖问题的解决
	default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
		return bean;
	}

}

4.3 ApplicationContextAwareProcessor

应用上下文感知Bean后置处理器,在postProcessBeforeInitialization方法中通过invokeAwareInterfaces方法给目标 Bean 注入指定的属性值:

class ApplicationContextAwareProcessor implements BeanPostProcessor {

	private final ConfigurableApplicationContext applicationContext;

	private final StringValueResolver embeddedValueResolver;


	/**
	 * Create a new ApplicationContextAwareProcessor for the given context.
	 */
	public ApplicationContextAwareProcessor(ConfigurableApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
		this.embeddedValueResolver = new EmbeddedValueResolver(applicationContext.getBeanFactory());
	}


	@Override
	@Nullable
	public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		if (!(bean instanceof EnvironmentAware || bean instanceof EmbeddedValueResolverAware ||
				bean instanceof ResourceLoaderAware || bean instanceof ApplicationEventPublisherAware ||
				bean instanceof MessageSourceAware || bean instanceof ApplicationContextAware ||
				bean instanceof ApplicationStartupAware)) {
			return bean;
		}

		AccessControlContext acc = null;

		if (System.getSecurityManager() != null) {
			acc = this.applicationContext.getBeanFactory().getAccessControlContext();
		}

		if (acc != null) {
			AccessController.doPrivileged((PrivilegedAction<Object>) () -> {
				invokeAwareInterfaces(bean);
				return null;
			}, acc);
		}
		else {
			invokeAwareInterfaces(bean);
		}

		return bean;
	}

	private void invokeAwareInterfaces(Object bean) {
		// 给bean设置Environment属性
		if (bean instanceof EnvironmentAware) {
			((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment());
		}
		// 给bean设置EmbeddedValueResolver属性
		if (bean instanceof EmbeddedValueResolverAware) {
			((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver);
		}
		// 给bean设置ResourceLoader属性
		if (bean instanceof ResourceLoaderAware) {
			((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext);
		}
		// 给bean设置ApplicationEventPublisher属性
		if (bean instanceof ApplicationEventPublisherAware) {
			((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext);
		}
		// 给bean设置MessageSource属性
		if (bean instanceof MessageSourceAware) {
			((MessageSourceAware) bean).setMessageSource(this.applicationContext);
		}
		// 给bean设置 ApplicationStartupAware属性
		if (bean instanceof ApplicationStartupAware) {
			((ApplicationStartupAware) bean).setApplicationStartup(this.applicationContext.getApplicationStartup());
		}
		// 给bean设置ApplicationContext属性
		if (bean instanceof ApplicationContextAware) {
			((ApplicationContextAware) bean).setApplicationContext(this.applicationContext);
		}
	}

}

4.4 ApplicationListenerDetector

判断目标 Bean 是否实现 ApplicationListener ,如果有则注册事件:

class ApplicationListenerDetector implements DestructionAwareBeanPostProcessor, MergedBeanDefinitionPostProcessor {

	private static final Log logger = LogFactory.getLog(ApplicationListenerDetector.class);

	private final transient AbstractApplicationContext applicationContext;

	private final transient Map<String, Boolean> singletonNames = new ConcurrentHashMap<>(256);


	public ApplicationListenerDetector(AbstractApplicationContext applicationContext) {
		this.applicationContext = applicationContext;
	}


	@Override
	public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
		if (ApplicationListener.class.isAssignableFrom(beanType)) {
			this.singletonNames.put(beanName, beanDefinition.isSingleton());
		}
	}

	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName) {
		return bean;
	}

	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName) {
		if (bean instanceof ApplicationListener) {
			// potentially not detected as a listener by getBeanNamesForType retrieval
			Boolean flag = this.singletonNames.get(beanName);
			if (Boolean.TRUE.equals(flag)) {
				// 添加事件
				this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
			}
			else if (Boolean.FALSE.equals(flag)) {
				if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
					// inner bean with other scope - can't reliably process events
					logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
							"but is not reachable for event multicasting by its containing ApplicationContext " +
							"because it does not have singleton scope. Only top-level listener beans are allowed " +
							"to be of non-singleton scope.");
				}
				this.singletonNames.remove(beanName);
			}
		}
		return bean;
	}

	@Override
	public void postProcessBeforeDestruction(Object bean, String beanName) {
		if (bean instanceof ApplicationListener) {
			try {
				ApplicationEventMulticaster multicaster = this.applicationContext.getApplicationEventMulticaster();
				multicaster.removeApplicationListener((ApplicationListener<?>) bean);
				multicaster.removeApplicationListenerBean(beanName);
			}
			catch (IllegalStateException ex) {
				// ApplicationEventMulticaster not initialized yet - no need to remove a listener
			}
		}
	}

	@Override
	public boolean requiresDestruction(Object bean) {
		return (bean instanceof ApplicationListener);
	}


	@Override
	public boolean equals(@Nullable Object other) {
		return (this == other || (other instanceof ApplicationListenerDetector &&
				this.applicationContext == ((ApplicationListenerDetector) other).applicationContext));
	}

	@Override
	public int hashCode() {
		return ObjectUtils.nullSafeHashCode(this.applicationContext);
	}

}

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

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

相关文章

《Effective Objective-C 2.0 》 阅读笔记 item6

第6条&#xff1a;理解“属性”这一概念 1. 属性的概念 “属性”&#xff08;property&#xff09;是Objective-C的一项特性&#xff0c;用于封装对象中的数据。 Objective-C对象通常会把所需要的数据保存为各种实例变量&#xff0c;实例变量一般通过“存取方法”&#xff08…

GPT-4 免费体验方法

POE 在Quora上非常受欢迎的手机聊天机器人Poe App已经集成ChatGPT助手&#xff01;除了最初集成的三个聊天机器人Sage、Claude和Dragonfly外&#xff0c;Poe现在还加入了第四位ChatGPT。由于使用了ChatGPT API&#xff0c;因此Poe拥有真正的ChatGPT。 现在更是第一批集成了GP…

JDK1.8去除永久代引入元空间的原因您知道吗

之前写了一篇文章 JVM中的堆和栈到底存储了什么 重点介绍了Java虚拟机运行时数据区中堆、栈以及方法区存储数据的相关知识很受大家欢迎&#xff0c;今天来介绍一下jdk 1.8开始引入的元空间&#xff0c;元空间的引入也是与Java虚拟机运行时存储数据有关。 元空间 JDK8之后就没…

02-Maven高级-分模块开发、依赖传递、聚合、继承(SpringBoot的部分底层原理)、多模块开发(环境切换)、Nexus私服搭建与使用

文章目录学习目标一、分模块开发与设计1. 分模块开发的意义问题导入模块拆分原则2. 分模块开发&#xff08;模块拆分&#xff09;问题导入2.1 创建Maven模块2.2 书写模块代码2.3 通过maven指令安装模块到本地仓库&#xff08;install指令&#xff09;2.4 代码演示二、依赖管理1…

高低温真空磁场探针台T8-EM5的技术参数

高低温真空磁场探针台是具备提供高低温、真空以及磁场环境的高精度实验台&#xff0c;它的诸多设计都是专用的。因此&#xff0c;高低温磁场探针台的配置主要是根据需求进行选配及设计。例如&#xff0c;要求的磁场值&#xff0c;均匀区大小、均匀度大小、样品台的尺寸等&#…

OJ系统刷题 第三篇

11202 - 任意两个数的和 时间限制 : 1 秒 内存限制 : 128 MB 编程序&#xff0c;电脑任意输入两个整数&#xff0c;计算出他们的和。 输入 a b&#xff08;a b为整数&#xff0c;范围是-2,147,483,648~2,147,483,647&#xff09; 输出 ab的和 样例 输入 1 1 输出 2 答案&a…

含分布式电源的配电网可靠性评估研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

外网访问本地Tomcat服务器【cpolar内网穿透】

文章目录前言1.本地Tomcat网页搭建1.1 Tomcat安装1.2 配置环境变量1.3 环境配置1.4 Tomcat运行测试1.5 Cpolar安装和注册2.本地网页发布2.1.Cpolar云端设置2.2 Cpolar本地设置3.公网访问测试4.结语前言 Tomcat作为一个轻量级的服务器&#xff0c;不仅名字很有趣&#xff08;让…

第二届ACC(AcWing Cup)全国联赛 C4943. 方格迷宫

题意 题目大意就是给定一个地图&#xff0c;给定一个起点和终点&#xff0c;要求我们以最小步数到达终点&#xff0c;其中不可以落入陷阱并且每步可以走1−−k步题目大意就是给定一个地图&#xff0c;给定一个起点和终点&#xff0c;要求我们以最小步数到达终点&#xff0c;其中…

基于粒子群优化算法的分布式电源选址与定容【多目标优化】【IEEE33节点】(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

crud删除(1.5小时)

一、servlet删除 页面效果 删除一个重复的韩非&#xff0c;可以看到无论是list显示还是navicate全都删除成功了 编写servlet页面时一定要注意&#xff0c;我们不光要在list页面开辟一个新的单元格以及加上超链接&#xff0c;还要给它传入当前行的id参数&#xff0c;这样delete…

企业如何利用大数据精准获客

打造大数据硬核组织 运营商大数据精准获客&#xff0c;助力企业高效获客 导语 获客难、成本高一直是困扰各个企业的一大难点。在大数据获客弥漫的今天&#xff0c;我们仿佛看见了眼前影影绰绰的都是客户&#xff0c;但当伸手去抓&#xff0c;却发现寥寥无几&#xff0c;什么…

Web-Http基本概念(请求与响应)

目录 1、http请求 &#xff08;1&#xff09;get &#xff08;2&#xff09;host &#xff08;3&#xff09;accept &#xff08;4&#xff09;referer &#xff08;5&#xff09;accept-language &#xff08;6&#xff09;user-agent 2、http响应 &#xff08;1&…

Linux 文件系统是怎么工作的?

同 CPU、内存一样&#xff0c;磁盘和文件系统的管理&#xff0c;也是操作系统最核心的功能。 磁盘为系统提供了最基本的持久化存储。 文件系统则在磁盘的基础上&#xff0c;提供了一个用来管理文件的树状结构。 那么&#xff0c;磁盘和文件系统是怎么工作的呢&#xff1f;又有…

毕业设计源码基于springboot的旧物置换系统的实现

摘 要 随着时代在一步一步在进步&#xff0c;旧物也成人们的烦恼&#xff0c;许多平台网站都在推广自已的产品像天猫、咸鱼、京东。所以开发出一套关于旧物置换网站成为必需。旧物置换网站主要是借助计算机&#xff0c;通过对用户进行管理。为减少管理员的工作&#xff0c;同…

WEB前端作业——banner的切换

实现banner的左右切换按钮 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title><style>div,ul,li,a,span,img{margin:0;padding:0;}#banner { overflow:hidden; width:100%; height:400px; position:rela…

火速上线zkSync Era主网,盘点SpaceFi的Web3布局

最近zkSync Era主网的上线引发了市场对Layer2的和零知识证明技术的关注&#xff0c;而作为Web3跨链应用平台的SpaceFi也在第一时间对zkSync Era进行了支持&#xff0c;并与3月28日上线DEX、Farm、Plant NFT等多个产品&#xff0c;一时间成为zkSync上的热门生态项目。打造一站式…

银行数字化转型导师坚鹏:数字化转型背景下的银行柜员提升之道

数字化转型背景下的银行柜员提升之道 课程背景&#xff1a; 很多银行都在开展银行数字化运营工作&#xff0c;目前存在以下问题急需解决&#xff1a; l 不清楚银行数字化运营包括哪些关键工作&#xff1f; l 不清楚银行数字化运营工作的核心方法论&#xff1f; l 不清楚银行数字…

【新2023Q2模拟题JAVA】华为OD机试 - 不含 101 的数

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:不含 101 的数 题目 橡皮擦…

如何正确选择7/8电连接器

7/8电连接器简称连接器&#xff0c;接线端子&#xff0c;是电子元器件的一个细分领域&#xff0c;主要用于电路与电路之间的连接。在工业生产中&#xff0c;线路连接可以说是无处不在&#xff0c;因而连接器的使用范围当然是十分广泛&#xff0c;应用在各个行业。 选择科迎法7/…