springboot系列--web相关知识探索五

一、前言

web相关知识探索四中研究了请求中所带的参数是如何映射到接口参数中的,也即请求参数如何与接口参数绑定。主要有四种、分别是注解方式、Servlet API方式、复杂参数、以及自定义对象参数。web相关知识探索四中主要研究了复杂参数底层绑定原理。本次主要是研究自定义对象参数数据绑定底层原理。

二、原理

一、测试用例 

@Data
public class Person {private String userName;private Integer age;private Date birth;private Pet pet;@Datapublic static class Pet {private String name;private String age;}}@PostMapping("/test")public Person testEntity(Person person){System.out.println(JSONUtil.toJsonStr(person));return person;}

二、postman请求方式

三、原理

一、寻找参数解析器

请求进来以后,直接到匹配合适的参数解析器这一步,由于我们的参数只有一个,也就是Person类型的自定义对象参数。可以找到是这个参数处理器处理自定义JavaBean参数。

ServletModelAttributeMethodProcessor

// org.springframework.web.method.annotation包下的ModelAttributeMethodProcessor类里面的方法
// 判断当前参数,这个解析器支不支持解析public boolean supportsParameter(MethodParameter parameter) {// 判断条件为,是否使用了ModelAttribute这个注解,或者annotationNotRequired这个属性是否为true也就是这个注解不是必须得,且这个参数是不是简单类型return parameter.hasParameterAnnotation(ModelAttribute.class) || this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType());}// 简单类型判断标准public static boolean isSimpleProperty(Class<?> type) {Assert.notNull(type, "'type' must not be null");return isSimpleValueType(type) || type.isArray() && isSimpleValueType(type.getComponentType());}// 数字,字符串,void、枚举等等public static boolean isSimpleValueType(Class<?> type) {return Void.class != type && Void.TYPE != type && (ClassUtils.isPrimitiveOrWrapper(type) || Enum.class.isAssignableFrom(type) || CharSequence.class.isAssignableFrom(type) || Number.class.isAssignableFrom(type) || Date.class.isAssignableFrom(type) || Temporal.class.isAssignableFrom(type) || URI.class == type || URL.class == type || Locale.class == type || Class.class == type);}

二、参数解析

一、请求参数到目标参数的数据类型转换,这部分只是获取转换器,到了准备转换数据类型步骤

