jeesite的陷阱需要注意

藍星花發表於2017-11-15

後端

  • 核心框架:Spring Framework 4.0

  • 安全框架:Apache Shiro 1.2

  • 檢視框架:Spring MVC 4.0

  • 服務端驗證:Hibernate Validator 5.1

  • 佈局框架:SiteMesh 2.4

  • 工作流引擎:Activiti 5.15、FoxBPM 6

  • 任務排程:Spring Task 4.0

  • 持久層框架:MyBatis 3.2

  • 資料庫連線池:Alibaba Druid 1.0

  • 快取框架:Ehcache 2.6、Redis

  • 日誌管理:SLF4J 1.7、Log4j

  • 工具類:Apache Commons、Jackson 2.2、Xstream 1.4、Dozer 5.3、POI 3.9

2、前端

  • JS框架:jQuery 1.9。

  • CSS框架:Twitter Bootstrap 2.3.1。

  • 客戶端驗證:JQuery Validation Plugin 1.11。

  • 富文字:CKEcitor

  • 檔案管理:CKFinder

  • 動態頁籤:Jerichotab

  • 手機端框架:Jingle

  • 資料表格:jqGrid

  • 對話方塊:jQuery jBox

  • 下拉選擇框:jQuery Select2

  • 樹結構控制元件:jQuery zTree

  • 日期控制元件: My97DatePicker


這裡對於jeesite,感覺其功能還是挺強大的,但是有一點致命缺點,就是其快取機制,本來快取是為了提速,但是,當這裡的快取加上了MVC,並且在前端進行請求後,不適時宜地將請求的相關類物件進行快取,這就導致了單例化和偽持久化。怎麼說來?就是說,當前端修改Person物件例項,並提交到服務端試圖儲存時,由於某些原因,如許可權不足導致儲存失敗,這本來應該是很正常的,但是,偏偏由於在這之前,快取將Person物件例項更新了,從而快取中的該例項是修改後的,這樣,後來再次獲取該物件,由於快取存在,優先取快取而不是從DB裡獲取,導致,後來獲取的物件的資料都是錯誤的(修改但儲存失敗的),這就變相單例化,而且是無法獲得正確資料了。

例如如下的介面

[java] view plain copy
  1. @RequiresPermissions("sys:user:edit")  
  2.     @RequestMapping(value = "save")  
  3.     public String save(User user, HttpServletRequest request, Model model, RedirectAttributes redirectAttributes) {  
  4.   
  5.           
  6.         //判斷是否有許可權修改使用者資訊  
  7.           
  8.         //先清快取:因為框架原因,只要更新了該使用者,則會同步更新該使用者快取,從而無法獲得真正的該使用者資訊,所以需要清除掉該快取,這裡先註釋掉,看問題  
  9.         //UserUtils.clearCache(user);  
  10.         User oldUser = systemService.getUser(user.getId());  
  11.         List<String>roleIdListOld = oldUser.getRoleIdList();  
  12.         User operator = UserUtils.getUser();  
  13.         List<String>roleIdListOperator = operator.getRoleIdList();  
  14.         //自己不能修改自己的許可權  
  15. //      if(user.getId().equals(operator.getId())){  
  16. //          addMessage(model, "修改使用者資訊失敗, 不能修改自己的許可權");  
  17. //          UserUtils.clearCache();  
  18. //          return form(oldUser, model);  
  19. //          }  
  20.         if(!roleIdListOperator.containsAll(roleIdListOld)){  
  21.             addMessage(model, "修改使用者資訊失敗, 您的許可權不足");  
  22.             UserUtils.clearCache();  
  23.             return form(oldUser, model);  
  24.         }  
  25.         user.setRoleList(roleList);  
  26.         // 儲存使用者資訊  
  27.         systemService.saveUser(user);  
  28.         // 清除當前使用者快取  
  29.         if (user.getPhone().equals(UserUtils.getUser().getPhone())){  
  30.             UserUtils.clearCache();  
  31.             //UserUtils.getCacheMap().clear();  
  32.         }  
  33.         addMessage(redirectAttributes, "儲存使用者'" + user.getPhone() + "'成功");  
  34.         return "redirect:" + adminPath + "/sys/user/list?repage";  
  35.     }  

