Fait référence à une classe Java définie pour stocker des données. C'est une classe qui n'a que "variable membre privé + getter & setter" ou variable membre public. Je le fais souvent lors de la récupération de données à partir d'une base de données avec une API Web ou JPA. Parfois appelé "DTO".
Même s'il existe un tel objet de données, il y a des moments où vous souhaitez sortir les données stockées ici dans un journal ou autre.
public class TestDto
{
public String stringvar;
public Integer integervar;
public int intvar;
public TestDto2 testDto2;
public BigDecimal decimal;
public java.util.Date date;
}
public class TestDto2
{
public String string2;
public Integer integer2;
public int int2;
public TestDto testDto;
}
Dans un tel cas, si vous pouvez sortir en JSON comme ↓, ce sera très facile à voir.
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": null
},
"decimal": null,
"date": "2020-08-12T00:00:00.000+0900"
}
Apache Commons'ToStringBuilder # reflexionToString` a toujours été familier. Il s'agit d'une méthode pratique qui utilise la réflexion pour convertir le contenu d'un objet de données en une chaîne. En fait, vous pouvez spécifier le format de sortie pour le deuxième argument, et JSON est également préparé correctement.
ToStringBuilder.reflectionToString(dto, ToStringStyle.JSON_STYLE)
↓ Résultat de sortie (joliment imprimé séparément)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": "jp.example.common.dto.TestDto2@ed17bee",
"decimal": null,
"date": "Wed Aug 12 00:00:00 JST 2020"
}
Hmmm, désolé ...
Pour les objets de données imbriqués, seuls le nom de la classe et la valeur de hachage tels que " testDto2 ":" jp.example.common.dto.TestDto2@ed17bee ",
sont affichés et les données ne peuvent pas être affichées.
ToStringBuilder # reflexionToString
utilise toString ()
pour convertir le contenu de chaque membre en une chaîne, donc si la classe parente n'est pas spécifiée comme cette fois, ʻObject # toString` est utilisé et ↑ Le résultat de sortie ressemble à ceci.
Ensuite, créons une classe de base pour un objet de données comme ↓.
import org.apache.commons.lang3.builder.ToStringBuilder;
public class CommonDto implements Serializable
{
@Override
public String toString()
{
return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
}
Et assurez-vous que l'objet de données hérite de la classe ↑.
public class TestDto extends CommonDto
{
public String stringvar;
public Integer integervar;
public int intvar;
public TestDto2 testDto2;
public BigDecimal decimal;
public java.util.Date date;
}
public class TestDto2 extends CommonDto
{
public String string2;
public Integer integer2;
public int int2;
public TestDto testDto;
}
Dans ce cas, vous pouvez sortir en JSON simplement par toString ()
.
dto.toString();
↓ Résultat de sortie (joliment imprimé séparément)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": null
},
"decimal": null,
"date": "Wed Aug 12 00:00:00 JST 2020"
}
On a l'impression que c'est parfait, mais il y a un piège. Lorsqu'un membre d'un objet de données fait référence à son parent. Si vous suivez docilement la référence, vous pourrez vous y référer pour toujours.
En fait, si vous essayez de faire référence à testDto2.testDto
, qui est nul dans l'exemple ci-dessus, en tant que parent ...
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"string2": "CCC",
"integer2": 2,
"int2": 0,
"testDto": {
:
Il boucle infiniment comme ça. Je ne pense pas que vous y feriez normalement référence comme ceci, mais si vous créez une telle entité avec JPA et que vous la reliez les uns aux autres, elle renverra exactement un tel objet. Même si vous regardez Stack Overflow, vous pouvez voir certaines personnes qui rencontrent des problèmes avec ce problème. Cela ressemble à un écueil qui est étonnamment facile à rencontrer.
Puisque le but de ce temps est la sortie du journal, je voudrais le rendre utilisable sans me soucier de savoir s'il s'agit d'une référence circulaire ou non.
(Par exemple, si le but est une réponse API REST, je ne pense pas que l'objet obtenu de JPA sera répondu tel quel, donc je ne pense pas que vous ayez à vous soucier du problème de référence circulaire.)
Tant que j'utilise ToStringStyle.JSON_STYLE
, je pensais que je ne pouvais pas gérer les références circulaires, donc cette fois je vais essayer de créer une classe originale qui est très similaire à ToStringStyle.JSON_STYLE
.
La substance de ToStringStyle.JSON_STYLE
est JsonToStringStyle
, qui est défini comme la classe interne de ToStringStyle
, donc copiez-le d'abord dans son intégralité et enregistrez-le dans votre projet.
↓ Comme ça (identique à JsonToStringStyle
sauf pour le nom de la classe)
public class OriginalJsonToStringStyle extends ToStringStyle {
private static final long serialVersionUID = 1L;
private static final String FIELD_NAME_QUOTE = "\"";
/**
* <p>
* Constructor.
* </p>
*
* <p>
* Use the static constant rather than instantiating.
* </p>
*/
OriginalJsonToStringStyle() {
super();
this.setUseClassName(false);
this.setUseIdentityHashCode(false);
:
Ensuite, seule la méthode ↓ réécrit le contenu.
En fait, ToStringStyle
a à l'origine un mécanisme pour gérer correctement les références circulaires, mais le traitement par la méthode de ↓ est un peu compliqué, et ce traitement est passé. À cause de cela, c'était dans une boucle infinie.
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
if (value == null) {
appendNullText(buffer, fieldName);
return;
}
if (value instanceof String || value instanceof Character) {
appendValueAsString(buffer, value.toString());
return;
}
if (value instanceof Number || value instanceof Boolean) {
buffer.append(value);
return;
}
final String valueAsString = value.toString();
if (isJsonObject(valueAsString) || isJsonArray(valueAsString)) {
buffer.append(value);
return;
}
appendDetail(buffer, fieldName, valueAsString);
}
↓ je l'ai changé comme ça
@Override
protected void appendDetail(final StringBuffer buffer, final String fieldName, final Object value) {
if (value == null) {
appendNullText(buffer, fieldName);
return;
}
if (value instanceof String || value instanceof Character) {
appendValueAsString(buffer, value.toString());
return;
}
if(value instanceof java.util.Date)
{
appendValueAsDate(buffer, (java.util.Date)value);
return;
}
buffer.append(value);
}
//Au fait java.util.Date de sortie au format étendu ISO8601
protected void appendValueAsDate(StringBuffer buffer, java.util.Date value) {
DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
buffer.append("\"" + df.format(value) + "\"");
}
Maintenant que nous avons un bon sens des références circulaires, nous allons ajouter la méthode suivante: Dans le cas d'un objet référencé de manière circulaire, la méthode de stringification normale réécrite dans ↑ n'est pas utilisée, et elle est traitée par cette méthode. Le contenu de sortie peut être n'importe quoi, donc le résultat de ʻObjectUtils # identityToString` est le même que la méthode source de substitution. Cependant, des guillemets doubles sont ajoutés avant et après afin de ne pas casser le format JSON.
@Override
protected void appendCyclicObject(final StringBuffer buffer, final String fieldName, final Object value) {
buffer.append(FIELD_NAME_QUOTE);
ObjectUtils.identityToString(buffer, value);
buffer.append(FIELD_NAME_QUOTE);
}
↓ Résultat de sortie (joliment imprimé séparément)
{
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"testDto2": {
"int2": 0,
"integer2": 2,
"string2": "CCC",
"testDto": {
"stringvar": "aaa",
"integervar": 1,
"intvar": 2,
"date": "2020-08-12T00:00:00.000+0900",
"decimal": null,
"testDto2": "jp.example.common.dto.TestDto2@5577140b"
}
}
"date": "2020-08-12T00:00:00.000+0900",
"decimal": null
}
Bonnes vibrations!
La partie référencée circulairement est sortie avec une certaine duplication, mais elle a été remplacée par la notation " jp.example.common.dto.TestDto2@5577140b "
, et elle n'est pas tombée dans une boucle infinie!
La conversion d'un objet de données en JSON est possible avec ToStringBuilder # reflexionToString
.
Cependant, si l'objet est référencé de manière circulaire, comme lors de l'utilisation de JPA, il était nécessaire de créer une classe de traitement spéciale.
En parlant de conversion JSON, il y a GSON et Jackson (ObjectMapper) en plus de ToStringBuilder, mais les objets référencés circulairement sont toujours inutiles.