Spring MVC ๋ฐ Spring ๋ถํธ๊ฐ ์๋ ํ๋ก์ ํธ๊ฐ ์์ต๋๋ค.
๋ด json ์๋น์ค๋ฅผ ๋ฌธ์ํํ๊ธฐ ์ํด Swagger springfox๋ฅผ ์ฌ์ฉํฉ๋๋ค. java.sql.Time ๋ฐ java.util.Date ์์ฑ์ด ์๋ ํด๋์ค๊ฐ ์์ต๋๋ค.
API๋ฅผ ํธ์ถํ๋ฉด ํด๋์ค๊ฐ ๋ค์๊ณผ ๊ฐ์ด ๋ํ๋ฉ๋๋ค.
"TranslationLabel":{
"properties":{
"change_date":{
"type":"string",
"format":"date-time"
},
"change_time":{
"$ref":"#/definitions/Time"
}
},
"description":"Traduction de libellรฉ"
}
Swagger UI์์ ๋ชจ๋ธ ์คํค๋ง๋ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
"change_date": "2015-10-29",
"change_time": {
"date": 0,
"day": 0,
"hours": 0,
"minutes": 0,
"month": 0,
"seconds": 0,
"time": 0,
"timezoneOffset": 0,
"year": 0
}
}
"15:31"๊ณผ ๊ฐ์ด "change_time"์ ํ์ํ๊ณ ์ถ์ต๋๋ค.
์ด๋ ๊ฒ ํ๋ ค๋ฉด ModelConverter๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค๊ณ ์๊ฐํฉ๋๋ค. ๊ทธ๋์ ์ด ์์ ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฌ์ฉ์ ์ง์ ๋ณํ๊ธฐ ํด๋์ค๋ฅผ ๋ง๋ญ๋๋ค. https://github.com/swagger-api/swagger-core/blob/master/modules/swagger-core/src/test/java/io/swagger/model/ ์ฌ์ ์/CustomConverterTest.java
๊ทธ๋ฌ๋ ๋๋ ๊ทธ๊ฒ์ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ์ฐพ์ง ๋ชปํ์ต๋๋ค.
์ฌ๊ธฐ ๋ด ์์ ์ด ์์ต๋๋ค :
class CustomConverter implements ModelConverter {
<strong i="18">@Override</strong>
public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations,
Iterator<ModelConverter> chain) {
final JavaType jType = Json.mapper().constructType(type);
if (jType != null) {
final Class<?> cls = jType.getRawClass();
if (cls.equals(Time.class)) {
HashMap<PropertyBuilder.PropertyId, Object> map =
new HashMap<>();
map.put(PropertyBuilder.PropertyId.FORMAT,"HH:mm");
map.put(PropertyBuilder.PropertyId.TYPE,"time");
map.put(PropertyBuilder.PropertyId.EXAMPLE,"12:30");
return PropertyBuilder.build("Time", "hh:mm", map);
} else {
return chain.next().resolveProperty(type, context, annotations, chain);
}
} else {
return null;
}
}
<strong i="19">@Override</strong>
public Model resolve(Type type, ModelConverterContext context, Iterator<ModelConverter> chain) {
return chain.next().resolve(type, context, chain);
}
}
์ด์ ModelConverters์ ๋ณํ๊ธฐ๋ฅผ ์ถ๊ฐํด์ผ ํฉ๋๋ค.
ModelConverters converters = new ModelConverters();
converters.addConverter(new CustomConverter());
์ด ์ฝ๋๋ฅผ ์ด๋์ ๋ฃ์ด์ผ ํ๋์ง์ ๋ํ ๋ฌธ์๋ฅผ ์ฐพ์ง ๋ชปํ์ต๋๋ค.
์ด๊ฑธ ์ด๋์ ๋ฃ์ด์ผ ํ์ด?
Springfox ์๋ ์ด๋ฅผ ๋ฌ์ฑํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๋๋ฑํ ๋ณํ๊ธฐ๋ฅผ ์ ์ํ๋ ค๋ฉด Docket.directmodelSubstitute(Time.class, String.class)
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์์ ๋ฑ์ ์ง์ํ์ง ์์ต๋๋ค.
๋ต๋ณํด ์ฃผ์
์ ๊ฐ์ฌํฉ๋๋ค.
๊ทํ์ ์๋ฃจ์
์ ์๋ํ์ง๋ง "change_time":"hh:mm" ๋์ "change_time":"string"์ ํ์ํฉ๋๋ค.
String ์ ํ์์ ์ง์ ํ ์ ์์ต๋๊น?
์๋ ํ์ธ์ ์ฌ๋ฌ๋ถ,
ModelConverters์ ๊ด๋ จ๋ ๋ฌธ์ ๊ฐ ์์ต๋๋ค. ๋ด Swagger.json์๋ ์๋์ ๊ฐ์ด ํ์ฅ๋ ํ์์ ์ผ๋ฐ ํด๋์ค๊ฐ ํฌํจ๋์ด ์์ต๋๋ค.
"JsonNode": {
"์ ํ": "๊ฐ์ฒด",
"์์ฑ": {
"floatingPointNumber": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๊ฐ ๋
ธ๋": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"bigInteger": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"ํ
์คํธ": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋ถ์ธ": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"์ปจํ
์ด๋ ๋
ธ๋": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"missingNode": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋ฌผ์ฒด": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"ํฌ์กฐ": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"์ซ์": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"integralNumber": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"์งง์": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"int": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๊ธด": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋๋ธ": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"bigDecimal": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋จ๋ค": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋
ธ๋ ์ ํ": {
"์ ํ": "๋ฌธ์์ด",
"์ด๊ฑฐ": [
"์ ๋ ฌ",
"๋ฐ์ด๋๋ฆฌ",
"๋ถ์ธ",
"์์ด๋ฒ๋ฆฐ",
"์๋",
"์ซ์",
"๋ฌผ์ฒด",
"ํฌ์กฐ",
"๋"
]
},
"๋ฐ์ด๋๋ฆฌ": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"๋ฐฐ์ด": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
},
"์๋": {
"์ ํ": "๋ถ์ธ",
"๊ธฐ๋ณธ๊ฐ": ๊ฑฐ์ง
}
}
}
์ด์ ์ด๊ฒ์ ํ์คํ ๋ด๊ฐ ์ํ์ง ์๋ ๊ฒ์ ๋๋ค. ์ฌ์ค ๋ด๊ฐ ์ํ๋ ๊ฒ์ ์ด ํน์ ํด๋์ค(com.fasterxml.jackson.databind.JsonNode)(๋ฐ ๋ด๊ฐ ์ ํํ ๋ค๋ฅธ ํด๋์ค)๊ฐ ํ์ฅ๋๋ ๊ฒ์ ๋ฐฉ์งํ๋ ๊ฒ์ ๋๋ค. ๊ทธ๋์ ์ด๋ฅผ ์ํํ๊ธฐ ์ํด ์๋์ ํ์๋ io.swagger.converter.ModelConverters ํด๋์ค์์ ์ ๊ณต๋๋ ๋ด์ฅ ํจ์๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
๊ณต๊ฐ ๋ฌดํจ addClassToSkip(๋ฌธ์์ด cls) {
LOGGER.warn("ํด๋์ค ๊ฑด๋๋ฐ๊ธฐ " + cls);
this.skippedClasses.add(cls);
}
์ด์ ์ด ์์ ์ ์ํํ ํ ModelConverters ํด๋์ค๋ฅผ ๋ค๋ฅธ ์ฌ๋๊ณผ ํจ๊ป ๊ณต๊ธ์ ๋ชฉ๋ก์ ์ถ๊ฐํ๋ ค๊ณ ์๋ํ์ง๋ง ๊ฒฐ๊ณผ๊ฐ ์์๊ธฐ ๋๋ฌธ์ ์ค์ ๋ก ModelConverters ํด๋์ค๋ฅผ ์ฃผ์ ํ ์์น๋ฅผ ์์๋ผ ์ ์๋ ๊ฒ ๊ฐ์ต๋๋ค. ๋๊ตฐ๊ฐ ์ด ๋ฌธ์ ๋ฅผ ๋์์ฃผ์ธ์. ๋๋ ๊ฑฐ์ 3 ์ผ ๋์ ๋ต์ ์ฐพ๊ณ ์์์ต๋๋ค.
์ฌ์ฉ์ ์ ์ ๋ณํ๊ธฐ๋ฅผ ์ฌ์ฉํ์ญ์์ค. ์ด๊ฒ์ ๋ด ๋ถ์์ ๋ฐ๋ฅธ ๋ฒ๊ทธ์ ๋๋ค. ModelResolver๋ฅผ ์์ ์ ์ฝ๋์ ๋ณต์ฌํ๊ณ ์ด์ ๊ฐ์ ์์ ์ ์ํํ ์ ์์ต๋๋ค.
ModelConverters.getInstance().addConverter(new ModelResolver(mapper));
ModelResolver์์ ๋ค์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์์ ํฉ๋๋ค.
private Model resolve(JavaType type, ModelConverterContext context, Iterator<ModelConverter> next) {
if (type.isEnumType() || PrimitiveType.fromType(type) != null) {
// We don't build models for primitive types
return null;
}
if (type instanceof SimpleType && ((SimpleType) type).getRawClass().getName().startsWith("your classname")) {
return yourConverter.resolve(type, context, next);
}
ํดํน์ ๋๋ค. ๋ฌธ์ ๋ https://github.com/swagger-api/swagger-core/issues/2260 ๊ณผ ๋์ผํฉ๋๋ค.
๊ฐ์ฅ ์ ์ฉํ ๋๊ธ
Springfox ์๋ ์ด๋ฅผ ๋ฌ์ฑํ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค. ๋๋ฑํ ๋ณํ๊ธฐ๋ฅผ ์ ์ํ๋ ค๋ฉด
Docket.directmodelSubstitute(Time.class, String.class)
๋ฅผ ์ฌ์ฉํด์ผ ํฉ๋๋ค. ๊ทธ๋ฌ๋ ์์ ๋ฑ์ ์ง์ํ์ง ์์ต๋๋ค.