脚手架开发-Common封装基础通用工具类<基础工具类>

书接上文

java一个脚手架搭建_redission java脚手架-CSDN博客

以微服务为基础搭建一套脚手架开始前的介绍-CSDN博客

脚手架开发-准备配置-进行数据初始化-配置文件的准备-CSDN博客

脚手架开发-准备配置-配置文件的准备项目的一些中间件-CSDN博客

脚手架开发-Nacos集成-CSDN博客

脚手架开发-网关集成-CSDN博客


Common包

        在项⽬开发过程中,常常会进⾏诸如操作JSON、操作字符串、操作时间等⼯具类。在不同的项⽬ ⾥,这些操作往往是相同的。因此,在这个项⽬中,我们可以将这部分操作封装为基础通⽤的部 分。

  • 了解封装基础⼯具类;
  • 公共模块及基础通⽤包创建;
  • 基础通⽤⼯具类封装完成;
  • 基础通⽤⼯具类功能验证测试通过。

公共模块及基础通用包创建

        相信有的朋友已经注意到了,在这个脚手架开发中是有一个公共模块的,它独立出来就是未来存放所有其他模块公共使用的类,而common包下有一个具体的层次common-core就是专门用来存放我们的基础通用工具类。

commoncommon-core都是用module创建出来的,到了后面的包则是package

        废话不多说,如图

  • 1. 创建公共模块 rs-common
  • 2.创建公共模块的⼦⼯程基础通⽤包( rs-common-core)
  • 3. 在 rs-common-core ⼯程中创建 com.renting.rscommoncore 包。
  • 4. 在 com.renting.rscommoncore 包下创建 utils 包⽤于存放基础通⽤⼯具类。

封装JSON基础⼯具类

        JSON(JavaScript Object Notation)是⼀种轻量级的数据交换格式。数据交换指的是客⼾端和服务 器之间业务数据的传输格式。 JSON 是⼀种完全独⽴于语⾔的⽂本格式,开发者很容易阅读和编写, 机器很容易解析和⽣成,能有效地提升⽹络传输效率。这些优点也使 JSON 成为⼀种理想的独⽴语⾔⽂本格式。

        需要所有⼯具类的理由都是⼀样的,就是项⽬开发中使⽤的频率⾼。

        在⽹络传输场景中,JSON 格式的数据量相对较⼩。与其他格式(如 XML)相⽐,JSON 没有过多的标签和冗余信息,这使得它在带宽有限的⽹络环境下能够更⾼效地传输数据。所以在⽹络传输中我们⼀ 般选择JSON作为数据传输格式。

        在项⽬开发中我们经常会涉及到要将对象数据进⾏存储。⽐如说要将对象存储到Redis等存储机制中。 描述同样的消息,json相⽐xml占⽤更少的空间。

        我们可以看一个直观的例子。

XML

<?xml version="1.0" encoding="UTF-8" ?>
<user><id>1</id><name>张三</name><age>30</age>
</user>

Json

{"id": 1,"name": "张三","age": 30}

那么我们应该做的,是什么?

  • 提供对象转json(对象序列化)
    • 提供对象转json格式化
  • 提供json转对象(对象反序列化)

怎么封装?

1.创建JsonUtil类

2.引入依赖

rs-common中的pom.xml文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><parent><artifactId>renting</artifactId><groupId>org.example</groupId><version>1.0-SNAPSHOT</version></parent><modelVersion>4.0.0</modelVersion><artifactId>rs-common</artifactId><packaging>pom</packaging><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>16</source><target>16</target></configuration></plugin></plugins></build><modules><module>rs-common-core</module></modules><properties><maven.compiler.source>17</maven.compiler.source><maven.compiler.target>17</maven.compiler.target></properties><dependencies><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-json</artifactId></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency></dependencies>
</project>

3.JsonUtil中的代码

package com.renting.rscommoncore.utils;import lombok.extern.slf4j.Slf4j;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;/*** JSON⼯具类*/@Slf4jpublic class JsonUtil {/*** 对象转换器*/private static ObjectMapper OBJECT_MAPPER;static {OBJECT_MAPPER =JsonMapper.builder().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false).configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false).configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false).configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false).configure(MapperFeature.USE_ANNOTATIONS, false).addModule(new JavaTimeModule()).defaultDateFormat(new SimpleDateFormat(CommonConstants.S)).serializationInclusion(JsonInclude.Include.NON_NULL).build();}/*** 禁⽤构造*/private JsonUtil() {}/*** 对象转Json格式字符串* @param obj 对象* @return Json格式字符串* @param <T> 对象类型*/public static <T> String obj2String(T obj) {if (obj == null) {return null;}try {return obj instanceof String ? (String) obj :OBJECT_MAPPER.writeValueAsString(obj);} catch (JsonProcessingException e) {log.warn("Parse Object to String error : {}", e.getMessage());return null;}}/*** 对象转Json格式字符串(格式化的Json字符串)* @param obj 对象* @return 美化的Json格式字符串* @param <T> 对象类型*/public static <T> String obj2StringPretty(T obj) {if (obj == null) {return null;}try {return obj instanceof String ? (String) obj :OBJECT_MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(obj);} catch (JsonProcessingException e) {log.warn("Parse Object to String error : {}", e.getMessage());return null;}}/*** 字符串转换为⾃定义对象* @param str 要转换的字符串* @param clazz ⾃定义对象的class对象* @return ⾃定义对象* @param <T> 对象类型*/public static <T> T string2Obj(String str, Class<T> clazz) {if (str == null || str.isEmpty()|| clazz == null) {return null;}try {return clazz.equals(String.class) ? (T) str :OBJECT_MAPPER.readValue(str, clazz);} catch (Exception e) {log.warn("Parse String to Object error : {}", e.getMessage());return null;}}/*** 字符串转换为⾃定义对象,⽀持复杂的泛型嵌套* @param str json字符串* @param valueTypeRef 对象模板信息* @return 对象类对应的对象* @param <T> 对象类*/public static <T> T string2Obj(String str, TypeReference<T> valueTypeRef){if (str == null || str.isEmpty() || valueTypeRef == null) {return null;}try {return OBJECT_MAPPER.readValue(str,valueTypeRef);} catch (Exception e) {log.warn("Parse String to Object error : {}", e.getMessage());return null;}}/*** 字符串转换为⾃定义字段转为list,⽀持List嵌套简单对象* @param str json字符串* @param clazz 对象类* @return 对象列表* @param <T> 对象类型*/public static <T> List<T> string2List(String str, Class<T> clazz) {if (str == null || str.isEmpty()|| clazz == null) {return null;}JavaType javaType =OBJECT_MAPPER.getTypeFactory().constructParametricType(List.class, clazz);try {return OBJECT_MAPPER.readValue(str, javaType);} catch (IOException e) {log.error("Parse String to Object error : {}" + e.getMessage());return null;}}/*** 字符串转换为⾃定义字段转为map,⽀持Map嵌套简单对象* @param str str 字符串信息* @param valueClass valueClass value的类别* @return map对象* @param <T> value 的类型*/public static <T> Map<String, T> string2Map(String str, Class<T>valueClass){if (str == null || str.isEmpty() || valueClass == null) {return null;}JavaType javaType =OBJECT_MAPPER.getTypeFactory().constructMapType(LinkedHashMap.class,String.class, valueClass);try {return OBJECT_MAPPER.readValue(str, javaType);} catch (JsonProcessingException e) {log.warn("Parse String to Object error : {}", e.getMessage());return null;}}
}

解释:

1.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)

在反序列化时,当遇到JSON数据中存在Java对象对应类中没有定义的属性时,默认情况下 Jackson会抛出异常。通过将这个配置项设置为false,表⽰允许在反序列化时忽略那些未知的属性,不会因为出现额外的属性⽽导致反序列化失败,增强了对不同来源JSON数据的兼容性。

2. .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)在序列化时,如果Java对象中包含⽇期类型默认情况下Jackson可能会将⽇期转换为时间戳(⼀个表⽰从某个特定时间点开始到当前时间的毫秒数等形式的数值来表⽰。将此配置设置为false,意味着不采⽤时间戳的⽅式来表⽰⽇期,⽽是会按照后续配置的其他⽇期相关格式进⾏序列 化,以便⽣成更具可读性的⽇期格式表⽰在JSON数据中。

3. .configure(SerializationFeature.FAIL_ON_EMPTY_BEANS,false)在序列化时,Java对象中没有任何属性值(为空),默认情况下Jackson可能会抛出异常。设置 此项为false后,允许对这样的空对象进⾏序列化,即便对象没有实质内容也能⽣成对应的JSON 表⽰(通常可能是⼀个空的JSON对象{}),避免了因对象为空导致序列化失败的情况。

4. .configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)在反序列化时,如果JSON数据中指定的类型信息与期望的Java类型层次结构不匹配(例如类型 标识错误等情况),默认会抛出异常。将这个配置设为false,可以放宽这种限制,使得在遇到类 型不太准确但仍有可能处理的情况下,尝试继续进⾏反序列化⽽不是直接失败,提⾼对可能存在错误类型标识的JSON数据的容错性。