// 开始进行参数解析@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");String name = ModelFactory.getNameForParameter(parameter);ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;if (mavContainer.containsAttribute(name)) {attribute = mavContainer.getModel().get(name);} else {// 这里以上都不用管,以上是使用ModelAttribute注解处理的逻辑try {// 创建一个空的参数对象,也就是接口参数里面的自定义javaBean对象,但是里面的属性值是空的。之后就会让这个对象的属性值与请求进来所带的参数进行一一绑定。attribute = this.createAttribute(name, parameter, binderFactory, webRequest);} catch (BindException var10) {if (this.isBindExceptionRequired(parameter)) {throw var10;}if (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();} else {attribute = var10.getTarget();}bindingResult = var10.getBindingResult();}}// 如果没有请求进来的参数进行绑定,就会进入下面进行绑定if (bindingResult == null) {// 创建绑定器,这里会把原生请求对象以及空属性person对象封装进去。同时还有各种类型的转换器,// 例如,所有请求都是通过http超文本协议传过来的,所以所有请求参数都是文本类型也就是String,比如年龄12,请求进来就会把"12"字符串类型转为Integer类型WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {// 这里就进行了参数绑定,也就是给Person绑定上了请求所携带的值。主要逻辑在里面this.bindRequestParameters(binder, webRequest);}this.validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}if (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;}// 这里会调用父类的方法创建一个Perrson对象protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {String value = this.getRequestValueForAttribute(attributeName, request);if (value != null) {Object attribute = this.createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);if (attribute != null) {return attribute;}}// 会进到这里return super.createAttribute(attributeName, parameter, binderFactory, request);}// 具体创建一个person对象逻辑protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {// 通过MethodParameter对象获取接口参数MethodParameter nestedParameter = parameter.nestedIfOptional();// 获取接口参数类型Class<?> clazz = nestedParameter.getNestedParameterType();// 获取参数类型对应的构造函数Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);// 这里就是通过底层反射之类的构造Perrson对象Object attribute = this.constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);if (parameter != nestedParameter) {attribute = Optional.of(attribute);}return attribute;}// 这里就进行了参数绑定,ServletModelAttributeMethodProcessor中的bindRequestParameters方法protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {// 获取请求ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);Assert.state(servletRequest != null, "No ServletRequest");ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;// 这里进行了数据绑定servletBinder.bind(servletRequest);}// 对应上面的bind数据绑定方法,ServletRequestDataBinder类里面的public void bind(ServletRequest request) {// 获取请求中参数的k-v数据对MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);if (multipartRequest != null) {this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);} else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {HttpServletRequest httpServletRequest = (HttpServletRequest)WebUtils.getNativeRequest(request, HttpServletRequest.class);if (httpServletRequest != null) {StandardServletPartUtils.bindParts(httpServletRequest, mpvs, this.isBindEmptyMultipartFiles());}}this.addBindValues(mpvs, request);// 这里是真正进行参数绑定的方法this.doBind(mpvs);}// WebDataBinder类里面的绑定方法protected void doBind(MutablePropertyValues mpvs) {this.checkFieldDefaults(mpvs);this.checkFieldMarkers(mpvs);this.adaptEmptyArrayIndices(mpvs);// 主要是调用父类的绑定方法super.doBind(mpvs);}// 父类DataBinder里面的绑定方法protected void doBind(MutablePropertyValues mpvs) {this.checkAllowedFields(mpvs);this.checkRequiredFields(mpvs);// 主要是这个方法this.applyPropertyValues(mpvs);}// 这个方法也是DataBinder里面的protected void applyPropertyValues(MutablePropertyValues mpvs) {try {// 设置属性值this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());} catch (PropertyBatchUpdateException var7) {PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();int var4 = var3.length;for(int var5 = 0; var5 < var4; ++var5) {PropertyAccessException pae = var3[var5];this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());}}}// 具体设置属性值的逻辑,AbstractPropertyAccessor这个类里面的方法public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {List<PropertyAccessException> propertyAccessExceptions = null;// 获取所有需要设置进去的属性值,也就是请求形成的k-v对List<PropertyValue> propertyValues = pvs instanceof MutablePropertyValues ? ((MutablePropertyValues)pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues());if (ignoreUnknown) {this.suppressNotWritablePropertyException = true;}try {// 循环遍历数据Iterator var6 = propertyValues.iterator();while(var6.hasNext()) {PropertyValue pv = (PropertyValue)var6.next();try {// 设置属性值this.setPropertyValue(pv);} catch (NotWritablePropertyException var14) {if (!ignoreUnknown) {throw var14;}} catch (NullValueInNestedPathException var15) {if (!ignoreInvalid) {throw var15;}} catch (PropertyAccessException var16) {if (propertyAccessExceptions == null) {propertyAccessExceptions = new ArrayList();}propertyAccessExceptions.add(var16);}}} finally {if (ignoreUnknown) {this.suppressNotWritablePropertyException = false;}}if (propertyAccessExceptions != null) {PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[0]);throw new PropertyBatchUpdateException(paeArray);}}// 这段源码就是上面设置属性值的具体代码,AbstractNestablePropertyAccessor类里面的方法public void setPropertyValue(PropertyValue pv) throws BeansException {PropertyTokenHolder tokens = (PropertyTokenHolder)pv.resolvedTokens;if (tokens == null) {String propertyName = pv.getName();AbstractNestablePropertyAccessor nestedPa;try {nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);} catch (NotReadablePropertyException var6) {throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);}tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));if (nestedPa == this) {pv.getOriginalPropertyValue().resolvedTokens = tokens;}// 这里是利用反射进行设置值的核心逻辑nestedPa.setPropertyValue(tokens, pv);} else {this.setPropertyValue(tokens, pv);}}// AbstractNestablePropertyAccessor类里面的方法protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {if (tokens.keys != null) {this.processKeyedProperty(tokens, pv);} else {this.processLocalProperty(tokens, pv);}}// 设置属性值的具体逻辑,AbstractNestablePropertyAccessor类里面的方法private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName);if (ph != null && ph.isWritable()) {Object oldValue = null;PropertyChangeEvent propertyChangeEvent;try {// 拿到请求中具体的值,例如年龄 12等等Object originalValue = pv.getValue();Object valueToApply = originalValue;if (!Boolean.FALSE.equals(pv.conversionNecessary)) {if (pv.isConverted()) {valueToApply = pv.getConvertedValue();} else {if (this.isExtractOldValueForEditor() && ph.isReadable()) {try {oldValue = ph.getValue();} catch (Exception var8) {Exception ex = var8;if (var8 instanceof PrivilegedActionException) {ex = ((PrivilegedActionException)var8).getException();}if (logger.isDebugEnabled()) {logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex);}}}// 这里是最主要的,首先会进行请求参数的数据转换,例如将String类型的年龄转为Integer类型的年龄。。valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());}pv.getOriginalPropertyValue().conversionNecessary = valueToApply != originalValue;}ph.setValue(valueToApply);} catch (TypeMismatchException var9) {throw var9;} catch (InvocationTargetException var10) {propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());if (var10.getTargetException() instanceof ClassCastException) {throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), var10.getTargetException());} else {Throwable cause = var10.getTargetException();if (cause instanceof UndeclaredThrowableException) {cause = cause.getCause();}throw new MethodInvocationException(propertyChangeEvent, cause);}} catch (Exception var11) {propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());throw new MethodInvocationException(propertyChangeEvent, var11);}} else if (pv.isOptional()) {if (logger.isDebugEnabled()) {logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + this.getRootClass().getName() + "]");}} else if (!this.suppressNotWritablePropertyException) {throw this.createNotWritablePropertyException(tokens.canonicalName);}}// 数据转换器,AbstractNestablePropertyAccessor类里面的方法@Nullableprivate Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException {Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");PropertyChangeEvent pce;try {// 这里使用了代理,进入查看数据是如何转化的return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);} catch (IllegalStateException | ConverterNotFoundException var8) {pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);throw new ConversionNotSupportedException(pce, requiredType, var8);} catch (IllegalArgumentException | ConversionException var9) {pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);throw new TypeMismatchException(pce, requiredType, var9);}}// 数据转化具体逻辑,TypeConverterDelegate里面的方法@Nullablepublic <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);ConversionFailedException conversionAttemptEx = null;// 这里是获取124个转换器服务ConversionService conversionService = this.propertyEditorRegistry.getConversionService();if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);// 然后判断那个转换器能够进行转换if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {try {// 这里就开始进行数据转换了return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);} catch (ConversionFailedException var14) {conversionAttemptEx = var14;}}}Object convertedValue = newValue;if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && newValue instanceof String) {TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();if (elementTypeDesc != null) {Class<?> elementType = elementTypeDesc.getType();if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {convertedValue = StringUtils.commaDelimitedListToStringArray((String)newValue);}}}if (editor == null) {editor = this.findDefaultEditor(requiredType);}convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);}boolean standardConversion = false;if (requiredType != null) {if (convertedValue != null) {if (Object.class == requiredType) {return convertedValue;}if (requiredType.isArray()) {if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {convertedValue = StringUtils.commaDelimitedListToStringArray((String)convertedValue);}return this.convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());}if (convertedValue instanceof Collection) {convertedValue = this.convertToTypedCollection((Collection)convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;} else if (convertedValue instanceof Map) {convertedValue = this.convertToTypedMap((Map)convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;}if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {convertedValue = Array.get(convertedValue, 0);standardConversion = true;}if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {return convertedValue.toString();}if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {try {Constructor<T> strCtor = requiredType.getConstructor(String.class);return BeanUtils.instantiateClass(strCtor, new Object[]{convertedValue});} catch (NoSuchMethodException var12) {if (logger.isTraceEnabled()) {logger.trace("No String constructor found on type [" + requiredType.getName() + "]", var12);}} catch (Exception var13) {if (logger.isDebugEnabled()) {logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", var13);}}}String trimmedValue = ((String)convertedValue).trim();if (requiredType.isEnum() && trimmedValue.isEmpty()) {return null;}convertedValue = this.attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);standardConversion = true;} else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {convertedValue = NumberUtils.convertNumberToTargetClass((Number)convertedValue, requiredType);standardConversion = true;}} else if (requiredType == Optional.class) {convertedValue = Optional.empty();}if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {if (conversionAttemptEx != null) {throw conversionAttemptEx;}if (conversionService != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);}}StringBuilder msg = new StringBuilder();msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");if (propertyName != null) {msg.append(" for property '").append(propertyName).append("'");}if (editor != null) {msg.append(": PropertyEditor [").append(editor.getClass().getName()).append("] returned inappropriate value of type '").append(ClassUtils.getDescriptiveType(convertedValue)).append("'");throw new IllegalArgumentException(msg.toString());}msg.append(": no matching editors or conversion strategy found");throw new IllegalStateException(msg.toString());}}if (conversionAttemptEx != null) {if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {throw conversionAttemptEx;}logger.debug("Original ConversionService attempt failed - ignored since PropertyEditor based conversion eventually succeeded", conversionAttemptEx);}return convertedValue;}// 判断能够进行转换public boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {Assert.notNull(targetType, "Target type to convert to cannot be null");if (sourceType == null) {return true;} else {GenericConverter converter = this.getConverter(sourceType, targetType);return converter != null;}}// GenericConversionService类里面的方法,具体判断逻辑@Nullableprotected GenericConverter getConverter(TypeDescriptor sourceType, TypeDescriptor targetType) {ConverterCacheKey key = new ConverterCacheKey(sourceType, targetType);// 先从缓存中获取GenericConverter converter = (GenericConverter)this.converterCache.get(key);if (converter != null) {return converter != NO_MATCH ? converter : null;} else {// 缓存中没有,然后进行判断逻辑converter = this.converters.find(sourceType, targetType);// 如果没有找到就给一个默认的if (converter == null) {converter = this.getDefaultConverter(sourceType, targetType);}// 放入缓存if (converter != null) {this.converterCache.put(key, converter);return converter;} else {this.converterCache.put(key, NO_MATCH);return null;}}}// GenericConversionService类里面的方法,这里就是具体如何找的,其实就是循环遍历,那个能够将sourceType请求带来的参数类型--》转换成targetType目标类型@Nullablepublic GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) {List<Class<?>> sourceCandidates = this.getClassHierarchy(sourceType.getType());List<Class<?>> targetCandidates = this.getClassHierarchy(targetType.getType());Iterator var5 = sourceCandidates.iterator();// 两次循环遍历while(var5.hasNext()) {Class<?> sourceCandidate = (Class)var5.next();Iterator var7 = targetCandidates.iterator();while(var7.hasNext()) {Class<?> targetCandidate = (Class)var7.next();GenericConverter.ConvertiblePair convertiblePair = new GenericConverter.ConvertiblePair(sourceCandidate, targetCandidate);GenericConverter converter = this.getRegisteredConverter(sourceType, targetType, convertiblePair);if (converter != null) {return converter;}}}return null;}// GenericConversionService类里面的方法,@Nullableprivate GenericConverter getRegisteredConverter(TypeDescriptor sourceType, TypeDescriptor targetType, GenericConverter.ConvertiblePair convertiblePair) {// this.converters是一个map,通过convertiblePair为key(“java.lang.String -> java.lang.Integer”)获取转换器ConvertersForPair convertersForPair = (ConvertersForPair)this.converters.get(convertiblePair);if (convertersForPair != null) {// 这里其实就是获取了转换器,然后返回GenericConverter converter = convertersForPair.getConverter(sourceType, targetType);if (converter != null) {return converter;}}Iterator var7 = this.globalConverters.iterator();GenericConverter globalConverter;do {if (!var7.hasNext()) {return null;}globalConverter = (GenericConverter)var7.next();} while(!((ConditionalConverter)globalConverter).matches(sourceType, targetType));return globalConverter;}

