[java基礎系列]平常工做中我所使用的java代碼技巧

Man, Glasses, Hipster, Beard, Adult
Man, Glasses, Hipster, Beard, Adult

前言

羅列工做中實際使用的一些代碼技巧或者叫工具類;知識無大小,但願你們都有收穫java

實用技巧

rpc服務出參統一化

什麼,出參統一化有什麼好說的????? 我不知道大家有沒有遇到過多少五花八門的外部服務提供的返回對象,可能別人沒有規範約束,咱們管不了,可是從咱們這裏出去的,咱們能夠強制約束一下,否則發生新老交替,這代碼還能看嗎git

首先出參都叫xxDTO的,阿里java開發手冊提到過;再者咱們是提供服務的一方,錯誤碼code,錯誤信息msg,以及返回結果data都是要明確體現出來的,像下面這樣github

 1public class TradeResultDTO<Timplements Serializable {
2    /**
3     * 默認失敗編碼
4     */

5    private static final String DEFAULT_FAIL_CODE = "500";
6    private boolean success;
7    private String code;
8    private String msg;
9    private T data;
10        public static <T> TradeResultDTO<T> success(T data) {
11        return base("200"nulltrue, data);
12    }
13
14    public static <T> TradeResultDTO<T> fail(String code, String msg) {
15        return base(code, msg, falsenull);
16    }
17
18    public static <T> TradeResultDTO<T> fail(String msg) {
19        return base(DEFAULT_FAIL_CODE, msg, falsenull);
20    }
21
22    public static <T> TradeResultDTO<T> success() {
23        return base("200"nulltruenull);
24    }
25
26    public static <T> TradeResultDTO<T> fail(IError iError) {
27        return base(iError.getErrorCode(), iError.getErrorMsg(), falsenull);
28    }
29}
複製代碼

統一對象返回的結構就是上面這樣web

接着這個我想說的是,做爲服務提供方,若是這個接口提供了返回值,我拿建立訂單接口舉例spring

1/**
2 * 建立交易單,業務系統發起
3 *
4 * @param req 建立入參
5 * @return 返回建立信息
6 */

7TradeResultDTO<TradeCreateOrderResponseDTO> createOrder(TradeCreateOrderRequestDTO req)
8

複製代碼

好比這個TradeCreateOrderResponseDTO 返回了訂單號之類的基本信息,這個接口對於具體業務場景只能產生一筆訂單號,我以前遇到過對方只是提示什麼的錯誤信息(訂單已存在),是的沒錯,他作了冪等,可是他沒有返回原值,那對應的調用方進入了死循環,可能對應的業務系統,須要返回的訂單號落到本身的數據庫,一直異常一直回滾重試,沒有任何意義;因此做爲一個負責人的服務提供方,相似這種狀況,若是你的方法有冪等,那麼請必定返回存在的那個對象;數據庫

異常統一化

統一化使用,杜絕項目出現各類各樣的自定義異常後端

對外統一拋出異常

我使用的統一化有兩個方面:api

  • 拋出的自定義異常不要五花八門,一個就夠了;不少人喜歡寫各類各樣的異常,初衷其實沒錯,可是人多手雜,自定義異常可能越寫越亂;數組

  • 異常信息最好儘量的具體化,描述出業務產生異常緣由就能夠了,好比入參校驗的用戶信息不存在之類的;或者在調用用戶中心的時候,捕獲了該異常,此時你只需定義調用用戶中心異常就能夠了app

而後看下工做中比較推薦的:

首先,須要搞一個統一拋出異常的工具 ExceptionUtil(這裏Exceptions是公司內部統一先後端交互的,基於這個包裝一個基礎util,統一整個組拋異常的入口)

 1public class ExceptionUtil {
2    public static OptimusExceptionBase fail(IError error) throws OptimusExceptionBase {
3        return Exceptions.fail(errorMessage(error));
4    }
5
6    public static OptimusExceptionBase fail(IError error, String... msg) throws OptimusExceptionBase {
7        return Exceptions.fail(errorMessage(error, msg));
8    }
9
10    public static OptimusExceptionBase fault(IError error) throws OptimusExceptionBase {
11        return Exceptions.fault(errorMessage(error));
12    }
13
14    public static OptimusExceptionBase fault(IError error, String... msg) throws OptimusExceptionBase {
15        return Exceptions.fault(errorMessage(error, msg));
16    }
17
18
19    private static ErrorMessage errorMessage(IError error) {
20        if (error == null) {
21            error = CommonErrorEnum.DEFAULT_ERROR;
22        }
23        return ErrorMessage.errorMessage("500""[" + error.getErrorCode() + "]" + error.getErrorMsg());
24    }
25
26    private static ErrorMessage errorMessage(IError error, String... msg) {
27        if (error == null) {
28            error = CommonErrorEnum.DEFAULT_ERROR;
29        }
30        return ErrorMessage.errorMessage("500""[" + error.getErrorCode() + "]" + MessageFormat.format(error.getErrorMsg(), msg));
31    }
32}
複製代碼

