一起学SF框架系列5.12-spring-beans-数据绑定dataBinding

news2024/12/23 19:05:01

数据绑定有助于将用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象),主要用于web层,但实际可用于任何层。Spring提供了DataBinder来做到这一点,并提供了Validator进行数据验证,两者组成了完整的数据输入数据处理机制。但数据绑定主要应用于 Spring 内部(如在SpringMVC中,http传入/返回参数如何绑定到具体参数或类上,就是通过数据绑定进行的),开发者直接使用的场景并不多。如果想要深入理解 Spring 内部的运行机制,数据绑定是必须了解的一块内容。

体验数据绑定

先以如下例子体验数据绑定:

@Component
public class DemoDatabinder{
	public void demo() {
		// 数据Address 
		Address addr=new Address();
		addr.setProvince("四川");
		addr.setCity("成都");
		addr.setCounty("高新区");
		addr.setDesc("学习大道168");
		// 待绑定的数据driver
		Driver driver=new Driver();

		/* 准备绑定的数据(实际应用中看做前端输入的数据) */
		Map<String, Object> input = new HashMap<>();
        input.put("surname", "wang");
        input.put("name", "wang");
        // age是整形  注1
        input.put("age", 20);
        // address是对象
        input.put("address", addr);

		/* 绑定数据 */
        //把准备绑定的数据转换成绑定PropertyValues 
        PropertyValues propertyValues = new MutablePropertyValues(input);
		// 用DataBinder 进行绑定
		DataBinder dataBinder = new DataBinder(driver);
        dataBinder.bind(propertyValues);	
        BindingResult br = dataBinder.getBindingResult();	
		if(br.hasErrors())
			System.out.println("数据绑定错误");
	}
}

注1:age是整形,输入值可以是整形20,也可以是字符串“20”或“20 ”(包含空格)
从上例中可看出,数据绑定就是把输入数据(input)绑定到对象(driver)的对应属性上,绑定有以下要求:
1、输入数据名同对象属性名相同。
2、输入数据值的数据类型同对象属性的数据类型可以不一致,绑定会自动转换。

Spring数据绑定

功能

Spring中,DataBinder有两大功能:
1、设置对象属性值。
2、对数据进行校验(数据校验见:https://blog.csdn.net/davidwkx/article/details/131401699)。

作用

Spring作为一个成熟框架,数据绑定是重要特性之一,数据绑定主要作用体现如下:
1、在WEB环境下,Spring可自动把Http中的请求参数或者请求体中的Json字符串绑定到对应实体对象上,这样可大大提高开发人员的效率,这是开发人员直观感知到的数据绑定。非WEB环境,开发人员也可按需使用数据绑定技术,只不过很少使用而已。
2、Spring框架本身也用到数据绑定:在xml中配置bean属性或注解配置属性,也是用的数据绑定来实现的。

类关系图

在这里插入图片描述

从图中可以看出,除了DataBinder使用的数据PropertyValues,关系图比较简单,只是实现了PropertyEditorRegistry, TypeConverter。
BindingResult继承于Errors,保存绑定结果,包括错误信息。

PropertyEditorRegistry

设置bean属性值的属性编辑器,代码如下:

public interface PropertyEditorRegistry {
	// 为给定类型的所有属性设置属性编辑器
	void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
	void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
	// 获取给定类型低属性编辑器
	@Nullable
	PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}

TypeConverter

转换数据到指定的类型,接口定义简单(仅参数不同):

public interface TypeConverter {
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException;
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException;
	@Nullable
	<T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field)	throws TypeMismatchException;
	@Nullable
	default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
		throw new UnsupportedOperationException("TypeDescriptor resolution not supported");
	}
}

源码跟踪