这里有124个转换器,会将http超文本协议传输过来的参数进行转换。

 二、准备进行数据转换与绑定数据

// 开始进行参数解析@Nullablepublic final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");String name = ModelFactory.getNameForParameter(parameter);ModelAttribute ann = (ModelAttribute)parameter.getParameterAnnotation(ModelAttribute.class);if (ann != null) {mavContainer.setBinding(name, ann.binding());}Object attribute = null;BindingResult bindingResult = null;if (mavContainer.containsAttribute(name)) {attribute = mavContainer.getModel().get(name);} else {// 这里以上都不用管,以上是使用ModelAttribute注解处理的逻辑try {// 创建一个空的参数对象,也就是接口参数里面的自定义javaBean对象,但是里面的属性值是空的。之后就会让这个对象的属性值与请求进来所带的参数进行一一绑定。attribute = this.createAttribute(name, parameter, binderFactory, webRequest);} catch (BindException var10) {if (this.isBindExceptionRequired(parameter)) {throw var10;}if (parameter.getParameterType() == Optional.class) {attribute = Optional.empty();} else {attribute = var10.getTarget();}bindingResult = var10.getBindingResult();}}// 如果没有请求进来的参数进行绑定,就会进入下面进行绑定if (bindingResult == null) {// 创建绑定器,这里会把原生请求对象以及空属性person对象封装进去。同时还有各种类型的转换器,// 例如,所有请求都是通过http超文本协议传过来的,所以所有请求参数都是文本类型也就是String,比如年龄12,请求进来就会把"12"字符串类型转为Integer类型WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);if (binder.getTarget() != null) {if (!mavContainer.isBindingDisabled(name)) {// 这里就进行了参数绑定,也就是给Person绑定上了请求所携带的值。主要逻辑在里面this.bindRequestParameters(binder, webRequest);}this.validateIfApplicable(binder, parameter);if (binder.getBindingResult().hasErrors() && this.isBindExceptionRequired(binder, parameter)) {throw new BindException(binder.getBindingResult());}}if (!parameter.getParameterType().isInstance(attribute)) {attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);}bindingResult = binder.getBindingResult();}Map<String, Object> bindingResultModel = bindingResult.getModel();mavContainer.removeAttributes(bindingResultModel);mavContainer.addAllAttributes(bindingResultModel);return attribute;}// 这里会调用父类的方法创建一个Perrson对象protected final Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest request) throws Exception {String value = this.getRequestValueForAttribute(attributeName, request);if (value != null) {Object attribute = this.createAttributeFromRequestValue(value, attributeName, parameter, binderFactory, request);if (attribute != null) {return attribute;}}// 会进到这里return super.createAttribute(attributeName, parameter, binderFactory, request);}// 具体创建一个person对象逻辑protected Object createAttribute(String attributeName, MethodParameter parameter, WebDataBinderFactory binderFactory, NativeWebRequest webRequest) throws Exception {// 通过MethodParameter对象获取接口参数MethodParameter nestedParameter = parameter.nestedIfOptional();// 获取接口参数类型Class<?> clazz = nestedParameter.getNestedParameterType();// 获取参数类型对应的构造函数Constructor<?> ctor = BeanUtils.getResolvableConstructor(clazz);// 这里就是通过底层反射之类的构造Perrson对象Object attribute = this.constructAttribute(ctor, attributeName, parameter, binderFactory, webRequest);if (parameter != nestedParameter) {attribute = Optional.of(attribute);}return attribute;}// 这里就进行了参数绑定,ServletModelAttributeMethodProcessor中的bindRequestParameters方法protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {// 获取请求ServletRequest servletRequest = (ServletRequest)request.getNativeRequest(ServletRequest.class);Assert.state(servletRequest != null, "No ServletRequest");ServletRequestDataBinder servletBinder = (ServletRequestDataBinder)binder;// 这里进行了数据绑定servletBinder.bind(servletRequest);}// 对应上面的bind数据绑定方法,ServletRequestDataBinder类里面的public void bind(ServletRequest request) {// 获取请求中参数的k-v数据对MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request);MultipartRequest multipartRequest = (MultipartRequest)WebUtils.getNativeRequest(request, MultipartRequest.class);if (multipartRequest != null) {this.bindMultipart(multipartRequest.getMultiFileMap(), mpvs);} else if (StringUtils.startsWithIgnoreCase(request.getContentType(), "multipart/")) {HttpServletRequest httpServletRequest = (HttpServletRequest)WebUtils.getNativeRequest(request, HttpServletRequest.class);if (httpServletRequest != null) {StandardServletPartUtils.bindParts(httpServletRequest, mpvs, this.isBindEmptyMultipartFiles());}}this.addBindValues(mpvs, request);// 这里是真正进行参数绑定的方法this.doBind(mpvs);}// WebDataBinder类里面的绑定方法protected void doBind(MutablePropertyValues mpvs) {this.checkFieldDefaults(mpvs);this.checkFieldMarkers(mpvs);this.adaptEmptyArrayIndices(mpvs);// 主要是调用父类的绑定方法super.doBind(mpvs);}// 父类DataBinder里面的绑定方法protected void doBind(MutablePropertyValues mpvs) {this.checkAllowedFields(mpvs);this.checkRequiredFields(mpvs);// 主要是这个方法this.applyPropertyValues(mpvs);}// 这个方法也是DataBinder里面的protected void applyPropertyValues(MutablePropertyValues mpvs) {try {// 设置属性值this.getPropertyAccessor().setPropertyValues(mpvs, this.isIgnoreUnknownFields(), this.isIgnoreInvalidFields());} catch (PropertyBatchUpdateException var7) {PropertyAccessException[] var3 = var7.getPropertyAccessExceptions();int var4 = var3.length;for(int var5 = 0; var5 < var4; ++var5) {PropertyAccessException pae = var3[var5];this.getBindingErrorProcessor().processPropertyAccessException(pae, this.getInternalBindingResult());}}}// 具体设置属性值的逻辑,AbstractPropertyAccessor这个类里面的方法public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException {List<PropertyAccessException> propertyAccessExceptions = null;// 获取所有需要设置进去的属性值,也就是请求形成的k-v对List<PropertyValue> propertyValues = pvs instanceof MutablePropertyValues ? ((MutablePropertyValues)pvs).getPropertyValueList() : Arrays.asList(pvs.getPropertyValues());if (ignoreUnknown) {this.suppressNotWritablePropertyException = true;}try {// 循环遍历数据Iterator var6 = propertyValues.iterator();while(var6.hasNext()) {PropertyValue pv = (PropertyValue)var6.next();try {// 设置属性值this.setPropertyValue(pv);} catch (NotWritablePropertyException var14) {if (!ignoreUnknown) {throw var14;}} catch (NullValueInNestedPathException var15) {if (!ignoreInvalid) {throw var15;}} catch (PropertyAccessException var16) {if (propertyAccessExceptions == null) {propertyAccessExceptions = new ArrayList();}propertyAccessExceptions.add(var16);}}} finally {if (ignoreUnknown) {this.suppressNotWritablePropertyException = false;}}if (propertyAccessExceptions != null) {PropertyAccessException[] paeArray = (PropertyAccessException[])propertyAccessExceptions.toArray(new PropertyAccessException[0]);throw new PropertyBatchUpdateException(paeArray);}}// 这段源码就是上面设置属性值的具体代码,AbstractNestablePropertyAccessor类里面的方法public void setPropertyValue(PropertyValue pv) throws BeansException {PropertyTokenHolder tokens = (PropertyTokenHolder)pv.resolvedTokens;if (tokens == null) {String propertyName = pv.getName();AbstractNestablePropertyAccessor nestedPa;try {nestedPa = this.getPropertyAccessorForPropertyPath(propertyName);} catch (NotReadablePropertyException var6) {throw new NotWritablePropertyException(this.getRootClass(), this.nestedPath + propertyName, "Nested property in path '" + propertyName + "' does not exist", var6);}tokens = this.getPropertyNameTokens(this.getFinalPath(nestedPa, propertyName));if (nestedPa == this) {pv.getOriginalPropertyValue().resolvedTokens = tokens;}// 这里是利用反射进行设置值的核心逻辑nestedPa.setPropertyValue(tokens, pv);} else {this.setPropertyValue(tokens, pv);}}// AbstractNestablePropertyAccessor类里面的方法protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException {if (tokens.keys != null) {this.processKeyedProperty(tokens, pv);} else {this.processLocalProperty(tokens, pv);}}// 设置属性值的具体逻辑,AbstractNestablePropertyAccessor类里面的方法private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {PropertyHandler ph = this.getLocalPropertyHandler(tokens.actualName);if (ph != null && ph.isWritable()) {Object oldValue = null;PropertyChangeEvent propertyChangeEvent;try {// 拿到请求中具体的值,例如年龄 12等等Object originalValue = pv.getValue();Object valueToApply = originalValue;if (!Boolean.FALSE.equals(pv.conversionNecessary)) {if (pv.isConverted()) {valueToApply = pv.getConvertedValue();} else {if (this.isExtractOldValueForEditor() && ph.isReadable()) {try {oldValue = ph.getValue();} catch (Exception var8) {Exception ex = var8;if (var8 instanceof PrivilegedActionException) {ex = ((PrivilegedActionException)var8).getException();}if (logger.isDebugEnabled()) {logger.debug("Could not read previous value of property '" + this.nestedPath + tokens.canonicalName + "'", ex);}}}// 这里是最主要的,首先会进行请求参数的数据转换,例如将String类型的年龄转为Integer类型的年龄。。valueToApply = this.convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor());}// 到这里就完成了类型转换,pv.getOriginalPropertyValue().conversionNecessary = valueToApply != originalValue;}// 这里就将转换好的数据设置进value中,其他属性也是这样的步骤,这里是一个循环遍历设置的逻辑,循环代码在上面,这里的value就是12,name就是age,也就是在这里进行了数据绑定ph.setValue(valueToApply);} catch (TypeMismatchException var9) {throw var9;} catch (InvocationTargetException var10) {propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());if (var10.getTargetException() instanceof ClassCastException) {throw new TypeMismatchException(propertyChangeEvent, ph.getPropertyType(), var10.getTargetException());} else {Throwable cause = var10.getTargetException();if (cause instanceof UndeclaredThrowableException) {cause = cause.getCause();}throw new MethodInvocationException(propertyChangeEvent, cause);}} catch (Exception var11) {propertyChangeEvent = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + tokens.canonicalName, oldValue, pv.getValue());throw new MethodInvocationException(propertyChangeEvent, var11);}} else if (pv.isOptional()) {if (logger.isDebugEnabled()) {logger.debug("Ignoring optional value for property '" + tokens.actualName + "' - property not found on bean class [" + this.getRootClass().getName() + "]");}} else if (!this.suppressNotWritablePropertyException) {throw this.createNotWritablePropertyException(tokens.canonicalName);}}// 数据转换器,AbstractNestablePropertyAccessor类里面的方法@Nullableprivate Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException {Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");PropertyChangeEvent pce;try {// 这里使用了代理,进入查看数据是如何转化的return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td);} catch (IllegalStateException | ConverterNotFoundException var8) {pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);throw new ConversionNotSupportedException(pce, requiredType, var8);} catch (IllegalArgumentException | ConversionException var9) {pce = new PropertyChangeEvent(this.getRootInstance(), this.nestedPath + propertyName, oldValue, newValue);throw new TypeMismatchException(pce, requiredType, var9);}}// 数据转化具体逻辑,TypeConverterDelegate里面的方法@Nullablepublic <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);ConversionFailedException conversionAttemptEx = null;// 这里是获取124个转换器服务ConversionService conversionService = this.propertyEditorRegistry.getConversionService();if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);// 然后判断那个转换器能够进行转换if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {try {// 这里就开始进行数据转换了return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);} catch (ConversionFailedException var14) {conversionAttemptEx = var14;}}}Object convertedValue = newValue;if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && newValue instanceof String) {TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();if (elementTypeDesc != null) {Class<?> elementType = elementTypeDesc.getType();if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {convertedValue = StringUtils.commaDelimitedListToStringArray((String)newValue);}}}if (editor == null) {editor = this.findDefaultEditor(requiredType);}convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);}boolean standardConversion = false;if (requiredType != null) {if (convertedValue != null) {if (Object.class == requiredType) {return convertedValue;}if (requiredType.isArray()) {if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {convertedValue = StringUtils.commaDelimitedListToStringArray((String)convertedValue);}return this.convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());}if (convertedValue instanceof Collection) {convertedValue = this.convertToTypedCollection((Collection)convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;} else if (convertedValue instanceof Map) {convertedValue = this.convertToTypedMap((Map)convertedValue, propertyName, requiredType, typeDescriptor);standardConversion = true;}if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {convertedValue = Array.get(convertedValue, 0);standardConversion = true;}if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {return convertedValue.toString();}if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {try {Constructor<T> strCtor = requiredType.getConstructor(String.class);return BeanUtils.instantiateClass(strCtor, new Object[]{convertedValue});} catch (NoSuchMethodException var12) {if (logger.isTraceEnabled()) {logger.trace("No String constructor found on type [" + requiredType.getName() + "]", var12);}} catch (Exception var13) {if (logger.isDebugEnabled()) {logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", var13);}}}String trimmedValue = ((String)convertedValue).trim();if (requiredType.isEnum() && trimmedValue.isEmpty()) {return null;}convertedValue = this.attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);standardConversion = true;} else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {convertedValue = NumberUtils.convertNumberToTargetClass((Number)convertedValue, requiredType);standardConversion = true;}} else if (requiredType == Optional.class) {convertedValue = Optional.empty();}if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {if (conversionAttemptEx != null) {throw conversionAttemptEx;}if (conversionService != null && typeDescriptor != null) {TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);}}StringBuilder msg = new StringBuilder();msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");if (propertyName != null) {msg.append(" for property '").append(propertyName).append("'");}if (editor != null) {msg.append(": PropertyEditor [").append(editor.getClass().getName()).append("] returned inappropriate value of type '").append(ClassUtils.getDescriptiveType(convertedValue)).append("'");throw new IllegalArgumentException(msg.toString());}msg.append(": no matching editors or conversion strategy found");throw new IllegalStateException(msg.toString());}}if (conversionAttemptEx != null) {if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {throw conversionAttemptEx;}logger.debug("Original ConversionService attempt failed - ignored since PropertyEditor based conversion eventually succeeded", conversionAttemptEx);}return convertedValue;}// 开始进行数据转换,GenericConversionService类里面的方法// sourceType:源数据类型// targetType:目标数据类型// source:源数据@Nullablepublic Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) {Assert.notNull(targetType, "Target type to convert to cannot be null");if (sourceType == null) {Assert.isTrue(source == null, "Source must be [null] if source type == [null]");return this.handleResult((TypeDescriptor)null, targetType, this.convertNullSource((TypeDescriptor)null, targetType));} else if (source != null && !sourceType.getObjectType().isInstance(source)) {throw new IllegalArgumentException("Source to convert from must be an instance of [" + sourceType + "]; instead it was a [" + source.getClass().getName() + "]");} else {GenericConverter converter = this.getConverter(sourceType, targetType);if (converter != null) {Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType);return this.handleResult(sourceType, targetType, result);} else {return this.handleConverterNotFound(source, sourceType, targetType);}}}// 调用converter里面的convert方法进行数据转换,ConversionUtils这个类里面的@Nullablepublic static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {try {return converter.convert(source, sourceType, targetType);} catch (ConversionFailedException var5) {throw var5;} catch (Throwable var6) {throw new ConversionFailedException(sourceType, targetType, source, var6);}}// 会调用StringToNumberConverterFactory类下的这个方法进行转换public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) {return new StringToNumber(targetType);}	// 同一个类下的私有方法,StringToNumber对象,将String转为Number数据// 未来我们可以给WebDataBinder里面放自己的Converter;也就是说可以模仿这个类进行自定义转换,然后放入WebDataBinder绑定器中private static final class StringToNumber<T extends Number> implements Converter<String, T> {private final Class<T> targetType;public StringToNumber(Class<T> targetType) {this.targetType = targetType;}@Nullablepublic T convert(String source) {// 这里就是真正的转换return source.isEmpty() ? null : NumberUtils.parseNumber(source, this.targetType);}}// NumberUtils类下的方法,会根据你的目标是什么类型,进行解码public static <T extends Number> T parseNumber(String text, Class<T> targetClass) {Assert.notNull(text, "Text must not be null");Assert.notNull(targetClass, "Target class must not be null");String trimmed = StringUtils.trimAllWhitespace(text);if (Byte.class == targetClass) {return isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed);} else if (Short.class == targetClass) {return isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed);} else if (Integer.class == targetClass) {// 例如年龄是Integer,会进入到这里,将String 类型转为Ingeter类型,将String类型进行解码Integer.decode(trimmed)return isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed);} else if (Long.class == targetClass) {return isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed);} else if (BigInteger.class == targetClass) {return isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed);} else if (Float.class == targetClass) {return Float.valueOf(trimmed);} else if (Double.class == targetClass) {return Double.valueOf(trimmed);} else if (BigDecimal.class != targetClass && Number.class != targetClass) {throw new IllegalArgumentException("Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]");} else {return new BigDecimal(trimmed);}}

三、自定义类型转换器

测试用例中,访问接口进行传参,接口参数中的Pet对象,里面的属性是这样传递的。pet.name=xxx以及pet.age = 12.如果我们缓存pet = 小猫,12;其中小猫是name的值,12是age的值,这样springmvc就没法进行类型绑定,这是由于WebDataBinder 中的Converters没办法进行将请求参数与javabean进行类型绑定。所以需要我们自定义类型转换器。进行数据类型转换,然后绑定。

 

 一、自定义类型转换器

// 类型转换函数表达式,S表示请求进来的参数,T表示转换成的目标参数
@FunctionalInterface
public interface Converter<S, T> {@NullableT convert(S var1);default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {Assert.notNull(after, "After Converter must not be null");return (s) -> {T initialResult = this.convert(s);return initialResult != null ? after.convert(initialResult) : null;};}
}

需要在spring容器中注入自定义的类型转换器。

@Configuration
public class SpringMvcConfig {/**WebMvcConfigurer可以定制化springmvc功能* 这个接口里面有一个方法,添加类型转化器以及格式化器(比如说日期格式化)*     default void addFormatters(FormatterRegistry registry) {*     }*   这里添加请求参数转换Person中Pet属性的转换器,只要是String-》Person.Pet对象的都是走这个转换器* @return*/@Beanpublic WebMvcConfigurer webMvcConfigurer(){return new WebMvcConfigurer() {@Overridepublic void addFormatters(FormatterRegistry registry) {registry.addConverter(new Converter<String, Person.Pet>() {@Overridepublic Person.Pet convert(String source) {if (!StringUtils.hasLength(source)){return null;}String[] split = source.split(",");Person.Pet pet = new Person.Pet();pet.setName(split[0]);pet.setAge(split[1]);return pet;}});}};}}

 

 

 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

