傳送門
JAVA8-lambda表達式1:什么是lambda表達式
JAVA8-lambda表達式2:常用的集合類api
JAVA8-lambda表達式3:并行流,提升效率的利器?
JAVA8-lambda表達式4:Optional用法
java8-lambda表達式5:toMap引發(fā)的線上故障
JAVA8-lambda表達式6:重構(gòu)和定制收集器
JAVA8-lambda表達式7:重要的函數(shù)接口
如何看待寫代碼這件事
最近在公司寫代碼(包括看代碼),突然有一點小小的感嘆。好多人整天研究什么高并發(fā),高可用,分布式,開口架構(gòu)閉口新技術(shù),就是不愿意花時間把自己的JAVA代碼寫的好一點。
把代碼寫好就是給自己印的最好的名片,也是對同事最大的負責!
可惜好多人不這樣認為,或者說可能是現(xiàn)在環(huán)境就是這樣吧:面試各種的造火箭,考算法導(dǎo)致從業(yè)者只能投其所好,刷題/刷各種高大上的所謂架構(gòu)技術(shù),而忽視一個最本質(zhì)的前提!
那就是技術(shù)是為業(yè)務(wù)服務(wù)的,絕大部分的公司是用不上所謂的大廠架構(gòu)的,強行匹配只會適得其反。
有空還是多琢磨琢磨怎么把那點JAVA代碼寫的更好吧。
這里的說法有點屬于"夾帶私貨"了,太過片面了,切勿對號入座。
什么是模板方法
在很早以前(真的是很早了,看了下發(fā)布日期是2018年2月?。?,學(xué)習過模板方法,它屬于常用的設(shè)計中的一種。當時里面介紹的例子取自《Head First設(shè)計模式》,所以文章算作是翻譯過來的。例子比較簡單,實現(xiàn)也是用的繼承+多態(tài)的。而還有一種很常用的模板方法,就是一個類+靜態(tài)方法,使用者直接通過靜態(tài)方法來調(diào)用!
Template.execute(a,b);
- Template為類名
- execute為方法
- a,b為參數(shù)
在上面的調(diào)用中,Template.execute是不會變化,顧名思義就是模板方法的意思。而a,b則是需要調(diào)用方傳遞的參數(shù),必須是模板規(guī)定的類型。下面就以一個實際場景來看看如何抽象一個模板方法。
接口調(diào)用場景
對于JAVA程序員來說,spring肯定繞不開的結(jié)。當需要寫一個后端接口的時候,通過springMVC可以很方便的來實現(xiàn),比如在Oauth2系列7:授權(quán)碼和訪問令牌的頒發(fā)流程是怎樣實現(xiàn)的?里面提到的準備工作-驗證基本信息:
@RestController
@RequestMapping("/auth")
public class OauthController
{
@GetMapping("/authorize")
public void authorize(@RequestParam("response_type") String responseType, @RequestParam("client_id") String clientId,
@RequestParam("redirect_uri") String redirectUri, String scope)
{
}
}
還有驗證客戶端-生成訪問令牌:
@PostMapping("/token")
public TokenModel getToken(@RequestBody GetTokenRequest getTokenRequest) {
// 獲取令牌前置檢驗
preGetTokenCheck(getTokenRequest);
// 檢驗授權(quán)碼
checkCode(getTokenRequest.getCode());
// 生成t訪問令牌
TokenModel tokenModel = generateToken();
return tokenModel;
}
private TokenModel generateToken() {
// 獲取code信息,比如從redis
// CodeModel codeModel = getCode;
TokenModel tokenModel = new TokenModel();
tokenModel.setAccessToken(UUID.randomUUID().toString());
tokenModel.setExpiresIn(3600);
tokenModel.setRefreshToken(UUID.randomUUID().toString());
// tokenModel.setScope(codeModel.getScope);
return tokenModel;
}
- 在類上打上注解RestController或Controller,現(xiàn)在一般自動轉(zhuǎn)換json就用RestController
- 在方法上打上注解RequestMapping或GetMapping/PostMapping等,表示這個是一個接口方法
- 在方法里面打上注解RequestParam或PathVariable,用來獲取參數(shù)
大致按照這3個步驟來操作,剩下的主要就是業(yè)務(wù)代碼編寫了(實際項目里面也沒有什么大的區(qū)別)。一般項目會分層,簡單的就三層:Web/Service/Dao。
- Web表示展示層:接口的入?yún)@取,參數(shù)檢驗;日志打?。豁憫?yīng)轉(zhuǎn)換/返回(包括異常處理)
- Service就是業(yè)務(wù)層:處理業(yè)務(wù)邏輯的,是方法的主體代碼
- Dao層稱為存儲層:一般表示db處理,也可以是其它持久操作
模板方法
根據(jù)上面簡單三層的理解,定義一個模板方法出來:
@Slf4j
public class WebTemplate
{
public static String execute(String req)
{
try
{
// 1:打印入?yún)? log.info("方法參數(shù):{}", req);
// 2:參數(shù)檢驗
// TODO
// 3:業(yè)務(wù)方法
}
catch (Exception e)
{
// 4:異常處理
return "fail";
}
return "success";
}
}
這里定義了模板方法的步驟:
- 入?yún)㈩愋停ㄈ绻巧厦娴睦又荒艿腟tring肯定有局限性),最好支持泛型,比如都繼承Request基類
- 參數(shù)打?。簩?shù)都打印出來,方便統(tǒng)計排查
- 參數(shù)檢驗:對輸入?yún)?shù)進行檢驗,如果不符合條件則拋出異常,讓步驟4異常來統(tǒng)一處理
- 業(yè)務(wù)方法執(zhí)行:對于這種業(yè)務(wù)方法的執(zhí)行,可以定義一個接口讓調(diào)用方來實現(xiàn)
- 異常執(zhí)行:異??梢苑譃闃I(yè)務(wù)/全局異常,進行統(tǒng)一的處理,直接拋出或轉(zhuǎn)換成對應(yīng)異常碼
- 組裝響應(yīng):響應(yīng)可以自定義,比如如上的異常碼/異常信息
- 后置處理:可以在方法結(jié)束時,進行需要的后置處理,比如打印日志,方便后續(xù)監(jiān)控
由此可見一個完整模板方法類似如下:
package com.tw.tsm.base.template;
import com.tw.tsm.base.request.BaseRequestDTO;
import com.tw.tsm.base.response.BaseResponseDTO;
import lombok.extern.slf4j.Slf4j;
/**
* 模板方法類
*/
@Slf4j
public class WebTemplate
{
/**
* 模板方法
* @param req 請求參數(shù)
* @param res 響應(yīng)參數(shù)
* @param callback 回調(diào)方法
* @param <T>
* @param <R>
*/
public static <T extends BaseRequestDTO, R extends BaseResponseDTO> void execute(T req, R res, ServiceCallback callback)
{
try
{
// 1:打印入?yún)? log.info("方法參數(shù):{}", req);
// 2:參數(shù)檢驗
callback.check(req);
// 3:業(yè)務(wù)方法
callback.doService(req);
}
catch (Exception e)
{
// 4:異常處理
}
finally
{
log.info("處理結(jié)果:{}", res);
}
}
}
/**
* 請求基類
*/
public class BaseRequestDTO
{
}
/**
* 響應(yīng)基類
*/
@Data
public class BaseResponseDTO<T>
{
/** 錯誤碼 */
private String code;
/** 錯誤信息 */
private String msg;
/** 返回內(nèi)容 */
private T data;
}
/**
* 模板處理接口
*
* @param <T>
*/
public interface ServiceCallback<T>
{
void check(T req);
void doService(T req);
}
至此,模板方法已經(jīng)初具雛形,對于調(diào)用方來說,即可如下:
WebTemplate.execute(req, new BaseResponseDTO(), new ServiceCallback() {
@Override
public void check(Object req) {
// 參數(shù)檢驗
}
@Override
public void doService(Object req) {
// 業(yè)務(wù)方法
}
});
異常處理?
上面的模板方法,統(tǒng)一捕獲了異常Exception,這樣所有的異常都會被處理。不過在很多時候,可能需要對異常進行分類處理,比如將異常分為業(yè)務(wù)/系統(tǒng)異常:
- 對于業(yè)務(wù)異常希望明確提示調(diào)用方,告訴它失敗的原因:比如參數(shù)不合法,重復(fù)提交,資源不存在等
- 對于系統(tǒng)異常,比如db連接超時,第三方服務(wù)不可用,Npe異常等又不希望直接返回給調(diào)用者:因為這種錯誤一般不是邏輯錯誤,調(diào)用者感知了也解決不了;再者可能會暴露代碼堆棧信息,也不太安全。所以這種一般處理是返回默認的提示,比如"系統(tǒng)繁忙,請稍候重試"
基于此,定義一個異常類:CheckedException讓它繼承自RuntimeException表示業(yè)務(wù)異常,跟Exception做區(qū)分:
/**
* 檢驗異常
*/
@Data
@RequiredArgsConstructor
public class CheckedException extends RuntimeException
{
/** 錯誤碼 */
private String code;
/** 錯誤信息 */
private String msg;
}
在模板方法里面,加入CheckException處理:
/**
* 模板方法類
*/
@Slf4j
public class WebTemplate
{
/**
* 模板方法
* @param req 請求參數(shù)
* @param res 響應(yīng)參數(shù)
* @param callback 回調(diào)方法
* @param <T>
* @param <R>
*/
public static <T extends BaseRequestDTO, R extends BaseResponseDTO> void execute(T req, R res, ServiceCallback callback)
{
try
{
// 1:打印入?yún)? log.info("方法參數(shù):{}", req);
// 2:參數(shù)檢驗
callback.check(req);
// 3:業(yè)務(wù)方法
callback.doService(req);
}
catch (Exception e)
{
// 4:異常處理
handleException(e, res);
}
finally
{
log.info("處理結(jié)果:{}", res);
}
}
/**
* 異常處理
* @param e
* @param response
*/
private static void handleException(Exception e, BaseResponseDTO response)
{
if (e instanceof CheckedException)
{
CheckedException ex = (CheckedException)e;
ex.setCode(ex.getCode());
ex.setMsg(ex.getMsg());
return;
}
// 設(shè)置默認異常
// TODO
}
}
異常碼
在模板類中單獨定義了一個方法handleException來處理異常:如果是業(yè)務(wù)異常,則將異常中的錯誤碼/錯誤信息填充到響應(yīng)里面,如果是系統(tǒng)異常則填充默認異常。因為這里定義的只有一個異常類CheckException來表示所有的業(yè)務(wù)異常,所以對于業(yè)務(wù)異常需要單獨定義一個類來表示不同的場景:
/**
* 通用錯誤碼
*/
@AllArgsConstructor
public enum ComErrorCode
{
/** 缺少參數(shù) */
PARAM_MISS("E01000", "缺少參數(shù)"),
/** 系統(tǒng)繁忙,請稍候重試 */
SYSTEM_ERROR("E01999", "系統(tǒng)繁忙,請稍候重試");
@Getter
private String code;
@Getter
private String msg;
}
模板方法補充如下:文章來源:http://www.zghlxwxcb.cn/news/detail-619903.html
/**
* 模板方法類
*/
@Slf4j
public class WebTemplate
{
/**
* 模板方法
* @param req 請求參數(shù)
* @param res 響應(yīng)參數(shù)
* @param callback 回調(diào)方法
* @param <T>
* @param <R>
*/
public static <T extends BaseRequestDTO, R extends BaseResponseDTO> void execute(T req, R res, ServiceCallback callback)
{
try
{
// 1:打印入?yún)? log.info("方法參數(shù):{}", req);
// 2:參數(shù)檢驗
callback.check(req);
// 3:業(yè)務(wù)方法
callback.doService(req);
}
catch (Exception e)
{
// 4:異常處理
handleException(e, res);
}
finally
{
log.info("處理結(jié)果:{}", res);
}
}
/**
* 異常處理
* @param e
* @param response
*/
private static void handleException(Exception e, BaseResponseDTO response)
{
if (e instanceof CheckedException)
{
CheckedException ex = (CheckedException)e;
ex.setCode(ex.getCode());
ex.setMsg(ex.getMsg());
return;
}
// 設(shè)置默認異常
response.setCode(ComErrorCode.SYSTEM_ERROR.getCode());
response.setMsg(ComErrorCode.SYSTEM_ERROR.getMsg());
}
}
lambda在模板方法中的應(yīng)用
既然是要用lambda表達式在模板方法中應(yīng)用,所以就不能像剛才那樣對于回調(diào)函數(shù)直接用匿名類,這里就改造一下:文章來源地址http://www.zghlxwxcb.cn/news/detail-619903.html
/**
* 模板回調(diào)函數(shù)
*
* @param <T>
*/
@FunctionalInterface
public interface ServiceCallback<T>
{
default void check(T req)
{
}
void doService(T req);
}
WebTemplate.execute(req, new BaseResponseDTO(), request -> {
});
- 首先改造一下回調(diào)函數(shù),增加@FunctionalInterface注解,表示這是一個函數(shù)式接口
- 將check()方法聲明為default,這里java8的默認方法
- 最后用lambda實現(xiàn)業(yè)務(wù)處理邏輯
到了這里,關(guān)于JAVA8-lambda表達式8:在設(shè)計模式-模板方法中的應(yīng)用的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!