diff --git a/47.Spring-Boot-Content-Negotiation/pom.xml b/47.Spring-Boot-Content-Negotiation/pom.xml
new file mode 100644
index 0000000..06b47b8
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/pom.xml
@@ -0,0 +1,43 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.1.0.RELEASE
+
+
+ com.example
+ demo
+ 0.0.1-SNAPSHOT
+ demo
+ Demo project for Spring Boot
+
+
+ 1.8
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/DemoApplication.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/DemoApplication.java
new file mode 100644
index 0000000..094d95b
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/DemoApplication.java
@@ -0,0 +1,13 @@
+package com.example.demo;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class DemoApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(DemoApplication.class, args);
+ }
+
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/config/WebConfigurer.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/config/WebConfigurer.java
new file mode 100644
index 0000000..07f8418
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/config/WebConfigurer.java
@@ -0,0 +1,57 @@
+package com.example.demo.config;
+
+import com.example.demo.converter.PropertiesHttpMessageConverter;
+import com.example.demo.handler.PropertiesHandlerMethodReturnValueHandler;
+import com.example.demo.resolver.PropertiesHandlerMethodArgumentResolver;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.http.converter.HttpMessageConverter;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
+
+import javax.annotation.PostConstruct;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author MrBird
+ */
+@Configuration
+public class WebConfigurer implements WebMvcConfigurer {
+
+
+ @Autowired
+ private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
+
+ @PostConstruct
+ public void init() {
+ // 获取当前 RequestMappingHandlerAdapter 所有的 ArgumentResolver对象
+ List argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
+ List newArgumentResolvers = new ArrayList<>(argumentResolvers.size() + 1);
+ // 添加 PropertiesHandlerMethodArgumentResolver 到集合第一个位置
+ newArgumentResolvers.add(0, new PropertiesHandlerMethodArgumentResolver());
+ // 将原 ArgumentResolver 添加到集合中
+ newArgumentResolvers.addAll(argumentResolvers);
+ // 重新设置 ArgumentResolver对象集合
+ requestMappingHandlerAdapter.setArgumentResolvers(newArgumentResolvers);
+
+ // 获取当前 RequestMappingHandlerAdapter 所有的 returnValueHandlers对象
+ List returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
+ List newReturnValueHandlers = new ArrayList<>(returnValueHandlers.size() + 1);
+ // 添加 PropertiesHandlerMethodReturnValueHandler 到集合第一个位置
+ newReturnValueHandlers.add(0, new PropertiesHandlerMethodReturnValueHandler());
+ // 将原 returnValueHandlers 添加到集合中
+ newReturnValueHandlers.addAll(returnValueHandlers);
+ // 重新设置 ReturnValueHandlers对象集合
+ requestMappingHandlerAdapter.setReturnValueHandlers(newReturnValueHandlers);
+ }
+
+ public void extendMessageConverters(List> converters) {
+ // converters.add(new PropertiesHttpMessageConverter());
+ // 指定顺序,这里为第一个
+ converters.add(0, new PropertiesHttpMessageConverter());
+ }
+
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/controller/TestController.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/controller/TestController.java
new file mode 100644
index 0000000..eb815f9
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/controller/TestController.java
@@ -0,0 +1,29 @@
+package com.example.demo.controller;
+
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.ResponseBody;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.Properties;
+
+/**
+ * @author MrBird
+ */
+// @RestController
+@Controller
+public class TestController {
+
+ @GetMapping(value = "test", consumes = "text/properties")
+ @ResponseBody
+ public Properties getUser(@RequestBody Properties properties) {
+ return properties;
+ }
+
+ @GetMapping(value = "test1", consumes = "text/properties")
+ // @ResponseBody
+ public Properties getUser1(Properties properties) {
+ return properties;
+ }
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/converter/PropertiesHttpMessageConverter.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/converter/PropertiesHttpMessageConverter.java
new file mode 100644
index 0000000..07288d7
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/converter/PropertiesHttpMessageConverter.java
@@ -0,0 +1,73 @@
+package com.example.demo.converter;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpInputMessage;
+import org.springframework.http.HttpOutputMessage;
+import org.springframework.http.MediaType;
+import org.springframework.http.converter.AbstractGenericHttpMessageConverter;
+import org.springframework.http.converter.HttpMessageNotReadableException;
+import org.springframework.http.converter.HttpMessageNotWritableException;
+
+import java.io.*;
+import java.lang.reflect.Type;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+/**
+ * @author MrBird
+ */
+public class PropertiesHttpMessageConverter extends AbstractGenericHttpMessageConverter {
+
+ public PropertiesHttpMessageConverter() {
+ super(new MediaType("text", "properties"));
+ }
+
+ @Override
+ protected void writeInternal(Properties properties, Type type, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException {
+ // 获取请求头
+ HttpHeaders headers = outputMessage.getHeaders();
+ // 获取 content-type
+ MediaType contentType = headers.getContentType();
+ // 获取编码
+ Charset charset = null;
+ if (contentType != null) {
+ charset = contentType.getCharset();
+ }
+
+ charset = charset == null ? Charset.forName("UTF-8") : charset;
+
+ // 获取请求体
+ OutputStream body = outputMessage.getBody();
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(body, charset);
+
+ properties.store(outputStreamWriter, "Serialized by PropertiesHttpMessageConverter#writeInternal");
+ }
+
+ @Override
+ protected Properties readInternal(Class extends Properties> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
+ Properties properties = new Properties();
+ // 获取请求头
+ HttpHeaders headers = inputMessage.getHeaders();
+ // 获取 content-type
+ MediaType contentType = headers.getContentType();
+ // 获取编码
+ Charset charset = null;
+ if (contentType != null) {
+ charset = contentType.getCharset();
+ }
+
+ charset = charset == null ? Charset.forName("UTF-8") : charset;
+
+ // 获取请求体
+ InputStream body = inputMessage.getBody();
+ InputStreamReader inputStreamReader = new InputStreamReader(body, charset);
+
+ properties.load(inputStreamReader);
+ return properties;
+ }
+
+ @Override
+ public Properties read(Type type, Class> contextClass, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException {
+ return readInternal(null, inputMessage);
+ }
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/handler/PropertiesHandlerMethodReturnValueHandler.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/handler/PropertiesHandlerMethodReturnValueHandler.java
new file mode 100644
index 0000000..f1f9864
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/handler/PropertiesHandlerMethodReturnValueHandler.java
@@ -0,0 +1,58 @@
+package com.example.demo.handler;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.MediaType;
+import org.springframework.http.server.ServletServerHttpResponse;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import javax.servlet.http.HttpServletResponse;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+/**
+ * @author MrBird
+ */
+public class PropertiesHandlerMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
+
+ @Override
+ public boolean supportsReturnType(MethodParameter returnType) {
+ return Properties.class.equals(returnType.getMethod().getReturnType());
+ }
+
+ @Override
+ public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
+ Properties properties = (Properties) returnValue;
+
+ ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
+
+ HttpServletResponse response = servletWebRequest.getResponse();
+ ServletServerHttpResponse servletServerHttpResponse = new ServletServerHttpResponse(response);
+
+ // 获取请求头
+ HttpHeaders headers = servletServerHttpResponse.getHeaders();
+
+ MediaType contentType = headers.getContentType();
+ // 获取编码
+ Charset charset = null;
+ if (contentType != null) {
+ charset = contentType.getCharset();
+ }
+
+ charset = charset == null ? Charset.forName("UTF-8") : charset;
+
+ // 获取请求体
+ OutputStream body = servletServerHttpResponse.getBody();
+ OutputStreamWriter outputStreamWriter = new OutputStreamWriter(body, charset);
+
+ properties.store(outputStreamWriter, "Serialized by PropertiesHandlerMethodReturnValueHandler#handleReturnValue");
+
+ // 告诉 Spring MVC 请求已经处理完毕
+ mavContainer.setRequestHandled(true);
+ }
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/resolver/PropertiesHandlerMethodArgumentResolver.java b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/resolver/PropertiesHandlerMethodArgumentResolver.java
new file mode 100644
index 0000000..e10d302
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/java/com/example/demo/resolver/PropertiesHandlerMethodArgumentResolver.java
@@ -0,0 +1,44 @@
+package com.example.demo.resolver;
+
+import org.springframework.core.MethodParameter;
+import org.springframework.http.MediaType;
+import org.springframework.web.bind.support.WebDataBinderFactory;
+import org.springframework.web.context.request.NativeWebRequest;
+import org.springframework.web.context.request.ServletWebRequest;
+import org.springframework.web.method.support.HandlerMethodArgumentResolver;
+import org.springframework.web.method.support.ModelAndViewContainer;
+
+import javax.servlet.http.HttpServletRequest;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.nio.charset.Charset;
+import java.util.Properties;
+
+/**
+ * @author MrBird
+ */
+public class PropertiesHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver {
+ @Override
+ public boolean supportsParameter(MethodParameter parameter) {
+ return Properties.class.equals(parameter.getParameterType());
+ }
+
+ @Override
+ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
+ ServletWebRequest servletWebRequest = (ServletWebRequest) webRequest;
+ HttpServletRequest request = servletWebRequest.getRequest();
+ String contentType = request.getHeader("Content-Type");
+
+ MediaType mediaType = MediaType.parseMediaType(contentType);
+ // 获取编码
+ Charset charset = mediaType.getCharset() == null ? Charset.forName("UTF-8") : mediaType.getCharset();
+ // 获取输入流
+ InputStream inputStream = request.getInputStream();
+ InputStreamReader inputStreamReader = new InputStreamReader(inputStream, charset);
+
+ // 输入流转换为 Properties
+ Properties properties = new Properties();
+ properties.load(inputStreamReader);
+ return properties;
+ }
+}
diff --git a/47.Spring-Boot-Content-Negotiation/src/main/resources/application.properties b/47.Spring-Boot-Content-Negotiation/src/main/resources/application.properties
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/main/resources/application.properties
@@ -0,0 +1 @@
+
diff --git a/47.Spring-Boot-Content-Negotiation/src/test/java/com/example/demo/DemoApplicationTests.java b/47.Spring-Boot-Content-Negotiation/src/test/java/com/example/demo/DemoApplicationTests.java
new file mode 100644
index 0000000..480d1ca
--- /dev/null
+++ b/47.Spring-Boot-Content-Negotiation/src/test/java/com/example/demo/DemoApplicationTests.java
@@ -0,0 +1,16 @@
+package com.example.demo;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class DemoApplicationTests {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}