5. .configure(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS, false)在序列化时,如果Java对象中有以⽇期类型作为键(⽐如在Map中,键是Date类型等情况的结构,如果设置为true会将⽇期键转换为时间戳形式。设置为false则按照其他⽇期相关配置来处 理,保证⽇期键也以期望的格式进⾏序列化,使得整个JSON结构中⽇期相关内容都能保持统⼀的 格式呈现。(默认值为false)

6. .configure(MapperFeature.USE_ANNOTATIONS, false) Jackson⽀持通过在Java类的属性或⽅法上添加各种注解来定制序列化和反序列化⾏为。将此配 置设为false,表⽰不依赖这些注解来进⾏相关操作,⽽是更多地依据全局配置(如上述其他配置 项)来控制序列化和反序列化过程,适⽤于希望统⼀管理JSON处理规则,减少因注解使⽤不当或 过多导致的复杂性的场景。

7. addModule(newJavaTimeModule()) 这是序列化LocalDateTIme和LocalDate的必要配置,由Jackson-data-JSR310实现。

8. .defaultDateFormat(new SimpleDateFormat(CommonConstants.STANDARD_FORMAT)) 所有的⽇期格式都统⼀为以下的样式,即yyyy-MM-dd HH:mm:ss

4.泛型擦除<理解>

我们给出这样一段代码

@Test2    
public void string2ListObjTest(){
//        TestRegion testRegion = new TestRegion();
//        testRegion.setId(1L);
//        testRegion.setName("北京");
//        testRegion.setFullName("北京市");
//        testRegion.setCode("110000");
//        List<TestRegion> regionList = new ArrayList<>();
//        regionList.add(testRegion);
//        System.out.println(JsonUtil.obj2String(regionList));
//        [{"id":1,"name":"北京","fullName":"北京市","code":"110000"}]
List list = JsonUtil.string2Obj("[{\"id\":1,\"name\":\"北京\",\"fullName\":\"北京市\",\"code\":\"110000\"}]", List.class);System.out.println(list);

我们单步调试可以看到list和我们定义的类型不⼀致

        这是因为java的泛型擦除导致的,如果后⾯代码我们基于List继续定义,那么会报类型不匹配。

什么是泛型擦除?

        Java中的泛型擦除(Type Erasure)是指Java编译器在编译泛型代码时,会移除泛型类型参数的相关信息,使得⽣成的字节码中不包含泛型类型信息。这使得Java泛型在运⾏时(Runtime)表现为原始类型(Raw Type)。

为什么出现泛型擦除:

        向后兼容:新旧代码兼容,在编译时泛型信息就擦除了,所以旧版本jvm可以运载新代码

        类型安全:尽管泛型信息在编译时被擦除,但在编译时会进⾏类型检查,确保泛型的使用是安全的。这意味着你可以在编译时发现类型错误,而不是在运行时

        避免性能开销:所有的类型信息在编译时就已经确定,泛型擦除避免了如果JVM需要在运⾏时维护泛型类型信息引入的额外开销。

解决方法:

        常见的Collection如 List、Map 中包含泛型,需要先反序列化Collection类型为泛型的CollectionType。

        如果是ArrayList那么使⽤ObjectMapper的 getTypeFactory().constructParametricType(collectionClass,YourBean.class)。

        HashMap那么 ObjectMapper 的 getTypeFactory().constructMapType(HashMap.class,String.class, YourBean.class)。


封装Bean拷贝基础工具类

        Bean拷⻉(BeanCopy)是指在⾯向对象编程中,将⼀个Java对象(通常称为源Bean)的属性值复制到另⼀个Java对象(目标Bean)中的操作。

示例:

@Testpublic void beanCopyTest() {//源bean : TestUser {"name" : "张三","sex" : "男","age" : 29}//⽬标bean: TestUserVO  TestUserVO userVO = new TestUserVO();userVO.setName(testUser.getName());userVO.setAge(testUser.getAge());
}

        Web项目中有控制器(Controller)层、业务逻辑(service)层、数据访问 (DAO)层。不同层之间通常会使用不同的数据模型,这就意味着项目中往往不止一种数据模型,经常需要将数据从一种数据模型转换成另外一种 数据模型,正因如此,才需要Bean拷贝。

具体来说分为这

几种常见的数据模型:

DO(Data Object)是⼀个与数据库表结构对应的对象,也叫持久化对象。DO通常⽤来在DAO层 (数据访问层)和数据库进⾏交互。

BO(Business Object)是⼀个封装业务逻辑的对象,主要存在于业务逻辑层。它封装了业务逻辑 相关的数据和操作。BO不仅仅是简单的数据容器,还包含了业务规则和业务流程相关的⽅法。 

DTO(Data Transfer Object)是⼀个⽤来传输数据的对象。DTO⽤来在不同层或不同系统之间传 递数据,⽐如从Service层传递到Controller层或者Controller层传递到service层等。DTO可以避免 在层与层之间传递过多的不必要的数据,同时也可以隐藏内部的数据结构和实现细节。

VO(View Object)是⼀个⽤来展⽰数据的对象(返回给前端的数据)。它是根据具体的视图(如 Web⻚⾯、桌⾯应⽤的界⾯等)的要求,将数据以⼀种合适的形式进⾏组织和封装,以便于在视图中进进行展示。

POJO(Plain Ordinary Java Object)是⼀个普通的Java对象,它没有任何特殊的要求或约束,可 以⽤来表⽰任何类型的数据。


拷贝

        手动拷贝
private String QQ;...............public TestUserVO convertToVO() {TestUserVO userVO = new TestUserVO();userVO.setName(this.name);userVO.setAge(this.age);............return userVO ;
        拷贝工具

BeanUtils.copyProperties() 是 Spring 框架提供的⼀个⼯具⽅法,它可以简化对象属性的拷贝操作

TestUserVO userVO = new TestUserVO();
BeanUtils.copyProperties(user, userVO);

        但如果源bean和⽬标bean之间的属性名称不⼀致,或者源bean向⽬标bean拷⻉时需要对源bean 中的属性值进⾏处理后再进⾏拷⻉。此时使⽤⼯具类就⽆法准确的完成拷⻉⼯作了

        拷贝工具类+手动拷贝

使⽤spring提供的⼯具类,针对具体的对象提供专⽤的bean拷⻉⽅法,就是拷贝工具类+手动拷贝

例如

@Getter
@Setter
@ToStringpublic class TestUserVO {private String nickName;private int age;private String school;private String contactInfo;
}
public class TestUser {private String name;private int age;private String sex;private String school;private String weChat;private String QQ;...............public TestUserVO convertToVO() {TestUserVO userVO = new TestUserVO();BeanUtils.copyProperties(user, userVO);userVO.setNickName(this.name);userVO.setContactInfo(this.weChat + "--" + this.QQ);return userVO ;}
}
        内置拷⻉⼯具类+拷⻉⼯具类+⼿动拷⻉

单个对象普通拷⻉:直接使⽤spring提供的⼯具类。

列表对象普通拷⻉:由于拷⻉spring框架提供的bean拷⻉⼯具类⽆法⽀持对列表对象的拷⻉⼯作,所 以我们需要⾃⼰封装内置Bean拷⻉⼯具类提供对列表对象的拷⻉⽅法。

特殊拷⻉(包括列表对象和单个对象):需要⼿动拷⻉。针对具体的对象提供专⽤的bean拷⻉⽅法。

Bean拷贝工具类

1.创建BeanCopyUtil工具类

BeanUtils.copyProperties 不⽀持列表拷⻉,默认只⽀持java bean,我们扩展下该⽅法⽀持 列表拷⻉

package com.renting.rscommoncore.utils;import org.springframework.beans.BeanUtils;import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;public class BeanCopyUtil extends BeanUtils {/*** 集合数据的拷⻉* @param sources 数据源类* @param target ⽬标类::new(eg: UserVO::new)* @return 拷⻉结果* @param <S> 源⽬标类型* @param <T> ⽬标对象类型*/public static <S, T> List<T> copyListProperties(List<S> sources,Supplier<T> target) {List<T> list = new ArrayList<>(sources.size());for (S source : sources) {T t = target.get();copyProperties(source, t);list.add(t);}return list;}
}

2.Spring框架提供的Bean拷⻉⼯具类问题

 属性类型不⼀致导致拷⻉失败

实际开发中,很可能会出现同⼀字段在不同的类中定义的类型不⼀致,例如ID,可能在A类中 定义的类型为Long,在B类中定义的类型为String,此时如果使⽤BeanUtils.copyProperties进⾏拷⻉,就会出现拷⻉失败的现象,导致对应的字段为null

@Data
@AllArgsConstructor
@NoArgsConstructorclass SourcePoJo{private String username;private Long id;
}@Data class TargetPoJo{private String username;private String id;
}@Testvoid copyfail1() {SourcePoJo sourcePoJo = new SourcePoJo("bite", (long) 1000000);TargetPoJo targetPoJo = new TargetPoJo();BeanUtils.copyProperties(sourcePoJo,targetPoJo);System.out.println(targetPoJo);
}输出结果会变成
BeanUtilsTests.TargetPoJo(username=bite, id=null)

在实际开发中,很可能会出现同⼀字段在不同的类中定义的类型不⼀致,例如ID,可能在A类中 定义的类型为Long,在B类中定义的类型为long,此时如果使BeanUtils.copyProperties进⾏拷⻉,就会出现拷⻉失败的现象,会抛出FatalBeanException。

@Data
@AllArgsConstructor
@NoArgsConstructorclass SourcePoJo{private String username;private Long id;
}@Dataclass TargetPoJo2{private String username;private long id;
}@Testvoid copyfail2() {SourcePoJo sourcePoJo = new SourcePoJo();sourcePoJo.setUsername("bite");TargetPoJo2 targetPoJo2 = new TargetPoJo2();BeanUtils.copyProperties(sourcePoJo,targetPoJo2);System.out.println(targetPoJo2);
}输出结果:
org.springframework.beans.FatalBeanException: Could not copy property 'id' 
from source to targetat 
org.springframework.beans.BeanUtils.copyProperties(BeanUtils.java...(报错信息)
底层实现为反射拷⻉效率低

BeanUtils.copyProperties底层是通过反射获取到对象的set和get⽅法,然后通过get、set完成数据的 拷⻉,整体拷⻉效率较低。

        使⽤BeanUtils.copyProperties拷⻉数据和直接set的⽅式赋值效率对⽐,为了便于直观的看出 效果,这⾥以拷⻉1万次为例:

@Testvoid copyslow(){long copyStartTime = System.currentTimeMillis();SourcePoJo source = new SourcePoJo("bite",1000000L);TargetPoJo2 target = new TargetPoJo2();for(int i = 0; i < 10000; i++) {BeanUtils.copyProperties(source, target);}System.out.println("copy⽅式:"+(System.currentTimeMillis()-
copyStartTime));long setStartTime = System.currentTimeMillis();for(int i = 0; i < 10000; i++) {target.setUsername(source.getUsername());target.setId(source.getId());}System.out.println("set⽅式:"+(System.currentTimeMillis()-setStartTime));
}结果:
copy⽅式:127
set⽅式:0
BeanUtils.copyProperties是浅拷⻉
        浅拷贝是说在创建⼀个新对象(⽬标对象),⽬标对象的属性值与原始对象相同,但对于引⽤类型的属性,仍然共享相同的引⽤。也就是说在浅拷⻉下,当源对象的引⽤属性的值发⽣变化时,⽬标对象的引⽤属性值也会随之发⽣变化

我们可以举一个简单的例子

@Data
@NoArgsConstructor
@AllArgsConstructorclass Card {private String num;
}@NoArgsConstructor
@AllArgsConstructor
@Dataclass Person {private String name;private Card card;
}@Testvoid copylight(){Person sourcePerson = new Person("bite",new Card("123456"));Person targetPerson = new Person();BeanUtils.copyProperties(sourcePerson, targetPerson);System.out.println(targetPerson);sourcePerson.getCard().setNum("654321");System.out.println(targetPerson);//修改后代码: 
Person sourcePerson = new Person("bite",new Card("123456"));Person targetPerson = new Person();BeanUtils.copyProperties(sourcePerson, targetPerson);Card targetCard = new Card();BeanUtils.copyProperties(sourcePerson.getCard(), targetCard);targetPerson.setCard(targetCard);System.out.println(targetPerson);sourcePerson.getCard().setNum("654321");System.out.println(targetPerson);
}结果如下:
BeanCopyUtilTests.Person(name=bite, card=BeanCopyUtilTests.Card(num=123456))
BeanCopyUtilTests.Person(name=bite, card=BeanCopyUtilTests.Card(num=654321))
内部类数据无法成功拷贝

Spring 的 BeanUtils.copyProperties 在处理内部类属性时会遇到拷贝失败的问题,这是由其设计机制决定的。

  1. 访问权限限制

    • 内部类的字段默认是 private 访问级别

    • BeanUtils 使用反射访问字段时,无法突破 Java 的访问控制规则

    • 示例:内部类 Inner 的字段 innerValue 无法被外部访问

  2. Getter/Setter 缺失

    • 内部类的字段通常没有公共的 getter/setter 方法

    • BeanUtils 依赖标准的 JavaBean 规范(通过 getter/setter 访问属性)

    • 当缺少这些方法时,属性拷贝会静默失败


其它的一些公共工具类

时间戳工具类

  • 获取当前时间戳
  • 获取未来x秒的时间戳
  • 获取未来x天的时间戳
  • 获取未来x⽉的时间戳
  • 获取未来x年的时间戳
  • 计算两个时间戳之间的差异
  • 所有⽅法均提供返回秒级时间和毫秒级时间两种实现

package com.renting.rscommoncore.utils;import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.temporal.ChronoUnit;public class TimeStampUtil {/*** 获取当前时间戳(秒级)* @return 当前时间戳(秒级)*/public static long getCurrentSeconds() {return Instant.now().getEpochSecond();}/*** 获取当前时间戳(毫秒级)* @return 当前时间戳(毫秒级)*/public static long getCurrentMillis() {return Instant.now().toEpochMilli();}/*** 获取未来x秒的时间戳(秒级)* @param seconds 秒* @return 时间戳*/public static long getSecondsLaterSeconds(long seconds) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime secondsLater = now.plusSeconds(seconds);return secondsLater.toEpochSecond();}/*** 获取未来x秒的时间戳(毫秒级)* @param seconds 秒* @return 时间戳*/public static long getSecondsLaterMillis(long seconds) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime secondsLater = now.plusSeconds(seconds);return secondsLater.toInstant().toEpochMilli();}/*** 获取未来x天的时间戳(秒级)* @param days 天* @return 时间戳*/public static long getDaysLaterSeconds(long days) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime secondsLater = now.plusDays(days);return secondsLater.toEpochSecond();}/*** 获取未来x天的时间戳(毫秒级)* @param days 天* @return 时间戳*/public static long getDaysLaterMillis(long days) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime monthsLater = now.plusDays(days);return monthsLater.toInstant().toEpochMilli();}/*** 获取未来x⽉的时间戳(秒级)* @param months ⽉* @return 时间戳*/public static long getMonthsLaterSeconds(long months) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime monthsLater = now.plusMonths(months);return monthsLater.toEpochSecond();}/*** 获取未来x⽉的时间戳(毫秒级)* @param months ⽉* @return 时间戳*/public static long getMonthsLaterMillis(long months) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime monthsLater = now.plusMonths(months);return monthsLater.toInstant().toEpochMilli();}/*** 获取未来x年的时间戳(秒级)* @param years 年* @return 时间戳*/public static long getYearLaterSeconds(long years) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime yearLater = now.plusMonths(years);return yearLater.toEpochSecond();}/*** 获取未来x年的时间戳(毫秒级)* @param years 年* @return 时间戳*/public static long getYearLaterMillis(long years) {ZonedDateTime now = ZonedDateTime.now(ZoneId.of("UTC"));ZonedDateTime yearLater = now.plusMonths(years);return yearLater.toInstant().toEpochMilli();}/*** 计算两个时间戳之间的差异(毫秒)* @param timestamp1 时间戳1* @param timestamp2 时间戳2* @return 时间戳差异(毫秒)*/public static long calculateDifferenceMillis(long timestamp1, longtimestamp2) {return ChronoUnit.MILLIS.between(Instant.ofEpochMilli(timestamp1),Instant.ofEpochMilli(timestamp2));}/*** 计算两个时间戳之间的差异(秒)* @param timestamp1 时间戳1* @param timestamp2 时间戳2* @return 时间戳差异(秒)*/public static long calculateDifferenceSeconds(long timestamp1, longtimestamp2) {return ChronoUnit.SECONDS.between(Instant.ofEpochSecond(timestamp1),Instant.ofEpochSecond(timestamp2));}
}

