需求
我们都知道springboot中使用 @RestController 标签可以方便的开发一个restful的微服务,在当今格技术栈混搭的情况下,有这么个需求:
@Service 所代表的核心逻辑服务可以打成jar包供本地调用,也可以发布成一个微服务以web的形式提供restful接口,后者的话,用 @RestController 包一层即可,但是懒惰的程序员可不想做这种无聊的事情,只是为了发布web服务,而写一堆繁琐的service包装代码,有没有一种方法,让 @Servcie 自动包装成web服务呢?
思路
先看看@RestController是怎么实现的:
RequestMappingHandlerMapping
将带有@ReqestMapping和@Controller注解的类添加到url映射中;当这些url被请求时, 从HandlerMethodArgumentResolverComposite
里找到合适的HandlerMethodArgumentResolver
进行参数的解析,使其对应到映射的Controller方法参数上;Controller里的方法执行结束后,从HandlerMethodReturnValueHandlerComposite
里找到合适的HandlerMethodReturnValueHandler
来进行返回值的包装。
所以针对@Servcie,也可以按照上面的过程走一遍即可。
方法
1 定义Service处理映射
ServiceHandlerMapping.java
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.RequestMappingInfoHandlerMapping;
import java.lang.reflect.Method;
public class ServiceHandlerMapping extends RequestMappingInfoHandlerMapping {
private RequestMappingInfo.BuilderConfiguration config = new RequestMappingInfo.BuilderConfiguration();
/**
* 支持@Service注解的类
* @param beanType
* @return
*/
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Service.class));
}
/**
* 将Service类里面的方法按照 /SimpleClassName/methodName 的方式去注册RequestMapping
* @param method
* @param handlerType
* @return
*/
@Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo.Builder builder = RequestMappingInfo
.paths(String.join("/", handlerType.getSimpleName(), method.getName()))
.consumes(MediaType.APPLICATION_JSON_VALUE)
.methods(RequestMethod.POST);
return builder.options(this.config).build();
}
/**
* 保证自定义的映射优先处理
* @return
*/
@Override
public int getOrder() {
return 0;
}
}
2 定义参数解析器
ServiceMethodArgumentResolver.java
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.GenericTypeResolver;
import org.springframework.core.MethodParameter;
import org.springframework.stereotype.Service;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
import java.util.Map;
import static org.springframework.web.context.request.RequestAttributes.SCOPE_REQUEST;
public class ServiceMethodArgumentResolver implements HandlerMethodArgumentResolver {
private static final String SERVICE_ARGS = "service_args";
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getMethod().getDeclaringClass().getDeclaredAnnotation(Service.class) != null;
}
/**
* 参数解析
* 第一次将请求内容解析成map, 并保存到request中,下个参数解析直接从map里面取
*/
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws IOException {
Map<String, Object> serviceArgs = (Map<String, Object>) webRequest.getAttribute(SERVICE_ARGS, SCOPE_REQUEST);
if (serviceArgs == null) {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
serviceArgs = objectMapper.readValue(servletRequest.getInputStream(), ModelMap.class);
webRequest.setAttribute(SERVICE_ARGS, serviceArgs, SCOPE_REQUEST);
}
Object parameterValueRaw = serviceArgs.get(parameter.getParameterName());
if (parameterValueRaw == null) {
return null;
}
String parameterValueStr = objectMapper.writeValueAsString(parameterValueRaw);
JavaType javaType = objectMapper.getTypeFactory().constructType(GenericTypeResolver.resolveType(parameter.getGenericParameterType(), parameter.getContainingClass()));
return objectMapper.readValue(parameterValueStr, javaType);
}
}
3 定义结果包装器
ServiceMethodReturnValueHandler.java
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.core.MethodParameter;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer;
import javax.servlet.http.HttpServletResponse;
public class ServiceMethodReturnValueHandler implements HandlerMethodReturnValueHandler {
private ObjectMapper objectMapper = new ObjectMapper();
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return returnType.getMethod().getDeclaringClass().getDeclaredAnnotation(Service.class) != null;
}
/**
* 将结果封装成json格式
*/
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
response.addHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_UTF8_VALUE);
objectMapper.writeValue(response.getWriter(), returnValue);
mavContainer.setRequestHandled(true);
}
}
4 配置组件
ServiceRequestMappingConfiguration.java
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
import java.util.ArrayList;
import java.util.List;
@Configuration
public class ServiceRequestMappingConfiguration {
@Autowired
private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
@Bean
public ServiceHandlerMapping serviceHandlerMapping() {
return new ServiceHandlerMapping();
}
@Bean
public ServiceMethodArgumentResolver serviceMethodArgumentResolver() {
ServiceMethodArgumentResolver serviceMethodArgumentResolver = new ServiceMethodArgumentResolver();
// 将此参数解析器的优先级提到最高
List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>();
argumentResolvers.add(serviceMethodArgumentResolver);
argumentResolvers.addAll(requestMappingHandlerAdapter.getArgumentResolvers());
requestMappingHandlerAdapter.setArgumentResolvers(argumentResolvers);
return serviceMethodArgumentResolver;
}
@Bean
public ServiceMethodReturnValueHandler serviceMethodReturnValueHandler() {
ServiceMethodReturnValueHandler serviceMethodReturnValueHandler = new ServiceMethodReturnValueHandler();
// 将此结果包装器的优先级提到最高
List<HandlerMethodReturnValueHandler> returnValueHandlers = new ArrayList<>();
returnValueHandlers.add(serviceMethodReturnValueHandler);
returnValueHandlers.addAll(requestMappingHandlerAdapter.getReturnValueHandlers());
requestMappingHandlerAdapter.setReturnValueHandlers(returnValueHandlers);
return serviceMethodReturnValueHandler;
}
}
5 使用
假设有以下Servcie代码:
UserService.java
import org.springframework.stereotype.Service;
@Service
public class UserService {
private static int i = 0;
@Override
public User getUserById(String userName) {
User user = new User();
user.setId(++i);
user.setName(userName);
return user;
}
}
启动服务后,向服务发起一个请求:
url:http://localhost/UseService/getUserById
body:{"userName":"我的名字"}
则返回以下内容:
{"userId":1, "userName":"我的名字"}
其他问题
参数校验
可以参考前面的一篇博文 如何在SpringBoot中做参数校验