DataBinder.bind(PropertyValues pvs)

	// 过渡类
	public void bind(PropertyValues pvs) {
		MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues mutablePropertyValues ?
				mutablePropertyValues : new MutablePropertyValues(pvs));
		doBind(mpvs);
	}
	// 绑定处理
	protected void doBind(MutablePropertyValues mpvs) {
		// 根据允许绑定的字段检查给定的属性值,删除不允许的字段的值
		checkAllowedFields(mpvs);
		// 根据所需绑定字段检查给定的属性值,对于必须有值的字段如果没有值,生成缺少值的错误信息
		checkRequiredFields(mpvs);
		// 赋值到对象属性
		applyPropertyValues(mpvs);
	}
	// 过渡类
	protected void applyPropertyValues(MutablePropertyValues mpvs) {
		try {
			// Bind request parameters onto target object.
			// 赋值到对象属性 注1
			getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());
		}
		catch (PropertyBatchUpdateException ex) {
			// 绑定过程发生错误,则生成错误放到BindingResult
			for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {
				getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());
			}
		}
	}

注1:此处实际调用BeanWrapperImpl.setPropertyValues,其类关系图如下:
在这里插入图片描述
BeanWrapperImpl是BeanWrapper的默认实现,BeanWrapper是Spring服务于JavaBeans的底层接口,主要提供分析和操作JavaBeans,包括:获取和设置属性值(单独或批量)、获取属性描述符以及查询属性可读性/可写性的能力等。BeanWrapperImpl关系图比较复杂,这里不具体分析,只关注数据绑定用到的实现在AbstractPropertyAccessor.setPropertyValues

AbstractPropertyAccessor.setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)

设置对象属性。

	@Override
	public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid)
			throws BeansException {

		List<PropertyAccessException> propertyAccessExceptions = null;
		// 把数据转换成MutablePropertyValues 
		List<PropertyValue> propertyValues = (pvs instanceof MutablePropertyValues mpvs ?
				mpvs.getPropertyValueList() : Arrays.asList(pvs.getPropertyValues()));

		if (ignoreUnknown) {
			this.suppressNotWritablePropertyException = true;
		}
		try {
			// 按数据逐一设置
			for (PropertyValue pv : propertyValues) {
				try {
					setPropertyValue(pv);
				}
				catch (NotWritablePropertyException ex) {
					if (!ignoreUnknown) {
						throw ex;
					}
					// Otherwise, just ignore it and continue...
				}
				catch (NullValueInNestedPathException ex) {
					if (!ignoreInvalid) {
						throw ex;
					}
					// Otherwise, just ignore it and continue...
				}
				catch (PropertyAccessException ex) {
					if (propertyAccessExceptions == null) {
						propertyAccessExceptions = new ArrayList<>();
					}
					propertyAccessExceptions.add(ex);
				}
			}
		}
		finally {
			if (ignoreUnknown) {
				this.suppressNotWritablePropertyException = false;
			}
		}

		// 如果有属性可访问性异常,组合成一个异常抛出
		if (propertyAccessExceptions != null) {
			PropertyAccessException[] paeArray = propertyAccessExceptions.toArray(new PropertyAccessException[0]);
			throw new PropertyBatchUpdateException(paeArray);
		}
	}
	// 过渡方法
	@Override
	public void setPropertyValue(PropertyValue pv) throws BeansException {
		// 实现在AbstractNestablePropertyAccessor 
		setPropertyValue(pv.getName(), pv.getValue());
	}

