使用 Spring 数据存储库自定义 JSON 审计信息序列化
Custom JSON serialization for Audit Infos with Spring Data Repositories
我正在使用 spring boot 1.5.2 以及 Spring Data JPA 和 Data Rest 实现公司内部 REST 服务。
问题
我正在寻找一种在使用 Spring Data Rest-Repositories 公开某些域模型时将对象序列化为字符串的有效方法。
上下文
我的领域模型都是从 BaseEntity
扩展而来的,看起来像这样:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@Version
private Long version;
}
有了这个,每个域模型都有属性 createdBy
、createDate
、lastModifiedBy
和 lastModifiedDate
,如本示例实体所示:
public class TestEntity extends BaseEntity { private String name; }
相应的 JSON 输出如下所示:
{
"createdBy":
{
"name": "testEM",
"contactInfo":
{
"title": null,
"givenName": "GivenName",
"surName": "Surname",
"mail": "test@test.mail.de"
},
"function": "EMPLOYEE",
"department":
{
"name": "mydep"
}
},
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy":
{
<same representation as "createdBy">
},
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,Name!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testres/1"
},
"testEntity":
{
"href": "http://localhost:8080/testres/1{?projection}",
"templated": true
}
}
}
我想要的
现在我想实现 createdBy
和 lastModfifiedBy
的更短表示,以便这些条目不包含 User
对象。而应该只显示名称(来自 User.getName()
):
{
"createdBy": "testEM",
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy": "testEM",
"lastModifiedDate": "2017-06-12T11:49:17.013Z",
... // other properties
}
实现此目标的最佳方法是什么?
我试过:
- 在用户实体上使用
@JsonIdentityInfo
-- 这个根本没有任何效果
- 通过
@Bean Jackson2ObjectMapperBuilderCustomizer customizer() {...}
为用户实体注册自定义(反)序列化程序 -- 呈现 { "createdBy": { "content": "testEM"}}
在我的 BaseEntity
class 中用 @JsonSerialize(using= UserJsonSerializer.class)
注释覆盖的方法 public User getCreatedBy()
-- 这个抛出异常
{
"timestamp": 1497515751192,
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.http.converter.HttpMessageNotWritableException",
"message": "Could not write content: Can not override serializer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer",
"path": "/testres/1"
}
我也读过有关使用 @JsonView
的内容,但是,我不清楚如何针对给定用例启用这些功能
更新
我创建了一些投影,这是减少输出的开箱即用支持方式。有关我编写的代码,请参阅此 Gist。
有了这些,并将投影设置为摘录,条目列表就可以正常显示了。但是,当您请求 localhost:8080/testRepo/1
等特定资源时,您会得到未投影的输出。我知道 Spring 默认情况下不会将投影应用于特定实体。所以我们必须将请求参数 ?=projection=testProjection
应用于每个请求。
因为这是可行的(因为应用程序不会 public),所以可能没问题,但对其他人来说可能不行。所以问题仍然存在,我们如何才能以有效的方式为每个资源更改审计信息?
更新 2
我又读了一遍 Spring Data REST Documentation 并偶然发现了这一段:
There is another route. If the Address domain object does not have it’s own repository definition, Spring Data REST will inline the data fields right inside the Person resource.
因此当审核员的类型为 User
时,您必须公开一个 UserRepository
。
巧合的是,这正是我在创建 MWE 时遇到的确切行为(最小的工作示例,无法上传到 github,因为我在代理 :( ) 后面。
因此,@RepositoryRestResource UserRepository extends JpaRepository<User, Long>
publicly 暴露,Spring 生成此 JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,EM!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testRepo/1"
},
"testEntity":
{
"href": "http://localhost:8080/testRepo/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/testRepo/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/testRepo/1/createdBy"
}
}
}
这种行为对我来说是可以接受的,所以认为这个问题已经解决了。
如果有人有其他意见,请随时 post!
非常感谢任何帮助!
这不是我提出的问题的解决方案,但它是我和公司可以接受的妥协。
快速解决方案:
当您在 API 中公开 RestRepository<User>
并且您的审计员属于同一类型 User
时,Spring 将生成指向 createdBy
和 [=18 的 HAL 链接=].两个审计日期仍将内联,因为它们是简单的字符串(由于 JodaTime 转换)。
示例代码:
// resolves auditor from SecurityContext
public class AuditorAwareImpl implements AuditorAware<User> {
@Override
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof WrappedUser) {
WrappedUser principal = (WrappedUser)authentication.getPrincipal();
return principal.getUser();
}
throw new IllegalStateException("No current auditor available!");
}
}
公开 UserRepository:
//exported is true by default
@RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String loginName);
}
创建所有其他域对象继承自的 AuditEntity:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@javax.persistence.Version
private Long version;
}
公开您的领域模型:
@Entity
public class Project extends BaseEntity {
private String project_name;
// other properties
}
@RepositoryRestResource
public interface ProjectRepo extends JpaRepository<User, Long> {}
这将为 /projects/{id}
生成以下 JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"project_name": "MyExampleProjectName",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/projects/1"
},
"project":
{
"href": "http://localhost:8080/projects/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/projects/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/projects/1/createdBy"
}
}
}
我正在使用 spring boot 1.5.2 以及 Spring Data JPA 和 Data Rest 实现公司内部 REST 服务。
问题
我正在寻找一种在使用 Spring Data Rest-Repositories 公开某些域模型时将对象序列化为字符串的有效方法。
上下文
我的领域模型都是从 BaseEntity
扩展而来的,看起来像这样:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@Version
private Long version;
}
有了这个,每个域模型都有属性 createdBy
、createDate
、lastModifiedBy
和 lastModifiedDate
,如本示例实体所示:
public class TestEntity extends BaseEntity { private String name; }
相应的 JSON 输出如下所示:
{
"createdBy":
{
"name": "testEM",
"contactInfo":
{
"title": null,
"givenName": "GivenName",
"surName": "Surname",
"mail": "test@test.mail.de"
},
"function": "EMPLOYEE",
"department":
{
"name": "mydep"
}
},
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy":
{
<same representation as "createdBy">
},
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,Name!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testres/1"
},
"testEntity":
{
"href": "http://localhost:8080/testres/1{?projection}",
"templated": true
}
}
}
我想要的
现在我想实现 createdBy
和 lastModfifiedBy
的更短表示,以便这些条目不包含 User
对象。而应该只显示名称(来自 User.getName()
):
{
"createdBy": "testEM",
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedBy": "testEM",
"lastModifiedDate": "2017-06-12T11:49:17.013Z",
... // other properties
}
实现此目标的最佳方法是什么?
我试过:
- 在用户实体上使用
@JsonIdentityInfo
-- 这个根本没有任何效果 - 通过
@Bean Jackson2ObjectMapperBuilderCustomizer customizer() {...}
为用户实体注册自定义(反)序列化程序 -- 呈现{ "createdBy": { "content": "testEM"}}
在我的
BaseEntity
class 中用@JsonSerialize(using= UserJsonSerializer.class)
注释覆盖的方法public User getCreatedBy()
-- 这个抛出异常{ "timestamp": 1497515751192, "status": 500, "error": "Internal Server Error", "exception": "org.springframework.http.converter.HttpMessageNotWritableException", "message": "Could not write content: Can not override serializer; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not override serializer", "path": "/testres/1" }
我也读过有关使用
@JsonView
的内容,但是,我不清楚如何针对给定用例启用这些功能
更新
我创建了一些投影,这是减少输出的开箱即用支持方式。有关我编写的代码,请参阅此 Gist。
有了这些,并将投影设置为摘录,条目列表就可以正常显示了。但是,当您请求 localhost:8080/testRepo/1
等特定资源时,您会得到未投影的输出。我知道 Spring 默认情况下不会将投影应用于特定实体。所以我们必须将请求参数 ?=projection=testProjection
应用于每个请求。
因为这是可行的(因为应用程序不会 public),所以可能没问题,但对其他人来说可能不行。所以问题仍然存在,我们如何才能以有效的方式为每个资源更改审计信息?
更新 2
我又读了一遍 Spring Data REST Documentation 并偶然发现了这一段:
There is another route. If the Address domain object does not have it’s own repository definition, Spring Data REST will inline the data fields right inside the Person resource.
因此当审核员的类型为 User
时,您必须公开一个 UserRepository
。
巧合的是,这正是我在创建 MWE 时遇到的确切行为(最小的工作示例,无法上传到 github,因为我在代理 :( ) 后面。
因此,@RepositoryRestResource UserRepository extends JpaRepository<User, Long>
publicly 暴露,Spring 生成此 JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"name": "Hello,EM!",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/testRepo/1"
},
"testEntity":
{
"href": "http://localhost:8080/testRepo/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/testRepo/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/testRepo/1/createdBy"
}
}
}
这种行为对我来说是可以接受的,所以认为这个问题已经解决了。 如果有人有其他意见,请随时 post!
非常感谢任何帮助!
这不是我提出的问题的解决方案,但它是我和公司可以接受的妥协。
快速解决方案:
当您在 API 中公开 RestRepository<User>
并且您的审计员属于同一类型 User
时,Spring 将生成指向 createdBy
和 [=18 的 HAL 链接=].两个审计日期仍将内联,因为它们是简单的字符串(由于 JodaTime 转换)。
示例代码:
// resolves auditor from SecurityContext
public class AuditorAwareImpl implements AuditorAware<User> {
@Override
public User getCurrentAuditor() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication != null && authentication.getPrincipal() instanceof WrappedUser) {
WrappedUser principal = (WrappedUser)authentication.getPrincipal();
return principal.getUser();
}
throw new IllegalStateException("No current auditor available!");
}
}
公开 UserRepository:
//exported is true by default
@RepositoryRestResource(exported = true)
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByName(String loginName);
}
创建所有其他域对象继承自的 AuditEntity:
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public abstract class BaseEntity extends AbstractAuditable<User, Long> implements Serializable {
@javax.persistence.Version
private Long version;
}
公开您的领域模型:
@Entity
public class Project extends BaseEntity {
private String project_name;
// other properties
}
@RepositoryRestResource
public interface ProjectRepo extends JpaRepository<User, Long> {}
这将为 /projects/{id}
生成以下 JSON:
{
"createdDate": "2017-06-12T11:49:17.013Z",
"lastModifiedDate": "2017-06-14T11:27:32.370Z",
"project_name": "MyExampleProjectName",
"new": false,
"_links":
{
"self":
{
"href": "http://localhost:8080/projects/1"
},
"project":
{
"href": "http://localhost:8080/projects/1{?projection}",
"templated": true
},
"lastModifiedBy":
{
"href": "http://localhost:8080/projects/1/lastModifiedBy"
},
"createdBy":
{
"href": "http://localhost:8080/projects/1/createdBy"
}
}
}