13)SpringBoot 請求方式、引數獲取註解、引數驗證、前後臺屬性名不一致問題、自定義引數驗證註解、BeanUtils的使用
1 請求方式
在定義一個Rest介面時通常會利用GET、POST、PUT、DELETE來實現資料的增刪改查;這幾種方式有的需要傳遞引數,後臺開發人員必須對接收到的引數進行引數驗證來確保程式的健壯性
1.1 GET
一般用於查詢資料,採用明文進行傳輸,一般用來獲取一些無關使用者資訊的資料
1.2 POST
一般用於插入資料
1.3 PUT
一般用於資料更新
1.4 DELETE
一般用於資料刪除
技巧01:一般都是進行邏輯刪除(即:僅僅改變記錄的狀態,而並非真正的刪除資料)
2 引數獲取註解
2.1 @PathVariable
路徑引數,形如 url/{param} 時會用到,我們可以通過該註解來獲取路徑後面的引數來進行GET、DELETE、PUT操作
@GetMapping(value = "/{id}") public ResultViewModel findGirlById( @PathVariable("id") String id ) { // 01 引數去空格 id = StringUtils.trim(id); // 02 判斷引數是否全為數字 Integer girlId = 0; if (JudgeUtil.allIsNumber(id)) { girlId = Integer.parseInt(id); } else { throw new GirlException(GirlEnum.PARAM_NOT_ALL_NUMBER); } // 03 呼叫服務層獲取資料 return ResultViewModelUtil.success(girlService.findGirlById(girlId)); }
2.1.1 為路徑引數新增正則表示式
如果前端請求的url中的路徑引數不滿足正則表示式時,請求就會失敗;錯誤資訊是找不到相應的請求方法
程式碼解釋:路徑引數必須是數字型別,否則就不會匹配到這個請求路徑,如果不是數字型別時返回給前端的資訊如下:
2.2 @RequestParam
用來獲取多個引數,常用語POST、PUT操作
技巧01:這種引數時通過請求rul進行傳遞的,請求url中?後面的引數就是這種型別
技巧02:預設就是利用這種方式進行傳參
技巧03:controller中方法引數可以利用一個物件來接收前端傳過來的引數
2.3 @RequestBody
利用一個物件去獲取前端傳過來的資料
技巧01:這種引數是通過請求體進行傳遞的
@PutMapping public ResultViewModel updateOneGirl( @Valid @RequestBody GirlFormModel girlFormModel, BindingResult result ) { // 01 引數驗證 if (result.hasErrors()) { // 0101 儲存錯誤資訊的字串變數 StringBuffer msgBuffer = new StringBuffer(); // 0102 錯誤欄位集合 List<FieldError> fieldErrors = result.getFieldErrors(); for (FieldError fieldError : fieldErrors) { // 0103 獲取錯誤資訊 msgBuffer.append(fieldError.getField() + ":" + fieldError.getDefaultMessage()); } // 0104 丟擲錯誤資訊 throw new GirlException(GirlEnum.PARAM_ERROR.getCode(), msgBuffer.toString()); } // 02 將表單物件轉化成資料物件 GirlModel girlModel = new GirlModel(); BeanUtils.copyProperties(girlFormModel, girlModel); // 03 呼叫服務層進行更新操作 Boolean updateResult = girlService.updateGirl(girlModel); if (updateResult) { return ResultViewModelUtil.success(GirlEnum.UPDATE_SUCCESS.getMessage()); } else { return ResultViewModelUtil.error(GirlEnum.UPDATE_ERROR.getCode(), GirlEnum.UPDATE_ERROR.getMessage()); } }
3 引數驗證註解
後臺單獨對接收到的引數進行驗證時比較麻煩,springboot專案的web元件集成了hibernate-validator,開發者可以直接使用hibernate-validator提供的註解對資料進行校驗,當有一些複雜的引數校驗時我們也可以自定義校驗註解
技巧01:接收到的引數預設都是字串型別的
技巧02:如果是SpringBoot專案必須匯入JPA先關的jar包,因為JPA是基於Hibernate的,所以JPA相關的jar包裡面會有先關的驗證註解
坑01:有的註解只能用在String型別的屬性上
3.1 常用的驗證註解有
技巧01:@JsonProperty可以實現前端的屬性名和後臺實體類的屬性名不一致問題,例如
程式碼解釋:前端傳過來的屬性名為nick,後臺接收的屬性名為nickname
技巧01:前端傳過來的引數和後臺不一致的情況只對@RequestBody這種引數有效
技巧02:後臺傳給前臺時使用的屬性名是@JsonProperty中的引數而不是實體中定義的屬性名
3.2 使用方法
直接在表單實體類的屬性上面新增相應的註解就可以啦
package cn.xiangxu.springboottest.model.dataFormModel; import cn.xiangxu.springboottest.commons.validators.GirlFormIdValidator; import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Data; import org.hibernate.validator.constraints.NotBlank; import org.hibernate.validator.constraints.NotEmpty; import javax.validation.constraints.Min; @Data public class GirlFormModel { // @NotBlank(message = "目標girl的ID不能為空") // @NotEmpty(message = "目標girl的ID不能為空喲") // @GirlFormIdValidator private Integer girlId; @Min(value = 123) private Integer age; private String name; private String password; @NotEmpty(message = "暱稱不能為空") @NotBlank(message = "暱稱不能為空喲") @JsonProperty("nick") // 當前端屬性為nick後臺接收物件的屬性為nickName時可以用@JsonProperty來保持一致 private String nickname; @NotEmpty(message = "地址不能為空") @NotBlank(message = "地址不能為空喲") private String address; }
3.3 如何使引數校驗生效
3.3.1 在控制層方法的形參前面新增@Valid註解
技巧01:如果在controller層控制方法中的引數標註了@Valid註解後,前端傳過來的引數一旦發現不合法就會丟擲請求錯誤(即:400錯誤)
3.3.2 利用BindingResult物件獲取引數錯誤欄位和引數錯誤資訊
技巧01:如果只是新增@Valid註解,如果引數錯誤就會直接丟擲異常
技巧02:後臺開發時通常都需要對前端傳過來的引數進行一次引數驗證,如果引數不正確我們需要丟擲一些使用者可以識別的錯誤資訊而不是丟擲原生的錯誤;所以我們需要在方法中新增一個BindingResult引數
技巧03:給控制方法添加了BindingResult引數後,即使前端傳過來的引數不合法也會繼續執行方法體;我們可以利用BindingResult物件去判斷時哪一個引數不合法,從而丟擲一些自定義的異常資訊
》獲取所有錯誤欄位和錯誤資訊的程式碼如下
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.validation; import java.beans.PropertyEditor; import java.util.Map; import org.springframework.beans.PropertyEditorRegistry; public interface BindingResult extends Errors { String MODEL_KEY_PREFIX = BindingResult.class.getName() + "."; Object getTarget(); Map<String, Object> getModel(); Object getRawFieldValue(String var1); PropertyEditor findEditor(String var1, Class<?> var2); PropertyEditorRegistry getPropertyEditorRegistry(); void addError(ObjectError var1); String[] resolveMessageCodes(String var1); String[] resolveMessageCodes(String var1, String var2); void recordSuppressedField(String var1); String[] getSuppressedFields(); }
》獲取單個錯誤欄位和錯誤資訊的程式碼如下
@PostMapping(value = "/user") public String create( @Valid @RequestBody User user, BindingResult errors ) { if (errors.hasErrors()) { errors.getAllErrors().stream().forEach( error -> { FieldError fieldError = (FieldError)error; log.info("錯誤欄位:{} -> 錯誤資訊:{}", fieldError.getField(), fieldError.getDefaultMessage() ); } ); } log.info("獲取到的引數資訊為:" + user); String info = "新增使用者成功"; return info; }
技巧04:利用BindingResult物件的hasErrors方法判斷是否有引數錯誤
· 技巧05:利用BindingResult物件的getFieldErrors方法獲取所有有引數錯誤的屬性
技巧06:利用錯誤屬性物件的getDefaultMessage去獲取錯誤提示資訊
4 自定義引數驗證註解
4.1 定義一個註解介面
技巧01:每個自定義的校驗註解必須要有這四個成員
package cn.xiangxu.springboottest.commons.validators; import cn.xiangxu.springboottest.commons.validators.validatorClass.GirlFormValidatorClass; import javax.validation.Constraint; import javax.validation.Payload; import java.lang.annotation.*; @Documented @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.PARAMETER, ElementType.FIELD}) @Constraint(validatedBy = GirlFormValidatorClass.class) public @interface GirlFormIdValidator { String values(); String message() default "girl的ID必須為純數字"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
4.2 定義一個註解介面實現類
技巧01:必須實現ConstraintValidator介面
技巧02:實現了ConstraintValidator介面後即使不進行Bean配置,spring也會將這個類進行Bean管理
技巧03:可以在實現了ConstraintValidator介面的類中依賴注入其它Bean
技巧04:實現了ConstraintValidator介面後必須重寫 initialize 和 isValid 這兩個方法;initialize方法主要來進行初始化,通常用來獲取自定義註解的屬性值;isValid 方法主要進行校驗邏輯,返回true表示校驗通過,返回false表示校驗失敗,通常根據註解屬性值和實體類屬性值進行校驗判斷
package cn.xiangxu.springboottest.commons.validators.validatorClass; import cn.xiangxu.springboottest.commons.validators.GirlFormIdValidator; import cn.xiangxu.springboottest.utils.JudgeUtil; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; public class GirlFormValidatorClass implements ConstraintValidator<GirlFormIdValidator, Object> { private String values; @Override public void initialize(GirlFormIdValidator girlFormValidator) { this.values = girlFormValidator.values(); } @Override public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { if (JudgeUtil.allIsNumber((String)value)) { return true; } return false; } }
4.3 在實體類中使用自定義的引數校驗註解
5 BeanUtils的使用
利用BeanUtils提供的copyProperties可以實現實體類資料的複製
坑01:只用屬性名和屬性型別一致的部分才可以進行復制操作,例如
BeanUtils.copyProperties(girlFormModel, girlModel);
程式碼解釋:將girlFormModel物件複製給girlModel物件
// // Source code recreated from a .class file by IntelliJ IDEA // (powered by Fernflower decompiler) // package org.springframework.beans; import java.beans.PropertyDescriptor; import java.beans.PropertyEditor; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.net.URI; import java.net.URL; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Set; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.core.MethodParameter; import org.springframework.util.Assert; import org.springframework.util.ClassUtils; import org.springframework.util.ConcurrentReferenceHashMap; import org.springframework.util.ReflectionUtils; import org.springframework.util.StringUtils; public abstract class BeanUtils { private static final Log logger = LogFactory.getLog(BeanUtils.class); private static final Set<Class<?>> unknownEditorTypes = Collections.newSetFromMap(new ConcurrentReferenceHashMap(64)); public BeanUtils() { } public static <T> T instantiate(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } else { try { return clazz.newInstance(); } catch (InstantiationException var2) { throw new BeanInstantiationException(clazz, "Is it an abstract class?", var2); } catch (IllegalAccessException var3) { throw new BeanInstantiationException(clazz, "Is the constructor accessible?", var3); } } } public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException { Assert.notNull(clazz, "Class must not be null"); if (clazz.isInterface()) { throw new BeanInstantiationException(clazz, "Specified class is an interface"); } else { try { return instantiateClass(clazz.getDeclaredConstructor()); } catch (NoSuchMethodException var2) { throw new BeanInstantiationException(clazz, "No default constructor found", var2); } } } public static <T> T instantiateClass(Class<?> clazz, Class<T> assignableTo) throws BeanInstantiationException { Assert.isAssignable(assignableTo, clazz); return instantiateClass(clazz); } public static <T> T instantiateClass(Constructor<T> ctor, Object... args) throws BeanInstantiationException { Assert.notNull(ctor, "Constructor must not be null"); try { ReflectionUtils.makeAccessible(ctor); return ctor.newInstance(args); } catch (InstantiationException var3) { throw new BeanInstantiationException(ctor, "Is it an abstract class?", var3); } catch (IllegalAccessException var4) { throw new BeanInstantiationException(ctor, "Is the constructor accessible?", var4); } catch (IllegalArgumentException var5) { throw new BeanInstantiationException(ctor, "Illegal arguments for constructor", var5); } catch (InvocationTargetException var6) { throw new BeanInstantiationException(ctor, "Constructor threw exception", var6.getTargetException()); } } public static Method findMethod(Class<?> clazz, String methodName, Class... paramTypes) { try { return clazz.getMethod(methodName, paramTypes); } catch (NoSuchMethodException var4) { return findDeclaredMethod(clazz, methodName, paramTypes); } } public static Method findDeclaredMethod(Class<?> clazz, String methodName, Class... paramTypes) { try { return clazz.getDeclaredMethod(methodName, paramTypes); } catch (NoSuchMethodException var4) { return clazz.getSuperclass() != null ? findDeclaredMethod(clazz.getSuperclass(), methodName, paramTypes) : null; } } public static Method findMethodWithMinimalParameters(Class<?> clazz, String methodName) throws IllegalArgumentException { Method targetMethod = findMethodWithMinimalParameters(clazz.getMethods(), methodName); if (targetMethod == null) { targetMethod = findDeclaredMethodWithMinimalParameters(clazz, methodName); } return targetMethod; } public static Method findDeclaredMethodWithMinimalParameters(Class<?> clazz, String methodName) throws IllegalArgumentException { Method targetMethod = findMethodWithMinimalParameters(clazz.getDeclaredMethods(), methodName); if (targetMethod == null && clazz.getSuperclass() != null) { targetMethod = findDeclaredMethodWithMinimalParameters(clazz.getSuperclass(), methodName); } return targetMethod; } public static Method findMethodWithMinimalParameters(Method[] methods, String methodName) throws IllegalArgumentException { Method targetMethod = null; int numMethodsFoundWithCurrentMinimumArgs = 0; Method[] var4 = methods; int var5 = methods.length; for(int var6 = 0; var6 < var5; ++var6) { Method method = var4[var6]; if (method.getName().equals(methodName)) { int numParams = method.getParameterTypes().length; if (targetMethod != null && numParams >= targetMethod.getParameterTypes().length) { if (!method.isBridge() && targetMethod.getParameterTypes().length == numParams) { if (targetMethod.isBridge()) { targetMethod = method; } else { ++numMethodsFoundWithCurrentMinimumArgs; } } } else { targetMethod = method; numMethodsFoundWithCurrentMinimumArgs = 1; } } } if (numMethodsFoundWithCurrentMinimumArgs > 1) { throw new IllegalArgumentException("Cannot resolve method '" + methodName + "' to a unique method. Attempted to resolve to overloaded method with the least number of parameters but there were " + numMethodsFoundWithCurrentMinimumArgs + " candidates."); } else { return targetMethod; } } public static Method resolveSignature(String signature, Class<?> clazz) { Assert.hasText(signature, "'signature' must not be empty"); Assert.notNull(clazz, "Class must not be null"); int firstParen = signature.indexOf("("); int lastParen = signature.indexOf(")"); if (firstParen > -1 && lastParen == -1) { throw new IllegalArgumentException("Invalid method signature '" + signature + "': expected closing ')' for args list"); } else if (lastParen > -1 && firstParen == -1) { throw new IllegalArgumentException("Invalid method signature '" + signature + "': expected opening '(' for args list"); } else if (firstParen == -1 && lastParen == -1) { return findMethodWithMinimalParameters(clazz, signature); } else { String methodName = signature.substring(0, firstParen); String[] parameterTypeNames = StringUtils.commaDelimitedListToStringArray(signature.substring(firstParen + 1, lastParen)); Class<?>[] parameterTypes = new Class[parameterTypeNames.length]; for(int i = 0; i < parameterTypeNames.length; ++i) { String parameterTypeName = parameterTypeNames[i].trim(); try { parameterTypes[i] = ClassUtils.forName(parameterTypeName, clazz.getClassLoader()); } catch (Throwable var10) { throw new IllegalArgumentException("Invalid method signature: unable to resolve type [" + parameterTypeName + "] for argument " + i + ". Root cause: " + var10); } } return findMethod(clazz, methodName, parameterTypes); } } public static PropertyDescriptor[] getPropertyDescriptors(Class<?> clazz) throws BeansException { CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); return cr.getPropertyDescriptors(); } public static PropertyDescriptor getPropertyDescriptor(Class<?> clazz, String propertyName) throws BeansException { CachedIntrospectionResults cr = CachedIntrospectionResults.forClass(clazz); return cr.getPropertyDescriptor(propertyName); } public static PropertyDescriptor findPropertyForMethod(Method method) throws BeansException { return findPropertyForMethod(method, method.getDeclaringClass()); } public static PropertyDescriptor findPropertyForMethod(Method method, Class<?> clazz) throws BeansException { Assert.notNull(method, "Method must not be null"); PropertyDescriptor[] pds = getPropertyDescriptors(clazz); PropertyDescriptor[] var3 = pds; int var4 = pds.length; for(int var5 = 0; var5 < var4; ++var5) { PropertyDescriptor pd = var3[var5]; if (method.equals(pd.getReadMethod()) || method.equals(pd.getWriteMethod())) { return pd; } } return null; } public static PropertyEditor findEditorByConvention(Class<?> targetType) { if (targetType != null && !targetType.isArray() && !unknownEditorTypes.contains(targetType)) { ClassLoader cl = targetType.getClassLoader(); if (cl == null) { try { cl = ClassLoader.getSystemClassLoader(); if (cl == null) { return null; } } catch (Throwable var5) { if (logger.isDebugEnabled()) { logger.debug("Could not access system ClassLoader: " + var5); } return null; } } String editorName = targetType.getName() + "Editor"; try { Class<?> editorClass = cl.loadClass(editorName); if (!PropertyEditor.class.isAssignableFrom(editorClass)) { if (logger.isWarnEnabled()) { logger.warn("Editor class [" + editorName + "] does not implement [java.beans.PropertyEditor] interface"); } unknownEditorTypes.add(targetType); return null; } else { return (PropertyEditor)instantiateClass(editorClass); } } catch (ClassNotFoundException var4) { if (logger.isDebugEnabled()) { logger.debug("No property editor [" + editorName + "] found for type " + targetType.getName() + " according to 'Editor' suffix convention"); } unknownEditorTypes.add(targetType); return null; } } else { return null; } } public static Class<?> findPropertyType(String propertyName, Class... beanClasses) { if (beanClasses != null) { Class[] var2 = beanClasses; int var3 = beanClasses.length; for(int var4 = 0; var4 < var3; ++var4) { Class<?> beanClass = var2[var4]; PropertyDescriptor pd = getPropertyDescriptor(beanClass, propertyName); if (pd != null) { return pd.getPropertyType(); } } } return Object.class; } public static MethodParameter getWriteMethodParameter(PropertyDescriptor pd) { return pd instanceof GenericTypeAwarePropertyDescriptor ? new MethodParameter(((GenericTypeAwarePropertyDescriptor)pd).getWriteMethodParameter()) : new MethodParameter(pd.getWriteMethod(), 0); } public static boolean isSimpleProperty(Class<?> clazz) { Assert.notNull(clazz, "Class must not be null"); return isSimpleValueType(clazz) || clazz.isArray() && isSimpleValueType(clazz.getComponentType()); } public static boolean isSimpleValueType(Class<?> clazz) { return ClassUtils.isPrimitiveOrWrapper(clazz) || clazz.isEnum() || CharSequence.class.isAssignableFrom(clazz) || Number.class.isAssignableFrom(clazz) || Date.class.isAssignableFrom(clazz) || URI.class == clazz || URL.class == clazz || Locale.class == clazz || Class.class == clazz; } public static void copyProperties(Object source, Object target) throws BeansException { copyProperties(source, target, (Class)null, (String[])null); } public static void copyProperties(Object source, Object target, Class<?> editable) throws BeansException { copyProperties(source, target, editable, (String[])null); } public static void copyProperties(Object source, Object target, String... ignoreProperties) throws BeansException { copyProperties(source, target, (Class)null, ignoreProperties); } private static void copyProperties(Object source, Object target, Class<?> editable, String... ignoreProperties) throws BeansException { Assert.notNull(source, "Source must not be null"); Assert.notNull(target, "Target must not be null"); Class<?> actualEditable = target.getClass(); if (editable != null) { if (!editable.isInstance(target)) { throw new IllegalArgumentException("Target class [" + target.getClass().getName() + "] not assignable to Editable class [" + editable.getName() + "]"); } actualEditable = editable; } PropertyDescriptor[] targetPds = getPropertyDescriptors(actualEditable); List<String> ignoreList = ignoreProperties != null ? Arrays.asList(ignoreProperties) : null; PropertyDescriptor[] var7 = targetPds; int var8 = targetPds.length; for(int var9 = 0; var9 < var8; ++var9) { PropertyDescriptor targetPd = var7[var9]; Method writeMethod = targetPd.getWriteMethod(); if (writeMethod != null && (ignoreList == null || !ignoreList.contains(targetPd.getName()))) { PropertyDescriptor sourcePd = getPropertyDescriptor(source.getClass(), targetPd.getName()); if (sourcePd != null) { Method readMethod = sourcePd.getReadMethod(); if (readMethod != null && ClassUtils.isAssignable(writeMethod.getParameterTypes()[0], readMethod.getReturnType())) { try { if (!Modifier.isPublic(readMethod.getDeclaringClass().getModifiers())) { readMethod.setAccessible(true); } Object value = readMethod.invoke(source); if (!Modifier.isPublic(writeMethod.getDeclaringClass().getModifiers())) { writeMethod.setAccessible(true); } writeMethod.invoke(target, value); } catch (Throwable var15) { throw new FatalBeanException("Could not copy property '" + targetPd.getName() + "' from source to target", var15); } } } } } } }
關注公眾號,將會有更多精彩每天送達:
公眾號內有精彩內容,可以提現