SSM框架中的前后端分離

認(rèn)識(shí)前后端分離

在傳統(tǒng)的web應(yīng)用開發(fā)中埋合,大多數(shù)的程序員會(huì)將瀏覽器作為前后端的分界線。將瀏覽器中為用戶進(jìn)行頁面展示的部分稱之為前端逾冬,而將運(yùn)行在服務(wù)器娇妓,為前端提供業(yè)務(wù)邏輯和數(shù)據(jù)準(zhǔn)備的所有代碼統(tǒng)稱為后端。

由于前后端分離這個(gè)概念相對(duì)來說剛出現(xiàn)不久辫塌,很多人都是只聞其聲漏策,不見其形,所以可能會(huì)對(duì)它產(chǎn)生一些誤解臼氨,誤以為前后端分離只是一種web應(yīng)用開發(fā)模式掺喻,只要在web應(yīng)用的開發(fā)期進(jìn)行了前后端開發(fā)工作的分工就是前后端分離。

其實(shí)前后端分離并不只是開發(fā)模式储矩,而是web應(yīng)用的一種架構(gòu)模式感耙。在開發(fā)階段,前后端工程師約定好數(shù)據(jù)交互接口持隧,實(shí)現(xiàn)并行開發(fā)和測(cè)試即硼;在運(yùn)行階段前后端分離模式需要對(duì)web應(yīng)用進(jìn)行分離部署,前后端之前使用HTTP或者其他協(xié)議進(jìn)行交互請(qǐng)求屡拨。然而作為一種架構(gòu)模式只酥,我們?cè)趯?shí)施的過程中主要對(duì)以下四個(gè)方面來進(jìn)行比較和重新認(rèn)識(shí)。

前后端分離大概可以從四個(gè)方面來理解:

  1. 交互形式
  2. 代碼組織方式
  3. 開發(fā)模式
  4. 數(shù)據(jù)接口規(guī)范流程

一呀狼、交互形式

29.jpg

在前后端分離架構(gòu)中裂允,后端只需要負(fù)責(zé)按照約定的數(shù)據(jù)格式向前端提供可調(diào)用的API服務(wù)即可。前后端之間通過HTTP請(qǐng)求進(jìn)行交互哥艇,前端獲取到數(shù)據(jù)后绝编,進(jìn)行頁面的組裝和渲染,最終返回給瀏覽器貌踏。

二十饥、代碼組織方式

30.jpg

在傳統(tǒng)架構(gòu)模式中,前后端代碼存放于同一個(gè)代碼庫(kù)中祖乳,甚至是同一工程目錄下逗堵。頁面中還夾雜著后端代碼。前后端工程師進(jìn)行開發(fā)時(shí)凡资,都必須把整個(gè)項(xiàng)目導(dǎo)入到開發(fā)工具中砸捏。

而前后端分離模式在代碼組織形式上有以下兩種:

  • 半分離
    前后端共用一個(gè)代碼庫(kù),但是代碼分別存放在兩個(gè)工程中隙赁。后端不關(guān)心或很少 關(guān)心前端元素的輸出情況垦藏,前端不能獨(dú)立進(jìn)行開發(fā)和測(cè)試,項(xiàng)目中缺乏前后端 交互的測(cè)試用例伞访。
  • 分離
    前后端代碼庫(kù)分離掂骏,前端代碼中有可以進(jìn)行Mock測(cè)試(通過構(gòu)造虛擬測(cè)試對(duì) 象以簡(jiǎn)化測(cè)試環(huán)境的方法)的偽后端,能支持前端的獨(dú)立開發(fā)和測(cè)試厚掷。而后端 代碼中除了功能實(shí)現(xiàn)外弟灼,還有著詳細(xì)的測(cè)試用例,以保證API的可用性冒黑,降低 集成風(fēng)險(xiǎn)田绑。

三、開發(fā)模式

我們之前的架構(gòu)屬于傳統(tǒng)的MVC架構(gòu)抡爹,整體沒有進(jìn)行前后端分離掩驱,在項(xiàng)目的開發(fā)階段,前端工程師負(fù)責(zé)編寫HTML冬竟,完成前端的頁面設(shè)計(jì)并套頁面欧穴,然后再使用模板技術(shù)將寫好的前端代碼轉(zhuǎn)換為Smarty腳本,同時(shí)內(nèi)嵌一些后端提供的模板變量和一些邏輯操作泵殴。應(yīng)用運(yùn)行期涮帘,將全部代碼進(jìn)行打包,和后端代碼部署到同一服務(wù)器上笑诅,同時(shí)會(huì)進(jìn)行簡(jiǎn)單的動(dòng)靜態(tài)分離部署调缨。

