当前位置: 首页 > news >正文

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;}});}};}}

 

 

 

相关文章:

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

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

开源商城系统crmeb phpstudy安装配置

BOSS让我最快时间部署一套开源商场系统&#xff0c;今天就以crmeb为例。 快速部署在linux中我会首选docker&#xff0c;因为我要在windows中部署&#xff0c;本文就选用phpstudy集成环境做了。 什么是crmeb 我从官网摘点&#xff1a; CRMEB产品与服务 CRMEB通过将CRM&#x…...

【论文阅读笔记】Bigtable: A Distributed Storage System for Structured Data

文章目录 1 简介2 数据模型2.1 行2.2 列族2.3 时间戳 3 API4 基础构建4.1 GFS4.2 SSTable4.3 Chubby 5 实现5.1 Tablet 位置5.2 Tablet 分配5.3 为 tablet 提供服务5.4 压缩5.4.1 小压缩5.4.2 主压缩 6 优化6.1 局部性组6.2 压缩6.3 缓存6.4 布隆过滤器6.5 Commit日志实现6.6 T…...

linux从入门到精通-从基础学起,逐步提升,探索linux奥秘(十一)--rpm管理和计划任务

linux从入门到精通-从基础学起&#xff0c;逐步提升&#xff0c;探索linux奥秘&#xff08;十一&#xff09;–rpm管理和计划任务 一、rpm管理&#xff08;重点&#xff09; 1、rpm管理 作用&#xff1a; rpm的作用类似于windows上的电脑管家中“软件管理”、安全卫士里面“…...

【C++几种单例模式解读及实现方式】

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、单例是什么&#xff1f;二、解读1.懒汉式2.饿汉式3.static变量特性4.call_once特性 总结 前言 单例模式几乎是每种语言都不可少的一种设计模式&#xff0c…...

QT开发--串口通信

第十六章 串口通信 16.1 串口通信基础 串口通信主要通过DB9接口&#xff0c;适用于短距离&#xff08;<10米&#xff09;。关键参数包括&#xff1a; 波特率&#xff1a;每秒传输bit数&#xff0c;如9600。数据位&#xff1a;信息包中的有效数据位数。停止位&#xff1a;…...

数据库(至少还的再花两天 )

1 连接查询 左连接 右连接 2 聚合函数 SQL 统计求和 求最值 count sum avg max min 3 SQL关键字 limit 分页 group by 分组 distinct 去重 4 Select执行顺序 from where group by order by 5 数据库三范式 原子性 唯一性 直接性 6 存储引擎 MyISAM InnoDB 7 …...

网络安全公司及其主要产品介绍

以下是一些全球领先的网络安全公司及其主要产品介绍&#xff1a; 一、思科&#xff08;Cisco&#xff09; 思科是全球最大的网络设备供应商之一&#xff0c;其网络安全产品以企业级解决方案为主&#xff0c;覆盖多种安全需求。 Cisco ASA&#xff08;Adaptive Security Appli…...

orjson:高性能的Python JSON库

在Python中处理JSON数据是一项常见任务,标准库的json模块虽然功能齐全,但在性能方面还有提升空间。今天我要向大家介绍一个出色的第三方JSON库 - orjson。 orjson简介 orjson是一个快速、正确的Python JSON库。它具有以下主要特点: 性能卓越 - 在序列化和反序列化方面都比标准…...

常见几大排序算法

排序算法是计算机科学中的基本算法&#xff0c;它们将一个无序的数组或列表按特定顺序进行排列&#xff08;如升序或降序&#xff09;。常见的排序算法可以根据其时间复杂度、空间复杂度和适用场景分类。以下是几种常见的排序算法&#xff1a; 1. 冒泡排序&#xff08;Bubble …...

Linux下CMake入门

CMake的基础知识 什么是 CMake CMake 是一个跨平台的构建工具&#xff0c;主要用于管理构建过程。CMake 不直接构建项目&#xff0c;而是生成特定平台上的构建系统&#xff08;如 Unix 下的 Makefile&#xff0c;Windows 下的 Visual Studio 工程&#xff09;&#xff0c;然后…...

网络资源模板--Android Studio 实现简易记事本App

目录 一、项目演示 二、项目测试环境 三、项目详情 四、完整的项目源码 一、项目演示 网络资源模板--基于Android studio 实现的简易记事本App 二、项目测试环境 三、项目详情 首页 创建一个空的笔记本列表 mNotebookList。使用该列表和指定的布局资源 item_notebook 创建…...

根据Vue对比来深入学习React 下 props 组件传值 插槽 样式操作 hooks 高阶组件 性能优化

文章目录 函数组件的特点props组件间的传值父传子看上例子传父兄弟组件传值祖先组件传值 插槽基础插槽具名插槽作用域插槽 样式操作**CSS Modules** 生命周期useRef常用hookuseStateuseEffectuseContextuseReduceruseMemouseCallback 高阶组件什么时候使用 react性能问题和优化…...

HTML(六)超链接

HTML讲解&#xff08;一&#xff09;body部分_html body-CSDN博客 <!DOCTYPE html> <html><head><meta charset"UTF-8" /><title>title</title> </head><body><a href"https://blog.csdn.net/2301_8034953…...

【Coroutines】Implement Lua Coroutine by Kotlin - 2

Last Chapter Link 文章目录 Symmetric CoroutinesNon-Symmetric Coroutine SampleSymmetric Coroutine SampleHow to Implement Symmetric CoroutinesWonderful TricksCode DesignTail Recursion OptimizationFull Sources Symmetric Coroutines in last blog, we have talk…...