AbstractNestablePropertyAccessor .setPropertyValue(String propertyName, @Nullable Object value)

	// 列出这个类便于后续理解-AbstractNestablePropertyAccessor内部类:用于存储属性token的Holder类
	protected static class PropertyTokenHolder {
		public PropertyTokenHolder(String name) {
			this.actualName = name;
			this.canonicalName = name;
		}
		public String actualName;
		public String canonicalName;
		@Nullable
		public String[] keys;
	}
	// 本类设置属性值入口
	@Override
	public void setPropertyValue(String propertyName, @Nullable Object value) throws BeansException {
		AbstractNestablePropertyAccessor nestedPa;
		try {
			// 获取属性访问器
			nestedPa = getPropertyAccessorForPropertyPath(propertyName);
		}
		catch (NotReadablePropertyException ex) {
			throw new NotWritablePropertyException(getRootClass(), this.nestedPath + propertyName,
					"Nested property in path '" + propertyName + "' does not exist", ex);
		}
		// 将属性名称解析为相应的属性名称标记PropertyTokenHolder 
		PropertyTokenHolder tokens = getPropertyNameTokens(getFinalPath(nestedPa, propertyName));
		// setPropertyValue在AbstractNestablePropertyAccessor 实现
		nestedPa.setPropertyValue(tokens, new PropertyValue(propertyName, value));
	}
	// 分两种情况处理
	protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {
		if (tokens.keys != null) {
			// 集合类属性赋值
			processKeyedProperty(tokens, pv);
		}
		else {
			// 单个(非集合类)属性赋值
			processLocalProperty(tokens, pv);
		}
	}
	// 集合类属性赋值
	@SuppressWarnings({"unchecked", "rawtypes"})
	private void processKeyedProperty(PropertyTokenHolder tokens, PropertyValue pv) {
		Object propValue = getPropertyHoldingValue(tokens);
		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
		if (ph == null) {
			throw new InvalidPropertyException(
					getRootClass(), this.nestedPath + tokens.actualName, "No property handler found");
		}
		Assert.state(tokens.keys != null, "No token keys");
		String lastKey = tokens.keys[tokens.keys.length - 1];

		// 属性是数组类型
		if (propValue.getClass().isArray()) {
			Class<?> requiredType = propValue.getClass().getComponentType();
			int arrayIndex = Integer.parseInt(lastKey);
			Object oldValue = null;
			try {
				if (isExtractOldValueForEditor() && arrayIndex < Array.getLength(propValue)) {
					oldValue = Array.get(propValue, arrayIndex);
				}
				// 转换数据为属性值类型
				Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
						requiredType, ph.nested(tokens.keys.length));
				int length = Array.getLength(propValue);
				if (arrayIndex >= length && arrayIndex < this.autoGrowCollectionLimit) {
					Class<?> componentType = propValue.getClass().getComponentType();
					Object newArray = Array.newInstance(componentType, arrayIndex + 1);
					System.arraycopy(propValue, 0, newArray, 0, length);
					int lastKeyIndex = tokens.canonicalName.lastIndexOf('[');
					String propName = tokens.canonicalName.substring(0, lastKeyIndex);
					setPropertyValue(propName, newArray);
					propValue = getPropertyValue(propName);
				}
				// Array赋值
				Array.set(propValue, arrayIndex, convertedValue);
			}
			catch (IndexOutOfBoundsException ex) {
				throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
						"Invalid array index in property path '" + tokens.canonicalName + "'", ex);
			}
		}
		// 属性是list类型
		else if (propValue instanceof List list) {
			Class<?> requiredType = ph.getCollectionType(tokens.keys.length);
			int index = Integer.parseInt(lastKey);
			Object oldValue = null;
			if (isExtractOldValueForEditor() && index < list.size()) {
				oldValue = list.get(index);
			}
			Object convertedValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
					requiredType, ph.nested(tokens.keys.length));
			int size = list.size();
			if (index >= size && index < this.autoGrowCollectionLimit) {
				for (int i = size; i < index; i++) {
					try {
						list.add(null);
					}
					catch (NullPointerException ex) {
						throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
								"Cannot set element with index " + index + " in List of size " +
								size + ", accessed using property path '" + tokens.canonicalName +
								"': List does not support filling up gaps with null elements");
					}
				}
				// list赋值-增加
				list.add(convertedValue);
			}
			else {
				try {
					// list赋值-增加或修改
					list.set(index, convertedValue);
				}
				catch (IndexOutOfBoundsException ex) {
					throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
							"Invalid list index in property path '" + tokens.canonicalName + "'", ex);
				}
			}
		}
		// 属性是Map 类型
		else if (propValue instanceof Map map) {
			Class<?> mapKeyType = ph.getMapKeyType(tokens.keys.length);
			Class<?> mapValueType = ph.getMapValueType(tokens.keys.length);
			// IMPORTANT: Do not pass full property name in here - property editors
			// must not kick in for map keys but rather only for map values.
			TypeDescriptor typeDescriptor = TypeDescriptor.valueOf(mapKeyType);
			Object convertedMapKey = convertIfNecessary(null, null, lastKey, mapKeyType, typeDescriptor);
			Object oldValue = null;
			if (isExtractOldValueForEditor()) {
				oldValue = map.get(convertedMapKey);
			}
			// Pass full property name and old value in here, since we want full
			// conversion ability for map values.
			Object convertedMapValue = convertIfNecessary(tokens.canonicalName, oldValue, pv.getValue(),
					mapValueType, ph.nested(tokens.keys.length));
			// map赋值
			map.put(convertedMapKey, convertedMapValue);
		}

		else {
			throw new InvalidPropertyException(getRootClass(), this.nestedPath + tokens.canonicalName,
					"Property referenced in indexed property path '" + tokens.canonicalName +
					"' is neither an array nor a List nor a Map; returned value was [" + propValue + "]");
		}
	}
	// 单个(非集合类)属性赋值
	private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {
		PropertyHandler ph = getLocalPropertyHandler(tokens.actualName);
		if (ph == null || !ph.isWritable()) {
			if (pv.isOptional()) {
				if (logger.isDebugEnabled()) {
					logger.debug("Ignoring optional value for property '" + tokens.actualName +
							"' - property not found on bean class [" + getRootClass().getName() + "]");
				}
				return;
			}
			if (this.suppressNotWritablePropertyException) {
				// Optimization for common ignoreUnknown=true scenario since the
				// exception would be caught and swallowed higher up anyway...
				return;
			}
			throw createNotWritablePropertyException(tokens.canonicalName);
		}

		Object oldValue = null;
		try {
			Object originalValue = pv.getValue();
			Object valueToApply = originalValue;
			if (!Boolean.FALSE.equals(pv.conversionNecessary)) {
				/* 数据类型转换 */
				if (pv.isConverted()) {
					// 已转换直接取转换后的值
					valueToApply = pv.getConvertedValue();
				}
				else {
					if (isExtractOldValueForEditor() && ph.isReadable()) {
						try {
							oldValue = ph.getValue();
						}
						catch (Exception ex) {
							if (ex instanceof PrivilegedActionException pae) {
								ex = pae.getException();
							}
							if (logger.isDebugEnabled()) {
								logger.debug("Could not read previous value of property '" +
										this.nestedPath + tokens.canonicalName + "'", ex);
							}
						}
					}
					// 数据类型转换
					valueToApply = convertForProperty(
							tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());
				}
				pv.getOriginalPropertyValue().conversionNecessary = (valueToApply != originalValue);
			}
			// 赋值
			ph.setValue(valueToApply);
		}
		catch (TypeMismatchException ex) {
			throw ex;
		}
		catch (InvocationTargetException ex) {
			PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(
					getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
			if (ex.getTargetException() instanceof ClassCastException) {
				throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), ex.getTargetException());
			}
			else {
				Throwable cause = ex.getTargetException();
				if (cause instanceof UndeclaredThrowableException) {
					// May happen e.g. with Groovy-generated methods
					cause = cause.getCause();
				}
				throw new MethodInvocationException(propertyChangeEvent, cause);
			}
		}
		catch (Exception ex) {
			PropertyChangeEvent pce = new PropertyChangeEvent(
					getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());
			throw new MethodInvocationException(pce, ex);
		}
	}

