网站首页 > java教程 正文
前言
罗列工作中实际使用的一些代码技巧或者叫工具类;知识无大小,希望大家都有收获
实用技巧
rpc服务出参统一化
什么,出参统一化有什么好说的????? 我不知道你们有没有遇到过多少五花八门的外部服务提供的返回对象,可能别人没有规范约束,我们管不了,但是从我们这里出去的,我们可以强制约束一下,不然发生新老交替,这代码还能看吗
首先出参都叫xxDTO的,阿里java开发手册提到过;再者我们是提供服务的一方,错误码code,错误信息msg,以及返回结果data都是要明确体现出来的,像下面这样
1public class TradeResultDTO<T> implements 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", null, true, data);
12 }
13
14 public static <T> TradeResultDTO<T> fail(String code, String msg) {
15 return base(code, msg, false, null);
16 }
17
18 public static <T> TradeResultDTO<T> fail(String msg) {
19 return base(DEFAULT_FAIL_CODE, msg, false, null);
20 }
21
22 public static <T> TradeResultDTO<T> success() {
23 return base("200", null, true, null);
24 }
25
26 public static <T> TradeResultDTO<T> fail(IError iError) {
27 return base(iError.getErrorCode(), iError.getErrorMsg(), false, null);
28 }
29}
统一对象返回的结构就是上面这样
接着这个我想说的是,作为服务提供方,如果这个接口提供了返回值,我拿创建订单接口举例
1/**
2 * 创建交易单,业务系统发起
3 *
4 * @param req 创建入参
5 * @return 返回创建信息6 */
7TradeResultDTO<TradeCreateOrderResponseDTO> createOrder(TradeCreateOrderRequestDTO req)
8
比如这个TradeCreateOrderResponseDTO 返回了订单号之类的基本信息,这个接口对于具体业务场景只能产生一笔订单号,我之前遇到过对方只是提示什么的错误信息(订单已存在),是的没错,他做了幂等,但是他没有返回原值,那对应的调用方进入了死循环,可能对应的业务系统,需要返回的订单号落到自己的数据库,一直异常一直回滚重试,没有任何意义;所以作为一个负责人的服务提供方,类似这种情况,如果你的方法有幂等,那么请一定返回存在的那个对象;
异常统一化
统一化使用,杜绝项目出现各种各样的自定义异常
对外统一抛出异常
我使用的统一化有两个方面:
- 抛出的自定义异常不要五花八门,一个就够了;很多人喜欢写各种各样的异常,初衷其实没错,但是人多手杂,自定义异常可能越写越乱;
- 异常信息最好尽可能的具体化,描述出业务产生异常原因就可以了,比如入参校验的用户信息不存在之类的;或者在调用用户中心的时候,捕获了该异常,此时你只需定义调用用户中心异常就可以了
然后看下工作中比较推荐的:
首先,需要搞一个统一抛出异常的工具 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(0, 1).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}
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> 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<>(0, 1, 10, 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
未完待续,大家如果有好的建议,希望在留言中提出
喜欢的可以点赞+关注,感谢支持
- 上一篇: Java IO流之文件输入输出流
- 下一篇: Java系统开发从入门到精通第三讲(文字版)
猜你喜欢
- 2025-01-12 java版gRPC实战之二:服务发布和调用
- 2025-01-12 Controller层代码这么写,任督二脉全打通
- 2025-01-12 Java手写数据库(第一章)
- 2025-01-12 使用Java语言写一个"Hello, World!" 程序
- 2025-01-12 Java系统开发从入门到精通第三讲(文字版)
- 2025-01-12 Java IO流之文件输入输出流
- 2025-01-12 深圳尚学堂:干货来啦!JAVA常用代码(四)
- 2025-01-12 阿里巴巴Java编程规范(6):安全规约
- 2025-01-12 Java 业务代码问题排查与异常处理:从“啊这”到“稳了”
- 2025-01-12 理解 Liquor :动态编译Java代码的神器
你 发表评论:
欢迎- 04-24Java Collections 工具类集合框架中常用算法解析
- 04-24桶排序的简单理解
- 04-24Java集合框架底层实现原理大揭秘
- 04-24Java 集合框架全面解析:选对数据结构,提升开发效率
- 04-24c#集合排序
- 04-24Java面试中常被问到的集合类深度解读
- 04-24VBA技术资料MF278:对集合进行排序
- 04-24Spring 最常用的 7 大类注解,史上最强整理
- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)