String⼯具类

  • 提供字符串常⽤操作
  • 判断url是否与规则匹配(url:特殊的字符串)
    • 判断指定字符串(url)是否与指定匹配规则链表中的任意⼀个匹配规则匹配

引入依赖

<dependencies><dependency><groupId>org.apache.commons</groupId><artifactId>commons-lang3</artifactId></dependency><dependency><groupId>org.apache.commons</groupId><artifactId>commons-collections4</artifactId></dependency>
</dependencies>

package com.renting.rscommoncore.utils;import org.apache.commons.lang3.StringUtils;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;import java.util.List;/*** 字符串⼯具类*/
public class StringUtil {/*** 判断指定字符串是否与指定匹配规则链表中的任意⼀个匹配规则匹配* @param str 指定字符串* @param patternList 匹配规则链表* @return 是否匹配*/public static boolean matches(String str, List<String> patternList){if (StringUtils.isEmpty(str) || CollectionUtils.isEmpty(patternList)){return false;}for (String pattern : patternList){if (isMatch(pattern, str)){return true;}}return false;}/*** 判断url是否与规则匹配* 匹配规则:* 精确匹配* 匹配规则中包含 ? 表⽰任意单个字符;* 匹配规则中包含 * 表⽰⼀层路径内的任意字符串,不可跨层级;* 匹配规则中包含 ** 表⽰任意层路径的任意字符,可跨层级* @param pattern 匹配规则* @param url 需要匹配的url* @return 是否匹配*/public static boolean isMatch(String pattern, String url){if (StringUtils.isEmpty(url) || StringUtils.isEmpty(pattern)) {return false;}AntPathMatcher matcher = new AntPathMatcher();return matcher.match(pattern, url);}
}