flask项目框架搭建

目录结构 blueprints python包&#xff0c;蓝图文件&#xff0c;相当于路由组的概念,方便模块化开发 例如auth.py文件 from flask import Blueprint, render_templatebp Blueprint("auth", __name__, url_prefix"/auth")bp.route("/login") d…

【双指针算法】移动零

1.题目解析 2.算法分析 可以归结为数组划分/数组分块&#xff08;采用双指针算法&#xff09;-->利用数组下标充当指针 &#xff08;1&#xff09;首先定义两个指针 dest&#xff1a;已处理的区间内&#xff0c;非零元素的最后一个位置cur&#xff1a;从左往右扫描数组&…

工业软件界面盲目追求美观性,或许是误入歧途。

在工业软件领域&#xff0c;界面盲目追求美观性确实可能是误入歧途。 工业软件的核心目的是为了满足工业生产、管理和控制等实际需求。 首先&#xff0c;实用性和功能性应该是工业软件界面设计的首要考虑因素。界面需要清晰地展示关键数据、操作按钮和流程指示&#xff0c;以…

K8s-services+pod详解1

一、Service 我们能够利用Deployment创建一组Pod来提供具有高可用性的服务。 虽然每个Pod都会分配一个单独的Pod IP&#xff0c;然而却存在如下两问题&#xff1a; Pod IP 会随着Pod的重建产生变化Pod IP 仅仅是集群内可见的虚拟IP&#xff0c;外部无法访问 这样对于访问这…