此時(shí),應(yīng)用的開發(fā)流程如下圖所示吆你。

31.jpg

而在實(shí)現(xiàn)前后端分離架構(gòu)之后同蜻,前端工程師只需要編寫HTML、js早处、CSS等前端資源湾蔓,然后通 過HTTP請(qǐng)求調(diào)用后端提供的服務(wù)即可。除了開發(fā)期的分離砌梆,在運(yùn)行期前后端資源也 會(huì)進(jìn)行分離部署默责。

前后端分離之后,開發(fā)流程將如下圖所示咸包。


32.jpg

通過上面的兩幅流程圖桃序,不難發(fā)現(xiàn),在開發(fā)模式上烂瘫,前后段分離不僅僅只是工程師的分工開發(fā)媒熊,更重要的意義在于實(shí)現(xiàn)了前后端的并行開發(fā)奇适,簡(jiǎn)化了開發(fā)流程

四芦鳍、數(shù)據(jù)接口規(guī)范流程

在開發(fā)期間前后端共同商定好數(shù)據(jù)接口的交互形式和數(shù)據(jù)格式嚷往。然后實(shí)現(xiàn)前后端的并行開發(fā),其中前端工程師再開發(fā)完成之后可以獨(dú)自進(jìn)行mock測(cè)試柠衅,而后端也可以使用接口測(cè)試平臺(tái)進(jìn)行接口自測(cè)皮仁,然后前后端一起進(jìn)行功能聯(lián)調(diào)并校驗(yàn)格式,最終進(jìn)行自動(dòng)化測(cè)試菲宴。


33.jpg

分離的四個(gè)好處

前后端分離模式和傳統(tǒng)的web應(yīng)用架構(gòu)相比有很大的不同贷祈,到底分還是不分,這還真是個(gè)問題喝峦。

從目前應(yīng)用軟件開發(fā)的發(fā)展趨勢(shì)來看势誊,主要有兩方面需要注意:

  1. 越來越注重用戶體驗(yàn),隨著互聯(lián)網(wǎng)的發(fā)展谣蠢,開始多終端化键科。
  2. 大型應(yīng)用架構(gòu)模式正在向云化、微服務(wù)化發(fā)展漩怎。

我們主要通過前后端分離架構(gòu)勋颖,為我們帶來以下四個(gè)方面的提升:

  • 為優(yōu)質(zhì)產(chǎn)品打造精益團(tuán)隊(duì)
    通過將開發(fā)團(tuán)隊(duì)前后端分離化,讓前后端工程師只需要專注于前端或后端的開發(fā)工作勋锤,是的前后端工程師實(shí)現(xiàn)自治饭玲,培養(yǎng)其獨(dú)特的技術(shù)特性,然后構(gòu)建出一個(gè)全棧式的精益開發(fā)團(tuán)隊(duì)叁执。
  • 提升開發(fā)效率
    前后端分離以后茄厘,可以實(shí)現(xiàn)前后端代碼的解耦,只要前后端溝通約定好應(yīng)用所需接口以及接口參數(shù)谈宛,便可以開始并行開發(fā)次哈,無需等待對(duì)方的開發(fā)工作結(jié)束。與此同時(shí)吆录,即使需求發(fā)生變更窑滞,只要接口與數(shù)據(jù)格式不變,后端開發(fā)人員就不需要修改代碼恢筝,只要前端進(jìn)行變動(dòng)即可哀卫。如此一來整個(gè)應(yīng)用的開發(fā)效率必然會(huì)有質(zhì)的提升。
  • 完美應(yīng)對(duì)復(fù)雜多變的前端需求
    如果開發(fā)團(tuán)隊(duì)能完成前后端分離的轉(zhuǎn)型撬槽,打造優(yōu)秀的前后端團(tuán)隊(duì)此改,開發(fā)獨(dú)立化,讓開發(fā)人員做到專注專精侄柔,開發(fā)能力必然會(huì)有所提升共啃,能夠完美應(yīng)對(duì)各種復(fù)雜多變的前端需求占调。
  • 增強(qiáng)代碼可維護(hù)性
    前后端分離后,應(yīng)用的代碼不再是前后端混合移剪,只有在運(yùn)行期才會(huì)有調(diào)用依賴關(guān)系究珊。