如果说还有需要添加的,不论是说你需要的还是后面加上来的,满足下面三个原则即可

⼯具类封装原则

  • 先从成熟的⼚商所提供的⼯具类当中寻找,推荐:spring和apache,具体不做限制,但是要确定安 全可靠,明确⼯具类的特点。
  • 如果成熟⼚商所提供的⼯具类当中没有们想要使⽤的⽅法,再封装内置⼯具类提供。
  • 公司如果对此有明确要求,以公司要求为准。(常⻅要求:对于⼀种⼯具类的使⽤尽可能统⼀)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/93654.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/93654.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件系统运维常见问题

系统部署常见问题 环境配置、兼容性问题。生产与测试环境的操作系统、库版本、中间件版本不一致&#xff0c;运行环境软件版本不匹配。新旧版本代码/依赖不兼容。依赖缺失或冲突问题。后端包启动失败&#xff0c;提示类/方法/第三方依赖库找不到或者版本冲突。配置错误。系统启…

2021 IEEE【论文精读】用GAN让音频隐写术骗过AI检测器 - 对抗深度学习的音频信息隐藏

使用GAN生成音频隐写术的隐写载体 本文为个人阅读GAN音频隐写论文&#xff0c;部分内容注解&#xff0c;由于原文篇幅较长这里就不再一一粘贴&#xff0c;仅对原文部分内容做注解&#xff0c;仅供参考详情参考原文链接 原文链接&#xff1a;https://ieeexplore.ieee.org/abstra…

