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



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

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

  • 相关阅读:
    Leetcode1545-找出第 N 个二进制字符串中的第 K 位
    使用vscode开发uniapp项目常用的辅助插件,提升开发效率
    Spring源码中的命名风格总结
    oppo r11 升级8.1系统 图文教程
    2023年湖北省电子商务示范企业认定,奖励补贴申报条件解析!
    物联网面试准备
    @Profile注解的作用是什么?
    Eureka介绍与使用
    产品经理基础--04流程图与结构图
    【问题思考总结】已知对角矩阵怎么求原矩阵?原矩阵唯一吗?【相似对角化】
  • 原文地址:https://blog.csdn.net/weixin_43554422/article/details/127060387