SpringBoot原理篇

目录 配置优先级 bean的管理 获取bean bean作用域 第三方bean 法一 法二 SpringBoot原理 起步依赖 自动配置 概述 方案 ComponentScan 组件扫描 lmport 导入 原理分析 源码跟踪 Conditional 案例 配置优先级 虽然springboot支持多种格式配置文件&#xff0c…

Python画笔案例-081 绘制 3D红球

1、绘制 3D红球 通过 python 的turtle 库绘制 3D红球,如下图: 2、实现代码 绘制 3D红球,以下为实现代码: """3D红球.py本程序不断地打直径越来越小,亮度越来越高的圆点。最后就形成了有种3D效果的圆球。 """ import turtle from coloradd …

亚马逊测评:虚拟支付卡的使用

在亚马逊测评自养号体系中&#xff0c;虚拟支付卡的使用越来越普遍&#xff0c;成为了一种重要的支付工具。以下是对虚拟支付卡的详细分析&#xff0c;包括其背景、使用方式、优势以及注意事项。 一、为什么要使用虚拟支付卡 亚马逊平台对支付方式有严格的规定&#xff0c;要求…

C# (.net6)实现Redis发布和订阅简单案例

概念&#xff1a; 在 .NET 6 中使用 Redis 的/订发布阅模式。发布/订阅&#xff08;Pub/Sub&#xff09;是 Redis 支持的一种消息传递模式&#xff0c;其中一个或多个发布者向一个或多个订阅者发送消息,Redis 客户端可以订阅任意数量的频道。 多个客户端可以订阅一个相同的频道…

