# jl-tools **Repository Path**: laoshirenggo/jl-tools ## Basic Information - **Project Name**: jl-tools - **Description**: 包含开发编码能用到的一些封装工具,提升开发效率以及代码优雅性。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 3 - **Forks**: 0 - **Created**: 2022-03-12 - **Last Updated**: 2023-10-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: Java ## README #### 介绍 包含开发编码能用到的一些封装工具,提升开发效率以及代码优雅性。 主要功能含: 1. spring动态管理bean工具类。 2. 增强Map。 3. 增加List。 4. 线程池工具类。 5. 抽象数据判断,让任意数据类型基本判断只需使用一个方法即可。 6. 元祖。 7. 注解版aes或rsa自动加解密。 8. lambda引用解析工具,可以根据lambda引用获取类class、方法名、属性名,避免硬编码是开发首要的第一步。 9. spring接口代理封装,抽象共性,极大减少代码量。 10. 注解版拦截器。 11. 注解版过滤器。 12. 简化版的@validated,一个注解实现99%项目所需的校验功能。 13. 纯反射实现bean的拷贝,性能是BeanUtils的10-20倍,也解决BeanCopier无法读取有返回值的set方法而导致无法使用@Accessors编程,还实现了常用数据类型的转换。 14. io工具类。 15. 注解版项目启动完成后执行任务。 #### 安装依赖 ``` io.gitee.laoshirenggo tools 1.6 ``` #### 增强Map JMap继承Map,JHashMap继承HashMap,JConcurrentHashMap继承ConcurrentHashMap,只做增强,封装了常用方法,让代码更优雅。 ##### 支持功能 1. 常用数据类型的get方法。 2. lambda方法引用的key。 3. 链式调用。 4. 转换为list。 5. 转换为实体。 6. 有HashMap以及ConcurrentHashMap两种实现。 ``` //创建HashMap JMap hashMap = new JHashMap<>(); //创建ConcurrentHashMap JMap concurrentHashMap = new JConcurrentHashMap<>(); ``` ##### 常用数据类型的get方法 1. getString 2. getInt 3. getLong 4. getDouble 5. getBigDecimal 6. getBoolean 7. getLocalDateTime 8. getTimestamp ``` jmap.getString("demo"); ``` ##### lambda方法引用的key ``` jmap.get(Demo::getName); ``` ##### 链式调用 ``` JMap jmap = new JHashMap() .set(Demo::getName, "张三") .set("age", 18); String name = jmap.getString("name"); Integer age = jmap.getInteger(Demo::getAge); ``` ##### 转换为list ``` //key转换为list JList key = jmap.toList().key(); //value转换为list JList value = jmap.toList().value(); ``` ##### 转换为实体 ``` Demo demo = jmap.toBean(Demo.class); ``` #### 增强List JList继承List,增强封装了常用方法的lambda操作,让代码更优雅。 ##### 支持功能 1. 链式调用。 2. 转换为map。 3. 数据去重。 4. 数据排序。 5. 获取某个属性集合。 6. 集合查询。 7. 随机洗牌。 8. 根据值获取下标。 9. 按指定长度切割为N个集合 10. 求差集。 11. 求交集。 12. 有JArrayList一种实现。 ``` JList jlist = new JArrayList<>(); ``` ##### 链式调用 ``` JList jlist = new JArrayList() .set(new Demo().setAge(18).setName("张三")) .set(new Demo().setAge(20).setName("李四")); ``` ##### 转换为map ``` //age属性为key转换为map,重复分组 JMap> group = jlist.toMap(Demo::getAge).group(); //age属性为key转换为map,重复覆盖 JMap cover = jlist.toMap(Demo::getAge).cover(); ``` ##### 数据去重 ``` //根据age字段去重 JList comparing = jlist.comparing(Demo::getAge); ``` ##### 数据排序 ``` //根据age字段倒序 JList desc = jlist.desc(Demo::getAge); ``` ##### 获取某个属性集合 ``` //获取name属性集合 JList property = jlist.getProperty(Demo::getName); ``` ##### 集合查询 条件表达式支持:eq,lt,gt,le,ge,ne,like,in,notIn,isNull,isNotNull。 查询结果支持:list,object。 ``` //查询age>=18 && name=张三 的数据 返回集合 JList zhangsan = jlist.filter() .ge(Demo::getAge, 18) .eq(Demo::getName, "张三") .list(); //查询name=李四 的数据 返回对象 Demo lisi = jlist.filter() .eq(Demo::getName, "李四") .object(); ``` ##### 随机洗牌 ``` JList shuffle = jlist.shuffle(); ``` ##### 根据值获取下标 ``` int index = list.getIndex(new Demo().setAge(18).setName("张三")); ``` ##### 按指定长度切割为N个集合 ``` //按长度1切割 JList> partition = jlist.partition(1); ``` ##### 求差集、交集 ``` JList jlist1 = new JArrayList() .set(new Demo().setAge(18).setName("张三")) .set(new Demo().setAge(20).setName("李四")); JList list2 = new JArrayList() .set(new Demo().setAge(18).setName("张三")); //差集 JList diff = jlist1.diff(list2); //交集 JList section = jlist1.section(list2); ``` #### 线程池工具 更优雅的使用线程池,支持如下: 1. 链式创建线程池。 2. 手动执行线程池。 创建线程池: ``` public ThreadPoolTaskExecutor test() { return JThreadPool.newPoll() .coreSize(10) .maxSize(100) .keepAlive(60) .queueSize(200) .name("test") .rejected(new ThreadPoolExecutor.CallerRunsPolicy()) .builder(); } ``` 手动执行线程池1: ``` public void demo() { //参数1为:线程池名称,参数2为:任务数 JThreadPool.excPoll("test", 3) .execute(() -> System.out.println(1)) .execute(() -> System.out.println(2)) .execute(() -> System.out.println(3)) //阻塞等待 .await(); System.out.println("执行完毕~"); } ``` 手动执行线程池2: ``` public void demo() { List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9); //参数1为:线程池名称,参数2为:任务数 JThreadPool.Execute test = JThreadPool.excPoll("test", list.size()); for (Integer integer : list) { test.execute(() -> System.out.println(integer)); } //阻塞等待,最多等5秒 test.await(5); System.out.println("执行完毕~"); } ``` #### 抽象判断 数据判断的封装,让任意数据类型基本判断只需使用JEmpty.check()这一个方法即可,判断数据变为无脑,支持功能如下: 1. 任意数据类型判空,注:集合长度=0也算空。 2. stirng字符 最小长度、最大长度、正则。 3. 任意数值数据类型 最小值、最大值。 ``` Object obj = null; String str = ""; BigDecimal number = new BigDecimal(0); //任意数据类型判空 boolean check = JEmpty.check(obj); //字符串 最小长度=1 最大长度=10 boolean check2 = JEmpty.check(str, 1, 10); //字符串 正则匹配 boolean check3 = JEmpty.check(str, "[a-zA-Z0-9_]+@[a-zA-Z0-9_]+(\\.[a-zA-Z0-9]+)+"); //数值 最小=1 最大=10 boolean check4 = JEmpty.check(number, 1, 10); ``` #### 元祖 顾名思义就是一组数据,当有多个值且数据类型不同时,使用Map并不能准确的表达值的数据类型,元祖就是为了解决这一问题,一共提供了五维元祖,足够满足大多数需求: 1. JTuple.Tuple2 二维 2. JTuple.Tuple3 三维 3. JTuple.Tuple4 四维 4. JTuple.Tuple5 五维 ``` //二维元祖,同时放入两种数据类型 JTuple.Tuple2 tuple2 = new JTuple.Tuple2<>(200, "操作成功"); Integer v21 = tuple2.getV1(); String v22 = tuple2.getV2(); ``` #### 注解版aes或rsa自动加解密 ##### 加解密 入参自动用aes或rsa私钥解密或返参自动用aes或rsa私钥加密,开发不用做任何加解密操作。 注:启动类上需加上@JEnableSecurity,解密入参方法必须有@RequestBody修饰,加密返参必须为json方式,yml需配置aes密钥或rsa私钥公钥。 1. @JDecode 解密注解 2. @JEncode 加密注解 ``` #aes对称 encrypt: #是否开启加解密 默认true open: true #是否将加解密日志输出到控制台 默认false showLog: false aes: #获取密钥可使用类 JAES key: GLfx0TH5zhQpnHQnNtrW1w== ``` ``` #rsa非对称 encrypt: #是否开启加解密 默认true open: true #是否将加解密日志输出到控制台 默认false showLog: false rsa: #获取密钥可使用类 JRSA publicKey: MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCv370hnpEizK5IUtTxCQVFsEPz2ZFuys4x6zR/2mpS/MooLecSyqsiF8kLUs+ZLqnZrtTRzuMZ5TowP6QeYqsWiAI9tv7x/Pahxi7BEYx/CQx3T7RZRZsnEuprxbmyCkW5WWjrF7NitMSGMSq8fi8DANUeV0aLV8lVz4b3LhCPqwIDAQAB privateKey: MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBAOz+7RPUvKUGfWnsWN+p6N27uzkLOuQyAiaMhxVN+IpMR6A+BHzGPSM+je/xLdoJLlaqoZXLhmtyH9EjQlIOcrPlP933OuTGwNDa9rXxjRI9r1nsfhorvVxmWk8Lq2EEP9pSsj4c83ilcoYIMB4dzPf/XN2DEhozNbth+sPZfIE/AgMBAAECgYBbqtr+camp1xHJV66kjG7S3Rs0nEBiJWmpiW9ycR8yNwD5XSOVM4RQTpDN/yZyEF0JDqTDcN6ETrc5yH6NiKMabXHtn4OD7Uciu46c3VOAAT8x3KAWqldMh0XwPWmepu9B1ajtWzEuytFG/aTscWrBd2zf3EBStS7ctLpLZmqmOQJBAP1EA6a3q+Jl2uoQlZSpmqQlfAPtn2gYHLpnbY4mev6TdbHnq0id4dZ5zQIASQgzGDuaG3lyzNWg063+H+nCaqsCQQDvjfHKTirqxH7OViu2+ms9ufJ4zI5AQfTcywE1MP7Hg861gb1BEzqGDlOUkEwbo2YMrBfG6AP7IXRJGU9pG0O9AkBxhshgNhrNTDz6CN8UGYahJ9BUbnKzFYPjJrOcMbGWZgEu8xr7XRI7srNrvzb9fvHQ3b6NDSG2bPYWG0Cw5x4rAkEA0rFMjTuNAalLQl2F20yLD+JBAcAgCSI5pAwkhs0N+RrTrs5qTxcDbS6iklMLrW9cbR7bVsVv4uu8pCJPtskVHQJBAIg0NHL72U8fbRfh2Oo4hYMk2NP1KcH/LscehaNrrIOF71dX5aOYwalX2Q0rEBvawlJptv33xbDP+M8lTB+ecHM= ``` 控制层按需增加@JDecode或@JEncode ``` //@JEncode @JDecode @GetMapping("test") public Map test(Map map) { System.out.println(map); return map; } ``` 启动类加@JEnableSecurity ``` @JEnableSecurity public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); } } ``` ##### aes和rsa工具类 提供工具类,方便生成密钥以及加解密调试。 aes例子: ``` //获取密钥 String aesKey = JAES.getKey(); //加密 String encode = JAES.encode("{\"name\": \"张三\"}", aesKey); //解密 String decode = JAES.decode(encode, aesKey); ``` rsa例子: ``` //获取密钥对 Map rsaMap = JRSA.getKey(); //私钥 String privateKey = rsaMap.get("privateKey"); //公钥 String publicKey = rsaMap.get("publicKey"); //公钥加密 String publicEncode = JRSA.publicEncode("{\"name\": \"张三\"}", publicKey); //公钥解密 String publicDecode = JRSA.publicDecode(publicEncode, publicKey); //私钥加密 String privateEncode = JRSA.privateEncode("{\"name\": \"张三\"}", privateKey); //私钥解密 String privateDecode = JRSA.privateDecode(privateEncode, privateKey); ``` #### 递归生成子级树 根据顶级对象递归获取子级树。 方法:JBean.tree(最顶层父级对象, 全部数据集合, 父级关联子级属性名, 子级关联父级属性名, 存放子级集合属性名) 例子: ``` public static void main(String[] args) { //生成测试数据 List datas = new ArrayList<>(); datas.add(new Entity().setId(1).setName("顶级1")); datas.add(new Entity().setId(3).setName("子级1").setPid(1)); datas.add(new Entity().setId(4).setName("子级2").setPid(3)); datas.add(new Entity().setId(5).setName("子级3").setPid(1)); //获取顶级菜单 Entity top = datas.get(0); //获取子级集合 List tree = JBean.tree(top, datas, Entity::getId, Entity::getPid, Entity::getSonEntitys); //存入下级集合 top.setSonEntitys(tree); //转json,方便观察结果 JSONArray array = JSONUtil.parseArray(list); //输出[{"name":"顶级1","id":1,"sonEntitys":[{"pid":1,"name":"子级1","id":3,"sonEntitys":[{"pid":3,"name":"子级2","id":4,"sonEntitys":[]}]},{"pid":1,"name":"子级3","id":5,"sonEntitys":[]}]},{"name":"顶级2","id":2,"sonEntitys":[]}] System.out.println(array.toString()); } @Data @Accessors(chain = true) public class Entity { //id private Integer id; private String name; //上级id 为null则表示顶级,规则是你自己定义 private Integer pid; //下级集合 private List sonEntitys; } ``` #### lambda引用解析 lambda解析工具,避免硬编码: 1. 获取class。 2. 获取方法名。 3. 获取属性名。 ``` public static void main(String[] args) { //获取class Class claszz = JLambda.getClass(Demo::getId); //获取属性名 id String property = JLambda.getProperty(Demo::getId); //获取方法名 getName String getMethod = JLambda.getMethod(Demo::getName); } @Data public class Demo { private Integer id; private String name; } ``` #### spring bean动态管理工具 类名JSpringBean,运行时动态管理bean,api如下: ``` getBean(Class) 根据类型获取bean getBean(String beanName) 根据名称获取bean getBean(String beanName, Class clazz) 根据名称+类型获取bean registerBean(T obj) 根据类型注册bean registerBean(String name, T obj) 根据类型注册bean registerBean(Class clazz, Map args) 根据类型注册bean,map为bena实体参数列表 registerBean(String name, Class clazz, Map args) 根据名称+类型注册bean,map为bena实体参数列表 removeBean(String name) 根据名称删除bean ``` 例子: ``` /** * bean对象 */ @Data public class TestBean { private Integer id; private String name; } ``` ``` @RestController public class TestService { @GetMapping("test") public void test() { //注册bean Map map = new HashMap<>(); map.put("id", 1); map.put("name", "小张"); TestBean testBean = JSpringBean.registerBean(TestBean.class, map); //获取bean TestBean testBean1 = JSpringBean.getBean(TestBean.class); System.out.println(testBean1); Object testBean2 = JSpringBean.getBean("testBean"); System.out.println(testBean2); //删除bean JSpringBean.removeBean("testBean"); } } ``` #### spring接口代理封装 spring接口代理封装,封装抽象了接口代理相关的操作,继承使用即可。 1. JInvocationHandler 单泛型父类。 2. JInvocationHandler2 两个泛型父类。 3. JInvocationHandler3 三个泛型父类。 使用例子: ``` /** * 测试bean,主要演示代理实现类获取spring bean */ @Service public class TestBean { /** * 输出泛型class名称 */ public void test(Class clazz) { System.out.println(clazz.getName()); } } ``` 下面开始编写代理类: ``` /** * 代理接口,用于给其他接口继承 * @param */ public interface DemoService { void test(); } ``` ``` /** * 代理接口实现 * * @param */ public class DemoServiceImpl extends JInvocationHandler implements DemoService { public DemoServiceImpl(Class interfaceType) { super(interfaceType); } /** * 实现接口方法,调用测试bean,输出泛型class名称 */ @Override public void test() { //classa为JInvocationHandler父类提供的泛型实例,JInvocationHandler2对应classa、classb,JInvocationHandler3对应classa、classb、classc //因该代理类不受spring管理,所以使用JInvocationHandler提供的getBean()手动获取用于演示的TestBean TestBean testBean = getBean(TestBean.class); testBean.test(classa); } } ``` 此时代理类已经完成,下面编写测试类: ``` /** * 声明一个接口,并继承代理类,指定泛型String */ public interface TestService extends DemoService { } ``` ``` /** * 测试控制层 */ @RestController public class TestServiceImpl { @Autowired private TestService testService; @GetMapping("test") public void test() { //输出java.lang.String testService.test(); } } ``` #### 注解版过滤器 @JFilter一个注解实现过滤器,支持传入基本参数,你只需保证注解参数能强转为形参即可,可选择返回布尔值或抛出异常来实现请求是否继续,如返回false则中断请求并抛出JFilterError异常。 ``` @JLFilter参数: 1. values 方法参数,顺序对应方法参数的顺序,仅支持常用数据类型。 2. excludes[] 过滤的url 3. order 执行顺序 4. error 返回false的异常错误信息 ``` ``` @Service @AllArgsConstructor public class Test { private HttpServletRequest request; @JFilter(error = "test过滤器异常") public boolean test() { System.out.println("请求url:" + request.getRequestURI()); return true; } } ``` #### 注解版拦截器 @JInterceptor一个注解实现拦截器,支持传入基本参数,你只需保证注解参数能强转为形参即可,可选择返回布尔值或抛出异常来实现请求是否继续,如返回false则中断请求并抛出JInrerceptorError异常。 ``` @JLInterceptor参数: 1. values 方法参数,顺序对应方法参数的顺序,仅支持常用数据类型。 2. excludes[] 过滤的url 3. order 执行顺序 4. error 返回false的异常错误信息 5. before 拦截方法之前(默认) 6. after 拦截方法之后 ``` ``` @Service @AllArgsConstructor public class Test { private HttpServletRequest request; @JInterceptor(excludes = {"/login", "/register"}) public boolean test() { System.out.println("请求url:" + request.getRequestURI()); return true; } } ``` #### 请求参数校验 注解@Check,简化版的@validated,基本思路和使用同@validated,基于aop,使用更简单,主要功能如下: 1. 一个注解实现校验,自动根据数据类型判断而不需要和@validated一样由开发人为去根据类型使用不同注解。 2. 自动封装校验异常返回值,而不用手动一个个属性指定,默认属性名,可自定义属性名称。 ``` @Check参数: value 校验不通过抛出异常的属性名称 max 最大,支持:数值、字符串、List min 最小,支持:数值、字符串、List regex 正则表达式,支持:字符串、数值 isValid 是否有效,有效才会校验。 isEmpty 是否可为空 groups[] 分组,同组才会被校验 ``` 例子: ``` public class OrderServiceimpl { @PostMapping("test") public void test(@RequestBody @Check(min = 1, max = 5) List user) { //指定user集合最小长度=1 最大长度=5 } @Data @Check//指定全局非空校验 public static class User { //指定数值最小=1 最大=10000 @Check(min = 1, max = 10000) private BigDecimal id; //指定字符最小长度=1 最大长度=20 @Check(min = 1, max = 20) private String name; //自定义属性名称,校验不通过会使用该名称抛出异常 @Check("订单集合") private List order; //指定集合最小长度=1 最大长度=10 并自定义属性名称 @Check(min = 1, max = 10, value = "测试名称") private List ints; private Boolean wq; @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss") private LocalDateTime time; } @Data @Check//指定全局非空校验 public static class Order { private BigDecimal id; private String no; private Money money; } @Data @Check//指定全局非空校验 public static class Money { private BigDecimal id; private String cal; } } ``` 使用分组功能: ``` public class OrderServiceimpl { @PostMapping("test") public void test(@RequestBody @Check(groups = {TestGroup.class}) List user) { //指定校验只被TestGroup分组修饰的属性 } @Data @Check public static class User { @Check(groups = {TestGroup.class}) private BigDecimal id; //该属性就不参与test接口的校验 private String name; @Check(groups = {TestGroup.class}) private List order; } @Data @Check public static class Order { @Check(groups = {TestGroup.class}) private BigDecimal id; //该属性就不参与test接口的校验 private String no; @Check(groups = {TestGroup.class}) private Money money; } @Data @Check(groups = {TestGroup.class}) public static class Money { private BigDecimal id; private String cal; } /** * 分组接口 */ interface TestGroup { } } ``` #### 对象拷贝 方法JBean.copy(),纯反射实现的bean拷贝,支持功能: 1. 性能是BeanUtils的10-20倍之上。 2. 解决BeanCopier无法读取有返回值的set方法而导致无法使用@Accessors(chain = true)链式编程。 3. 实现了常用数据类型的转换,所以无需要求源bean和目标bean的属性数据类型一致,只要能保证数据类型可以强转即可。 4. 支持对象拷贝、集合拷贝。 ``` public static void main(String[] args) { User user = new User() .setId("1") .setName("test") .setMoney(99.99) .setTime(LocalDateTime.now()); //对象拷贝 UserVo1 userVo1 = JBean.copy(UserVo1.class, user); //对象拷贝 属性数据类型不一样,但能保证数据类型可以转换,同样可以拷贝 UserVo2 userVo2 = JBean.copy(UserVo2.class, user); //集合拷贝 List userVo2s = JBean.copy(UserVo2.class, Arrays.asList(user)); } @Data @Accessors(chain = true) public static class User { private String id; private String name; private Double money; private LocalDateTime time; } @Data @Accessors(chain = true) public static class UserVo1 extends User { } @Data @Accessors(chain = true) public static class UserVo2 { private Integer id; private String name; private BigDecimal money; private String time; } ``` #### io工具 类JIo,整合了字节流的操作,链式调用,使用方便些。 api列表: ``` InputStreamMethod inputStream(String path) 字节输入流,读取路径,支持项目相对路径、服务器绝对路径、网络url。 InputStreamMethod inputStream(File file) 字节输入流,读取file OutputStreamMethod outputStream(String path) 字节输出流,输出到路径 OutputStreamMethod outputStream(File file) 字节输出流,输出到File OutputStreamMethod outputStream(String path, boolean append) 字节输出流,输出到路径,append为是否追加输出 OutputStreamMethod outputStream(File file, boolean append) 字节输出流,输出到File,append为是否追加输出 ``` InputStreamMethod方法列表: ``` InputStream get() 获取字节输入流 String content() 字符串形式获取读取文件的内容 ``` OutputStreamMethod方法列表: ``` OutputStream get() 获取字节输出流 void save(String content) 保存字符串内容到文件 void save(InputStream inputStream) 保存字节输入流到文件 ``` 例子: ``` //获取字节输入流 InputStream inputStream = JIo.inputStream("d://test.txt").get(); //获取字节输入流内容 String testContent = JIo.inputStream("d://test.txt").content(); //读取demo.txt文件内容,追加到test.txt文件 JIo.outputStream("d://demo.txt", true).save(inputStream); ``` #### 注解版项目启动完成后执行任务 注解JRunner,项目启动成功后执行的方法执行被注解的方法。 ``` @Service public class Test { @JLRunner public void demo() { System.out.println("demo"); } @JRunner({"张三", "18"}) public void test(String name, Integer age) { System.out.println(name); System.out.println(age); } } ```