其實上面代碼裏也體現出來IError這個接口了,咱們的錯誤枚舉都須要實現這個異常接口,方便統一獲取對應的錯誤碼和錯誤信息,這裏列舉一下通用異常的定義

 1public interface IError {
2    String getErrorCode();
3
4    String getErrorMsg();
5}
6@AllArgsConstructor
7public enum CommonErrorEnum implements IError {
8    /**
9     *
10     */

11    DEFAULT_ERROR("00000000""系統異常"),
12    REQUEST_OBJECT_IS_NULL_ERROR("00000001""入參對象爲空"),
13    PARAMS_CANNOT_BE_NULL_ERROR("00000002""參數不能爲空"),
14    BUILD_LOCK_KEY_ERROR("00000003""系統異常:lock key異常"),
15    REPEAT_COMMIT_ERROR("00000004""正在提交中,請稍候");
16
17    private String code;
18    private String msg;
19
20    @Override
21    public String getErrorCode() {
22        return code;
23    }
24
25    @Override
26    public String getErrorMsg() {
27        return msg;
28    }
29}
複製代碼

相似上面CommonErrorEnum的方式咱們能夠按照具體業務定義相應的枚舉,好比OrderErrorEnum、PayErrorEnum之類,不只具備區分度並且,也能瞬速定位問題;

因此對外拋出異常統一化就一把梭:統一util和業務錯誤枚舉分類

對內統一捕獲外部異常

不少時候咱們須要調用別人的服務而後寫了一些adapter適配類,而後在裏面trycatch一把;其實這時候能夠利用aop好好搞一把就完事了,而且統一輸出adapter層裏面的日誌

 1    public Object transferException(ProceedingJoinPoint joinPoint) {
2        try {
3            Object result = joinPoint.proceed();
4            log.info("adapter result:{}", JSON.toJSONString(result));
5            return result;
6        } catch (Throwable exception) {
7            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
8            Method method = signature.getMethod();
9            log.error("{}.{} throw exception", method.getDeclaringClass().getName(), method.getName(), exception);
10            throw ExceptionUtils.fault(CommonErrorEnum.ADAPTER_SERVICE_ERROR);
11            return null;
12        }
13    }
複製代碼

上面這段統一捕獲了外部服務,記錄異常日誌,避免了每一個adapter類重複捕獲的問題

參數校驗

用過swagger的應該瞭解api方法裏有對應的註解屬性約束是否必填項,可是若是判斷不是在api入口又或者沒有相似的註解,大家通常怎麼作的,下面給出我本身的一種簡單工具;有更好大佬的能夠推薦一下

ParamCheckUtil.java

 1@Slf4j
