一.多使用lombok的新特性
lombok对每个java后端来说应该都不陌生,但对它的使用不应该仅停留在@Data,@Getter,@Setter...上,推荐多使用以下几个注解:
@Builder
让类转换为建造者模式,可以让类的创建和赋值变得更优雅,特别是在该类有很多属性需要设置的时候
Employee employee = new Employee();
employee.setName("lombok");
employee.setAge(10);
Employee employee1 = Employee.builder()
.name("lombok")
.age(10)
.build();
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
在Spring项目中,当一个类里面依赖的模块很多,会有很多Service被注入进来,脱离Lombok,我们通常会用下面这种方式进行注入:
public class TestController {
@Autowired
private TestService1 testService1;
@Autowired
private TestService2 testService2;
...
}
这样注入会有大量重复的@Autowired注解,如果你用的是Idea编辑器,还会收到烦人的injection not recomand,通过Lombok注解,就可以同时化解上面量大问题:
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TestController {
private final TestService1 testService1;
private final TestService2 testService2;
...
}
当有新的类需要被注入时,不再需要加@Autowied注解,直接引入即可,值得注意的是,加了该@RequiredArgsConstructor(onConstructor = @__(@Autowired))注解后,引入的类需要定义为final类型或者加@NotNull注解,推荐用final.
另外还有@AllArgsConstructor,@NoArgsconstructor注解也推荐使用,不要再手动写全参/无参构造了,交给插件去处理不香吗?
二.巧用Strsubstitutor类处理字符串拼接
在实际项目开发中,总会有一些场景需要字符串拼接,大量的字符串拼接不仅会影响性能,更会让你的代码看着很丑很Low,即便可以用StringBuilder来改善性能,但代码依旧非常丑,这里我举个例子,假设最终想要的字符串为: 2020年5月14日 午餐(11:30-12:30)
如果用传统方式来实现:
//用字符串拼接
String date = "2020年5月14日";
String skuName = "午餐";
String startTime = "11:30";
String endTime = "12:30";
String result = date + " " + skuName + "(" + startTime + "-" + endTime + ")";
//用StringBuilder实现
String result1 = new StringBuilder()
.append(date)
.append(" ")
.append(skuName)
.append("(")
.append(startTime)
.append("-")
.append(endTime)
.append(")")
.toString();
这样写代码似乎还能看,但如果拼接的内容比上面还多,那代码就又丑性能又差, 可以考虑用apache-commons-lang3提供的Strsubstitutor重构一下:
Map<String, String> valueMap = new HashMap<>();
valueMap.put("date", date);
valueMap.put("skuName", skuName);
valueMap.put("startTime", startTime);
valueMap.put("endTime", endTime);
final String template = "${date} ${skuName}(${startTime}-${endTime})";
StrSubstitutor strSub = new StrSubstitutor(valueMap);
String result2 = strSub.replace(template);
这样除去向Map中put的操作,实际仅需要三行代码即可完成复杂的字符串拼接,再多拼接都不怕!而且底层用的是正则匹配,没有频繁的创建String对象,性能要比前面这两种实现方式高很多,代码也优雅不少.
三.统一的日志打印工具
日志的打印其实是很有讲究的,好的日志不仅可以帮助开发排查线上问题,还可以提高系统性能,但遗憾的是目前大部分公司的日志都没有什么规范可言,到开发这里更是随心所欲...我曾在上家公司的生产环境下用Jenkins做过测试,日志对系统吞吐量有不小的影响,关闭info级别日志和开启,QPS大约能相差100多,一分钟下来就是6000-8000条请求的差距! 所以日志该如何打,打什么信息真的值得深究.
这里举两个真实的反例:
A.某开发同学在线上系统,调用一个批量返回数据的rpc接口,并在所有场景下都打印该rpc接口返回的数据,偏偏该接口的使用频率很高,没过几个月,监控开始告警了,磁盘空间不够用,排查发现日志文件竟高达28GB...
B.一个项目组有多个成员共同开发,每个人都有自己的日志打印风格,而且同一个人在不同的接口打印的日志风格也有差异,于是整个代码在日志这块看着真是一言难尽,非常乱...更可怕的是,有人在接口调用异常的时候居然没有打日志,而是仅在每次成功的时候打,当有天线上出了故障,没有日志记录案发现场,排查无从下手...
所以对于日志,千万不要忽视,好的日志应该是记录必要的案发现场信息,对不同级别的日志打印内容区别处理,最大限度提高日志效率,同时尽量做到统一,所有人都一套风格,不要一个系统,N种风格,会让代码丑陋,下面我贴一个工具类,供大家参考,以后若有日志需要打印,可以Copy此工具类到系统中,当然也可以自己封装.
public class LogUtil {
public static void dataIncoming(Logger log, String methodName, String invokeParam) {
LogUtil.info(log,
StrFormatter.format("dataIncoming;{}", methodName),
"",
invokeParam);
}
public static void invokeSuccess(Logger log, String methodName, String invokeParam, String invokeResult) {
LogUtil.info(log,
StrFormatter.format("{};invoke;success", methodName),
StrFormatter.format("{}", invokeResult),
invokeParam);
}
public static void invokeFail(Logger log, String methodName, String invokeParam, String errorMsg) {
LogUtil.warn(log,
StrFormatter.format("{};invoke;fail", methodName),
StrFormatter.format("errorMsg={}", errorMsg),
invokeParam);
}
public static void invokeAbort(Logger log, String methodName, String invokeParam, String abortReason) {
LogUtil.info(log,
StrFormatter.format("{};invoke;abort", methodName),
StrFormatter.format("{}", abortReason),
invokeParam);
}
public static void invokeEmptyResult(Logger log, String methodName, String invokeParam, String invokeResult) {
LogUtil.warn(log,
StrFormatter.format("{};invoke;emptyResult", methodName),
StrFormatter.format("{}", invokeResult),
invokeParam);
}
public static void invokeError(Logger log, String methodName, String invokeParam, Throwable e) {
LogUtil.error(log,
StrFormatter.format("{};invoke;error", methodName),
StrFormatter.format("cause={}", e.getMessage()),
invokeParam, e);
}
public static void invokeBlocked(Logger log, String methodName, String invokeParam, Throwable e) {
LogUtil.error(log,
StrFormatter.format("{};invoke;blocked by sentinel", methodName),
StrFormatter.format("cause={}", e.getMessage()),
invokeParam, e);
}
/**
* 打印日志,格式如下:
* 执行了什么操作|得到了什么结果|对应的参数
*
* @param logger logger
* @param operate 执行了什么操作,不能为空
* @param result 得到了什么结果,可能为空
* @param param 对应的参数,可能为空,若有多个可以转成json
*/
public static void debug(Logger logger, String operate, String result, String param) {
logger.debug("{}|{}|{}", operate, result, param);
}
/**
* 打印日志,格式如下:
* 执行了什么操作|得到了什么结果|对应的参数
*
* @param logger logger
* @param operate 执行了什么操作,不能为空
* @param result 得到了什么结果,可能为空
* @param param 对应的参数,可能为空,若有多个可以转成json
*/
public static void info(Logger logger, String operate, String result, String param) {
logger.info("{}|{}|{}", operate, result, param);
}
/**
* 打印日志,格式如下:
* 执行了什么操作|得到了什么结果|对应的参数
*
* @param logger logger
* @param operate 执行了什么操作,不能为空
* @param result 得到了什么结果,可能为空
* @param param 对应的参数,可能为空,若有多个可以转成json
*/
public static void warn(Logger logger, String operate, String result, String param) {
logger.warn("{}|{}|{}", operate, result, param);
}
/**
* 打印日志,格式如下:
* 执行了什么操作|得到了什么结果|对应的参数
*
* @param logger logger
* @param operate 执行了什么操作,不能为空
* @param result 得到了什么结果,可能为空
* @param param 对应的参数,可能为空,若有多个可以转成json
* @param e 出现的异常
*/
public static void error(Logger logger, String operate, String result, String param, Throwable e) {
logger.error(StrFormatter.format("{}|{}|{}", operate, result, param), e);
}
}
四.统一的参数合法性校验
对于入参的校验,有很多种方式,从最简单的If判断到自己写AssertUtil,使用hutoolUtil工具类,到Hibernate Validator框架... 对于三个以上的参数校验,个人更推崇用框架来实现,会让代码更工整优雅,而且还可以轻松通过框架实现校验提示内容的国际化。
比如我有一个商品对象,里面有非常多的字段需要做非空,长度,字段类型等合法性校验,如果不合法,则封装后统一返回给前端,展示给用户
@Data
public class ProductDTO {
@NotEmpty(message = "商品名称必填")
@Length(max = 15,message = "商品名称最多只能输入15个字符")
private String productName;
@NotEmpty(message = "商品编号必填")
@Length(max = 6,message = "商品编号最多只能输入6个字符")
private String productCode;
...
}
通过框架,仅需2行代码就可以拿到所有错误信息的集合Set,非常强大,而且建议将Validator封装仅Util类中,之后需要参数校验的地方,仅需一行代码就可以搞定,非常优雅!
Validator validator = Validation.byProvider(HibernateValidator.class).configure().buildValidatorFactory().getValidator();
Set<ConstraintViolation<ProductDTO>> validResult = validator.validate(productDTO);
如果有国际化需求的可以参考这两篇文档:
https://www.ibm.com/developerworks/cn/java/j-cn-hibernate-validator/index.html
https://docs.oracle.com/javase/tutorial/i18n/locale/create.html
关于代码优雅,是学不完的,最有效的几种方式就是啃阿里巴巴代码规范,啃设计模式,然后在平时多学习别人写的代码,实时总结,取其精华,他为己用,毕竟每个人都有值得学习的地方,如果以上内容有收获,不妨点个赞加个关注啥的,防迷路!