C002.22届0818怪
C002.22届0818怪
1.pom.xml 60-63
tail-backend 2022-08-18 18:39:03 /tail-common/pom.xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>easyexcel</artifactId>
<version>2.2.8</version>
</dependency>
maven版本号统一定义
Maven的实践中,版本号在parent的porperties中定义, 格式为*.version
,如easyexcel.version
。
这也就要求,任何一个依赖必须明确定义,知道去用途,特性,尤其漏洞等。
2.TruckController.java 82-83
bbl_backend 2022-09-04 12:20:23
logger.info("truck rate {}", JSONObject.toJSONString(request));
日志性能和碎片
使用slf4j的占位符{}
,其目标之一是避免无意义的性能消耗和内存碎片。 此处不管日志级别如何,都会执行toJSONString。应修改为
// 常规写法,推荐,简单明了
if (logger.isInfoEnabled()) {
logger.info("truck rate {}", JSONObject.toJSONString(request));
}
// 2.0+的fluent+lambda写法,视情况而定,不推荐
logger.atInfo()
.setMessage("truck rate {}")
.addArgument(() -> JSONObject.toJSONString(request))
.log();
3.CommonShipResponse.java 47-49
bbl_backend 2022-09-04 17:52:27
final String str = JSONObject.toJSONString(response);
final CommonResponse res = JSONObject.parseObject(str, CommonResponse.class);
低效不安全的复制
业务代码中,经常有Dto值赋值或克隆。按以下优先级从高到低考虑。
- MapStruct - 强类型,编译时,静态赋值。
- BeanUtil - 弱类型,运行时属性反射赋值或浅clone
- 序列化反序列化 - 弱类型,深度复制,kryo优于json。
4.ExpressAccountCache.java 55-58
bbl_backend 2022-09-04 19:01:07
private Optional<ExpressAccount> getExpressAccountById(Long id) {
final ExpressAccount ea = expressAccountDao.fetchOneById(id);
return Optional.ofNullable(ea);
}
getter命名误导
慎用get
动词前缀,以免误导为getter
,set
同。 在JavaBean体系中,带有getter特征,意味在简单取值,无副作用。
对于此类方法,应该采用能够翻译任务层面和轻重的动词。
- 服务层 - make/save/load/find
- 服务层 - create/remove/modify/search
- 数据层 - purge/store/fetch
- sql层 - insert/delete/update/select
容器Optional及值对象
非函数式,不推荐用Optional
,因java中stream+lambda非常丑陋。 Long
和long
,后者@NotNull
,前者要判断NPE
。
5.TruckServiceImpl.java 239-242
bbl_backend 2022-09-05 11:31:12
if (StringUtils.isBlank(request.getDelivery().getTimeZone()) ||
TimeZone.getTimeZone(request.getDelivery().getTimeZone()).toZoneId() == null) {
throw new BblException("请先确认时区");
}
重复链式调用
无法确定链式调用是否具有幂等性的时候,必须提取final变量。 此外,推荐提取变量以减少代码长度,除非是非幂等性需要。
final String timeZone = request.getDelivery().getTimeZone();
if (StringUtils.isBlank(timeZone) || TimeZone.getTimeZone(timeZone).toZoneId() == null) {
throw new BblException("请先确认时区");
}
使用I18n异常
在需要I18n的场景,需要使用Code或Enum转换业务Message。 此处异常,应该换成无堆栈的MessageException,可提高性能。
6.MotherShipServiceImpl.java 66-68
bbl_backend 2022-09-05 18:49:36
final String format = String.format("?zip=%s&city=%s&state=%s&street=%s", zip, city, state, street);
final String s = format.replaceAll("\\+", "%20");
String.format效果不经济
应该避免使用C语言printf
风格的字符串格式化,效果不经济。 建议直接使用字符串拼接,或支持预编译的工具类,如StringTemplate
。
与此Formatter情景类似的还有Pattern的split, replace 要注意线程安全,单例安全编译,多线程下高性能使用。
+
还是%20
URL中的空格是replace("+", "%20")
- 字符串替换全部replaceAll("\\+", "%20")
- 正则替换全部
空格在RFC3986
约定为%20
,如js的encodeURI。 在RFC1866
约定为+
,如java中的URLEncoder。
以下工具默认%20
,就不必使用蹩脚的replace了
- spring-web的UriUtils
- guava的UrlEscapers.urlFragmentEscaper()
7.MotherShipServiceImpl.java 171-174
bbl_backend 2022-09-06 17:47:25
if (response.isSuccessful()) {
return JSON.parseObject(Objects.requireNonNull(response.body()).string(), \
new TypeReference<MotherCommonResponse<List<T>>>(clz) {
}).getData();
}
单行多调用代码
含有多调用的单行代码,不宜过长,影响可读性外,重要的是模糊了异常错误的行数,建议段成短句。
if (response.isSuccessful()) {
final String str = Objects.requireNonNull(response.body()).string();
final Type typ = new TypeReference<MotherCommonResponse<List<T>>>(clz) {}.getType();
final MotherCommonResponse<List<T>> obj = JSON.parseObject(str, typ);
return obj.getData();
}
Json反序列化的泛型
在FastJson使用中,wings禁用AutoType,并和jackson有场景区分。 对于Json的反序列化,两者提供了相同的TypeReference类和用法。推荐用法如下,
- 免编译擦除的单行声明
new TypeReference.getType()
- spring的
ResolvableType.getType()
- jackson的
TypeFactory
获取JavaType
8.AliPayService.java 62-72
bbl_backend 2022-09-07 09:25:32
Map<String, String> params = new HashMap<>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
String name = (String) iter.next();
String[] values = (String[]) requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
params.put(name, valueStr);
}
数据结构和编程底子薄
场景的key-val签名算法,不过有些官方文档的代码很不负责人,写的非常HelloWorld
- 字母序排key,用TreeMap完成,按先后顺序则LinkedHashMap
- 有泛型的要补全,不要用raw,这点根据IDE提示修改就好
- Map同时去key-val的loop,用entry,不要获得key再get,非二遍事
- 集合及数组的join,有现成的工具类。
- 如果非要loop,参考注释的代码,使用substring和setLength
Map<String, String> params = new TreeMap<>();
Map<String, String[]> requestParams = request.getParameterMap();
// final StringBuilder sb = new StringBuilder(512);
for (Map.Entry<String, String[]> entry : requestParams.entrySet()) {
String name = entry.getKey();
String valueStr = String.join(",", entry.getValue());
params.put(name, valueStr);
// for (String s: entry.getValue()) {
// sb.append(',').append(s);
// }
// params.put(name, sb.length() > 0 ? sb.substring(1) : "");
// sb.setLength(0); // 需要复用时
}
9.BblProgressService.java 122-124
bbl_backend 2022-09-07 11:37:32
@Transactional
@Retryable(maxAttempts = 5, value = Exception.class, backoff = @Backoff(delay = 1000L, multiplier = 2))
public void updateStatus(int status, String message, Long id, BigDecimal rate, String express) {
// ...
}
Retry和Transaction共存的隐患
多个切面存在于同一方法,是按照切面的Order顺序来执行的,上述代码堆栈如下
- updateStatus(BblProgressService.java:124)
- proceedWithInvocation(TransactionInterceptor.java:123)
- doWithRetry(RetryOperationsInterceptor.java:93)
可以看出,实际为在Retry中执行Transaction,但这样存在隐患,导致事务rollback。 即在@Transactional中调用updateStatus方法时,虽然retry成功,但实际会回滚。
此类场景,最好解耦,由调度类中明确是在Retry中Tx,还是在Tx中Retry。
@SpringBootTest
public class RetryTest {
@Setter(onMethod_ = {@Autowired})
protected ServiceB serviceB;
@Test
public void testB1() {
assertThrows(UnexpectedRollbackException.class, () -> serviceB.b1());
}
@Test
public void testB2() {
serviceB.b2();
}
}
//
@Service
public class ServiceB {
@Setter(onMethod_ = {@Autowired})
protected ServiceA serviceA;
@Transactional
public void b1() {
serviceA.a();
}
public void b2() {
serviceA.a();
}
}
//
@Service
public class ServiceA {
final AtomicInteger count = new AtomicInteger(0);
@Transactional
@Retryable(maxAttempts = 2, value = Exception.class)
public void a() {
if (count.getAndIncrement() % 2 == 0) throw new IllegalStateException("");
}
}