2public class ParamCheckUtil {
3
4    /**
5     * 校驗請求參數是否爲空
6     *
7     * @param requestParams 請求入參
8     * @param keys          屬性值數組
9     */

10    public static void checkParams(Object requestParams, String... keys) {
11        if (null == requestParams) {
12            throw ExceptionUtil.fault(CommonErrorEnum.REQUEST_OBJECT_IS_NULL_ERROR);
13        }
14        StringBuilder sb = new StringBuilder();
15        for (String fieldName : keys) {
16            Object value = null;
17            Type type = null;
18            try {
19                String firstLetter = fieldName.substring(01).toUpperCase();
20                String getter = "get" + firstLetter + fieldName.substring(1);
21                Method method = requestParams.getClass().getMethod(getter);
22                value = method.invoke(requestParams);
23                type = method.getReturnType();
24            } catch (Exception e) {
25                log.error("獲取屬性值出錯,requestParams={}, fieldName={}", requestParams, fieldName);
26            } finally {
27                // 判空標誌 String/Collection/Map特殊處理
28                boolean isEmpty =
29                        (String.class == type && StringUtil.isEmpty((String) value))
30                                || (Collection.class == type && CollectionUtils.isEmpty((Collection<? extends Object>) value))
31                                || (Map.class == type && CollectionUtils.isEmpty((Collection<? extends Object>) value))
32                                || (null == value);
33                if (isEmpty) {
34                    if (sb.length() != 0) {
35                        sb.append(",");
36                    }
37                    sb.append(fieldName);
38                }
39            }
40        }
41
42        if (sb.length() > 0) {
43            log.error(sb.toString() + CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR.getErrorMsg());
44            throw ExceptionUtil.fault(CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR, sb.toString() + CommonErrorEnum.PARAMS_CANNOT_BE_NULL_ERROR.getErrorMsg());
45        }
46    }
47
48    // test
49    public static void main(String[] args) {
50        TradeCreateOrderRequestDTO tradeCreateOrderRequestDTO = new TradeCreateOrderRequestDTO();
51        tradeCreateOrderRequestDTO.setBusinessNo("");
52        ParamCheckUtil.checkParams(tradeCreateOrderRequestDTO, "businessNo""tradeType""tradeItemDTOS");
53    }
54
55}
複製代碼

基於了上面統一異常的形式,只要參數校驗出空我就拋出異常中斷程序,而且告知其缺什麼參數

我在業務代碼須要判斷字段非空的地方只須要一行就夠了,就行下面這樣

1ParamCheckUtil.checkParams(tradeCreateOrderRequestDTO, "businessNo""tradeType""tradeItemDTOS");
複製代碼

而不是咱們經常使用的一堆判斷,像下面這樣;看到這些我人都暈了,一次兩次就算了,一大段全是這種

 1if (null == tradeCreateOrderRequestDTO) {
2// 提示tradeCreateOrderRequestDTO爲空
3}
4if (StringUtil.isEmpty(tradeCreateOrderRequestDTO.getBusinessNo())) {
5// 提示businessNo爲空
6}
7if (StringUtil.isEmpty(tradeCreateOrderRequestDTO.getTradeType())) {
8// 提示tradeType爲空
9}
10if (CollectionUtils.isEmpty(tradeCreateOrderRequestDTO.getTradeItemDTOS())) {
11// 提示tradeItemDTOS列表爲空
12}
複製代碼

若是你是上面說的這種形式,不妨試試我提供的這種

bean相關

對象的構造

關於對象的構造,我想提兩點,構造變的對象和不變的對象

  • 構造不變對象,使用builder,不提供set方法,推薦使用lombok @Builder
1@Builder
2public class UserInfo {
3    private String id;
4    private String name;
5
6    public static void main(String[] args) {
7        UserInfo userInfo = UserInfo.builder().id("a").name("name").build();
8    }
9}
複製代碼
  • 構造可變對象,推薦提供鏈式調用形式 使用lombok @Accessors(chain = true)註解
1@Data
2@Accessors(chain = true)
3public class CardInfo {
4    private String id;
5    private String name;
6    public static void main(String[] args) {
7        CardInfo cardInfo = new CardInfo().setId("c").setName("name");
8    }
9}
複製代碼

對象轉換

就一把梭:lambda工具類+mapstruct進行轉換

BeanConvertUtil.java 通用的對象、list、Page轉換

 1public class BeanConvertUtil {
2    /**
3     * 對象轉換
4     *
5     * @param source     源對象
6     * @param convertFun T -> R lambda轉換表達式
7     * @param <T>        輸入類型
8     * @param <R>        輸出類型
9     * @return 返回轉化後輸出類型的對象
10     */

11    public static <T, R> convertObject(T source, Function<T, R> convertFun) {
12        if (null == source) {
13            return null;
14        }
15        return convertFun.apply(source);
16    }
17
18    /**
19     * Page轉換
20     *
21     * @param page       源對象
22     * @param convertFun T -> R lambda轉換表達式
23     * @param <T>        輸入類型
24     * @param <R>        輸出類型
25     * @return 返回轉化後輸出類型的對象
26     */

27    public static <T, R> Page<R> convertPage(Page<T> page, Function<T, R> convertFun) {
28        if (Objects.isNull(page)) {
29            return new Page<>(0110, Collections.emptyList());
30        }
31        List<R> pageList = convertList(page.getItems(), convertFun);
32        return new Page<>(page.getTotalNumber(), page.getCurrentIndex(), page.getPageSize(), pageList);
33    }
34
35    /**
36     * ListData轉換
37     *
38     * @param inputList  數據源
39     * @param convertFun T -> R lambda轉換表達式
40     * @param <T>        輸入類型
41     * @param <R>        輸出類型
42     * @return 輸出
43     */

44    public static <T, R> List<R> convertList(List<T> inputList, Function<T, R> convertFun) {
45        if (org.springframework.util.CollectionUtils.isEmpty(inputList)) {
46            return Lists.newArrayList();
47        }
48        return inputList
49                .stream()
50                .map(convertFun)
51                .collect(Collectors.toList());
52    }
53}
複製代碼

