新手编码指北
本文主要记录了一些常用的Java编码中使用到的类或方法,或编码的小技巧,以期望写出更容易,更安全,更优美的代码。本文中的内容是本人工作中总结和记录,不一定适合所有人,也不一定是最优的,欢迎大家共同讨论学习进步。
本文会持续更新…
常用代码
此部分记录一些编码中常用的接口或者方法,在开发中可以复制粘贴直接使用,或简单的修改可以直接使用。
Json转换
public class DemoApplication {
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public static class Range implements Serializable {
private Integer min;
private Integer max;
private List<Integer> enums;
private Integer length;
@Override
public String toString() {
return "Range{" +
"min=" + min +
", max=" + max +
", enums=" + enums +
", length=" + length +
'}';
}
}
public static void main(String[] args) throws Exception {
List<Integer> e = new ArrayList<Integer>(Arrays.asList(0, 1, 2, 3));
Range range = Range.builder().max(100).min(0).length(999).enums(e).build();
// 类转json
String toJson = JSON.toJSONString(range);
System.out.println(toJson);
// json转类
Range object = JSON.parseObject(toJson, Range.class);
System.out.println(object);
}
}
输出结果
{"enums":[0,1,2,3],"length":999,"max":100,"min":0}
Range{min=0, max=100, enums=[0, 1, 2, 3], length=999}
获取当前时间
Long time = System.currentTimeMillis();
计时器
有时会需要计算某个方法或某些代码执行了多少毫秒,可以使用org.springframework.util.StopWatch
public void receive(String data){
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 执行代码
stopWatch.stop();
long timeMillis = stopWatch.getTotalTimeMillis();
System.out.println(timeMillis);
}
判空相关
// javax.validation.constraints.NotNull @NotNull 判断接口入参是否为空的注解
@NotNull
private Integer type;
// javax.validation.constraints.NotEmpty @NotEmpty 判断接口入参的集合是否为空的注解
@NotEmpty
List<String> codes;
// java.util.Optional 如果type为空就赋值orElse里的值,相当于if(为空){赋值} 的逻辑的简写
Optional.ofNullable(type).orElse(0)
// org.apache.commons.collections4.CollectionUtils 判断集合是否为空的工具类
CollectionUtils.isNotEmpty(collect)
// org.apache.commons.lang3.StringUtils 判断字符串是否为空或空串的工具类
StringUtils.isNotBlank(name)
字符串aes加解密工具类
public class TransformUtil {
private final static String AES_KEY = "abcdefghijklmnop";
private final static String AES_IV = "0123456789012345";
/**
* AES对称加密
*/
public static String aesEncryptStr(String content) {
AES aes = new AES(Mode.OFB, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes());
return aes.encryptHex(content);
}
/**
* AES对称解密
*/
public static String aesDecryptStr(String content) {
AES aes = new AES(Mode.OFB, Padding.PKCS5Padding, AES_KEY.getBytes(), AES_IV.getBytes());
return aes.decryptStr(content);
}
}
使用:
public static void main(String[] args) throws Exception {
String test = "Hello world!";
String encrypt= TransformUtil.aesEncryptStr(test);
String decrypt= TransformUtil.aesDecryptStr(encrypt);
System.out.println("原字符串:" + test + "\n加密后:" + encrypt + "\n解密后:" + decrypt);
}
输出:
原字符串:Hello world!
加密后:0ac139cbb73c29c07d459e53d822986b
解密后:Hello world!
编码技巧
事务提交后再处理某些方法
有些时候,可能在增删改之后,需要执行某些方法,处理某些逻辑,比如新注册了一个用户,在用户信息入库后,可能需要执行再赠送该用户3个月会员的业务逻辑,而在赠送的接口里,可能需要去查库拿到用户信息,此时,如果刚刚用户信息入库的事务还未提交,这个时候很可能查不到该用户,导致无法赠送会员,产生业务上的bug。
这个时候我们需要先保证前面插入数据的事务提交了,再执行后续的方法,下面这个单例类可以打到此目的:在执行一个方法时判断当前是否有事务,如果没有则立即执行,如果有则事务提交完后执行。
单例类TransactionSupport,采用枚举方式实现单例
public enum TransactionSupport {
INSTANCE;
public TransactionSupport getInstance(){
return INSTANCE;
}
private TransactionAfterCommitExecutor afterCommitExecutor
= new TransactionAfterCommitExecutor();
public void addJobAfterTransaction(Runnable runnable) {
afterCommitExecutor.execute(runnable);
}
public void addJobAfterTransaction(List<Runnable> runnable) {
if (CollectionUtil.isEmpty(runnable)) {
return;
}
runnable.forEach(r -> afterCommitExecutor.execute(r));
}
}
执行的线程池:
public class TransactionAfterCommitExecutor extends ThreadPoolExecutor {
public TransactionAfterCommitExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
public TransactionAfterCommitExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory);
}
public TransactionAfterCommitExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, handler);
}
public TransactionAfterCommitExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);
}
public TransactionAfterCommitExecutor() {
this(
10, 500,
500L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
new ThreadFactoryBuilder().setNameFormat("transaction-after-commit-executor-pool-%d").build(),
new DiscardOldestPolicy());
}
private ThreadLocal<List<Runnable>> currentRunables = ThreadLocal.withInitial(() -> new ArrayList<>(5));
private ThreadLocal<Boolean> registed = ThreadLocal.withInitial(() -> false);
@Override
public void execute(final Runnable runnable) {
//如果事务同步未启用则认为事务已经提交,马上进行异步处理
if (!TransactionSynchronizationManager.isSynchronizationActive()) {
System.out.println("no transaction");
super.execute(runnable);
} else {
//同一个事务的合并到一起处理
currentRunables.get().add(runnable);
//如果存在事务则在事务结束后异步处理
if (!registed.get()) {
TransactionSynchronizationManager.registerSynchronization(new AfterCommitTransactionSynchronizationAdapter());
registed.set(true);
}
}
}
private class AfterCommitTransactionSynchronizationAdapter extends TransactionSynchronizationAdapter {
@Override
public void afterCompletion(int status) {
final List<Runnable> txRunables = new ArrayList<>(currentRunables.get());
currentRunables.remove();
registed.remove();
if (status == STATUS_COMMITTED) {
TransactionAfterCommitExecutor.super.execute(() -> {
for (Runnable runnable : txRunables) {
try {
System.out.println("after transaction");
runnable.run();
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
}
}
}
关于TransactionSynchronizationManager的具体用法,可以学习这篇文章:TransactionSynchronizationManager事务同步器的使用
这里简单介绍一下代码逻辑:
execute()方法中,先调用TransactionSynchronizationManager.isSynchronizationActive()判断是否有事务,有事务则调用TransactionSynchronizationManager的registerSynchronization方法注入一个TransactionSynchronization接口实例,通常是使用TransactionSynchronizationAdapter适配器类,重写其afterCommit()方法,实现在事务提交后执行对应的逻辑。
使用和测试,随便写一个service方法,里面调用dao层,存储数据,在方法加上事务注解,在service层方法最后使用该工具执行其他方法,看输出打印。
@Transactional
public void updateMsgConfig(String account) {
// 存储一个用户信息,简写了此处
configInfoRepository.saveAndFlush();
// 事务提交后打印haha!!!
TransactionSupport.INSTANCE.addJobAfterTransaction(()->{
System.out.println("haha!!!");
});
}
结果输出,证明是事务提交后,执行的打印。
after transaction
haha!!!
某个方法新加参数时,写重载方法
有业务场景如下:比如原来有一个老接口func处理一些固定逻辑,而现在新增了一个接口,其实是func里面大部分逻辑相同,只有少部分需要走新逻辑,代码大致如下:
void func(String name, String age){
// 前置校验name和age是否合法
// 中间存入库表中
// 结尾处理会员赠送逻辑
}
// 调用的地方
userService.func("abc", 18);
现在要新增一种送会员方式,比如web登录用户还是按照原来规则赠送,app用户登录按照新的赠送方式。
分析:实际上整个func方法中,只有最后处理会员赠送的逻辑需要判断一下按哪种方式赠送,前面都是一样的逻辑
不好的处理方式:(仅个人看法,觉得不够简洁优美)
- CV一个func1,只修改最后的逻辑
void func(String name, String age){
// 前置校验name和age是否合法
// 中间存入库表中
// 老逻辑:结尾处理会员赠送逻辑
}
void func1(String name, String age){
// 前置校验name和age是否合法
// 中间存入库表中
// 新逻辑:结尾处理会员赠送逻辑
}
// 调用的地方
userService.func("abc", 18);
userService.func1("abc", 18);
- CV一个func1,把公共的部分提炼出一个方法,func和func1都调用公共的方法,最后写各自的逻辑
void checkAndSave(String name, String age){
// 前置校验name和age是否合法
// 中间存入库表中
}
void func(String name, String age){
checkAndSave(name, age);
// 老逻辑:结尾处理会员赠送逻辑
}
void func1(String name, String age){
checkAndSave(name, age);
// 新逻辑:结尾处理会员赠送逻辑
}
// 调用的地方
userService.func("abc", 18);
userService.func1("abc", 18);
好的处理方式:重载该方法,增加同名方法,加一个参数,此处举例使用布尔类型,也可以加枚举实现。老的方法调用新的重载方法,并传入指定参数。相当于在老方法外包了一层。代码大致如下:
void func(String name, String age){
// 调用另一个重载方法
this.func(name, age, false);
}
void func(String name, String age, boolean isApp){
// 前置校验name和age是否合法
// 中间存入库表中
if(isApp){
// 新逻辑处理会员赠送逻辑
return;
}
// 老逻辑处理会员赠送逻辑
}
// 调用处,老的地方不变
userService.func("abc", 18);
// 新的地方
userService.func("abc", 18, true);
处理同一类型业务,具体逻辑有差别的代码:策略模式变种
有些业务场景,需要处理同一类业务,但是因为类型不同,各自有各自处理的逻辑,此时可以采用策略模式,有一个共同基类,子类继承基类,实现父类的方法,完善自己的业务处理逻辑。
传统的策略模式,在外层使用时一般会采用switch case的方式,根据不同类型,new不同子类,获取实例,调用父类方法进行逻辑处理。
关于策略模式,可以参考该文章简单了解:Java 策略模式 模板方法模式 状态模式
改进:通过spring的自动注入,把不同的类型放入到map中,取代switch case,这样后续新增一个子类,只需要新增这个子类,无需修改已有代码
代码示例如下:
接口和子类如下:
// 接口,定义类型和具体需要子类重写的方法
public interface VIPHandler {
String getType();
void process(String data);
}
// 具体实现的子类,注明自己的类型,实现自己的逻辑
@Component
public class SilverVIPHandler implements VIPHandler{
@Override
public String getType() {
return VIPTypeEnum.SILVER.getDesc();
}
@Override
public void process(String data) {
// 转换成具体的类
//User user = JSON.parseObject(data, User.class);
// 执行白银会员处理相应的逻辑
System.out.println("SilverVIPHandler processing...");
}
}
// 具体实现的子类,注明自己的类型,实现自己的逻辑
@Component
public class BronzeVIPHandler implements VIPHandler{
@Override
public String getType() {
return VIPTypeEnum.BRONZE.getDesc();
}
@Override
public void process(String data) {
// 转换成具体的类
//User user = JSON.parseObject(data, User.class);
// 执行青铜会员处理相应的逻辑
System.out.println("BronzeVIPHandler processing...");
}
}
// 具体实现的子类,注明自己的类型,实现自己的逻辑
@Component
public class GoldVIPHandler implements VIPHandler{
@Override
public String getType() {
return VIPTypeEnum.GOLD.getDesc();
}
@Override
public void process(String data) {
// 转换成具体的类
//User user = JSON.parseObject(data, User.class);
// 执行黄金会员处理相应的逻辑
System.out.println("GoldVIPHandler processing...");
}
}
使用handler的service中:
public interface VIPService {
void dealVIPData(String type, String data);
}
@Service
public class VIPServiceImpl implements VIPService {
// key:handler的类型 value:handler实例
private static final Map<String, VIPHandler> routerMap = Maps.newHashMap();
@Resource
private List<VIPHandler> vipHandlers;
// 初始化map
@PostConstruct
public void init() {
for (VIPHandler handler : vipHandlers) {
routerMap.put(handler.getType(), handler);
}
}
@Override
public void dealVIPData(String type, String data) {
VIPHandler handler = routerMap.get(type);
if(ObjectUtil.isNotNull(handler)){
handler.process(data);
}
}
}
插曲:关于@PostConstruct
Java中该注解的说明:@PostConstruct该注解被用来修饰一个非静态的void()方法。被@PostConstruct修饰的方法会在服务器加载Servlet的时候运行,并且只会被服务器执行一次。PostConstruct在构造函数之后执行,init()方法之前执行。
通常我们会是在Spring框架中使用到@PostConstruct注解 该注解的方法在整个Bean初始化中的执行顺序:
Constructor(构造方法) -> @Autowired(依赖注入) -> @PostConstruct(注释的方法)
进行测试,controller中调用service层方法:
@Resource
private VIPService vipService;
@GetMapping(value = "/v1/handler/test")
public void testHandler(){
vipService.dealVIPData(VIPTypeEnum.BRONZE.getDesc(), "abc");
vipService.dealVIPData(VIPTypeEnum.SILVER.getDesc(), "abc");
vipService.dealVIPData(VIPTypeEnum.GOLD.getDesc(), "abc");
}
输出结果:
BronzeVIPHandler processing...
SilverVIPHandler processing...
GoldVIPHandler processing...
sql查询拼接时使用where 1=1
动态拼接查询sql时,可能要根据字段为空不为空进行一些条件的拼接,而此时如果通用拼接是and+条件(条件+and)的话,那么还要单独处理第一个(最后一个)多出来的and,而直接使用where 1=1 或者最后加上1=1的条件,就不必单独判断了,拼接语句就统一了。具体可以了解可以参考:where 1=1有什么用?
流程相似但具体每一步有差异时,使用handler+接口
比如在项目中,需要做的逻辑是两个接口处理的流程很类似,核心步骤是一致的,但是在核心方法以为的处理又各自有不同,此时可以写一个handler,入参是接口,接口中用于实现不同的处理方法,一致的核心步骤在handler里调用。
比如现在有鸟和猫两种角色,他们的一些行为不一样,但是最终都通过这些行为增加了血量。
示例代码如下:
一个角色的信息
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Persona {
private String id;
private String name;
private String classification;
}
handler方法
@Component
public class ActionHandler {
public interface ActionProcess {
Persona before(String id);
void run();
void eat();
void exception(String cause);
void finallyDeal();
}
public void invokeDealAction(String id, ActionProcess actionProcess){
try {
Persona persona = actionProcess.before(id);
System.out.println("get persona is : " + persona);
actionProcess.run();
// 两个类型的角色共同的处理,比如恢复血量
addHP(id);
actionProcess.eat();
}catch (Exception e){
actionProcess.exception(e.getMessage());
throw e;
}finally {
actionProcess.finallyDeal();
}
}
public void addHP(String id){
// 此处调用数据库将id为指定值的角色增加一定血量
}
}
controller层和service层
@RestController
@RequestMapping("/")
public class TestController {
@Resource
private PersonaService personaService;
@GetMapping(value = "/v1/bird/test/{id}")
public void testBird(@PathVariable("id") String id){
personaService.doBirdAction(id);
}
@GetMapping(value = "/v1/cat/test/{id}")
public void testCat(@PathVariable("id") String id){
personaService.doCatAction(id);
}
}
public interface PersonaService {
void doBirdAction(String id);
void doCatAction(String id);
}
@Service
public class PersonaServiceImpl implements PersonaService {
@Resource
private ActionHandler actionHandler;
@Override
public void doBirdAction(String id) {
actionHandler.invokeDealAction(id, new ActionHandler.ActionProcess() {
@Override
public Persona before(String id) {
// 此处模拟根据id从数据库查到这个人并返回
return Persona.builder().id(id).name("小鸟一号").classification("bird").build();
}
@Override
public void run() {
System.out.println("鸟在飞行...");
}
@Override
public void eat() {
System.out.println("鸟吃米粒...");
}
@Override
public void exception(String cause) {
System.out.println("error is " + cause);
}
@Override
public void finallyDeal() {
System.out.println("飞向天空...");
}
});
}
@Override
public void doCatAction(String id) {
actionHandler.invokeDealAction(id, new ActionHandler.ActionProcess() {
@Override
public Persona before(String id) {
// 此处模拟根据id从数据库查到这个人并返回
return Persona.builder().id(id).name("小猫七号").classification("cat").build();
}
@Override
public void run() {
System.out.println("猫在跳跃...");
}
@Override
public void eat() {
System.out.println("猫在吃鱼...");
}
@Override
public void exception(String cause) {
System.out.println("error is " + cause);
}
@Override
public void finallyDeal() {
System.out.println("躺在太阳下...");
}
});
}
}
调用两个接口,输出结果分别如下: