開發過程中,難免會對數據進行校驗或處理,難道每次都對不同的實體類的屬性挨個判斷是否符合規範或者對其進行特殊處理,程序員不應該這麼做。在這個時候,自定義註解就派上大用場了。比如自定義一個 @Email 註解,將其標註在只能存放email格式的屬性(private String guestEmail)上,再在程序入口上加一個判斷工具類。那麼程序將利用你事先寫好的方法進行校驗該屬性值是否符合郵件的格式,並進行相應處理。廢話不多說,看看下面的輪子吧。(注:文末付完整工具類鏈接)
1、定義@Email 註解
package java1.lang.annotation.validation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author zxiaofan 郵件地址,該註解只能String使用
*
*/
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Email {
}
2、構建校驗工具類ValidationUtils
package java1.lang.annotation.validation;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* @author github.zxiaofan.com
*
* 用於參數校驗
*/
@SuppressWarnings({"rawtypes", "unused", "unchecked"})
public final class ValidationUtils {
/**
* regex_Email.
*/
private static final String REGEXMAIL = "^\\w+([\\.-]?\\w+)*@\\w+([\\.-]?\\w+)*(\\.\\w{2,3})+$";
/**
* 構造函數.
*
*/
private ValidationUtils() {
throw new RuntimeException("this is a util class,can not instance!");
}
/**
* 參數校驗.
*
* @param obj
* obj
* @return obj
* @throws Exception
* e
*/
public static String validate(Object obj) throws Exception {
List<String> list = new ArrayList<String>();
if (null == obj) {
return "參數爲null";
}
Field[] fields = obj.getClass().getDeclaredFields();
long now = System.currentTimeMillis();
String claName = obj.getClass().getName();
for (Field field : fields) {
realValidate(obj, list, now, claName, field);
}
return list.isEmpty() ? null : packaging(list);
}
/**
* 添加方法註釋.
*
* @param list
* 結果
* @return 結果
*/
private static String packaging(List<String> list) {
StringBuffer sb = new StringBuffer();
for (String s : list) {
sb.append(s).append(';');
}
sb.setLength(sb.length() - 1);
return sb.toString();
}
/**
* 對指定註解進行校驗.
*
* @param obj
* 參數
* @param list
* 結果
* @param now
* 當前時間
* @param claName
* 類名
* @param field
* 屬性
* @throws Exception
* e
*/
private static void realValidate(Object obj, List<String> list, long now, String claName, Field field) throws Exception {
Email email;
FutureTime futureTime;
StringLength stringLength;
PastTime pastTime;
Pattern pattern;
Phone phone;
PostalCode postalCode;
Tel tel;
NotNullAndEmpty nullAndEmpty;
Number number;
ToLower lower;
ToUpper upper;
Date date;
Object value;
String fieldName;
Class type;
boolean isNotNull;
type = field.getType();
fieldName = field.getName();
Annotation[] annotations = field.getAnnotations();
field.setAccessible(true);
value = field.get(obj);
isNotNull = field.isAnnotationPresent(NotNull.class);
for (Annotation an : annotations) {
if (an.annotationType().getName().equals(Email.class.getName())) { // 有email註解
validateEmail(list, claName, field, value, fieldName, type, isNotNull, an);
}
}
field.setAccessible(false);
email = null;
}
/**
* 校驗email.
*
* @param list
* 結果
* @param claName
* 類型
* @param field
* 屬性
* @param value
* 屬性值
* @param fieldName
* 屬性名稱
* @param type
* 類型
* @param an
* 註解
* @param isNotNull
* 不能爲空
* @throws Exception
* e
*/
private static void validateEmail(List<String> list, String claName, Field field, Object value, String fieldName, Class type, boolean isNotNull, Annotation an) throws Exception {
Email email;
email = (Email) an;
if (String.class.equals(type)) {
if (isNotNull) {
if (null == value || !value.toString().matches(REGEXMAIL)) {
list.add("參數:[" + field.getName() + "]必須是郵箱地址;格式爲:" + REGEXMAIL);
}
} else {
if (null != value && !value.toString().matches(REGEXMAIL)) {
list.add("參數:[" + field.getName() + "]必須是郵箱地址;格式爲:" + REGEXMAIL);
}
}
} else {
throw new Exception(claName + "類中字段[" + fieldName + "]註解:validation.Email只能使用在java.lang.String上");
}
}
}
3、工具類的使用
首先定義一個javaBean
package java1.lang.annotation;
import java1.lang.annotation.validation.AssertFalse;
import java1.lang.annotation.validation.Email;
import java1.lang.annotation.validation.StringCut;
import java1.lang.annotation.validation.ToUpper;
@StringCut
public class AnnoVo {
@Email
@StringCut(minLength = 7, completion = "zxiaofan")
private String guestEmail;
@ToUpper
private String toUpper;
@StringCut(maxLength = 5)
private String cutName;
@AssertFalse
private boolean boolFalse;
/**
* 設置guestEmail.
*
* @return 返回guestEmail
*/
public String getGuestEmail() {
return guestEmail;
}
/**
* 獲取guestEmail.
*
* @param guestEmail
* 要設置的guestEmail
*/
public void setGuestEmail(String guestEmail) {
this.guestEmail = guestEmail;
}
/**
* 設置toUpper.
*
* @return 返回toUpper
*/
public String getToUpper() {
return toUpper;
}
/**
* 獲取toUpper.
*
* @param toUpper
* 要設置的toUpper
*/
public void setToUpper(String toUpper) {
this.toUpper = toUpper;
}
/**
* 設置boolFalse.
*
* @return 返回boolFalse
*/
public boolean isBoolFalse() {
return boolFalse;
}
/**
* 獲取boolFalse.
*
* @param boolFalse
* 要設置的boolFalse
*/
public void setBoolFalse(boolean boolFalse) {
this.boolFalse = boolFalse;
}
/**
* 設置cutName.
*
* @return 返回cutName
*/
public String getCutName() {
return cutName;
}
/**
* 獲取cutName.
*
* @param cutName
* 要設置的cutName
*/
public void setCutName(String cutName) {
this.cutName = cutName;
}
}
註解單元測試:
package java1.lang.annotation;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
import com.google.gson.Gson;
import java1.lang.annotation.validation.ValidationUtils;
/**
* 自定義註解,參數驗證
*
* @author zxiaofan
*/
@SuppressWarnings("unchecked")
public class DIYAnnotation_Study {
Gson gson = new Gson();
@Test
public void testDIYAnno() {
AnnoVo vo = new AnnoVo();
String param = "I am about to change Upper";
String validate = null;
try {
vo.setGuestEmail("[email protected]");
validate = ValidationUtils.validate(vo);
} catch (Exception e) {
e.printStackTrace();
}
assertNull(validate); // 斷言,爲空則參數正確
}
}
如果實體類傳入的參數GuestEmail不符合email的格式,將會報錯。
這只是自定義註解的小試牛刀,比如我們還可以實現字符串的長度判斷、是否是電話格式、是否允許爲空等等。寫好一個工具類後,只需在JavaBean需要做判斷的屬性上添加註解,並程序的入口或出口對javaBean做一次統一判斷即可。
下面介紹字符串超長截取註解@StringCut,源於前段時間公司項目開發時,發現個別字段數據超長,而這些超長數據可以直接截斷處理的,另外升庫已經不現實了,一張表好幾百萬的數據,有50多張表,再加上這些字段截取後不影響業務,也就打算直接截斷處理了,流程簡化如下:
現在主要是數據流向B_1-->數據流向B_2這條線路;可能有同學會考慮直接在接口服務A做數據截斷處理,這是不符合業務發展需要的,現在這些數據可以截斷,不代表以後也可以;那麼在B處截斷呢,不影響以後的業務,貌似可以,但是B處對數據操作實在太多,一不留神就漏了,而且以後的同學添加方法,不瞭解這事,就又悲催了。思前想後,還是直接在數據流的末尾(數據服務C)處做截斷,安全便捷。如果只想對業務服務B傳過來的數據做截斷呢,在B發向C的數據中加個參數就好。
說幹就幹,太簡單了,自定義一個註解@StringCut,如果比註解設置的maxLength大,就直接把該值截斷。看一下該註解的定義:
package java1.lang.annotation.validation;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/**
* @author github.zxiaofan.com 數據超長截取
*
* 註解於待截取字段及其類、泛型
*
*/
@Retention(RetentionPolicy.RUNTIME)
public @interface StringCut {
// 最大長度
int maxLength() default 0;
// 最小長度
int minLength() default 0;
// length小於最小長度,用該字符補上,默認"0"
String completion() default "0";
// 保留頭部
boolean isKeepHead() default true;
// 泛型
String isGeneric() default "";
// 參數或者字段描述,這樣能夠顯示友好的異常信息
String description() default "";
}
這是目前完整的註解參數,現在我們只需管maxLength這一參數即可,例如在JavaBean屬性上加上該註解,
@StringCut(maxLength = 5)
private String cutName;
再在工具類ValidationUtils 裏新增方法Object stringCut(Object obj)對@StringCut的判斷並處理截斷即可。
完了嗎?還早呢,你還要考慮截斷時保留頭部還是尾部,價格參數解決,isKeepHead(),默認保留頭部;是不是該支持最小長度呢,再加兩個參數吧,minLength()表示最小長度,如果不夠用completion()來補齊吧。大致如下:
/**
* 處理含有@StringCut的屬性,超長則截取.
*
* @param obj
* 對象
* @param an
* 註解
* @param claName
* 類型
* @param field
* 屬性
* @param value
* 屬性值
* @param fieldName
* 屬性名稱
* @param type
* 類型
* @throws Exception
* e
*/
private static void dealTooLong(Object obj, String claName, Field field, Object value, String fieldName, Class type, Annotation an) {
StringCut stringCut = (StringCut) an;
if (String.class.equals(type)) {
if (null != value && value.toString().length() > stringCut.maxLength() && stringCut.maxLength() != 0) {
try {
// WriteLogBusi.writeLocalLog(fieldName, value.toString());
if (stringCut.isKeepHead()) {
field.set(obj, value.toString().substring(0, stringCut.maxLength()));
} else {
field.set(obj, value.toString().substring(value.toString().length() - stringCut.maxLength()));
}
} catch (IllegalArgumentException | IllegalAccessException e) {
// WriteLogBusi.writeLocalLog(fieldName, e.getMessage() + e.toString());
}
}
if (null != value && value.toString().length() < stringCut.minLength()) {
int addLength = stringCut.minLength() - value.toString().length();
String addValue = "";
while (addValue.length() < addLength) {
addValue += stringCut.completion();
}
addValue = addValue.substring(0, addLength);
try {
// WriteLogBusi.writeLocalLog(fieldName, value.toString());
if (stringCut.isKeepHead()) {
field.set(obj, value.toString() + addValue);
} else {
field.set(obj, addValue + value.toString());
}
} catch (IllegalArgumentException | IllegalAccessException e) {
// WriteLogBusi.writeLocalLog(fieldName, e.getMessage() + e.toString());
}
}
}
}
大功告成,我一直這樣以爲的,然而實際應用中發現,現有的工具類實在是不夠用,dealTooLong只能對最基本的JavaBean做處理,而從業務服務B處傳過來的數據可沒那麼簡單,多層次嵌套了Map、List、泛型,簡直欲哭無淚啊,感覺自己入坑了,不能輕言放棄,反射終於派上用場了,花了兩天的休息時間,完善了該工具類,經測試,支持Map、List、Set、泛型等常見覆雜類型的嵌套,來看看吧,本來二三十行的代碼瞬間漲了好多倍,好在問題解決了。
/**
* 長度處理.
*
* @param obj
* obj
* @return obj
* @throws Exception
* Exception.
*/
public static Object stringCut(Object obj) throws Exception { // 對外入口
if (null == obj) {
return null;
}
// 處理集合
if (obj instanceof List) {
List<Class> listRealClass = getRealClass(obj);
List<Object> listNew = (List<Object>) obj.getClass().newInstance();
List<Object> listOld = (List) obj; // gson.fromJson(gson.toJson(obj), List.class);
for (int i = 0; i < listOld.size(); i++) {
Object objNew = listOld.get(i);
objNew = gson.fromJson(gson.toJson(objNew), listRealClass.get(i));
objNew = stringCut(objNew);
listNew.add(objNew);
}
obj = listNew;
} else if (obj instanceof Set) {
List<Class> listRealClass = getRealClass(obj);
Set<Object> setNew = (Set<Object>) obj.getClass().newInstance();
Set<Object> setOld = (Set) obj; // gson.fromJson(gson.toJson(obj), Set.class);
for (Object set : setOld) {
int i = 0;
Object objNew = gson.fromJson(gson.toJson(set), listRealClass.get(i));
objNew = stringCut(objNew);
setNew.add(objNew);
i++;
}
obj = setNew;
} else if (obj instanceof Map) {
Map<?, ?> mapClass = getRealClassOfMap((Map<?, ?>) obj);
Map<Object, Object> mapNew = (Map<Object, Object>) obj.getClass().newInstance();
Map<?, ?> mapOld = (Map<?, ?>) obj; // gson.fromJson(gson.toJson(obj), Map.class);
for (Entry<?, ?> set : mapOld.entrySet()) {
Iterator ite = mapClass.entrySet().iterator();
Entry entry = null;
if (ite.hasNext()) {
entry = (Entry) ite.next();
Object objKey = stringCut(set.getKey());
// objKey = gson.fromJson(set.getKey().toString(), (Class) entry.getKey());
objKey = stringCut(objKey);
Object objValue = stringCut(set.getValue());
// objValue = gson.fromJson(gson.toJson(set.getValue()), (Class) entry.getValue());
mapNew.put(objKey, objValue);
}
}
obj = mapNew;
} else { // Object 或 泛型T
cutObject(obj);
}
return obj;
}
/**
* 處理泛型Object.
*
* @param obj
* obj
* @param field
* field
* @param genericName
* genericName
* @throws IllegalAccessException
* IllegalAccessException
*/
private static void dealGenericObj(Object obj, Field field, String genericName) throws IllegalAccessException {
List<Class> listGenericClass = getGenericClass(obj, genericName);
field.setAccessible(true);
Object objGen = gson.fromJson(gson.toJson(field.get(obj)), listGenericClass.get(0)); // field.get(obj)獲取泛型對象
try {
objGen = stringCut(objGen);
} catch (Exception e) {
e.printStackTrace();
}
field.set(obj, objGen);
}
/**
* 該Object內部屬性含有泛型註解.
*
* @param field
* field
* @return isGeneric
*/
private static String isGeneric(Field field) {
Annotation[] classAnnotation = field.getAnnotations();
StringCut stringCut = null;
for (Annotation annotation : classAnnotation) {
if (annotation.annotationType().getName().equals(StringCut.class.getName())) {
stringCut = (StringCut) annotation;
if (!"".equals(stringCut.isGeneric())) {
return stringCut.isGeneric();
}
}
}
return "";
}
/**
* Object有StringCut註解.
*
* @param obj
* obj
* @return hasAnno
*/
private static boolean hasAnnoOfStringCut(Object obj) {
Annotation[] classAnnotation = obj.getClass().getAnnotations();
boolean isTooLong = false;
for (Annotation annotation : classAnnotation) {
Class annotationType = annotation.annotationType();
if (annotationType.getName().equals(StringCut.class.getName())) {
isTooLong = true;
break;
}
}
return isTooLong;
}
/**
* 處理非集合類Object,包括泛型.
*
* @param obj
* obj
*/
private static void cutObject(Object obj) {
Field[] fields = obj.getClass().getDeclaredFields();
String claName = obj.getClass().getName();
for (Field field : fields) {
try {
String isGeneric = isGeneric(field);
if (!"".equals(isGeneric)) {
dealGenericObj(obj, field, isGeneric);
} else {
dealBasicObject(obj, claName, field);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
private static List<Class> getGenericClass(Object obj, String genericName) throws IllegalAccessException {
List<Class> listGenericClass = new ArrayList<>();
Field[] fds = obj.getClass().getDeclaredFields();
for (Field fd : fds) {
fd.setAccessible(true);
if (genericName.equals(fd.getName())) {
listGenericClass.add(fd.get(obj).getClass());
}
}
return listGenericClass;
}
private static List<Class> getRealClass(Object obj) throws IllegalAccessException {
List<Class> listRealClass = new ArrayList<>();
Field[] fields = obj.getClass().getDeclaredFields();
if (obj instanceof List) {
for (Field fd : fields) {
fd.setAccessible(true);
if ("elementData".equals(fd.getName())) {
Object[] objs = (Object[]) fd.get(obj);
for (Object object : objs) {
if (null != object) {
listRealClass.add(object.getClass());
}
}
}
}
} else if (obj instanceof Set) {
for (Field fd : fields) {
if ("map".equals(fd.getName())) {
fd.setAccessible(true);
Map<?, ?> map = (Map<?, ?>) fd.get(obj);
for (Object object : map.keySet()) {
if (null != object) {
listRealClass.add(object.getClass());
}
}
}
}
} else {
listRealClass.add(obj.getClass());
}
return listRealClass;
}
private static Map<Object, Object> getRealClassOfMap(Map<?, ?> obj) throws IllegalAccessException {
Map<Object, Object> map = new HashMap();
Field[] fields = obj.getClass().getDeclaredFields();
for (Field fd : fields) {
fd.setAccessible(true);
if ("table".equals(fd.getName())) {
Object key = null;
Object value = null;
for (Entry<?, ?> entry : obj.entrySet()) {
if (null != entry) {
Class keyClass = getRealClass(entry.getKey()).get(0);
Type typeKey = null;
if (entry.getKey() instanceof List) {
typeKey = new SpecialParameterizedType(List.class, keyClass);
} else if (entry.getKey() instanceof Set) {
typeKey = new SpecialParameterizedType(Set.class, keyClass);
} else if (entry.getKey() instanceof Map) {
typeKey = new SpecialParameterizedType(Map.class, keyClass);
}
Class valueClass = getRealClass(entry.getValue()).get(0);
Type typeValue = null;
if (entry.getValue() instanceof List) {
typeValue = new SpecialParameterizedType(List.class, keyClass);
} else if (entry.getValue() instanceof Set) {
typeValue = new SpecialParameterizedType(Set.class, keyClass);
} else if (entry.getValue() instanceof Map) {
typeValue = new SpecialParameterizedType(Map.class, keyClass);
}
if (null != typeKey) {
key = typeKey;
} else {
key = keyClass;
}
if (null != typeValue) {
value = typeValue;
} else {
value = valueClass;
}
map.put(key, value);
}
}
}
}
return map;
}
/**
* 處理基礎Object.
*
* @param obj
* 參數
* @param claName
* 類名
* @param field
* 屬性
* @throws Exception
* e
*/
private static void dealBasicObject(Object obj, String claName, Field field) throws Exception {
boolean isTooLong = hasAnnoOfStringCut(obj);
if (!isTooLong) {
return;
}
Date date;
Object value;
String fieldName;
Class type;
type = field.getType();
fieldName = field.getName();
Annotation[] annotations = field.getAnnotations();
field.setAccessible(true);
value = field.get(obj);
if (type.isPrimitive() || String.class.getName().equals(type.getName())) {
for (Annotation an : annotations) {
if (an.annotationType().getName().equals(StringCut.class.getName())) {
dealTooLong(obj, claName, field, value, fieldName, type, an);
}
}
} else {
value = stringCut(value);
field.set(obj, value);
}
}
private static Class getRealClass(Field field) {
Type fc = field.getGenericType();
ParameterizedType pt = null;
try {
if (fc instanceof ParameterizedType) {
pt = (ParameterizedType) fc;
}
} catch (Exception e) {
e.printStackTrace();
}
Class genericClazz = null;
if (null != pt) {
genericClazz = (Class) pt.getActualTypeArguments()[0];
}
return genericClazz;
}
/**
* 處理含有@StringCut的屬性,超長則截取.
*
* @param obj
* 對象
* @param an
* 註解
* @param claName
* 類型
* @param field
* 屬性
* @param value
* 屬性值
* @param fieldName
* 屬性名稱
* @param type
* 類型
* @throws Exception
* e
*/
private static void dealTooLong(Object obj, String claName, Field field, Object value, String fieldName, Class type, Annotation an) {
StringCut stringCut = (StringCut) an;
if (String.class.equals(type)) {
if (null != value && value.toString().length() > stringCut.maxLength() && stringCut.maxLength() != 0) {
try {
// WriteLogBusi.writeLocalLog(fieldName, value.toString());
if (stringCut.isKeepHead()) {
field.set(obj, value.toString().substring(0, stringCut.maxLength()));
} else {
field.set(obj, value.toString().substring(value.toString().length() - stringCut.maxLength()));
}
} catch (IllegalArgumentException | IllegalAccessException e) {
// WriteLogBusi.writeLocalLog(fieldName, e.getMessage() + e.toString());
}
}
if (null != value && value.toString().length() < stringCut.minLength()) {
int addLength = stringCut.minLength() - value.toString().length();
String addValue = "";
while (addValue.length() < addLength) {
addValue += stringCut.completion();
}
addValue = addValue.substring(0, addLength);
try {
// WriteLogBusi.writeLocalLog(fieldName, value.toString());
if (stringCut.isKeepHead()) {
field.set(obj, value.toString() + addValue);
} else {
field.set(obj, addValue + value.toString());
}
} catch (IllegalArgumentException | IllegalAccessException e) {
// WriteLogBusi.writeLocalLog(fieldName, e.getMessage() + e.toString());
}
}
}
}
大致思路爲:
1、先判斷傳入的JavaBean類型,Map、List、Set、普通Bean(含泛型),對前面幾種集合類型依次循環遍歷,調用stringCut(Object obj)方法處理,也就是說,進入cutObject(Object obj) 的Bean最外層不能是集合;
2、cutObject方法判斷傳入的Bean內是否包含泛型,對泛型部分取出數據再次調用stringCut(Object obj)方法處理,非泛型Bean直接調用dealBasicObject(Object obj, String claName, Field field)方法處理;
3、dealBasicObject方法判斷傳入的數據類型是否是String類型,實則調用dealTooLong做截斷操作,不是String類型且不是基本類型則繼續調用stringCut方法處理;
4、List<Class> getRealClass(Object obj)犯法獲取List、Set和基本Bean的Class類型;Map<Object, Object> getRealClassOfMap(Map<?, ?> obj)獲取Map的各鍵值對的class類型。
輪子完成,歡迎各位朋友使用並提出寶貴意見。該工具類中個別註解源於學習牛人代碼,自己新增註解時深受啓發,附上該工具類的完整鏈接:https://github.com/zxiaofan/JDK-Study/tree/master/src/java1/lang/annotation/validation,除以上提到的註解外,還包含null校驗、數字、時間等的校驗,以及大小寫轉換等。另外,https://github.com/zxiaofan/JavaUtils也包含常用工具類,歡迎使用,分享。
不喜歡重複且沒技術含量的工作,要麼沉淪要麼改變。
歡迎個人轉載,但須在文章頁面明顯位置給出原文連接;
未經作者同意必須保留此段聲明、不得隨意修改原文、不得用於商業用途,否則保留追究法律責任的權利。
【 CSDN 】:csdn.zxiaofan.com
【GitHub】:github.zxiaofan.com
如有任何問題,歡迎留言。祝君好運!
Life is all about choices!
將來的你一定會感激現在拼命的自己!