應(yīng)用代碼將會(huì)變得整潔清晰,不論是代碼閱讀還是代碼維護(hù)都會(huì)比以前輕松挂滓。


以上轉(zhuǎn)自前后端分離實(shí)踐(一)


利用JSON串來實(shí)現(xiàn)前后端分離

在自己最近做的項(xiàng)目中苦银,使用的是利用SSM框架中的controller層來傳出JSON串啸胧,再通過jQuery中的.getJSON()來進(jìn)行解析赶站,再將數(shù)據(jù)傳到前端頁面。

controller層

部分代碼省略纺念,放出關(guān)鍵代碼:

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>();
        //判斷驗(yàn)證碼是否正確
        if(!CodeUtil.checkVerifyCode(request)){
            modelMap.put("success",false);
            modelMap.put("errMsg","輸入了錯(cuò)誤的驗(yàn)證碼");
            return modelMap;
        }
        //1.接收并轉(zhuǎn)化相應(yīng)的參數(shù)贝椿,包括店鋪信息以及圖片信息
        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());
        // 檢測(cè)文件是否有上傳文件流
        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","請(qǐng)輸入店鋪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請(qǐng)求中獲得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這個(gè)url
    var shopInfoUrl = "/shopadmin/getshopbyid?shopId=" + shopId;
//從/shopadmin/getshopbyid?shopId這個(gè)url中獲得controller層返回的JSON串,通過$.getJSON函數(shù)進(jìn)行解析烟逊。
//獲取相關(guān)的鍵值對(duì)渣窜,然后插入前端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宪躯,即可獲得商店信息

238.png

流程圖:

240.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末乔宿,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子访雪,更是在濱河造成了極大的恐慌详瑞,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,194評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件臣缀,死亡現(xiàn)場(chǎng)離奇詭異坝橡,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)精置,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,058評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門计寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人脂倦,你說我怎么就攤上這事饲常。” “怎么了狼讨?”我有些...
    開封第一講書人閱讀 156,780評(píng)論 0 346
  • 文/不壞的土叔 我叫張陵贝淤,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我政供,道長(zhǎng)播聪,這世上最難降的妖魔是什么朽基? 我笑而不...
    開封第一講書人閱讀 56,388評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮离陶,結(jié)果婚禮上稼虎,老公的妹妹穿的比我還像新娘。我一直安慰自己招刨,他們只是感情好霎俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,430評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著沉眶,像睡著了一般打却。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谎倔,一...
    開封第一講書人閱讀 49,764評(píng)論 1 290
  • 那天柳击,我揣著相機(jī)與錄音,去河邊找鬼片习。 笑死捌肴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的藕咏。 我是一名探鬼主播状知,決...
    沈念sama閱讀 38,907評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼孽查!你這毒婦竟也來了饥悴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,679評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤卦碾,失蹤者是張志新(化名)和其女友劉穎铺坞,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體洲胖,經(jīng)...
    沈念sama閱讀 44,122評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡济榨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,459評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绿映。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片擒滑。...
    茶點(diǎn)故事閱讀 38,605評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖叉弦,靈堂內(nèi)的尸體忽然破棺而出丐一,到底是詐尸還是另有隱情,我是刑警寧澤淹冰,帶...
    沈念sama閱讀 34,270評(píng)論 4 329
  • 正文 年R本政府宣布库车,位于F島的核電站,受9級(jí)特大地震影響樱拴,放射性物質(zhì)發(fā)生泄漏柠衍。R本人自食惡果不足惜洋满,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,867評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望珍坊。 院中可真熱鬧牺勾,春花似錦、人聲如沸阵漏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,734評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽履怯。三九已至回还,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間虑乖,已是汗流浹背懦趋。 一陣腳步聲響...
    開封第一講書人閱讀 31,961評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工晾虑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留疹味,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,297評(píng)論 2 360
  • 正文 我出身青樓帜篇,卻偏偏與公主長(zhǎng)得像糙捺,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子笙隙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,472評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容