新手编码指北(持续更新...)


本文主要记录了一些常用的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("躺在太阳下...");
            }
        });
    }
}

调用两个接口,输出结果分别如下:
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值