我遇到了这个问题,真的很想继续使用@JsonIgnore,但也使用实体/POJO 在 JSON 调用中使用。
经过大量挖掘后,我想出了在每次调用对象映射器时自动从数据库中检索被忽略字段的解决方案。
当然,此解决方案需要一些要求。就像您必须使用存储库一样,但在我的情况下,这正是我需要的方式。
为此,您需要确保 MappingJackson2HttpMessageConverter 中的 ObjectMapper 被截获,并且标有 @JsonIgnore 的字段已填充。因此我们需要我们自己的 MappingJackson2HttpMessageConverter bean:
public class MvcConfig extends WebMvcConfigurerAdapter {
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (HttpMessageConverter converter : converters) {
if (converter instanceof MappingJackson2HttpMessageConverter) {
((MappingJackson2HttpMessageConverter)converter).setObjectMapper(objectMapper());
}
}
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper objectMapper = new FillIgnoredFieldsObjectMapper();
Jackson2ObjectMapperBuilder.json().configure(objectMapper);
return objectMapper;
}
}
每个 JSON 请求都由我们自己的 objectMapper 转换为一个对象,它通过从存储库中检索忽略的字段来填充它们:
/**
* Created by Sander Agricola on 18-3-2015.
*
* When fields or setters are marked as @JsonIgnore, the field is not read from the JSON and thus left empty in the object
* When the object is a persisted entity it might get stored without these fields and overwriting the properties
* which where set in previous calls.
*
* To overcome this property entities with ignored fields are detected. The same object is than retrieved from the
* repository and all ignored fields are copied from the database object to the new object.
*/
@Component
public class FillIgnoredFieldsObjectMapper extends ObjectMapper {
final static Logger logger = LoggerFactory.getLogger(FillIgnoredFieldsObjectMapper.class);
@Autowired
ListableBeanFactory listableBeanFactory;
@Override
protected Object _readValue(DeserializationConfig cfg, JsonParser jp, JavaType valueType) throws IOException, JsonParseException, JsonMappingException {
Object result = super._readValue(cfg, jp, valueType);
fillIgnoredFields(result);
return result;
}
@Override
protected Object _readMapAndClose(JsonParser jp, JavaType valueType) throws IOException, JsonParseException, JsonMappingException {
Object result = super._readMapAndClose(jp, valueType);
fillIgnoredFields(result);
return result;
}
/**
* Find all ignored fields in the object, and fill them with the value as it is in the database
* @param resultObject Object as it was deserialized from the JSON values
*/
public void fillIgnoredFields(Object resultObject) {
Class c = resultObject.getClass();
if (!objectIsPersistedEntity(c)) {
return;
}
List ignoredFields = findIgnoredFields(c);
if (ignoredFields.isEmpty()) {
return;
}
Field idField = findIdField(c);
if (idField == null || getValue(resultObject, idField) == null) {
return;
}
CrudRepository repository = findRepositoryForClass(c);
if (repository == null) {
return;
}
//All lights are green: fill the ignored fields with the persisted values
fillIgnoredFields(resultObject, ignoredFields, idField, repository);
}
/**
* Fill the ignored fields with the persisted values
*
* @param object Object as it was deserialized from the JSON values
* @param ignoredFields List with fields which are marked as JsonIgnore
* @param idField The id field of the entity
* @param repository The repository for the entity
*/
private void fillIgnoredFields(Object object, List ignoredFields, Field idField, CrudRepository repository) {
logger.debug("Object {} contains fields with @JsonIgnore annotations, retrieving their value from database", object.getClass().getName());
try {
Object storedObject = getStoredObject(getValue(object, idField), repository);
if (storedObject == null) {
return;
}
for (Field field : ignoredFields) {
field.set(object, getValue(storedObject, field));
}
} catch (IllegalAccessException e) {
logger.error("Unable to fill ignored fields", e);
}
}
/**
* Get the persisted object from database.
*
* @param id The id of the object (most of the time an int or string)
* @param repository The The repository for the entity
* @return The object as it is in the database
* @throws IllegalAccessException
*/
@SuppressWarnings("unchecked")
private Object getStoredObject(Object id, CrudRepository repository) throws IllegalAccessException {
return repository.findOne((Serializable)id);
}
/**
* Get the value of a field for an object
*
* @param object Object with values
* @param field The field we want to retrieve
* @return The value of the field in the object
*/
private Object getValue(Object object, Field field) {
try {
field.setAccessible(true);
return field.get(object);
} catch (IllegalAccessException e) {
logger.error("Unable to access field value", e);
return null;
}
}
/**
* Test if the object is a persisted entity
* @param c The class of the object
* @return true when it has an @Entity annotation
*/
private boolean objectIsPersistedEntity(Class c) {
return c.isAnnotationPresent(Entity.class);
}
/**
* Find the right repository for the class. Needed to retrieve the persisted object from database
*
* @param c The class of the object
* @return The (Crud)repository for the class.
*/
private CrudRepository findRepositoryForClass(Class c) {
return (CrudRepository)new Repositories(listableBeanFactory).getRepositoryFor(c);
}
/**
* Find the Id field of the object, the Id field is the field with the @Id annotation
*
* @param c The class of the object
* @return the id field
*/
private Field findIdField(Class c) {
for (Field field : c.getDeclaredFields()) {
if (field.isAnnotationPresent(Id.class)) {
return field;
}
}
return null;
}
/**
* Find a list of all fields which are ignored by json.
* In some cases the field itself is not ignored, but the setter is. In this case this field is also returned.
*
* @param c The class of the object
* @return List with ignored fields
*/
private List findIgnoredFields(Class c) {
List ignoredFields = new ArrayList();
for (Field field : c.getDeclaredFields()) {
//Test if the field is ignored, or the setter is ignored.
//When the field is ignored it might be overridden by the setter (by adding @JsonProperty to the setter)
if (fieldIsIgnored(field) ? setterDoesNotOverrideIgnore(field) : setterIsIgnored(field)) {
ignoredFields.add(field);
}
}
return ignoredFields;
}
/**
* @param field The field we want to retrieve
* @return True when the field is ignored by json
*/
private boolean fieldIsIgnored(Field field) {
return field.isAnnotationPresent(JsonIgnore.class);
}
/**
* @param field The field we want to retrieve
* @return true when the setter is ignored by json
*/
private boolean setterIsIgnored(Field field) {
return annotationPresentAtSetter(field, JsonIgnore.class);
}
/**
* @param field The field we want to retrieve
* @return true when the setter is NOT ignored by json, overriding the property of the field.
*/
private boolean setterDoesNotOverrideIgnore(Field field) {
return !annotationPresentAtSetter(field, JsonProperty.class);
}
/**
* Test if an annotation is present at the setter of a field.
*
* @param field The field we want to retrieve
* @param annotation The annotation looking for
* @return true when the annotation is present
*/
private boolean annotationPresentAtSetter(Field field, Class annotation) {
try {
Method setter = getSetterForField(field);
return setter.isAnnotationPresent(annotation);
} catch (NoSuchMethodException e) {
return false;
}
}
/**
* Get the setter for the field. The setter is found based on the name with "set" in front of it.
* The type of the field must be the only parameter for the method
*
* @param field The field we want to retrieve
* @return Setter for the field
* @throws NoSuchMethodException
*/
@SuppressWarnings("unchecked")
private Method getSetterForField(Field field) throws NoSuchMethodException {
Class c = field.getDeclaringClass();
return c.getDeclaredMethod(getSetterName(field.getName()), field.getType());
}
/**
* Build the setter name for a fieldName.
* The Setter name is the name of the field with "set" in front of it. The first character of the field
* is set to uppercase;
*
* @param fieldName The name of the field
* @return The name of the setter
*/
private String getSetterName(String fieldName) {
return String.format("set%C%s", fieldName.charAt(0), fieldName.substring(1));
}
}
在所有情况下,也许不是最干净的解决方案,但在我的情况下,它可以按照我希望它工作的方式来解决问题。