再看下getUser:

[java] view plain copy
  1. public static User getUser(String id){  
  2.     User user = (User)CacheUtils.get(USER_CACHE, USER_CACHE_ID_ + id);  
  3.     if (user ==  null){  
  4.         user = userDao.get(id);  
  5.         if (user == null){  
  6.             return null;  
  7.         }  
  8.         user.setRoleList(roleDao.findList(new Role(user)));  
  9.         CacheUtils.put(USER_CACHE, USER_CACHE_ID_ + user.getId(), user);  
  10.         CacheUtils.put(USER_CACHE, USER_CACHE_LOGIN_NAME_ + user.getPhone(), user);  
  11.     }  
  12.     return user;  
  13. }  


這裡的

[java] view plain copy
  1. systemService.getUser(user.getId());  
會一直拿到該物件例項的快取值,而該值,在修改提交到服務端時,框架已經更新了,再進到controller中。


所以,即使在

[java] view plain copy
  1. if(!roleIdListOperator.containsAll(roleIdListOld)){  
  2.             addMessage(model, "修改使用者資訊失敗, 您的許可權不足");  
  3.             UserUtils.clearCache();  
  4.             return form(oldUser, model);  
  5.         }  
這裡返回了,其他地方獲取該user的值
[java] view plain copy
  1. getUser(user.getId());  
還是會是快取的值。

也相當於單例的、全域性的例項值

解決方法:

在關係到修改等的地方,每次都需要對該例項進行快取的清空。同時,在修改時,修改物件最好就是拿出db的該記錄,逐個引數進行修改替換:



[java] view plain copy
  1. @RequiresPermissions("user:list:edit")  
  2.     @RequestMapping(value = "editUserInfoSave")  
  3.     public String editUserInfoSave(User user,Model model, RedirectAttributes redirectAttributes) {  
  4.           
  5.         //先清除該user的快取,防止干擾到其他地方的引用。其實還是會有併發問題,會在清除之前被引用到  
  6.         UserUtils.clearCache(user);  
  7.                 //從db中獲取user,注意這個userSave 是修改前的,與user的值不一樣,注意一點:如果直接從getUser(user.getId());中獲取,同時並沒有清快取的前提下  
  8.                 //UserUtils.clearCache(user);則會導致拿到的user並非DB裡的user,而是快取前端提交的                  
  9.                 User userSave = systemService.getUserFromDB(user.getId());  
  10.         /** 
  11.          * 替換更新修改資訊 
  12.          */  
  13.         userSave.setName(user.getName());  
  14.         userSave.setFirstnameStr(user.getFirstnameStr());  
  15.         userSave.setLastnameStr(user.getLastnameStr());  
  16.         userSave.setIdStr(user.getIdStr());  
  17.         userSave.setUsername(user.getUsername());  
  18.         userSave.setBirthdateStr(user.getBirthdateStr());  
  19.         userSave.setEmail(user.getEmail());  
  20.         userSave.setUserType(user.getUserType());  
  21.         userSave.setGenderStr(user.getGenderStr());  
  22.         // 儲存使用者資訊  
  23.         systemService.saveUser(userSave);  
  24.         addMessage(redirectAttributes, "儲存使用者'" + user.getPhone() + "'成功");  
  25.         return "redirect:" + adminPath + "/user/user/list?repage";  
  26.     }  

這裡的getUserFromDB:

[java] view plain copy
  1. /** 
  2.  * 根據ID獲取使用者——通過DB 
  3.  * @param id 
  4.  * @return 取不到返回null 
  5.  */  
  6. public static User getUserFromDB(String id){  
  7.   
  8.     User user = userDao.get(id);  
  9.     user.setRoleList(roleDao.findList(new Role(user)));  
  10.     return user;  
  11. }  


因此特別需要注意快取的使用,不是任何地方都適合使用快取。





相關文章