一起学SF框架系列5.12-spring-beans-数据绑定dataBinding
数据绑定有助于将用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象),主要用于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");// 待绑定的数据driverDriver driver=new Driver();/* 准备绑定的数据(实际应用中看做前端输入的数据) */Map<String, Object> input = new HashMap<>();input.put("surname", "wang");input.put("name", "wang");// age是整形 注1input.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);// 获取给定类型低属性编辑器@NullablePropertyEditor 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;@Nullabledefault <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.// 赋值到对象属性 注1getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());}catch (PropertyBatchUpdateException ex) {// 绑定过程发生错误,则生成错误放到BindingResultfor (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)
设置对象属性。
@Overridepublic 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);}}// 过渡方法@Overridepublic 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;@Nullablepublic String[] keys;}// 本类设置属性值入口@Overridepublic 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 methodscause = 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
相关文章:
一起学SF框架系列5.12-spring-beans-数据绑定dataBinding
数据绑定有助于将用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象),主要用于web层,但实际可用于任何层。Spring提供了DataBinder来做到这一点,并提供了Validator进行数据验证,两者组成了…...
火热报名中 | 赛宁独家技术支持第七届“蓝帽杯”网络安全技能大赛
由公安部网络安全保卫局、教育部教育管理信息中心、中国教育协会指导,中国人民公安大学主办,奇安信科技集团股份有限公司协办,南京赛宁信息技术有限公司提供技术支持的2023第七届“蓝帽杯”全国大学生网络安全技能大赛于近日正式开启报名。 …...
无涯教程-jQuery - Ajax Tutorial函数
AJAX是用于创建交互式Web应用程序的Web开发技术。如果您了解JavaScript,HTML,CSS和XML,则只需花费一个小时即可开始使用AJAX。 为什么要学习Ajax? AJAX代表 A 同步 Ja vaScript和 X ML。 AJAX是一项新技术,可借助XML,HTML,CSS和Java Script创建更好,更快,更具交互性的Web应用…...
Android日志
Android中的日志工具类是Log(android.util.Log),这个类中提供了如下5个方法来供我们打印日志。 Log.v()。用于打印那些最为琐碎的、意义最小的日志信息。对应级别verbose,是Android日志里面级别最低的一种。 Log.d()。用于打印一…...
【Golang 接口自动化08】使用标准库httptest完成HTTP请求的Mock测试
目录 前言 http包的HandleFunc函数 http.Request/http.ResponseWriter httptest 定义被测接口 测试代码 测试执行 总结 资料获取方法 前言 Mock是一个做自动化测试永远绕不过去的话题。本文主要介绍使用标准库net/http/httptest完成HTTP请求的Mock的测试方法。 可能有…...
SpringBoot自定义注解 + AOP+分布式Redis 防止重复提交
第一步 引入依赖pom.xml: <dependency><groupId>org.redisson</groupId><artifactId>redisson</artifactId><version>3.16.3</version> <!-- 使用最新版本 --></dependency><dependency><groupId&g…...
3.yum安装分布式LNMP--剧本
文章目录 修改hosts创建剧本文件 修改hosts vim /etc/ansible/hosts[webservers] 192.168.242.67[dbservers] 192.168.242.68[phpservers] 192.168.242.69创建剧本文件 vim lnmp.yaml- name: nginx playhosts: webserversremote_user: rootvars:- http_port: 192.168.242.67:…...
论文笔记:Fine-Grained Urban Flow Prediction
2021 WWW 1 intro 细粒度城市流量预测 两个挑战 细粒度数据中观察到的网格间的转移动态使得预测变得更加复杂 需要在全局范围内捕获网格单元之间的空间依赖性单独学习外部因素(例如天气、POI、路段信息等)对大量网格单元的影响非常具有挑战性——>论…...
系统集成|第八章(笔记)
目录 第八章 进度管理8.1 主要过程8.1.1 规划进度管理8.1.2 定义活动8.1.3 排列活动顺序8.1.4 估算活动资源8.1.5 估算活动持续时间8.1.6 制定进度计划8.1.7 控制进度 8.2 注意与问题 上篇:第七章、范围管理 第八章 进度管理 8.1 主要过程 包括: 规划进…...
【分布式】分布式唯一 ID 的 几种生成方案以及优缺点snowflake优化方案
在互联网的业务系统中,涉及到各种各样的ID,如在支付系统中就会有支付ID、退款ID等。那一般生成ID都有哪些解决方案呢?特别是在复杂的分布式系统业务场景中,我们应该采用哪种适合自己的解决方案是十分重要的。下面我们一一来列举一…...
FFmpeg5.0源码阅读——av_interleaved_write_frame
摘要:本文主要详细描述FFmpeg中封装时写packet到媒体文件的函数av_interleaved_write_frame的实现。 关键字:av_interleaved_write_frame 读者须知:读者需要熟悉ffmpeg的基本使用。 1 基本调用流程 av_interleaved_write_frame的基本…...
力扣 70. 爬楼梯
题目来源:https://leetcode.cn/problems/climbing-stairs/description/ C题解(来源代码随想录): 本质上是一道斐波那契数题。 动规五部曲:定义一个一维数组来记录不同楼层的状态 确定dp数组以及下标的含义。dp[i]&am…...
AVFoundation - 媒体捕捉
文章目录 注意使用 NSCameraUsageDescriptioniOS 的摄像头可能比 Mac 更多功能特性@interface Capture ()<AVCaptureFileOutputRecordingDelegate>@property (strong, nonatomic) AVCaptureSession *captureSession; @property (weak, nonatomic) AVCaptureDeviceInput *…...
【新版系统架构补充】-嵌入式技术
嵌入式微处理体系结构 冯诺依曼结构 传统计算机采用冯诺依曼结构,也称普林斯顿结构,是一种将程序指令存储器和数据存储器合并在一起的存储器结构 冯诺依曼的计算机程序和数据共用一个存储空间,程序指令存储地址和数据存储地址指向同一个存…...
fpga开发--蜂鸣器发出连续不同的音调
描述 使用fpga蜂鸣器连续发出do,re,mi,fa,so,la,xi七个不同的音调,每个音调的持续时间为0.5s。 思路 采用状态机实现音调的转化,当do状态持续了0.5s之后转移到re状态,…...
Redis 主从同步原理
一、什么是主从同步? 主从同步,就是将数据冗余备份,主库(Master)将自己库中的数据,同步给从库(Slave)。 从库可以一个,也可以多个,如图所示: 二…...
opencv-28 自适应阈值处理-cv2.adaptiveThreshold()
什么是自适应阈值处理? 对于色彩均衡的图像,直接使用一个阈值就能完成对图像的阈值化处理。但是,有时图像的色彩是不均衡的,此时如果只使用一个阈值,就无法得到清晰有效的阈值分割结果图像。 有一种改进的阈值处理技术ÿ…...
Java泛型5——泛型通配符
注:以下内容基于Java 8,所有代码都已在Java 8环境下测试通过 目录: Java泛型1——概述Java泛型2——泛型类Java泛型3——泛型接口Java泛型4——泛型方法Java泛型5——泛型通配符Java泛型6——类型擦除 什么是通配符 在Java中,类…...
牛客 AB25 ranko的手表 JAVA 枚举
描述 ranko 的手表坏了,正常应该显示 xx:xx 的形式(4 个数字),比如下午 1 点半应该显示 13:30 ,但现在经常会有一些数字有概率无法显示。 ranko 在 �1t1 时刻看了下时间,过了一段时间在 &am…...
常微分方程建模R包ecode(二)——绘制相速矢量场
本节中我们考虑一个更为复杂的常微分方程模型, d X C d t ν ( X A Y A ) − β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ) ⋅ X C , ( 1 ) d Y C d t β ⋅ X C ⋅ ( Y C Y A ) − ( μ g ρ ) ⋅ Y C , ( 2 ) d X A d t g ⋅ X C − β ⋅ X A ⋅ ( Y C Y A …...
《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)
CSI-2 协议详细解析 (一) 1. CSI-2层定义(CSI-2 Layer Definitions) 分层结构 :CSI-2协议分为6层: 物理层(PHY Layer) : 定义电气特性、时钟机制和传输介质(导线&#…...
可靠性+灵活性:电力载波技术在楼宇自控中的核心价值
可靠性灵活性:电力载波技术在楼宇自控中的核心价值 在智能楼宇的自动化控制中,电力载波技术(PLC)凭借其独特的优势,正成为构建高效、稳定、灵活系统的核心解决方案。它利用现有电力线路传输数据,无需额外布…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...
《用户共鸣指数(E)驱动品牌大模型种草:如何抢占大模型搜索结果情感高地》
在注意力分散、内容高度同质化的时代,情感连接已成为品牌破圈的关键通道。我们在服务大量品牌客户的过程中发现,消费者对内容的“有感”程度,正日益成为影响品牌传播效率与转化率的核心变量。在生成式AI驱动的内容生成与推荐环境中࿰…...
MVC 数据库
MVC 数据库 引言 在软件开发领域,Model-View-Controller(MVC)是一种流行的软件架构模式,它将应用程序分为三个核心组件:模型(Model)、视图(View)和控制器(Controller)。这种模式有助于提高代码的可维护性和可扩展性。本文将深入探讨MVC架构与数据库之间的关系,以…...
学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
GitHub 趋势日报 (2025年06月08日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...
MySQL用户和授权
开放MySQL白名单 可以通过iptables-save命令确认对应客户端ip是否可以访问MySQL服务: test: # iptables-save | grep 3306 -A mp_srv_whitelist -s 172.16.14.102/32 -p tcp -m tcp --dport 3306 -j ACCEPT -A mp_srv_whitelist -s 172.16.4.16/32 -p tcp -m tcp -…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
