SSM框架中的前後端分離
認識前後端分離
在傳統的web應用開發中,大多數的程式設計師會將瀏覽器作為前後端的分界線。將瀏覽器中為使用者進行頁面展示的部分稱之為前端,而將執行在伺服器,為前端提供業務邏輯和資料準備的所有程式碼統稱為後端。
由於前後端分離這個概念相對來說剛出現不久,很多人都是隻聞其聲,不見其形,所以可能會對它產生一些誤解,誤以為前後端分離只是一種web應用開發模式,只要在web應用的開發期進行了前後端開發工作的分工就是前後端分離。
其實前後端分離並不只是開發模式,而是web應用的一種架構模式
。在開發階段,前後端工程師約定好資料互動介面,實現並行開發和測試;在執行階段前後端分離模式需要對web應用進行分離部署,前後端之前使用HTTP或者其他協議進行互動請求。然而作為一種架構模式,我們在實施的過程中主要對以下四個方面來進行比較和重新認識。
前後端分離大概可以從四個方面來理解:
- 互動形式
- 程式碼組織方式
- 開發模式
- 資料介面規範流程
一、互動形式
在前後端分離架構中,後端只需要負責按照約定的資料格式向前端提供可呼叫的API服務即可。前後端之間通過HTTP請求進行互動,前端獲取到資料後,進行頁面的組裝和渲染,最終返回給瀏覽器。
二、程式碼組織方式
在傳統架構模式中,前後端程式碼存放於同一個程式碼庫中,甚至是同一工程目錄下。頁面中還夾雜著後端程式碼。前後端工程師進行開發時,都必須把整個專案匯入到開發工具中。
而前後端分離模式在程式碼組織形式上有以下兩種:
- 半分離
前後端共用一個程式碼庫,但是程式碼分別存放在兩個工程中。後端不關心或很少 關心前端元素的輸出情況,前端不能獨立進行開發和測試,專案中缺乏前後端 互動的測試用例。 - 分離
前後端程式碼庫分離,前端程式碼中有可以進行Mock測試(通過構造虛擬測試對 象以簡化測試環境的方法)的偽後端,能支援前端的獨立開發和測試。而後端 程式碼中除了功能實現外,還有著詳細的測試用例,以保證API的可用性,降低 整合風險。
三、開發模式
我們之前的架構屬於傳統的MVC架構,整體沒有進行前後端分離,在專案的開發階段,前端工程師負責編寫HTML,完成前端的頁面設計並套頁面,然後再使用模板技術將寫好的前端程式碼轉換為Smarty指令碼,同時內嵌一些後端提供的模板變數和一些邏輯操作。應用執行期,將全部程式碼進行打包,和後端程式碼部署到同一伺服器上,同時會進行簡單的動靜態分離部署。
此時,應用的開發流程如下圖所示。
而在實現前後端分離架構之後,前端工程師只需要編寫HTML、js、CSS等前端資源,然後通 過HTTP請求呼叫後端提供的服務即可。除了開發期的分離,在執行期前後端資源也 會進行分離部署。
前後端分離之後,開發流程將如下圖所示。
通過上面的兩幅流程圖,不難發現,在開發模式上,前後段分離不僅僅只是工程師的分工開發,更重要的意義在於實現了前後端的並行開發,簡化了開發流程
。
四、資料介面規範流程
在開發期間前後端共同商定好資料介面的互動形式和資料格式。然後實現前後端的並行開發,其中前端工程師再開發完成之後可以獨自進行mock測試,而後端也可以使用介面測試平臺進行介面自測,然後前後端一起進行功能聯調並校驗格式,最終進行自動化測試。
分離的四個好處
前後端分離模式和傳統的web應用架構相比有很大的不同,到底分還是不分,這還真是個問題。
從目前應用軟體開發的發展趨勢來看,主要有兩方面需要注意:
- 越來越注重使用者體驗,隨著網際網路的發展,開始多終端化。
- 大型應用架構模式正在向雲化、微服務化發展。
我們主要通過前後端分離架構,為我們帶來以下四個方面的提升:
- 為優質產品打造精益團隊
通過將開發團隊前後端分離化,讓前後端工程師只需要專注於前端或後端的開發工作,是的前後端工程師實現自治,培養其獨特的技術特性,然後構建出一個全棧式的精益開發團隊。 - 提升開發效率
前後端分離以後,可以實現前後端程式碼的解耦,只要前後端溝通約定好應用所需介面以及介面引數,便可以開始並行開發,無需等待對方的開發工作結束。與此同時,即使需求發生變更,只要介面與資料格式不變,後端開發人員就不需要修改程式碼,只要前端進行變動即可。如此一來整個應用的開發效率必然會有質的提升。 - 完美應對複雜多變的前端需求
如果開發團隊能完成前後端分離的轉型,打造優秀的前後端團隊,開發獨立化,讓開發人員做到專注專精,開發能力必然會有所提升,能夠完美應對各種複雜多變的前端需求。 - 增強程式碼可維護性
前後端分離後,應用的程式碼不再是前後端混合,只有在執行期才會有呼叫依賴關係。
應用程式碼將會變得整潔清晰,不論是程式碼閱讀還是程式碼維護都會比以前輕鬆。
以上轉自前後端分離實踐(一)
利用JSON串來實現前後端分離
在自己最近做的專案中,使用的是利用SSM框架中的controller層來傳出JSON串,再通過jQuery中的.getJSON()
來進行解析,再將資料傳到前端頁面。
controller層
部分程式碼省略,放出關鍵程式碼:
ShopManagementController
@Controller
@RequestMapping("/shopadmin")
public class ShopManagementController {
@Autowired
private ShopService shopService;
@Autowired
private ShopCategoryService shopCategoryService;
@Autowired
private AreaService areaService;
@RequestMapping(value = "/getshopmanagementinfo",method = RequestMethod.GET)
@ResponseBody
private Map<String,Object> getShopManagementInfo(HttpServletRequest request) {
Map<String,Object> modelMap = new HashMap<String, Object>();
long shopId = HttpServletRequestUtil.getLong(request,"shopId");
if(shopId <= 0){
Object currentShopObj = request.getSession().getAttribute("shopId");
if(currentShopObj == null){
modelMap.put("redirect",true);
modelMap.put("url","/shopadmin/shoplist");
}else {
Shop currentShop = (Shop) currentShopObj;
modelMap.put("redirect",false);
modelMap.put("shopId",currentShop.getShopId());
}
}else{
Shop currentShop = new Shop();
currentShop.setShopId(shopId);
request.getSession().setAttribute("currentShop",currentShop);
modelMap.put("redirect",false);
}
return modelMap;
}
@RequestMapping(value = "/getshoplist",method = RequestMethod.GET)
@ResponseBody
private Map<String,Object> getShopList(HttpServletRequest request){
Map<String,Object> modelMap = new HashMap<String, Object>();
PersonInfo user = new PersonInfo();
user.setUserId(1L);
user.setName("test");
request.getSession().setAttribute("user",user);
user = (PersonInfo) request.getSession().getAttribute("user");
try{
Shop shopCondition;
shopCondition = new Shop();
shopCondition.setOwner(user);
ShopExecution se = shopService.getShopList(shopCondition,0,100);
modelMap.put("shopList",se.getShopList());
modelMap.put("user",user);
modelMap.put("success",true);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
}
return modelMap;
}
@RequestMapping(value = "/getshopbyid",method = RequestMethod.GET)
@ResponseBody
private Map<String,Object> getShopById(HttpServletRequest request){
Map<String,Object> modelMap = new HashMap<String,Object>();
Long shopId = HttpServletRequestUtil.getLong(request,"shopId");
if(shopId > -1){
try {
Shop shop = shopService.getByShopId(shopId);
List<Area> areaList = areaService.getAreaList();
modelMap.put("shop",shop);
modelMap.put("areaList",areaList);
modelMap.put("success",true);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
}
}else {
modelMap.put("success",false);
modelMap.put("errMsg","empty shopId");
}
return modelMap;
}
@RequestMapping(value = "/getshopinitinfo",method = RequestMethod.GET)
@ResponseBody
public Map<String,Object> getShopInitInfo(){
Map<String,Object> modelMap = new HashMap<String, Object>();
List<ShopCategory> shopCategoryList = new ArrayList<ShopCategory>();
List<Area> areaList = new ArrayList<Area>();
try {
shopCategoryList = shopCategoryService.getShopCategoryList(new ShopCategory());
areaList = areaService.getAreaList();
modelMap.put("shopCategoryList",shopCategoryList);
modelMap.put("areaList",areaList);
modelMap.put("success",true);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
}
return modelMap;
}
@RequestMapping(value = "/modifyshop",method = {RequestMethod.POST})
@ResponseBody
public Map<String,Object> modifyShop(HttpServletRequest request) throws IOException {
Map<String,Object> modelMap = new HashMap<String, Object>();
//判斷驗證碼是否正確
if(!CodeUtil.checkVerifyCode(request)){
modelMap.put("success",false);
modelMap.put("errMsg","輸入了錯誤的驗證碼");
return modelMap;
}
//1.接收並轉化相應的引數,包括店鋪資訊以及圖片資訊
String shopStr = HttpServletRequestUtil.getString(request,"shopStr");
ObjectMapper mapper = new ObjectMapper();
Shop shop = null;
try {
shop = mapper.readValue(shopStr,Shop.class);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
return modelMap;
}
CommonsMultipartFile shopImg = null;
CommonsMultipartResolver commonsMultipartResolver =new CommonsMultipartResolver(
request.getSession().getServletContext());
// 檢測檔案是否有上傳檔案流
if (commonsMultipartResolver.isMultipart(request)){
MultipartHttpServletRequest multipartHttpServletRequest = (MultipartHttpServletRequest) request;
shopImg = (CommonsMultipartFile) multipartHttpServletRequest.getFile("shopImg");
}
//2.修改店鋪資訊
if(shop != null && shop.getShopId() != null){
PersonInfo owner =(PersonInfo) request.getSession().getAttribute("user");
// session
owner.setUserId(1L);
shop.setOwner(owner);
ShopExecution se;
try {
if (shopImg == null){
se = shopService.modifyShop(shop, new ImageHolder(null, null));
}else {
se = shopService.modifyShop(shop,
new ImageHolder(shopImg.getInputStream(), shopImg.getOriginalFilename()));
}
if(se.getState()==ShopStateEnum.SUCCESS.getState()){
modelMap.put("success",true);
}else {
modelMap.put("success",false);
modelMap.put("errMsg",se.getStateInfo());
}
}catch (ShopOperationException e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
} catch (IOException e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
}
return modelMap;
}else {
modelMap.put("success",false);
modelMap.put("errMsg","請輸入店鋪Id");
return modelMap;
}
}
}
拿getShopById
做分析
//返回/getshopbyid的url,方法為get
@RequestMapping(value = "/getshopbyid",method = RequestMethod.GET)
//返回JSON串
@ResponseBody
private Map<String,Object> getShopById(HttpServletRequest request){
//初始化要返回的JSON串
Map<String,Object> modelMap = new HashMap<String,Object>();
//從Http請求中獲得shopId值
Long shopId = HttpServletRequestUtil.getLong(request,"shopId");
if(shopId > -1){
try {
/**
* 下面程式碼做service層查詢,獲得商店Id為shopId的商店資訊
*/
Shop shop = shopService.getByShopId(shopId);
List<Area> areaList = areaService.getAreaList();
modelMap.put("shop",shop);
modelMap.put("areaList",areaList);
modelMap.put("success",true);
}catch (Exception e){
modelMap.put("success",false);
modelMap.put("errMsg",e.getMessage());
}
}else {
modelMap.put("success",false);
modelMap.put("errMsg","empty shopId");
}
return modelMap;
}
再來看看前端頁面是如何處理的。
(function() {
var shopId = getQueryString('shopId');
var isEdit = shopId?true:false;
var initUrl = '/shopadmin/getshopinitinfo';
var registerShopUrl = '/shopadmin/registershop';
//訪問/shopadmin/getshopbyid這個url
var shopInfoUrl = "/shopadmin/getshopbyid?shopId=" + shopId;
//從/shopadmin/getshopbyid?shopId這個url中獲得controller層返回的JSON串,通過$.getJSON函式進行解析。
//獲取相關的鍵值對,然後插入前端HTML頁面。
function getShopInfoUrl(shopId) {
$.getJSON(shopInfoUrl,function (data) {
if(data.success){
var shop = data.shop;
$('#shop-name').val(shop.shopName);
$('#shop-addr').val(shop.shopAddr);
$('#shop-phone').val(shop.phone);
$('#shop-desc').val(shop.shopDesc);
var shopCategory = '<option data-id="'
+ shop.shopCategory.shopCategoryId +'"selected>'
+ shop.shopCategory.shopCategoryName +'</option>';
var tempAreaHtml = '';
data.areaList.map(function (item,index) {
tempAreaHtml += '<option data-id="' + item.areaId+'"selected>'
+ item.areaName+'</option>';
});
$('#shop-category').html(shopCategory);
$('#shop-category').attr('disable','disable');
$('#shop-area').html(tempAreaHtml);
$("#shop-area option[data-id='"+shop.area.areaId+"']").attr("selected","selected");
}
});
}
訪問url,即可獲得商店資訊
流程圖: