只因資料過濾,方可模擬beanutils框架
導讀
上一篇文章已經詳細介紹了 框架與RTTI的關係,RTTI與反射之間的關係 。其中詳細介紹了框架與反射的關係,這也是很多培訓機構把反射作為高階教程來講解。
其實,我工作年限也不長,大概八九個月吧。但我見過很多技術人員,而我喜歡與別人討論技術。從中也知道了,很多公司沒有實現資料過濾。
什麼是資料過濾?比如客戶端向伺服器端傳送展示專案圖片的請求,服務端接收到前端的請求並從資料庫中拿到專案圖片的物件,我們只要返回圖片的在服務端的地址和名稱即可,沒必要將整個圖片物件返回給客戶端,因為,那樣將會造成資料的冗餘。因而,我們這時需要過濾資料(物件),如程式碼所示:
/** * 常用的把圖片轉成{id: 1, path: "xxx"}結構 */ public static JSONObject img2Json(Picture picture) { if (isNotNull(picture)) { String[] PICTURE_JSON = {"id", "remoteRelativeUrl:path"}; JSONObject jsonObject = propsFilter(picture, PICTURE_JSON); return jsonObject; } else { return null; } }
如上訴程式碼的轉換,公司使用的是commons-beanutils這個框架。我們只要在專案中農新增其maven配置即可:
<dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency>
我個人比較喜歡研究原始碼,於是,仿照這個框架寫了自己的框架,下面,就是介紹我個人的框架。
我的beanutils框架
-
框架使用的演算法或技術
- 遞迴演算法。我們並不推薦使用遞迴,因為,方法自呼叫自己。根據JVM的內部原理,每個方法都是一個方法棧。而棧是存放資料的一種結構,其採用FIFO(First In Last Out),即先進後出。和我們堆放菜盤一樣,先壘的最後拿出來。既然是資料儲存,肯定會超出容量,因為,記憶體不是無限大的,恰如水滿自溢。但是,我們在這裡還是使用遞迴,因為,在深度呼叫演算法當中,採用遞迴是合適的。
- java的反射機制。我們根據Javabean的屬性名稱獲取值。
- 核心演算法說明。
如果javabean的物件屬性型別不是使用者自定義的型別,我們根據反射呼叫get方法拿到屬性的值
如果javabean的物件屬性型別是使用者自定義的型別,我們利用遞迴重新呼叫改方法,直到出現遇見上面的條件
/** * Created By zby on 20:28 2019/2/13 * * @param bean實體物件 * @param props 屬性名稱 */ public static Object getProperty(Object bean, String props) { if (bean == null) throw new RuntimeException("例項化物件不存在bean=" + bean); if (StringHelper.isBlank(props)) throw new RuntimeException("屬性名稱不存在props=" + props); Class<?> clazz = null; String methodName = null; String fieldName = null; String typeName = null; try { clazz = bean.getClass(); if (props.indexOf(".") != -1) { methodName = MethodHelper.propsToGetMethod(props.substring(0, props.indexOf("."))); Method method = clazz.getDeclaredMethod(methodName); Object obj = method.invoke(bean); return getProperty(obj, props.substring(props.indexOf(".") + 1)); } Field field = clazz.getDeclaredField(props); typeName = field.getType().getName(); if (typeName.equalsIgnoreCase("boolean")) { field.setAccessible(true); return field.getBoolean(bean); } methodName = MethodHelper.propsToGetMethod(props); Method method = clazz.getDeclaredMethod(methodName); return method.invoke(bean); } catch (NoSuchMethodException e) { logger.error(clazz + "型別沒有" + methodName + "方法"); e.printStackTrace(); } catch (NoSuchFieldException e) { logger.error(clazz + "型別沒有" + fieldName + "屬性"); e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } return null; }
如何使用上訴演算法
我們既然是通過屬性名稱來獲取屬性物件。我們可以設計一個演算法,演算法該演算法有兩個引數,一個是當前物件,一個是物件的屬性陣列。屬性陣列還可以有別名。
為什麼需要別名?比如當前物件採用組合關係,使用自定義的類。比如說訂單類使用使用者類(User)的物件作為屬性,我們在訂單中希望看到使用者姓名,我們可以這樣呼叫user.name,以該欄位傳給客戶端,但客戶端需要轉換才能拿到使用者名稱,因而,我們需要一個別名,前端不用轉換,就可以拿到使用者名稱,比如:user.name:username。
當然,我們需要將物件轉化為json格式的框架, 這裡使用的阿里巴巴的fastjson框架,我們可以在專案中配置maven:
<dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.28</version> </dependency>
所示,演算法設計為:
/** * Created By zby on 9:40 2019/2/13 * 模擬框架中的資料 * * @param object 引數物件 * @param propsString型別的變長陣列,比如{"id:projectId", "mobile",...} *前半部分是javabean的屬性名,後半部分是返回到給客戶端的引數名 */ public static JSONObject propsFilter(Object object, String... props) { //【1】判斷物件和變長陣列的是否為空,以及變長陣列的長度是否為0 boolean isNull = object == null || (null == props && props.length == 0); if (isNull) { logger.warn("引數為空object=" + object + "props=" + props); return null; } JSONObject jsonObject = new JSONObject(); for (String prop : props) { //【2】再判斷物件不為空,或者長度不為0 if (prop == null && prop.length() == 0) { logger.warn("引數為空prop= " + prop); throw new RuntimeException("引數為空prop=" + prop); } Object o = null; String[] namePair = StringUtils.split(prop, ":"); try { o = PropertyUtil.getProperty(object, namePair[0]); } catch (Exception e) { logger.warn("類" + object.getClass() + ",屬性" + namePair[0] + "不存在"); } String key = namePair.length <= 1 ? namePair[0] : namePair[1]; if (o instanceof Date) jsonObject.put(key, DateUtil.simpleFormate((Date) o)); else if (o instanceof BigDecimal) jsonObject.put(key, CommonUtil.toFiexd((BigDecimal) o, 2)); else if (o instanceof TitleEnum) jsonObject.put(key, CommonUtil.builderEnum((TitleEnum) o)); else jsonObject.put(key, o); } return jsonObject; }
測試框架和類
我們既然寫好了這個框,也使用了這個框架,因而,我們可以使用Junit來測試:
@Test public void test(){ Address address = new Address(); address.setAddressTag(AddressTagEnum.ADDRESS_TAG_COMPANY); address.setArea("杭州市...."); address.setConsignee("zby"); User user = new User(); user.setHobby(HobbyEnum.HOBBY_DANCING); user.setGender("男"); user.setUserName("蔣三"); OrderSnapshot orderSnapshot = new OrderSnapshot(); orderSnapshot.setAddress(address); orderSnapshot.setId(1L); orderSnapshot.setName("復讀機"); orderSnapshot.setOrderNo(Long.valueOf(System.currentTimeMillis()).toString() + "1L"); orderSnapshot.setUser(user); String[] json = {"address.consignee:consignee","user.hobby:hobby", "address.addressTag:addressTag", "address.area:area" ,"address.consignee:consignee","user.userName:userName"}; System.out.println(JsonUtil.propsFilter(orderSnapshot, json));
測試結果為:
可見,我們演算法時成功的。
總結
我們還是要時常看原始碼,因為,你的目的不是寫出框架,而是看別人寫框架的思想。畢竟,思想主導一切行為,行為成就一個的未來。致努力的自己。