SpringMVC4.1之Controller層最佳實(shí)踐

原文地址:https://github.com/kuitos/kuitos.github.io/issues/9

先說(shuō)說(shuō)我們要實(shí)現(xiàn)的目標(biāo)(接口層):

統(tǒng)一的響應(yīng)體、請(qǐng)求體,規(guī)避Map、List作參數(shù)或者響應(yīng)結(jié)果的方式(尤其是參數(shù)用Map來(lái)包裝,這種代碼有時(shí)候看起來(lái)真的讓人很沮喪)

統(tǒng)一的錯(cuò)誤信息

統(tǒng)一的請(qǐng)求數(shù)據(jù)校驗(yàn)

統(tǒng)一的接口異常捕獲

首先來(lái)介紹下springMVC新增的一個(gè)很人性化的注解:

@RestController

@RestController組合了@controller和@responsebody,使用該注解聲明的controller下的每一個(gè)@requestmapping方法,都會(huì)默認(rèn)加上@responsebody,即默認(rèn)該controller提供的全部是rest服務(wù),返回的不會(huì)是視圖。

@RestController

public class DemoRestController {

@Resource

private DemoService demoService;

@RequestMapping(value = "getUser", method = RequestMethod.GET)

public ResponseResult> getUser(String userName) {

// do something

}

}

基于開(kāi)頭提到的四個(gè)目標(biāo),我們以代碼的形式來(lái)說(shuō)明一下具體的實(shí)現(xiàn)方案

統(tǒng)一的請(qǐng)求體、響應(yīng)體

思路:所有的rest響應(yīng)均返回一致的數(shù)據(jù)格式,所有的post請(qǐng)求均采用bean接收。(不要使用List、Map萬(wàn)金油。。。)

目的:統(tǒng)一的響應(yīng)體能確保rest接口的一致性,同時(shí)可以提供給前端js一個(gè)可封裝http請(qǐng)求的環(huán)境(如:封裝的http錯(cuò)誤日志、結(jié)果攔截等)(吐槽一句,有時(shí)候我們想在前端做統(tǒng)一的響應(yīng)攔截和日志處理,可是接口返回的數(shù)據(jù)格式五花八門(mén),實(shí)在讓人無(wú)能為力。。。) post請(qǐng)求均采用bean接收可以使得代碼更具可讀性,直接通過(guò)bean可以獲知接口所需參數(shù),而不是一行行讀代碼看你從map里面get出了些什么玩意。

ps:部分思路來(lái)源于忠誠(chéng)度項(xiàng)目接口實(shí)現(xiàn)方式,特此表示感謝!

統(tǒng)一響應(yīng)體

@JsonInclude(JsonInclude.Include.NON_EMPTY)

public class ResponseResult {

private boolean success;

private String message;

private T data;

/* 不提供直接設(shè)置errorCode的接口,只能通過(guò)setErrorInfo方法設(shè)置錯(cuò)誤信息 */

private String errorCode;

private ResponseResult() {

}

.........

}

統(tǒng)一結(jié)果生成方式

public class RestResultGenerator {

private static final Logger LOGGER = LoggerFactory.getLogger(RestResultGenerator.class);

/**

* 生成響應(yīng)成功(帶正文)的結(jié)果

*

* @param data? ? 結(jié)果正文

* @param message 成功提示信息

* @return ResponseResult

*/

public static ResponseResult genResult(T data, String message) {

ResponseResult result = ResponseResult.newInstance();

result.setSuccess(true);

result.setData(data);

result.setMessage(message);

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("--------> result:{}", JacksonMapper.toJsonString(result));

}

return result;

}

........

}

調(diào)用示例

@RestController

public class DemoRestController {

@Resource

private DemoService demoService;

@RequestMapping(value = "getUser", method = RequestMethod.GET)

public ResponseResult> getUser(String userName) {

List userList = demoService.getUser(userName);

return RestResultGenerator.genResult(userList, "成功!");

}

}

統(tǒng)一的錯(cuò)誤信息

思路:需要使用errorCode來(lái)聲明的錯(cuò)誤信息,統(tǒng)一通過(guò)enum定義,ResponseResult不提供單獨(dú)設(shè)置errorCode的接口