geometry()、frameGeometry()、pos()、size()、rect()的区别

QWidget为单独的窗口展示 QWidget的这几个方法都与窗口的几何信息有关&#xff0c;作为单独的窗口展示时&#xff0c;我们来看一下他们的一些区别 geometry()&#xff1a;获取的矩形不包括窗口自带的标题栏&#xff0c;只包括窗口的内容区frameGeometry()&#xff1a;获取的矩…

Spring Boot知识管理系统:技术与方法论

2相关技术 2.1 MYSQL数据库 MySQL是一个真正的多用户、多线程SQL数据库服务器。 是基于SQL的客户/服务器模式的关系数据库管理系统&#xff0c;它的有点有有功能强大、使用简单、管理方便、安全可靠性高、运行速度快、多线程、跨平台性、完全网络化、稳定性等&#xff0c;非常适…

昇思MindSpore进阶教程--数据处理性能优化(中)

大家好&#xff0c;我是刘明&#xff0c;明志科技创始人&#xff0c;华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享&#xff0c;如果你也喜欢我的文章&#xff0c;就点个关注吧 shuffle性能优化 shuffle操作主要是对有…

vue3中HTML标签元素使用ref的作用

首先我们需要两个界面 APP.vue主界面 <template><!-- html --><div class"app"><h1 ref"title">您好啊&#xff01;</h1><button click"printTitle">点我</button> <refTest/></div> &…