java计算机毕设课设—扫雷游戏(附源码、文章、相关截图、部署视频)

这是什么系统&#xff1f; 资源获取方式再最下方&#xff08;本次10月份活动福利&#xff0c;免费提供下载&#xff0c;自行到对应的方式1下载&#xff0c;csdn的0积分下载&#xff09; java计算机毕设课设—扫雷游戏(附源码、文章、相关截图、部署视频) 基于Java的扫雷游戏…...

AndroidLogger 使用问题

Q1&#xff1a;解压zip后&#xff0c;启动Notepad未看到AndroidLogger工具栏 请检查plugins下安装位置是否正确&#xff0c;必须与下图一致&#xff0c;再确认Notepad 是否为 x64 &#xff1f; Q2&#xff1a;使用 adb 可以显示已连接&#xff0c;但是获取不到日志 暂时不确定问…...

数据库常见面试

8道面试题 目录 目录 7道面试题 1.怎样进行sql优化 4、group by优化 5、limit优化 6、count优化 7、update优化 2.。怎样查看sql执行情况呢(哪个关键字)&#xff0c;说说你对这个关键字的认识 4) possible_key&#xff1a; 5) key 3.说说你对innodb和 myisam的理解 …...

boxplot 绘制箱线图,添加数据点

先看效果图 import matplotlib.pyplot as plt #! 解决不显示的问题&#xff1a;中文设置为宋体格式 plt.rcParams[font.family] ["Times New Roman", SimSun]def plot_boxplot(data_list, out_file, x_custom_labels):# 画图fig, ax plt.subplots(figsize(90, 6…...

用sdkman管理多个jdk切换

前言 最近项目前后端进行升级&#xff0c;需要在jdk8和jdk17两个版本切换。最简单的是通过手动切换&#xff0c;但切换过程太繁琐&#xff0c;修改环境变量&#xff0c;达到切换目的。于是尝试其它解决方案&#xff0c;最终确实使用sdkman工具。 sdkman 是一款面向Java开发者的…...

Golang 面试经典题:map 的 key 可以是什么类型?哪些不可以?

Golang 面试经典题&#xff1a;map 的 key 可以是什么类型&#xff1f;哪些不可以&#xff1f; 在 Golang 的面试中&#xff0c;map 类型的使用是一个常见的考点&#xff0c;其中对 key 类型的合法性 是一道常被提及的基础却很容易被忽视的问题。本文将带你深入理解 Golang 中…...

【单片机期末】单片机系统设计

主要内容&#xff1a;系统状态机&#xff0c;系统时基&#xff0c;系统需求分析&#xff0c;系统构建&#xff0c;系统状态流图 一、题目要求 二、绘制系统状态流图 题目&#xff1a;根据上述描述绘制系统状态流图&#xff0c;注明状态转移条件及方向。 三、利用定时器产生时…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

自然语言处理——Transformer

自然语言处理——Transformer 自注意力机制多头注意力机制Transformer 虽然循环神经网络可以对具有序列特性的数据非常有效&#xff0c;它能挖掘数据中的时序信息以及语义信息&#xff0c;但是它有一个很大的缺陷——很难并行化。 我们可以考虑用CNN来替代RNN&#xff0c;但是…...

Java多线程实现之Thread类深度解析

Java多线程实现之Thread类深度解析 一、多线程基础概念1.1 什么是线程1.2 多线程的优势1.3 Java多线程模型 二、Thread类的基本结构与构造函数2.1 Thread类的继承关系2.2 构造函数 三、创建和启动线程3.1 继承Thread类创建线程3.2 实现Runnable接口创建线程 四、Thread类的核心…...

深度学习习题2

1.如果增加神经网络的宽度&#xff0c;精确度会增加到一个特定阈值后&#xff0c;便开始降低。造成这一现象的可能原因是什么&#xff1f; A、即使增加卷积核的数量&#xff0c;只有少部分的核会被用作预测 B、当卷积核数量增加时&#xff0c;神经网络的预测能力会降低 C、当卷…...

实战三:开发网页端界面完成黑白视频转为彩色视频

​一、需求描述 设计一个简单的视频上色应用&#xff0c;用户可以通过网页界面上传黑白视频&#xff0c;系统会自动将其转换为彩色视频。整个过程对用户来说非常简单直观&#xff0c;不需要了解技术细节。 效果图 ​二、实现思路 总体思路&#xff1a; 用户通过Gradio界面上…...

基于鸿蒙(HarmonyOS5)的打车小程序

1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...

一些实用的chrome扩展0x01

简介 浏览器扩展程序有助于自动化任务、查找隐藏的漏洞、隐藏自身痕迹。以下列出了一些必备扩展程序&#xff0c;无论是测试应用程序、搜寻漏洞还是收集情报&#xff0c;它们都能提升工作流程。 FoxyProxy 代理管理工具&#xff0c;此扩展简化了使用代理&#xff08;如 Burp…...

文件上传漏洞防御全攻略

要全面防范文件上传漏洞&#xff0c;需构建多层防御体系&#xff0c;结合技术验证、存储隔离与权限控制&#xff1a; &#x1f512; 一、基础防护层 前端校验&#xff08;仅辅助&#xff09; 通过JavaScript限制文件后缀名&#xff08;白名单&#xff09;和大小&#xff0c;提…...