public class RestResultGenerator {

private static final Logger LOGGER = LoggerFactory.getLogger(RestResultGenerator.class);

.......

/**

* 生成響應(yīng)失敗(帶errorCode)的結(jié)果

*

* @param responseErrorEnum 失敗信息

* @return ResponseResult

*/

public static ResponseResult genErrorResult(ResponseErrorEnum responseErrorEnum) {

ResponseResult result = ResponseResult.newInstance();

result.setSuccess(false);

result.setErrorInfo(responseErrorEnum);

if (LOGGER.isDebugEnabled()) {

LOGGER.debug("--------> result:{}", JacksonMapper.toJsonString(result));

}

return result;

}

}

統(tǒng)一的請(qǐng)求數(shù)據(jù)校驗(yàn)

思路:基于注解的bean校驗(yàn),采用JSR-303的Bean Validation。

目的:xx參數(shù)不能為空,格式必須為xxx等校驗(yàn)就不用在接口中去硬編碼干擾業(yè)務(wù)邏輯了。讓框架統(tǒng)一幫忙驗(yàn)證

bean示例

@JsonInclude(JsonInclude.Include.NON_EMPTY)

public class User {

@NotBlank

private String userName;

@NotNull

@Max(150)

@Min(1)

private Integer age;

private User() {

}

}

調(diào)用示例

@RestController

public class DemoRestController {

@Resource

private DemoService demoService;

@RequestMapping(value = "saveUser", method = RequestMethod.POST)

public ResponseResult saveUser(@Valid @RequestBody User user, Errors errors) {

if (errors.hasErrors()) {

return RestResultGenerator.genErrorResult(ResponseErrorEnum.ILLEGAL_PARAMS);

} else {

demoService.saveUser(user);

return RestResultGenerator.genResult("保存成功!");

}

}

}

由于依賴于JSR-303規(guī)范,我們的pom文件需要加入新的依賴

maven配置

javax.validation

validation-api

1.1.0.Final

org.hibernate

hibernate-validator

5.0.1.Final

統(tǒng)一的接口異常捕獲

思路:起初想通過(guò)代碼中try..catch的方式捕獲異常,然后通過(guò)RestResultGenerator生成錯(cuò)誤信息。后來(lái)覺(jué)得這種方式太傻了,然后想到通過(guò)aop的方式,以Controller的RequestMapping為切面織入異常捕獲代碼,然后返回錯(cuò)誤信息。再后來(lái)發(fā)現(xiàn)springMVC早在3.x時(shí)代便提供了@ExceptionHandler注解。。。再后來(lái)又發(fā)現(xiàn)了@controlleradvice。。。這不就是我想要的嘛!! 可見(jiàn)使用一門(mén)技術(shù)前對(duì)其有一定的系統(tǒng)認(rèn)知該多么重要,不僅能避免重復(fù)造輪子還能避免坑自己坑別人

目的:無(wú)侵入式的異常捕獲,不干擾業(yè)務(wù)邏輯

名詞解釋:

ExceptionHandler:顧名思義,異常處理器。單獨(dú)的ExceptionHandler沒(méi)什么特別之處,配合ControllerAdvice就會(huì)分分鐘變神器!

ControllerAdvice: 從命名我們就能猜到,這家伙肯定是基于aop實(shí)現(xiàn)的一個(gè)東西,用于增強(qiáng)controller功能的。它可以把@controlleradvice注解內(nèi)部使用@ExceptionHandler、@initbinder、@modelattribute注解的方法應(yīng)用到所有的 @requestmapping注解的方法。其中ExceptionHandler實(shí)際作用最大,其他兩個(gè)用的少。Spring3.x時(shí)代ControllerAdvice會(huì)增強(qiáng)一個(gè)servlet中的所有controller,Spring4以后 ControllerAdvice又得到了增強(qiáng),可以應(yīng)用于controller的子類(lèi),控制范圍更精確。

代碼示例

使用controllerAdvice實(shí)現(xiàn)的全局異常處理

// 指定增強(qiáng)范圍為使用RestContrller注解的控制器

@ControllerAdvice(annotations = RestController.class)