PWA技术》》渐进式Web应用 Push API 和 WebSocket 、webworker 、serviceworker

PWA # 可离线 # 高性能 # 无需安装 # 原生体验Manifest {"name": "天气助手", // 应用全名"short_name": "天气", // 短名称&#xff08;主屏幕显示&#xff09;"start_url": "/index.html&…

数据结构——栈和队列oj练习

225. 用队列实现栈 - 力扣&#xff08;LeetCode&#xff09; 这一题需要我们充分理解队列和栈的特点。 队列&#xff1a;队头出数据&#xff0c;队尾入数据。 栈&#xff1a;栈顶出数据和入数据。 我们可以用两个队列实现栈&#xff0c;在这过程中&#xff0c;我们总要保持其…

Java基础 8.19

目录 1.局部内部类的使用 总结 1.局部内部类的使用 说明&#xff1a;局部内部类是定义在外部类的局部位置&#xff0c;比如方法中&#xff0c;并且有类名可以直接访问外部类的所有成员&#xff0c;包含私有的不能添加访问修饰符&#xff0c;因为它的地位就是一个局部变量。局…

从父类到子类:C++ 继承的奇妙旅程(2)

前言&#xff1a;各位代码航海家&#xff0c;欢迎回到C继承宇宙&#xff01;上回我们解锁了继承的「基础装备包」&#xff0c;成功驯服了public、protected和花式成员隐藏术。但——⚠️前方高能预警&#xff1a; 继承世界的暗流涌动远不止于此&#xff01;今天我们将勇闯三大神…

