在线不卡日本ⅴ一区v二区_精品一区二区中文字幕_天堂v在线视频_亚洲五月天婷婷中文网站

  • <menu id="lky3g"></menu>
  • <style id="lky3g"></style>
    <pre id="lky3g"><tt id="lky3g"></tt></pre>

    Spring Boot 業(yè)務(wù)邏輯層

    關(guān)于業(yè)務(wù)邏輯層(Service層)

    業(yè)務(wù)邏輯層是被Controller直接調(diào)用的層(Controller不允許直接調(diào)用持久層),通常,在業(yè)務(wù)邏輯層中編寫的代碼是為了保證數(shù)據(jù)的完整性和安全性,使得數(shù)據(jù)是隨著我們設(shè)定的規(guī)則而產(chǎn)生或發(fā)生變化。

    通常,在業(yè)務(wù)邏輯層的代碼會(huì)由接口和實(shí)現(xiàn)類組件,其中,接口被視為是必須的

  • – 推薦使用基于接口的編程方式
  • – 部分框架在處理某些功能時(shí),會(huì)使用基于接口的代理模式,例如Spring JDBC框架在處理事務(wù)時(shí)
  • 在接口中,聲明抽象方法時(shí),僅以操作成功為前提來設(shè)計(jì)返回值類型(不考慮失?。?,如果業(yè)務(wù)在執(zhí)行過程可能出現(xiàn)某些失?。ú环纤O(shè)定的規(guī)則),可以通過拋出異常來表示!

    關(guān)于拋出的異常,通常是自定義的異常,并且,自定義異常通常是`RuntimeException`的子類,主要原因:

  • – 不必顯式的拋出或捕獲,因?yàn)闃I(yè)務(wù)邏輯層的異常永遠(yuǎn)是拋出的,而控制器層會(huì)調(diào)用業(yè)務(wù)邏輯層,在控制器層的Controller中其實(shí)也是永遠(yuǎn)拋出異常的,這些異常會(huì)通過Spring MVC統(tǒng)一處理異常的機(jī)制進(jìn)行處理,關(guān)于異常的整個(gè)過程都是固定流程,所以,沒有必要顯式拋出或捕獲
  • – 部分框架在處理某些事情時(shí),默認(rèn)只對`RuntimeException`的子孫類進(jìn)行識(shí)別并處理,例如Spring JDBC框架在處理事務(wù)時(shí)
  • 所以,在實(shí)際編寫業(yè)務(wù)邏輯層之前,應(yīng)該先規(guī)劃異常,例如先創(chuàng)建`ServiceException`類:

    public class ServiceException extends RuntimeException { //TODU}

    接下來,再創(chuàng)建具體的對應(yīng)某種“失敗”的異常,例如,在添加管理員時(shí),可能因?yàn)椤?span id="4onqbpb" class="wpcom_tag_link">用戶名已經(jīng)存在”而失敗,則創(chuàng)建對應(yīng)的`UsernameDuplicateException`異常:

    public class UsernameDuplicateException extends ServiceException { //TODU}

    另外,當(dāng)插入數(shù)據(jù)時(shí),如果返回的受影響行數(shù)不是1時(shí),必然是某種錯(cuò)誤,則創(chuàng)建對應(yīng)的插入數(shù)據(jù)異常:

    public class InsertException extends ServiceException { //TODU}

    關(guān)于抽象方法的參數(shù),應(yīng)該設(shè)計(jì)為客戶端提交的數(shù)據(jù)類型或?qū)?yīng)的封裝類型,不可以是數(shù)據(jù)表對應(yīng)的實(shí)體類型!如果使用封裝的類型,這種類型在類名上應(yīng)該添加某種后綴,例如`DTO`或其它后綴,例如:

    public class AdminAddNewDTO implements Serializable { private String username; private String password; private String nickname; private String avatar; private String phone; private String email; private String description; // Setters & Getters // hashCode(), equals() // toString()}

    然后,在`cn.celinf.boot.demo.service`包下聲明接口及抽象方法:

    public interface IAdminService { void addNew(AdminAddNewDTO adminAddNewDTO);}

    并在以上`service`包下創(chuàng)建`impl`子包,再創(chuàng)建`AdminServiceImpl`類:

    import cn.celinf.boot.demo.entity.Admin;import cn.celinf.boot.demo.ex.InsertException;import cn.celinf.boot.demo.ex.UsernameDuplicateException;import cn.celinf.boot.demo.mapper.AdminMapper;import cn.celinf.boot.demo.pojo.dto.AdminAddNewDTO;import cn.celinf.boot.demo.service.IAdminService;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.time.LocalDateTime;@Servicepublic class AdminServiceImpl implements IAdminService { @Autowired private AdminMapper adminMapper; @Override public void addNew(AdminAddNewDTO adminAddNewDTO) { // 通過參數(shù)獲取用戶名 String username = adminAddNewDTO.getUsername(); // 調(diào)用adminMapper的Admin getByUsername(String username)方法執(zhí)行查詢 Admin queryResult = adminMapper.getByUsername(username); // 判斷查詢結(jié)果是否不為null if (queryResult != null) { // 是:表示用戶名已經(jīng)被占用,則拋出UsernameDuplicateException throw new UsernameDuplicateException(); } // 通過參數(shù)獲取原密碼 String password = adminAddNewDTO.getPassword(); // 通過加密方式,得到加密后的密碼encodedPassword String encodedPassword = password; // 創(chuàng)建當(dāng)前時(shí)間對象now > LocalDateTime.now() LocalDateTime now = LocalDateTime.now(); // 創(chuàng)建Admin對象 Admin admin = new Admin(); // 補(bǔ)全Admin對象的屬性值:通過參數(shù)獲取username,nickname…… admin.setUsername(username); admin.setNickname(adminAddNewDTO.getNickname()); admin.setAvatar(adminAddNewDTO.getAvatar()); admin.setPhone(adminAddNewDTO.getPhone()); admin.setEmail(adminAddNewDTO.getEmail()); admin.setDescription(adminAddNewDTO.getDescription()); // 以上這些從一個(gè)對象中把屬性賦到另一個(gè)對象中,還可以使用: // BeanUtils.copyProperties(adminAddNewDTO, admin); // 補(bǔ)全Admin對象的屬性值:password > encodedPassword admin.setPassword(encodedPassword); // 補(bǔ)全Admin對象的屬性值:isEnable > 1 admin.setIsEnable(1); // 補(bǔ)全Admin對象的屬性值:lastLoginIp > null // 補(bǔ)全Admin對象的屬性值:loginCount > 0 admin.setLoginCount(0); // 補(bǔ)全Admin對象的屬性值:gmtLastLogin > null // 補(bǔ)全Admin對象的屬性值:gmtCreate > now admin.setGmtCreate(now); // 補(bǔ)全Admin對象的屬性值:gmtModified > now admin.setGmtModified(now); // 調(diào)用adminMapper的insert(Admin admin)方法插入管理員數(shù)據(jù),獲取返回值 int rows = adminMapper.insert(admin); // 判斷以上返回的結(jié)果是否不為1,拋出InsertException異常 if (rows != 1) { throw new InsertException(); } }}

    以上代碼未實(shí)現(xiàn)對密碼的加密處理!關(guān)于密碼加密,相關(guān)的代碼應(yīng)該定義在別的某個(gè)類中,不應(yīng)該直接將加密過程編寫在以上代碼中,因?yàn)榧用艿拇a需要在多處應(yīng)用(添加用戶、用戶登錄、修改密碼等),并且,從分工的角度上來看,也不應(yīng)該是業(yè)務(wù)邏輯層的任務(wù)!所以,在`cn.celinf.boot.demo.util`(包不存在,則創(chuàng)建)下創(chuàng)建`PasswordEncoder`類,用于處理密碼加密:

    package cn.celinf.boot.demo.util;@Componentpublic class PasswordEncoder { public String encode(String rawPassword) { return “aaa” + rawPassword + “aaa”; }}

    完成后,需要在`AdminServiceImpl`中自動(dòng)裝配以上`PasswordEncoder`,并在需要加密時(shí)調(diào)用`PasswordEncoder`對象的`encode()`方法。

    控制器層開發(fā)

    Spring MVC是用于處理控制器層開發(fā)的,在使用Spring Boot時(shí),在`pom.xml`中添加`spring-boot-starter-web`即可整合Spring MVC框架及相關(guān)的常用依賴項(xiàng)(包含`jackson-databind`),可以將已存在的`spring-boot-starter`直接改為`spring-boot-starter-web`,因?yàn)樵赻spring-boot-starter-web`中已經(jīng)包含了`spring-boot-starter`。

    先在項(xiàng)目的根包下創(chuàng)建`controller`子包,并在此子包下創(chuàng)建`AdminController`,此類應(yīng)該添加`@RestController`和`@RequestMapping(value = “/admins”, produces = “application/json; charset=utf-8”)`注解,例如:

    @RestController@RequestMapping(values = “/admins”, produces = “application/json; charset=utf-8”)public class AdminController {}

    由于已經(jīng)決定了服務(wù)器端響應(yīng)時(shí),將響應(yīng)JSON格式的字符串,為保證能夠響應(yīng)JSON格式的結(jié)果,處理請求的方法返回值應(yīng)該是自定義的數(shù)據(jù)類型,則從此前學(xué)習(xí)的`spring-mvc`項(xiàng)目中找到`JsonResult`類及相關(guān)類型,復(fù)制到當(dāng)前項(xiàng)目中來。

    @Autowiredprivate IAdminService adminService;// 注意:暫時(shí)使用@RequestMapping,不要使用@PostMapping,以便于直接在瀏覽器中測試// http://localhost:8080/admins/add-new?username=root&password=1234@RequestMapping(“/add-new”)public JsonResult addNew(AdminAddNewDTO adminAddNewDTO) { adminService.addNew(adminAddNewDTO); return JsonResult.ok();}

    完成后,運(yùn)行啟動(dòng)類,即可啟動(dòng)整個(gè)項(xiàng)目,在`spring-boot-starter-web`中,包含了Tomcat的依賴項(xiàng),在啟動(dòng)時(shí),會(huì)自動(dòng)將當(dāng)前項(xiàng)目打包并部署到此Tomcat上,所以,執(zhí)行啟動(dòng)類時(shí),會(huì)執(zhí)行此Tomcat,同時(shí),因?yàn)槭莾?nèi)置的Tomcat,只為當(dāng)前項(xiàng)目服務(wù),所以,在將項(xiàng)目部署到Tomcat時(shí),默認(rèn)已經(jīng)將Context Path(例如spring_mvc_war_exploded)配置為空字符串,所以,在啟動(dòng)項(xiàng)目后,訪問的URL中并沒有此前遇到的Context Path值。

    當(dāng)項(xiàng)目啟動(dòng)成功后,即可在瀏覽器的地址欄中輸入網(wǎng)址進(jìn)行測試訪問!

    【注意】:如果是未添加的管理員賬號(hào),可以成功執(zhí)行結(jié)束,如果管理員賬號(hào)已經(jīng)存在,由于尚未處理異常,會(huì)提示500錯(cuò)誤。

    public enum State {OK(200),ERR_USERNAME(201),ERR_PASSWORD(202),ERR_INSERT(500); // 新增的枚舉值// 原有其它代碼}

    然后,在`cn.celinf.boot.demo.controller`下創(chuàng)建`handler.GlobalExceptionHandler`類,用于統(tǒng)一處理異常,例如:

    package cn.celinf.boot.demo.controller.handler;import cn.celinf.boot.demo.ex.ServiceException;import cn.celinf.boot.demo.ex.UsernameDuplicateException;import cn.celinf.boot.demo.web.JsonResult;import cn.celinf.boot.demo.web.State;import org.springframework.web.bind.annotation.ExceptionHandler;import org.springframework.web.bind.annotation.RestControllerAdvice;@RestControllerAdvicepublic class GlobalExceptionHandler { @ExceptionHandler(ServiceException.class) public JsonResult handleServiceException(ServiceException e) { if (e instanceof UsernameDuplicateException) { return JsonResult.fail(State.ERR_USERNAME, “用戶名錯(cuò)誤!”); } else { return JsonResult.fail(State.ERR_INSERT, “插入數(shù)據(jù)失敗!”); } }}

    完成后,重新啟動(dòng)項(xiàng)目,當(dāng)添加管理員時(shí)的用戶名沒有被占用時(shí),將正常添加,當(dāng)用戶名已經(jīng)被占用時(shí),會(huì)根據(jù)處理異常的結(jié)果進(jìn)行響應(yīng)!

    由于在統(tǒng)一處理異常的機(jī)制下,同一種異常,無論是在哪種業(yè)務(wù)中出現(xiàn),處理異常時(shí)的描述信息都是完全相同的,也無法精準(zhǔn)的表達(dá)錯(cuò)誤信息,這是不合適的!另外,基于面向?qū)ο蟮摹胺止ぁ彼枷?,關(guān)于錯(cuò)誤信息(異常對應(yīng)的描述信息),應(yīng)該是由Service來描述,即“誰拋出誰描述”,因?yàn)閽伋霎惓5拇a片段是最了解、最明確出現(xiàn)異常的原因的!

    為了更好的描述異常的原因,應(yīng)該在自定義的`ServiceException`和其子孫類異常中添加基于父類的全部構(gòu)造方法(5個(gè)),然后,在`AdminServiceImpl`中,當(dāng)拋出異常時(shí),可以在異常的構(gòu)造方法中添加`String`類型的參數(shù),對異常發(fā)生的原因進(jìn)行描述,例如:

    @Overridepublic void addNew(AdminAddNewDTO adminAddNewDTO) { // ===== 原有其它代碼 ===== // 判斷查詢結(jié)果是否不為null if (queryResult != null) { // 是:表示用戶名已經(jīng)被占用,則拋出UsernameDuplicateException log.error(“此賬號(hào)已經(jīng)被占用,將拋出異常”); throw new UsernameDuplicateException(“添加管理員失敗,用戶名(” + username + “)已經(jīng)被占用!”); } // ===== 原有其它代碼 ===== // 判斷以上返回的結(jié)果是否不為1,拋出InsertException異常 if (rows != 1) { throw new InsertException(“添加管理員失敗,服務(wù)器忙,請稍后再次嘗試!”); }}

    最后,在處理異常時(shí),可以調(diào)用異常對象的`getMessage()`方法獲取拋出時(shí)封裝的描述信息,例如:

    @ExceptionHandler(ServiceException.class)public JsonResult handleServiceException(ServiceException e) { if (e instanceof UsernameDuplicateException) { return JsonResult.fail(State.ERR_USERNAME, e.getMessage()); } else { return JsonResult.fail(State.ERR_INSERT, e.getMessage()); }}

    完成后,再次重啟項(xiàng)目,當(dāng)用戶名已經(jīng)存在時(shí),可以顯示在Service中描述的錯(cuò)誤信息!

    //響應(yīng)的JSON數(shù)據(jù)例如:{“state”:200,”message”:null,”data”:null}//添加失敗時(shí),響應(yīng)的JSON數(shù)據(jù)例如:{“state”:201,”message”:”添加管理員失敗,用戶名(liuguobin)已經(jīng)被占用!”,”data”:null}

    可以看到,無論是成功還是失敗,響應(yīng)的JSON中都包含了不必要的數(shù)據(jù)(為`null`的數(shù)據(jù)),這些數(shù)據(jù)屬性是沒有必要響應(yīng)到客戶端的,如果需要去除這些不必要的值,可以在對應(yīng)的屬性上使用注解進(jìn)行配置,例如:

    //NON_NULL 則響應(yīng)的JSON中只會(huì)包含不為`null`的部分。@Datapublic class JsonResult implements Serializable { // 狀態(tài)碼,例如:200 private Integer state; // 消息,例如:”登錄失敗,用戶名不存在” @JsonInclude(JsonInclude.Include.NON_NULL) private String message; // 數(shù)據(jù) @JsonInclude(JsonInclude.Include.NON_NULL) private T data; // ===== 原有其它代碼 =====}

    此注解還可以添加在類上,則作用于當(dāng)前類中所有的屬性,例如:

    @Data@JsonInclude(JsonInclude.Include.NON_NULL)public class JsonResult implements Serializable { // ===== 原有其它代碼 =====}

    即使添加在類上,也只對當(dāng)前類的3個(gè)屬性有效,后續(xù),當(dāng)響應(yīng)某些數(shù)據(jù)時(shí),`data`屬性可能是用戶、商品、訂單等類型,這些類型的數(shù)據(jù)中為`null`的部分依然會(huì)被響應(yīng)到客戶端去,所以,還需要對這些類型也添加相同的注解配置!

    以上做法相對比較繁瑣,可以在`application.properties` / `application.yml`中添加全局配置,則作用于當(dāng)前項(xiàng)目中所有響應(yīng)時(shí)涉及的類,例如在`properties`中配置為:

    //yml文件配置寫法spring: jackson: default-property-inclusion: non_null

    注意:當(dāng)你需要在`yml`中添加以上配置時(shí),前綴屬性名可能已經(jīng)存在,則不允許出現(xiàn)重復(fù)的前綴屬性名的:

    spring: profiles: active: dev jackson: default-property-inclusion: non_null

    最后,以上配置只是“默認(rèn)”配置,如果在某些類型中還有不同的配置需求,仍可以在類或?qū)傩陨贤ㄟ^`@JsonInclude`進(jìn)行配置。

    15. 解決跨域問題

    在使用前后端分離的開發(fā)模式下,前端項(xiàng)目和后端項(xiàng)目可能是2個(gè)完全不同的項(xiàng)目,并且,各自己獨(dú)立開發(fā),獨(dú)立部署,在這種做法中,如果前端直接向后端發(fā)送異步請求,默認(rèn)情況下,在前端會(huì)出現(xiàn)類似以下錯(cuò)誤:

    //web端 跨域錯(cuò)誤Access to XMLHttpRequest at ‘http://localhost:8080/admins/add-new’ from origin ‘http://localhost:8081’ has been blocked by CORS policy:No ‘Access-Control-Allow-Origin’ header is present on the requested resource.

    以上錯(cuò)誤信息的關(guān)鍵字是`CORS`,通常稱之為“跨域問題”。

    在基于Spring MVC框架的項(xiàng)目中,當(dāng)需要解決跨域問題時(shí),需要一個(gè)Spring MVC的配置類(實(shí)現(xiàn)了`WebMvcConfigurer`接口的類),并重寫其中的方法,以允許指定條件的跨域訪問,例如:

    //解決跨域的config配置import org.springframework.context.annotation.Configuration;import org.springframework.web.servlet.config.annotation.CorsRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;@Configurationpublic class SpringMvcConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(“/**”) .allowedOriginPatterns(“*”) .allowedMethods(“*”) .allowedHeaders(“*”) .allowCredentials(true) .maxAge(3600); }}

    16. 關(guān)于客戶端提交請求參數(shù)的格式

    通常,客戶端向服務(wù)器端發(fā)送請求時(shí),請求參數(shù)可以有2種形式,第1種是直接通過`&`拼接各參數(shù)與值,例如:

    // FormData//第一種 直接url地址拼接// username=root&password=123456&nickname=jackson&phone=13800138001let data = ‘username=’ + this.ruleForm.username+ ‘&password=’ + this.ruleForm.password+ ‘&nickname=’ + this.ruleForm.nickname+ ‘&phone=’ + this.ruleForm.phone//第2種方式是使用JSON語法來組織各參數(shù)與值let data = {‘username’: this.ruleForm.username, // ‘root”password’: this.ruleForm.password, // ‘123456”nickname’: this.ruleForm.nickname, // ‘jackson”phone’: this.ruleForm.phone, // ‘13800138001’};

    具體使用哪種做法,取決于服務(wù)器端的設(shè)計(jì):

    – 如果服務(wù)器端處理請求的方法中,在參數(shù)前添加了`@RequestBody`,則允許使用以上第2種做法(JSON數(shù)據(jù))提交請求參數(shù),不允許使用以上第1種做法(使用`&`拼接)

    – 如果沒有使用`@RequestBody`,則只能使用以上第1種做法

    學(xué)習(xí)記錄,如有侵權(quán)請聯(lián)系刪除

    鄭重聲明:本文內(nèi)容及圖片均整理自互聯(lián)網(wǎng),不代表本站立場,版權(quán)歸原作者所有,如有侵權(quán)請聯(lián)系管理員(admin#wlmqw.com)刪除。
    (0)
    用戶投稿
    上一篇 2022年6月13日 15:28
    下一篇 2022年6月13日 15:29

    相關(guān)推薦

    聯(lián)系我們

    聯(lián)系郵箱:admin#wlmqw.com
    工作時(shí)間:周一至周五,10:30-18:30,節(jié)假日休息