public class RestExceptionHandler {

private static final Logger LOGGER = LoggerFactory.getLogger(RestExceptionHandler.class);

/**

* bean校驗(yàn)未通過(guò)異常

*

* @see javax.validation.Valid

* @see org.springframework.validation.Validator

* @see org.springframework.validation.DataBinder

*/

@ExceptionHandler(UnexpectedTypeException.class)

@ResponseBody

@ResponseStatus(HttpStatus.BAD_REQUEST)

private ResponseResult illegalParamsExceptionHandler(UnexpectedTypeException e) {

LOGGER.error("--------->請(qǐng)求參數(shù)不合法!", e);

return RestResultGenerator.genErrorResult(ResponseErrorEnum.ILLEGAL_PARAMS);

}

}

Controller里面不用寫(xiě)任何多余的代碼,如果@Valid校驗(yàn)失敗接口會(huì)拋出UnexpectedTypeException從而被ControllerAdvice捕獲并返回錯(cuò)誤信息,httpstatus為503 Bad Request 錯(cuò)誤

@RestController

public class DemoRestController {

@Resource

private DemoService demoService;

@RequestMapping(value = "saveUser", method = RequestMethod.POST)

public ResponseResult saveUser(@Valid @RequestBody User user) {

demoService.saveUser(user);

return RestResultGenerator.genResult("保存成功!");

}

}

注意這里參數(shù)列表里面就不要加Errors或其子類(lèi)作參數(shù)了,有這個(gè)參數(shù)校驗(yàn)失敗就不會(huì)拋異常,而是把錯(cuò)誤信息填充到Errors對(duì)象中。

寫(xiě)在最后

至此,在Controller層我們一開(kāi)始的目標(biāo)基本上都已經(jīng)達(dá)成了,之后我們編寫(xiě)接口只需要實(shí)現(xiàn)業(yè)務(wù)邏輯,參數(shù)校驗(yàn)、異常捕獲等工作全部交由外圍設(shè)施處理,而不是手動(dòng)編碼做重復(fù)工作。SpringMVC部分還有很多已有的東西我們沒(méi)有開(kāi)發(fā),有點(diǎn)暴殄天物的感覺(jué)。磨刀不誤砍柴工,這樣才能避免重復(fù)造輪子跟寫(xiě)出可維護(hù)的代碼。雖然是碼農(nóng),但是也不能只滿足于復(fù)制粘貼吧。。。

附(目前大部分項(xiàng)目中關(guān)于springMVC錯(cuò)誤的(更準(zhǔn)確說(shuō)是不合理的)配置一覽表):

schema無(wú)效引入:也就是xml頭部引入的xsd,很多都是無(wú)效的引入,不過(guò)切換到idea之后IDE會(huì)提示你哪些引入是無(wú)效的。

和 :component-scan會(huì)自動(dòng)加上annotation-config功能,有了component-scan不用再寫(xiě)annotation-config了。參見(jiàn)spring官方reference

applicationContext.xml中配置了context:component-scan,在springmvc-servlet.xml中又配置了context:component-scan,這樣會(huì)導(dǎo)致容器中的bean注冊(cè)兩次。

更合理的配置

// applicationContext.xml

// springmvc-servlet.xml

spring容器不注冊(cè)controller層組件,controller組件由springMVC容器單獨(dú)注冊(cè)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,569評(píng)論 19 139
  • Spring的模型-視圖-控制器(MVC)框架是圍繞一個(gè)DispatcherServlet來(lái)設(shè)計(jì)的,這個(gè)Servl...
    alexpdh閱讀 2,746評(píng)論 0 3
  • 一步一步的搭建JAVA WEB項(xiàng)目,采用Maven構(gòu)建,基于MYBatis+Spring+Spring MVC+B...
    葉子的翅膀閱讀 12,903評(píng)論 5 25
  • 第一次用簡(jiǎn)書(shū),覺(jué)得簡(jiǎn)書(shū)有著印象筆記沒(méi)有的功能,在線分享資源,并且還是富文本編輯器類(lèi)型?,F(xiàn)在項(xiàng)目是采用前后端分離,用...
    zhuyuansj閱讀 3,080評(píng)論 0 16
  • 與人相處,
    33174dada65f閱讀 68評(píng)論 0 0

友情鏈接更多精彩內(nèi)容