【图像算法 - 16】庖丁解牛:基于YOLO12与OpenCV的车辆部件级实例分割实战(附完整代码)

庖丁解牛&#xff1a;基于YOLO12与OpenCV的车辆部件级实例分割实战&#xff08;附完整代码&#xff09; 摘要&#xff1a; 告别“只见整车不见细节”&#xff01;本文将带您深入实战&#xff0c;利用YOLO12-seg训练实例分割模型&#xff0c;结合OpenCV的强大图像处理能力&…

ubuntu22.04配置远程桌面

文章目录前言检查桌面类型xorg远程桌面(xrdp)安装xrdpxrdp添加到ssl-certwayland远程桌面(gnome-remote-desktop)检查安装开启开启状况检查自动登录奇技淫巧前言 在windows上使用远程桌面服务&#xff0c;连接ubuntu主机的远程桌面 检查桌面类型 查看桌面类型、协议 echo $…

SQL Server 中子查询、临时表与 CTE 的选择与对比

在 SQL Server 的实际开发过程中&#xff0c;我们常常需要将复杂的查询逻辑分解为多个阶段进行处理。实现这一目标的常见手段有 子查询 (Subquery)、临时表 (Temporary Table) 和 CTE (Common Table Expression)。这三者在语法、执行效率以及可维护性方面各有优势与局限。如何选…

