如何在JAX-RS中全局格式化LocalDateTime并解决启动报错?
我来帮你梳理下问题的根源,然后给出针对性的解决方案:
首先,你遇到的核心问题有两个:一是@XmlJavaTypeAdapter只能覆盖带注解的实体属性,没法处理Map这类集合里的LocalDateTime;二是错误地把ContextResolver当成根资源注册,导致启动报错。下面是具体的解决步骤:
一、正确配置Jackson全局处理LocalDateTime
我们需要借助Jackson的JSR310模块来统一处理Java 8日期类型,替代XmlAdapter的方式,这样不管是实体属性还是Map里的日期,都能被统一序列化。
1. 编写正确的ObjectMapperContextResolver
注意一定要加上@Provider注解,标记这是一个JAX-RS Provider:
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; import javax.ws.rs.ext.ContextResolver; import javax.ws.rs.ext.Provider; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; @Provider public class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> { private final ObjectMapper objectMapper; public ObjectMapperContextResolver() { objectMapper = new ObjectMapper(); JavaTimeModule timeModule = new JavaTimeModule(); // 自定义你需要的日期格式,比如常用的"yyyy-MM-dd HH:mm:ss" DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); timeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter)); // 如果需要反序列化也统一格式,添加对应的反序列化器 // timeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter)); objectMapper.registerModule(timeModule); } @Override public ObjectMapper getContext(Class<?> type) { return objectMapper; } }
2. 移除实体属性上的@XmlJavaTypeAdapter
现在全局配置已经生效,之前加在getExpirationDate()上的@XmlJavaTypeAdapter(LocalDateTimeAdapter.class)可以删掉了。
二、正确注册ContextResolver(解决启动报错)
你之前的错误是把ObjectMapperContextResolver放到了resteasy.resources参数里——这个参数是用来注册带@Path注解的根资源类的,而ContextResolver属于JAX-RS Provider,应该用下面两种方式之一注册:
方式1:通过Application类注册
如果你的项目里有继承javax.ws.rs.core.Application的类(比如报错里的com.kopi.rest.RestApplication),直接在里面添加Provider注册:
import javax.ws.rs.core.Application; import java.util.HashSet; import java.util.Set; public class RestApplication extends Application { @Override public Set<Class<?>> getClasses() { Set<Class<?>> classes = new HashSet<>(); // 添加你的业务资源类(比如UserResource、OrderResource等) // classes.add(YourResourceClass.class); // 注册日期处理的ContextResolver classes.add(ObjectMapperContextResolver.class); return classes; } }
方式2:通过web.xml的resteasy.providers参数注册
如果不想修改Application类,把web.xml里原来的resteasy.resources参数替换成resteasy.providers:
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1"> <display-name>Servlet 3.1 Web Application</display-name> <listener> <listener-class>com.kopi.web.InitListener</listener-class> </listener> <!-- 替换原resteasy.resources为resteasy.providers --> <context-param> <param-name>resteasy.providers</param-name> <param-value>com.kopi.utils.ObjectMapperContextResolver</param-value> </context-param> </web-app>
这样就不会再出现“Class is not a root resource”的启动错误了,因为resteasy.providers是专门用来注册JAX-RS Provider类的。
三、补充:确保依赖完整
检查你的pom.xml(或gradle配置),确保已经引入jackson-datatype-jsr310依赖——resteasy-jackson-provider不会自动包含这个模块,需要手动添加:
<dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-jsr310</artifactId> <version>2.9.9</version> <!-- 版本要和resteasy-jackson-provider 3.5.1.Final匹配,它依赖Jackson 2.9.x --> </dependency>
完成以上配置后,不管是实体类里的LocalDateTime属性,还是Map集合中的LocalDateTime值,都会被序列化为你指定的字符串格式,无需再单独加注解。
内容的提问来源于stack exchange,提问作者kopi_b