注:关于数据类型转换详见:https://blog.csdn.net/davidwkx/article/details/131913840

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

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

相关文章

高通滤波器,低通滤波器

1.高通滤波器是根据像素与邻近像素的亮度差值来提升该像素的亮度。 import cv2 import numpy as np from scipy import ndimagekernel_3_3 np.array([[-1,-1,-1],[-1,8,-1],[-1,-1,-1]]) print(kernel_3_3) kernel_5_5 np.array([[-1,-1,-1,-1,-1],[-1,1,2,1,-1],[-1,2,4,2,-…

网络安全进阶学习第八课——信息收集

文章目录 一、什么是信息收集&#xff1f;二、信息收集的原则三、信息收集的分类1.主动信息收集2.被动信息收集 四、资产探测1、Whois查询#常用网站&#xff1a; 2、备案信息查询#常用网站&#xff1a; 3、DNS查询#常用网站&#xff1a; 4、子域名收集#常用网站&#xff1a;#常…

在访问一个网页时弹出的浏览器窗口,如何用selenium 网页自动化解决?

相信大家在使用selenium做网页自动化时&#xff0c;会遇到如下这样的一个场景&#xff1a; 在你使用get访问某一个网址时&#xff0c;会在页面中弹出如上图所示的弹出框。 首先想到是利用Alert类来处理它。 然而&#xff0c;很不幸&#xff0c;Alert类处理的结果就是没有结果…

SNAT和DNAT原理与应用

iptables的备份和还原 1.写在命令行当中的都是临时配置。 2.把我们的规则配置在 备份&#xff08;导出&#xff09;&#xff1a;iptables-save > /opt/iptables.bak 默认配置文件&#xff1a;/etc/sysconfig/iptables 永久配置&#xff1a;cat /opt/iptables.bak > /etc…

最近安全事件频发,分享一些如何在Sui上构建安全应用的建议

在Sui上构建任何应用程序的一个关键部分是防止恶意攻击和威胁行为。对安全性的重视对于减少用户的资产损失&#xff0c;以及降低对开发者和企业的声誉损害至关重要。 Sui安全和密码学社区的成员总结了一些开发者在开发过程中应该注意避免的最佳实践。虽然其中一些经验是单独针…

AD21 PCB设计的高级应用(十)Gerber文件转PCB

&#xff08;十&#xff09;Gerber文件转PCB Altium Designer 导入 Gerber 并转换成 PCB 的操作步骤如下: (1)打开 Altium Designer 软件,执行菜单栏中“文件”→“新的”→“项目”命令,新建一个 PCB Project,并且新建一个 CAM 文档添加到工程中,如图所示。 (2)Gerber 文件有两…

Web性能测试模型全面解读

前言 性能测试用例主要分为预期目标用户测试、用户并发测试、疲劳强度与大数据量测试、网络性能测试、服务器性能测试五大部分。 具体编写用例时要根据实际情况去进行&#xff0c;遵守低成本、策略为中心&#xff0c;裁减、完善模型&#xff0c;具体化等原则。 Web性能测试模…

视频监控汇聚平台EasyCVR告警消息生成后,合成录像显示不了是什么原因?

智能视频监控平台TSINGSEE青犀视频EasyCVR具备视频融合汇聚能力&#xff0c;作为安防视频监控综合管理平台&#xff0c;它支持多协议接入、多格式视频流分发&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&…

苍穹外卖项目解读(三) redis、cache缓存解读

前言 HM新出springboot入门项目《苍穹外卖》&#xff0c;笔者打算写一个系列学习笔记&#xff0c;“苍穹外卖项目解读”&#xff0c;内容主要从HM课程&#xff0c;自己实践&#xff0c;以及踩坑填坑出发&#xff0c;以技术&#xff0c;经验为主&#xff0c;记录学习&#xff0…

2023年智能优化算法---能量谷优化器 Energy valley optimizer(EVO),附MATLAB代码和文献...

简介 能量谷优化器(EVO)是一种新的元启发式算法&#xff0c;它的算法是受到了关于稳定性和不同粒子衰变模式的先进物理原理的启发。在文献中&#xff0c;作者与CEC函数中最先进的算法进行了比较&#xff0c;并且证明该算法确实很强劲。算法原理大家请参考文献。 01 结果展示 在…

算法通关村第三关——不简单的数组增删改查

线性表基础 线性表概念 线性表就是具有相同特征数据元素的一个有限序列&#xff0c;其中包含元素的个数称为线性表的长度 线性表类型 从不同的角度看&#xff0c;线性表有不同的分类 语言实现角度 顺序表有两种实现方式 一体式 分离式 一体式结构 一体式&#xff1a;存储信息…

Softing mobiLink——连接现场设备的优选方案

| 用于智能现场设备管理的移动配置工具 从技术人员的角度来看&#xff0c;过程自动化可能会成为一个主要的管理难题&#xff0c;因为其实际上有无限数量的可配置参数及各种通信协议和现场设备接口。由来自多个制造商生产的上千台现场设备组成的现代过程架构&#xff0c;使技术…

Chapter 10: Dictionaries | Python for Everybody 讲义笔记_En

文章目录 Python for Everybody课程简介DictionariesDictionariesDictionary as a set of countersDictionaries and filesLooping and dictionariesAdvanced text parsingDebuggingGlossary Python for Everybody Exploring Data Using Python 3 Dr. Charles R. Severance 课程…

《向量数据库指南》——腾讯云向量数据库Tencent Cloud VectorDB产品特性,架构和应用场景

腾讯云向量数据库(Tencent Cloud VectorDB)是一款全托管的自研企业级分布式数据库服务,专用于存储、检索、分析多维向量数据。该数据库支持多种索引类型和相似度计算方法,单索引支持 10 亿级向量规模,可支持百万级 QPS 及毫秒级查询延迟。腾讯云向量数据库不仅能为大模型提…

Window版本ES(ElasticSearch)的安装,使用,启动

首先我这里是根据小破站狂神说up主&#xff0c;而学习的&#xff0c;下面是笔记文档&#xff0c;文档可能比我更细&#xff0c;但我还是记录一下&#xff0c;嘿嘿嘿 ElasticSearch7.6入门学习笔记-KuangStudy-文章 下面直接开始&#xff0c;首先我们需要下载ELK三个安装包&…

spring boot合并 http请求(前后端实现)

为什么要合并http请求 页面加载时要初始化很多资源信息&#xff0c;会发送好多http请求&#xff0c;根据http的通信握手特点&#xff0c;多个http请求比较浪费资源。进而如果能将多个http请求合并为一个发送给服务器&#xff0c;由服务器获取对应的资源返回给客户端 奇思妙解 …

echarts实现正负轴柱状图

效果&#xff1a; data变量配置&#xff1a; // 正负柱状图zhengfuZhu: {},data1: [],data2: [],data3: [],data4: [],xAxisData1: [],//横轴刻度seriesLabel: { //柱子上的字体show: false,position: outside,color: #BEC3EB,},seriesEmphasis: { //itemStyle: {shadowBlur: …

【福建事业单位-语言理解】01中心理解

【福建事业单位-语言理解】01中心理解 一、中心理解题&#xff1a;关键词1.1 转折关系词总结 1.2因果关系词总结 1.3必要条件关系对策&#xff08;只有才的变体题型&#xff09;反面论证&#xff08;如果等词汇错误做法不好的结果&#xff09;对策特殊脉络总结 1.4 并列结构总结…

怎么找小红书美食探店博主合作?

在小红书上有许多活跃的美食探店博主&#xff0c;他们通过分享自己的餐厅体验、特色美食以及口味评价&#xff0c;吸引了大量用户的关注。对于想要在小红书上进行美食推广的企业来说&#xff0c;与这些博主合作是一种非常直接的方式&#xff0c;接下来伯乐网络传媒来给大家分享…

【已解决】标签死活不响应单击事件

大家好&#xff0c;我是执念斩长河。今天在公司写代码的时候突然遇到一个问题&#xff0c;这个问题困扰了我不久&#xff0c;就是html中li标签不能响应我的单击事件。最后在仔细分析下&#xff0c;解决了这个问题。 文章目录 1、问题来源2、问题解决方案3、问题解决效果4、总结…