肖臻《区块链技术与应用》第20-22讲 - 以太坊难度调整、权益证明和智能合约

以太坊的“冰河时代”:详解难度调整算法与“难度炸弹” 摘要: 为了实现远快于比特币的十几秒出块速度,以太坊必须设计一套更为灵敏和复杂的挖矿难度调整算法。本文基于北京大学肖臻老师的公开课内容,深入剖析了以太坊独特的逐块难度调整机制。文章首先解释了其维持15秒平均…

C++中内存池(Memory Pool)详解和完整示例

1. 什么是内存池&#xff1f; 内存池&#xff08;Memory Pool / Pool Allocator&#xff09; 是一种内存管理机制&#xff0c;提前向系统申请一大块内存&#xff0c;再在这块内存里切分、分配和回收。 它相当于在用户空间建立了一层 “小型堆管理器”&#xff0c;避免频繁调用系…

测试 Next.js 应用:工具与策略

1. 引言 Next.js 作为一个基于 React 的全栈框架&#xff0c;在构建复杂 Web 应用时&#xff0c;测试是确保代码质量、功能稳定性和用户体验的关键步骤。测试可以分为单元测试、集成测试和端到端测试三种类型&#xff0c;每种类型针对不同的层面&#xff1a;单元测试验证单个组…

IP 分片和组装的具体过程

IP 分片和组装的具体过程 在这里插入图片描述 • 16 位标识(id): 唯一的标识主机发送的报文. 如果 IP 报文在数据链路层被分片了, 那么每一个片里面的这个 id 都是相同的. • 3 位标志字段: 第一位保留(保留的意思是现在不用, 但是还没想好说不定以后要用到). 第二位置为 1 表示…

数据仓库OLTPOLAP维度讲解

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;大数据、Java、测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/…

OpenHarmony之编译配置白名单机制深度解析:构建系统的安全防线

一、白名单机制概述 在OpenHarmony的构建系统中&#xff0c;compile_standard_whitelist.json是一个关键的安全验证机制&#xff0c;它作为编译过程中的"守门人"&#xff0c;确保只有经过验证的组件和依赖关系才能被纳入最终构建产物。这个机制是OpenHarmony构建系统…

backward怎么计算的是torch.tensor(2.0, requires_grad=True)变量的梯度

import torch import torch.nn as nn import torch.optim as optim# 一个参数 w 2 w torch.tensor(2.0, requires_gradTrue) # 预测值 y_pred w * 3 # 6 # 真实值 y_true torch.tensor(10.0) # 损失 (预测 - 真实)^2 loss (y_pred - y_true) ** 2 # (6-10)^2 16loss.b…

戴永红×数图:重构零售空间价值,让陈列创造效益!

风雨同舟&#xff0c;智赢未来。近日&#xff0c;湖南戴永红商业连锁有限公司&#xff08;以下简称“戴永红”&#xff09;正式携手数图信息科技有限公司&#xff0c;全面启动“可视化品类空间管理”项目。以数图可视化陈列系统为引擎&#xff0c;双方将共同推进企业零售管理的…

排查Redis数据倾斜引发的性能瓶颈

以下是针对 Redis 数据倾斜问题的完整排查与优化方案&#xff0c;结合实战案例说明如何提升吞吐量和响应速度&#xff1a;一、问题现象定位1. ​性能监控异常​# Redis集群节点负载差异 $ redis-cli -c cluster nodes | grep master e1d7b... 10.0.0.1:637916379 master - 0 16…

元宇宙的硬件设备:从 VR 头显到脑机接口

1 元宇宙的主流硬件设备1.1 VR 头显&#xff1a;沉浸式体验的核心入口VR 头显是当前进入元宇宙最主要的硬件设备&#xff0c;通过封闭的显示系统为用户营造沉浸式虚拟环境。主流 VR 头显采用双屏 LCD 或 OLED 显示技术&#xff0c;单眼分辨率已从早期的 1080P 提升至 4K 级别&a…

具身智能2硬件架构(人形机器人)摘自Openloong社区

青龙人形机器人: 硬件 身体全身自由度43,手部自由度6*2,电池续航3h,运动控制算法(zmp/slip/mpc/深度学习)MPC+WBC+强化学习,54Tops(FP16),具有路径建图和自主导航能力,感官系统深度视觉传感器*3全景环视*1,具备语音识别与声源定位,可扩展嗅觉传感器 OpenLoong通…