这个系列讲解项目的构建方式,主要使用 父项目 parent 和 自定义 starter 结合。项目使用最新的 springboot3 和 jdk19。本系列的代码仓库看查看 gitee 仓库 的 starter 目录。
这篇我们开始学习创建属于自己的 starter ,实现一些常用模块的封装和自动配置,模拟 spirngboot 的 starter 模式,看看怎么将项目构建为 gson starter
一、创建 gson-spring-boot-starter 项目
一般官方的 starter 是以 spring-boot-starter-{模块名},所以我们这边自定义的时候,区分于官方的命令,将模块名放在前面。
我们还是以一个 springboot 项目的方式来创建,如下图。
选择目前最新的3.0.0版本,下面的依赖不需要勾选,等下我们再添加。
二、添加 pom 文件依赖
先贴上 pom.xml 代码,这里使用到上一章介绍的 backend-parent 父级项目作为这里的 parent
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.liurb.springboot.scaffold</groupId>
<artifactId>backend-parent</artifactId>
<version>1.0.0</version>
<relativePath />
</parent>
<artifactId>gson-spring-boot-starter</artifactId>
<version>1.0.0</version>
<name>gson-spring-boot-starter</name>
<description>gson-spring-boot-starter</description>
<properties>
<common-spring-boot-starter.version>1.0.0</common-spring-boot-starter.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-json</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
<optional>true</optional>
<exclusions>
<exclusion>
<artifactId>tomcat-embed-el</artifactId>
<groupId>org.apache.tomcat.embed</groupId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.liurb.springboot.scaffold</groupId>
<artifactId>common-spring-boot-starter</artifactId>
<version>${common-spring-boot-starter.version}</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
</dependency>
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<classifier>exec</classifier>
</configuration>
</plugin>
</plugins>
</build>
</project>
依赖说明:
1)移除 spring-boot-starter-json :因为项目将使用 fastjson 或者 gson ,所以这里要移除springboot默认的 jackson 依赖。
2)gson:引入gson包。
3)spring-data-redis:项目需要redis序列化等配置。
三、构建配置
搭建好的 starter 目录与代码如下图。
1. FieldExclude 注解
由于 gson 没有像 fastjson 那样的 serialize 属性,所以需要自己实现序列化策略的方式,来设置字段不序列化,使用
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface FieldExclude {
}
2.AnnotationExclusionStrategy 策略
结合第1点的注解使用,使添加了注解的字段不进行序列化。
public class AnnotationExclusionStrategy implements ExclusionStrategy {
@Override
public boolean shouldSkipField(FieldAttributes fieldAttributes) {
return fieldAttributes.getAnnotation(FieldExclude.class) != null;
}
@Override
public boolean shouldSkipClass(Class<?> aClass) {
return false;
}
}
3. GsonManager 管理器
提供一个单例的gson对象管理器,并统一设置相关gson的策略配置等,如第2点的策略。
public class GsonManager {
private static Gson gson;
private static GsonManager gsonManager;
private GsonManager() {
//默认配置
gson = new GsonBuilder().serializeNulls().disableHtmlEscaping()
.setExclusionStrategies(new AnnotationExclusionStrategy()).create();
}
public static GsonManager getInstance() {
if (gsonManager == null) {
synchronized (GsonManager.class) {
if (gsonManager == null) {
gsonManager = new GsonManager();
}
}
}
return gsonManager;
}
/**
* 获取gson对象
*
* @return
*/
public Gson getGson() {
return gson;
}
}
同时还提供一些通用的 json字符换 互转 对象 的方法等。
/**
* json字符串转实体对象
*
* @param json
* @param type
* @return
* @param <T>
*/
private <T> T fromJson(String json, Type type) {
return gson.fromJson(json, type);
}
/**
* json字符串转实体对象
*
* @param json
* @param clz
* @return
* @param <T>
*/
public <T> T convert(String json, Class<T> clz) {
return fromJson(json, clz);
}
/**
* 对象转json字符串
*
* @param obj 对象
* @return String
*/
public String toJsonText(Object obj) {
return gson.toJson(obj);
}
4. webmvc的配置类 GsonWebMvcConfig
@AutoConfiguration
@ConditionalOnClass({GsonParserArgumentResolver.class})
@ConditionalOnProperty(
name = "web.starter.http-message.config.converter",
havingValue = "gson"
)
public class GsonWebMvcConfig extends WebMvcConfigurationSupport {
//todo...
}
这里有一个配置开关,通过 @ConditionalOnProperty 解析,我们可以在 web starter 项目(后面会说到) 通过配置文件设置 web 项目将使用哪种消息解析器 ,这里的配置就是使用gson。
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
//字符串转换
StringHttpMessageConverter strConverter = new StringHttpMessageConverter(StandardCharsets.UTF_8);
converters.add(strConverter);
//json消息
GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
gsonHttpMessageConverter.setGson(GsonManager.getInstance().getGson());
converters.add(gsonHttpMessageConverter);
}
消息解析器添加 gson 的配置,可以看到 GsonHttpMessageConverter 有一个 setGson 方法,这里我们将传入我们上面定义 gson管理器的单例gson对象。
5. GsonRedisSerializer 自定义redis序列化
实现 redisTempalte 的自定义序列化,因为 gson 本身并没有相关的实现,该类实现RedisSerializer接口,主要实现序列化和反序列化方法。
public class GsonRedisSerializer<T> implements RedisSerializer<T> {
private Class<T> clazz;
public GsonRedisSerializer(Class<T> clazz) {
this.clazz = clazz;
}
@Override
public byte[] serialize(T t) throws SerializationException {
if (t == null) {
return new byte[0];
}
String str = GsonManager.getInstance().toJsonText(t);
byte[] bytes = new byte[0];
try {
bytes = str.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("gson序列化异常", e);
}
return bytes;
}
@Override
public T deserialize(byte[] bytes) throws SerializationException {
if (bytes == null || bytes.length <= 0) {
return null;
}
String str = null;
try {
str = new String(bytes,"UTF-8");
} catch (UnsupportedEncodingException e) {
log.error("gson反序列化异常", e);
}
return GsonManager.getInstance().convert(str, clazz);
}
这也是 构建redis项目starter 文章中,涉及到的redis配置gson序列化类。
/**
* 获取redis模板实例
*
* @param database 数据库
* @return
*/
public RedisTemplate<String, Object> getRedisTemplate(int database) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(getLettuceConnectionFactory(database));
redisTemplate.setKeySerializer(new StringRedisSerializer());
GsonRedisSerializer gsonRedisSerializer = new GsonRedisSerializer(Object.class);
redisTemplate.setValueSerializer(gsonRedisSerializer);
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(gsonRedisSerializer);
return redisTemplate;
}
6.GsonUtil
gson对象操作的工具类,因为在使用中发现一个坑,就是对值的判空,不能单纯判断是否为null,还有判断是否为JsonNull,所以就对一些常用的类型进行封装。
如需要获取gson对象的字符串值:
/**
* 获取gson对象的string值
*
* @param jsonObject
* @param key
* @return
*/
public static String getStringValue(JsonObject jsonObject, String key) {
JsonElement jsonElement = jsonObject.get(key);
if (jsonElement != null && !jsonElement.isJsonNull()) {
return jsonElement.getAsString();
}
return null;
}
这样就方便获取对象值,不需要每次都需要对判空写一次判断。
四、加载自动化配置
从 springboot 2.7 的时候,spring.factories 这种方式已经标记为过期的,所以从 springboot3 开始已经完全移除了。所以我们要创建 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件。
将我们 conf 目录下的 GsonWebMvcConfig 类加包路径定义在这里。如果有多个的情况,就一行配置一个。
五、打包
这时候执行 mvn package & mvn install ,这样就将这个 starter 安装到本地仓库中。
六、使用
可以看 gitee 仓库的 springboot-gson-demo 项目。