Deprecated 예정인 항목 중
The locations and merge attributes of the @ConfigurationProperties annotation have been deprecated in favor of directly configuring the Environment.
이 부분이 있다.
@ConfigurationProperties를 통해 yml설정을 사용하던 부분이 앞으로 locations 가 제거되고 나면 문제가 될 듯 하다.
[관련 링크]
@PropertySource는 yml 호출을 아직 지원하지 않고 있다.
MessageSourceAutoConfiguration classpath
그동안 몰랐었는데 Spring Boot MessageSourceAutoConfiguration가 변경된 부분이 있다. [관련 링크]
1.3.2, 1.2.9 이전의 경우
Thymeleaf 3을 쓰는 경우
thymeleaf-extras-java8time 2.1.0.RELEASE -> 3.0.0.RELEASE로 변경해서 사용해야 한다.
템플릿 엔진이 변경되면서 주석처리에 문제가 있던 부분도 해결이 되었다.
따라서 thymeleaf-extras-conditionalcomments 라이브러리는 제거해야 한다.
HttpMessageConvertersAutoConfiguration을 사용해서 messageConverters를 구현하지 않는다.
이 경우 RestTemplate의 생성자에 설정된 messageConverter 추가 규칙대로 converter를 호출하여 사용하게 된다.
public RestTemplate() {
this.messageConverters.add(new ByteArrayHttpMessageConverter());
this.messageConverters.add(new StringHttpMessageConverter());
this.messageConverters.add(new ResourceHttpMessageConverter());
this.messageConverters.add(new SourceHttpMessageConverter());
this.messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
this.messageConverters.add(new AtomFeedHttpMessageConverter());
this.messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
this.messageConverters.add(new MappingJackson2XmlHttpMessageConverter());
}
else if (jaxb2Present) {
this.messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
this.messageConverters.add(new MappingJackson2HttpMessageConverter());
}
else if (gsonPresent) {
this.messageConverters.add(new GsonHttpMessageConverter());
}
}
이 경우 jackson xml messageConverter가 json 보다 우선 추가가 되며 restTemplate 호출 시 HttpMessageConverterExtractor의 extractData 메소드에서 추가된 순서대로 converter를 찾아 canRead(Class clazz, MediaType mediaType) 호출이 true인 messageConverter를 사용하게 된다.
restTemplate을 별다른 설정없이 호출해서 사용하게 되면 mediaType은 null로 없다.
대부분의 messageConverter는 생성자에서 자신이 지원하는 mediaType을 선언하여 mediaType에 대한 제한을 두지만 MappingJackson2XmlHttpMessageConverter는 mediaType이 없는 경우도 canRead에서 true로 응답한다. (다르게 말하자면 제한을 두지 않는다.)
이에 대해 spring boot의 HttpMessageConverters 생성 시 reorderXmlConvertersToEnd 메소드를 통해 AbstractXmlHttpMessageConverter이나 MappingJackson2XmlHttpMessageConverter인 경우는 converters 리스트의 가장 맨뒤로 위치시키는 처리를 하여 가장 나중에 체크 되도록 하였다.
따라서 MappingJackson2XmlHttpMessageConverter에 의존성이 있으며 boot 의 autoConfiguration을 통한 생성을 하지 않은 new RestTemplate을 사용할 경우엔 converter에서 MappingJackson2XmlHttpMessageConverter를 제거하거나 아니면 HttpMessageConvertersAutoConfiguration을 통해 생성된 HttpMessageConverters를 constructor를 통해 넘겨주면 된다.
@Autowired
private HttpMessageConverters messageConverters;
new RestTemplate(messageConverters.getConverters());
spring boot를 사용하지 않고 restTemplate을 쓰는 경우 xmlConveter를 맨 뒤로 보내는 HttpMessageConverters의 reorderXmlConvertersToEnd method를 참고하면 된다.
private void reorderXmlConvertersToEnd(List> converters) {
List> xml = new ArrayList>();
for (Iterator> iterator = converters.iterator(); iterator.hasNext();) {
HttpMessageConverter converter = iterator.next();
if ((converter instanceof AbstractXmlHttpMessageConverter) || (converter instanceof MappingJackson2XmlHttpMessageConverter)) {
xml.add(converter);
iterator.remove();
}
}
converters.addAll(xml);
}
restTemplate에서 xmlConverter를 안 쓴다면 restTemplate에서 해당 converter를 제거하는 방법도 있다.
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().removeIf(messageConverter -> messageConverter instanceof MappingJackson2XmlHttpMessageConverter);
MessageConverter를 사용한다면 되도록 boot에서 bean으로 등록해준 messageConverter를 사용하는게 좋다
jquery로 요청하는 경우 POST나 PUT 요청은 기본이 form data 요청이다.
json을 변수로 선언해도 jquery는 form data 형태로 변경하여 요청을 한다.
var parameter = {};
parameter.key1 = "value1";
parameter.key2 = "value2";
$.ajax({
url : "요청url",
type : "POST",
data : parameter
});
만약 json body로 요청을 하고 싶은 경우 다음과 같이 요청한다.
var parameter = {};
parameter.key1 = "value1";
parameter.key2 = "value2";
$.ajax({
url : "요청url",
type : "POST",
contentType: "application/json; charset=utf-8",
data : JSON.stringify(parameter)
});
restTemplate 요청 시
전통적인 방식의 form submit은 Controller에서 @RequestBody를 사용하지 않아야 올바르게 맵핑이 된다.
post form data submit은 다음과 같은 모양으로 요청을 한다.
위 형태로 요청하는 것을 restTemplate으로 구현할 땐 MultiValueMap으로 해당 data를 선언하여 전달한다.
MultiValueMap map = new LinkedMultiValueMap();
map.add("키1", "데이터1");
map.add("키2", "데이터2");
String result = restTemplate.postForObject("호출 주소", map, String.class);
하지만 중간에 map에 데이터를 선언하는 처리가 들어가면 불편한 점이 많다.
만약 @RequestBody로 요청을 받는 경우 단순히 아래와 같이 간단하게 사용이 가능하다.
String result = restTemplate.postForObject("호출 주소", 대상object, String.class);
언제 써야할까?
GET 이나 DELETE 요청은 body에 데이터를 담지 않는다.
따라서 @RequestBody를 사용하지 않는 것이 좋다.
@RequestBody를 사용하지 않는 경우에도 Spring MVC는 기본적으로 GET과 DELETE 요청시 form data처리를 하지 않는다.
간혹 요청 시 필요한 파라메터가 많은 경우 object에 바인딩을 하고 사용하기 위해 @RequestBody로 요청으로 사용하는 경우가 있지만 개인적으로 원래 사용방식을 따르는 형태가 좋지 않을까 싶다.
angular.js나 backbone.js같은 mv? javascript 라이브러리를 사용하는 경우 기본으로 json 포맷의 requestBody를 전송한다.
이런 경우는 @RequestBody로 요청을 처리하는게 좋다.
restTemplate 요청은 아무래도 form data 전송보다 json body형태가 훨씬 간결하고 유지보수하기 편하다.
이렇게 사용하면 javascript에서 사용하기가 편해지고 timezone 설정이 들어간 경우도 moment.js 와 같은 라이브러리를 사용하면 쉽게 사용할 수 있다.
요청 처리
위와 같이 설정하는 경우 응답에 대해서 처리는 되었지만 요청이 온 경우에 대해서도 처리를 해주어야 한다.
요청에 대해 date format string 을 넘겨받는 설정은 WebMvcConfigurerAdapter에 다음과 같이 선언을 해준다.
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
// request date conversion 처리
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}
여기까지 설정을 하면 @ResponseBody로 리턴되는 produces = MediaType.APPLICATION_JSON_VALUE 응답처리에 올바르게 적용된다.
하지만 html, json에 대한 contentNegotiation 처리에도 올바른 응답을 처리하기 위해선 WebMvcConfigurerAdapter에 다음과 같이 선언해야한다.
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
...
@Autowired
private ObjectMapper objectMapper;
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.viewResolver(thymeleafViewResolver); // thymeleaf를 쓰는 경우
registry.enableContentNegotiation(new MappingJackson2JsonView(objectMapper));
}
}
위에서 중요한 핵심은 objectMapper를 spring boot에서 생성한 것을 써야한다는 점이다.
만약 new MappingJackson2JsonView() 로 생성한다면 spring boot 설정으로 선언한 objectMapper가 아닌 spring web의 Jackson2ObjectMapperBuilder로 생성한 objectMapper를 사용하기 때문에 jackson 설정이 동작하지 않는다.