【无人机设计与控制】PID_积分滑模_积分反步四旋翼无人机轨迹跟踪控制算法

摘要 本文基于四旋翼无人机设计与控制&#xff0c;提出了一种结合PID控制、积分滑模控制以及积分反步控制的轨迹跟踪算法。该算法通过调节无人机的运动轨迹&#xff0c;提升其在复杂环境下的稳定性与抗扰动能力。实验结果表明&#xff0c;该算法能有效改善无人机的轨迹跟踪精度…

Python Django 查询集的延迟加载特性

Django 查询集的延迟加载特性 一、引言 在 Django 的开发过程中&#xff0c;查询集&#xff08;QuerySet&#xff09;是我们与数据库进行交互的重要工具。查询集提供了一种高效的方式来检索和操作数据库中的数据&#xff0c;且能够进行懒加载&#xff08;Lazy Loading&#x…

Element中el-table组件设置max-height右侧出现空白列的解决方法

之前就出现过这个情况&#xff0c;没理过&#xff0c;因为不影响啥除了不美观...但今天看着实在是难受&#xff0c;怎么都不顺眼(可能是我自己烦躁--) 试了很多网上的方法&#xff0c;都不得行&#xff0c;后面发现了这篇文章&#xff0c;解决了! 感谢&#xff01; Element中t…

【数据结构】:破译排序算法--数字世界的秩序密码(一)

文章目录 一.排序算法概述1.定义和目的2.排序算法的分类2.1比较排序2.2非比较排序 二.插入排序算法1.InsertSort直接插入排序1.1.插入排序原理1.2.插入排序过程1.3.代码实现1.4.复杂度和稳定性 2.ShellSort希尔排序2.1.希尔排序原理2.2.希尔排序过程2.3.代码实现2.4.复杂度和稳…

【.net core使用minio大文件分片上传】.net core使用minio大文件分片上传以及断点续传、秒传思路

版本&#xff1a;.net core 7 需求&#xff1a;net限制了上传的大小&#xff0c;只能上传25M上下的文件&#xff0c;如果上传一个八十多兆的文件&#xff0c;swagger接口报错&#xff0c;如果前端调用上传接口&#xff0c;会报CORS跨域错误&#xff0c;这篇文章介绍怎么使用分片…

使用CSS和HTML实现3D图片环绕效果

使用CSS和HTML实现3D图片环绕效果 在本篇博客中&#xff0c;将介绍如何使用HTML和CSS实现一个3D图片环绕效果。这个效果不仅具有视觉吸引力&#xff0c;而且具有高度的互动性&#xff0c;鼠标悬停时动画会暂停。接下来将一步步讲解这个效果的实现过程。 1. 效果 2. 页面结构与…

【华为HCIP实战课程十一】OSPF网络NBMA网络解决方案,网络工程师

上节我们讲解了DR DBR 选举,每台设备可以学到全网路由,但是通信是有问题的 DR BDR的选举是基于接口的,而不是基于路由器的 一、OSPF路由通信问题 R5虽然可以学到全网的OSPF路由,但是R5无法ping通44.1.1.1 原因是R5到达R4 lo0的下一跳是10.1.1.4, 而R5和R4直连无法ping通…

数码准备记录

1.数据结构 常见的数据结构包括数组、链表、栈、队列、树&#xff08;如二叉树、B树、B树&#xff09;、图等 2.队列和栈的区别 队列是一种先入先出的数据结构&#xff0c;即最先加入的元素被最先移除&#xff1b; 栈是一种后进后出的数据结构&#xff0c;即最后加入的元素…