實戰使用,在lambda方法進行轉換: 先轉換相同屬性,再進行剩餘屬性賦值

 1 public interface OrderConverter {
2    OrderConverter INSTANCE = Mappers.getMapper(OrderConverter.class);
3        // 入參進行相同屬性轉換
4    TradeOrderDO createOrder2TradeOrderDO(TradeCreateOrderRequestDTO req);
5}
6 TradeOrderDO mainTradeOrder = BeanConvertUtil.convertObject(req, x -> {
7     TradeOrderDO tod = OrderConverter.INSTANCE.createOrder2TradeOrderDO(req);
8     tod.setOrderType(mainOrderType);
9     tod.setOrderCode(snowflakeIdAdapterService.getId());
10     tod.setOrderStatus(TradeStateEnum.ORDER_CREATED.getValue());
11     tod.setDateCreate(new Date());
12     tod.setDateUpdate(new Date());
13     return tod;
14});
複製代碼

其實對象轉換也能夠徹底經過mapstruct提供的一些表達式進行轉換,可是有時候寫那個感受不是很直觀,其實均可以,我比較喜歡我這種形式,你們有建議也能夠提出

NPE解決指南

1.null值手動判斷[強制]

嵌套取值<3 推薦 null值判斷(PS:強制null寫在前面,別問爲何,問就是這樣寫你會意識到這裏要NPE)

學會這點 基本意識有了

1null!=obj&&null!=obj.getXX()
複製代碼

2.Optional

2.1 Optional嵌套取值[強制]

參數>=3的取值操做
學會這點 基本告別NPE

這裏以OrderInfo對象爲例 獲取purchaseType

1Optional<OrderInfo> optional = Optional.ofNullable(dto);
2Integer purchaseType = optional.map(OrderInfo::getOrderCarDTO)
3                                 .map(OrderCarDTO::getPurchaseType)
4                                 .orElse(null);
複製代碼

若是對取出的值如需再次進行判斷操做 參考第1點

2.2 中斷拋出異常[按需]

仍是以上面的例子

1{
2    // ...
3    optional.map(OrderInfo::getOrderDTO).map(OrderDTO::getOrderBusinessType)
4              .orElseThrow(() -> new Exception("獲取cityCode失敗"));
5}
複製代碼

若是依賴某些值,可儘早fail-fast

3.對象判空[強制]

1Objects.equals(obj1,obj2);
複製代碼

4.Boolean值判斷[強制]

棄用如下方式謝謝(PS:不少時候null判斷你會丟的)

1null!=a&&a;
複製代碼

正確食用

1Boolean.TRUE.equals(a);
複製代碼

5.list正確判空姿式[強制]

1if (CollectionUtils.isEmpty(list)) {
2    // fail fast
3    // return xxObj or return;
4}
5List<Object> safeList = list.stream().filter(Objects::nonNull).collect(Collectors.toList());
6if (CollectionUtils.isEmpty(safeList)) {
7    // fail fast
8    // return xxObj or return;
9}
複製代碼

6.String正確判空姿式[強制]

1// 不爲空
2if (StringUtil.isNotEmpty(s)) {
3    // ...
4}
複製代碼

7.包裝類型轉換判空[強制]

特別是遍歷過程當中使用,須要判斷是否爲空。

1int i = 0;
2list.forEach(item -> {
3    if(Objects.nonNull(item.getType)){
4     i += item.getType; //item.getType 返回 Integer   
5    }
6});
複製代碼

小結

融會貫通以上幾招絕對告別NPE

END

未完待續,你們若是有好的建議,但願在留言中提出
原文git

相關文章
相關標籤/搜索