将@Service直接暴露为http接口
- 将@Service直接暴露为http接口
- 思路
- 实现
- 扫描service接口及方法
- 将service方法注册到spring mvc requestMapping中,交由spring mvc管理
- 解决参数映射、结果映射的问题
- 代码
- RequestMappingHandlerAdapterConfig
- ControllessRequestMappingRegister
- ControllessMethodArgumentReturnValueHandler
- EnableControlless
- 测试
- 项目地址
将@Service直接暴露为http接口
本人目前在做公司的基础服务开发,一般是将具有基础能力的jar包提供给公司的其他部门做开发使用,
我们提供的基础能力包只会有service层,不会包含接入层(http controller),但是如果只是service包的话,
我们团队的测试同学就不太方便测试,所以写了个小工具,可以将service直接暴露为http接口。
仅仅是作为测试使用,并不支持用于生产环境哦。
思路
- 扫描service接口及方法
- 将service方法注册到spring mvc requestMapping中,交由spring mvc管理
- 解决参数映射、结果映射的问题
实现
按照上面的思路,开始一个个解决
扫描service接口及方法
这个很简单,通过bean工厂获取service bean,然后再通过反射获取service的方法即可,代码如下
//获取service bean
Map<String, Object> serviceBeans = applicationContext.getBeansWithAnnotation(Service.class);
反射获取service方法
Class<?> beanClass = bean.getClass();
String simpleName = beanClass.getSimpleName();
Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(beanClass);
for (Class<?> itf : interfaces) {
Method[] methods = itf.getDeclaredMethods();
for (Method method : methods) {
// addMapping(bean, simpleName, method);
}
}
将service方法注册到spring mvc requestMapping中,交由spring mvc管理
这个没有思路,只能调试源码,看spring mvc是怎么用的,然后再想
跟随源码
DispatcherServlet#doService->DispatcherServlet#doDispatch->DispatcherServlet#getHandler,可以看到一个重要的类HandlerMapping,官方文档和java doc都有介绍
HandlerMapping的解释就是:用来将一个request映射到对应的处理器,有两个主要的实现,其中一个是RequestMappingHandlerMapping(用来支持@RequestMapping注解声明的方法)
看到这里好像是找到了下一个比较重要的类RequestMappingHandlerMapping,看一下它的文档
根据@RequestMapping和@Controller创建RequestMappingInfo的实例
从spring的测试用例中可以看到HandlerMapping是怎样使用的
这段代码,就是先注册接口, 然后再模拟了处理请求,所以这个接口就是我们要用的
我们只要把需要注册的接口通过RequestMappingHandlerMapping注册就可以了,代码如下
@Configuration
@Order
public class RequestMappingHandlerAdapterConfig {
@Autowired
public void initHandlerMapping(RequestMappingHandlerMapping mapping, ApplicationContext applicationContext) {
Map<String, Object> serviceBeans = applicationContext.getBeansWithAnnotation(Service.class);
Collection<Object> objects = serviceBeans.values();
ControllessRequestMappingRegister register = new ControllessRequestMappingRegister(mapping);
register.register(objects);
}
}
先通过applicationContext获取到service bean,然后定义了一个注册RequestMapping的注册器,在注册器中注册了需要暴露的接口
public class ControllessRequestMappingRegister {
private final RequestMappingHandlerMapping mapping;
public ControllessRequestMappingRegister(RequestMappingHandlerMapping mapping) {
this.mapping = mapping;
}
public void register(Collection<?> mappingBeans) {
mappingBeans.forEach(this::addMapping);
}
private void addMapping(Object bean) {
Class<?> beanClass = bean.getClass();
String simpleName = beanClass.getSimpleName();
Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(beanClass);
for (Class<?> itf : interfaces) {
Method[] methods = itf.getDeclaredMethods();
for (Method method : methods) {
addMapping(bean, simpleName, method);
}
}
}
private void addMapping(Object bean, String className, Method method) {
String name = method.getName();
String path = "/" + className + "/" + name;
RequestMappingInfo info =
RequestMappingInfo
.paths(path)
.methods(RequestMethod.GET, RequestMethod.POST)
.build();
mapping.registerMapping(info, bean, method);
System.err.printf("ControllessRequestMappingRegister register request mapping, bean class : %s, method: %s, path: %s, http method: %s\n", className, name, path, "GET,POST");
}
}
通过主动注册RequestMapping实现
@Configuration
@Order
public class RequestMappingHandlerAdapterConfig {
@Autowired
public void initHandlerMapping(RequestMappingHandlerMapping mapping, ApplicationContext applicationContext) {
Map<String, Object> serviceBeans = applicationContext.getBeansWithAnnotation(Service.class);
Collection<Object> objects = serviceBeans.values();
ControllessRequestMappingRegister register = new ControllessRequestMappingRegister(mapping);
register.register(objects);
}
@Autowired
public void initMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter) {
ControllessMethodArgumentReturnValueHandler resolverAndReturnValueHandler = new ControllessMethodArgumentReturnValueHandler(requestMappingHandlerAdapter);
requestMappingHandlerAdapter.setReturnValueHandlers(Collections.singletonList(resolverAndReturnValueHandler));
requestMappingHandlerAdapter.setArgumentResolvers(Collections.singletonList(resolverAndReturnValueHandler));
}
}
先测试一下
定义一个service
@Service
public class TestService implements ITestService {
@Override
public String call(String name) {
return "call me baby: " + name;
}
}
启动一下
有我们的这条日志
使用http访问一下试试
控制台信息
说明接口调用成功,但是返回结果的时候失败了
解决参数映射、结果映射的问题
跟踪源码,发现比较重要的HandlerMethodReturnValueHandler
看一下selectHandler的逻辑
会遍历所有的returnValueHandlers,通过handler.supportsReturnType(returnType)判断handler是否支持处理该类型的返回值,如果支持就会返回这个handler,用这个handler处理返回结果,所以应该是handler匹配错误引起的调用错误。再跟一下源码,看到returnValueHandlers的内容如下
看这些对象的类名,大概就知道了他们支持的处理类型,
例如:
ModelAndViewMethodReturnValueHandler支持的返回值类型就是
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return ModelAndView.class.isAssignableFrom(returnType.getParameterType());
}
RequestResponseBodyMethodProcessor支持的返回值类型就是
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
returnType.hasMethodAnnotation(ResponseBody.class));
}
所以RequestResponseBodyMethodProcessor应该就是我们期望处理返回值的handler
继续跟踪源码
可以看到,此时匹配到的是ViewNameMethodReturnValueHandler,并不是我们期望的
我们期望匹配到的是RequestResponseBodyMethodProcessor,但是RequestResponseBodyMethodProcessor又只支持@ResponseBody声明的方法,我们的方法没有这个声明,所以我们只能自己实现一个RequestResponseBodyMethodProcessor,并且注册到returnValueHandlers中
返回值处理的思路就到这里
其实参数也是需要处理的,思路类似,直接贴代码喽
代码
RequestMappingHandlerAdapterConfig
@Configuration
@Order
public class RequestMappingHandlerAdapterConfig {
@Autowired
public void initHandlerMapping(RequestMappingHandlerMapping mapping, ApplicationContext applicationContext) {
Map<String, Object> serviceBeans = applicationContext.getBeansWithAnnotation(Service.class);
Collection<Object> objects = serviceBeans.values();
ControllessRequestMappingRegister register = new ControllessRequestMappingRegister(mapping);
register.register(objects);
}
@Autowired
public void initMappingHandlerAdapter(RequestMappingHandlerAdapter requestMappingHandlerAdapter, ApplicationContext applicationContext) {
ControllessMethodArgumentReturnValueHandler resolverAndReturnValueHandler = new ControllessMethodArgumentReturnValueHandler(requestMappingHandlerAdapter, applicationContext);
requestMappingHandlerAdapter.setReturnValueHandlers(Collections.singletonList(resolverAndReturnValueHandler));
requestMappingHandlerAdapter.setArgumentResolvers(Collections.singletonList(resolverAndReturnValueHandler));
}
}
ControllessRequestMappingRegister
public class ControllessRequestMappingRegister {
private final RequestMappingHandlerMapping mapping;
public ControllessRequestMappingRegister(RequestMappingHandlerMapping mapping) {
this.mapping = mapping;
}
public void register(Collection<?> mappingBeans) {
mappingBeans.forEach(this::addMapping);
}
private void addMapping(Object bean) {
Class<?> beanClass = bean.getClass();
String simpleName = beanClass.getSimpleName();
Class<?>[] interfaces = ClassUtils.getAllInterfacesForClass(beanClass);
for (Class<?> itf : interfaces) {
Method[] methods = itf.getDeclaredMethods();
for (Method method : methods) {
addMapping(bean, simpleName, method);
}
}
}
private void addMapping(Object bean, String className, Method method) {
String name = method.getName();
String path = "/" + className + "/" + name;
RequestMappingInfo info =
RequestMappingInfo
.paths(path)
.methods(RequestMethod.GET, RequestMethod.POST)
.build();
mapping.registerMapping(info, bean, method);
System.err.printf("ControllessRequestMappingRegister register request mapping, bean class : %s, method: %s, path: %s, http method: %s\n", className, name, path, "GET,POST");
}
}
ControllessMethodArgumentReturnValueHandler
/**
* Created on 2021/5/26.
*
* @author lan
* @since 1.0
*/
public class ControllessMethodArgumentReturnValueHandler implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
private final List<HandlerMethodReturnValueHandler> returnValueHandlers;
private final List<HandlerMethodArgumentResolver> argumentResolvers;
private final ServiceRequestResponseBodyMethodProcessor serviceRequestResponseBodyMethodProcessor;
private final ApplicationContext applicationContext;
public ControllessMethodArgumentReturnValueHandler(RequestMappingHandlerAdapter requestMappingHandlerAdapter, ApplicationContext applicationContext) {
this.returnValueHandlers = requestMappingHandlerAdapter.getReturnValueHandlers();
this.argumentResolvers = requestMappingHandlerAdapter.getArgumentResolvers();
this.serviceRequestResponseBodyMethodProcessor = new ServiceRequestResponseBodyMethodProcessor(requestMappingHandlerAdapter.getMessageConverters());
this.applicationContext = applicationContext;
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
return isService(parameter.getExecutable()) || argumentResolvers.stream().anyMatch(handlerMethodArgumentResolver -> handlerMethodArgumentResolver.supportsParameter(parameter));
}
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
if (isService(parameter.getExecutable())) {
if (BeanUtils.isSimpleProperty(parameter.getNestedParameterType())) {
return argumentResolvers.stream()
.filter(handlerMethodArgumentResolver -> handlerMethodArgumentResolver instanceof RequestParamMethodArgumentResolver)
.findFirst()
.map(handlerMethodArgumentResolver -> resolveArgument(handlerMethodArgumentResolver, parameter, mavContainer, webRequest, binderFactory))
.orElse(null);
}
return resolveArgument(serviceRequestResponseBodyMethodProcessor, parameter, mavContainer, webRequest, binderFactory);
}
return argumentResolvers.stream()
.filter(handlerMethodArgumentResolver -> handlerMethodArgumentResolver.supportsParameter(parameter))
.findFirst()
.map(handlerMethodArgumentResolver -> resolveArgument(handlerMethodArgumentResolver, parameter, mavContainer, webRequest, binderFactory))
.orElse(null);
}
@Override
public boolean supportsReturnType(MethodParameter returnType) {
return isService(returnType.getExecutable()) || returnValueHandlers.stream().anyMatch(returnValueHandler -> returnValueHandler.supportsReturnType(returnType));
}
@Override
public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (isService(returnType.getExecutable())) {
handleReturnValue(serviceRequestResponseBodyMethodProcessor, returnValue, returnType, mavContainer, webRequest);
} else {
returnValueHandlers.stream()
.filter(returnValueHandler -> returnValueHandler.supportsReturnType(returnType))
.findFirst()
.ifPresent(returnValueHandler -> handleReturnValue(returnValueHandler, returnValue, returnType, mavContainer, webRequest));
}
}
private boolean isService(Executable executable) {
Map<String, Object> beans = applicationContext.getBeansWithAnnotation(Service.class);
Class<?> declaringClass = executable.getDeclaringClass();
Map<String, ?> beansOfType = applicationContext.getBeansOfType(declaringClass);
HashSet<?> objects = new HashSet<>(beansOfType.values());
return beans.values().stream()
.anyMatch(objects::contains);
}
private Object resolveArgument(HandlerMethodArgumentResolver handlerMethodArgumentResolver, MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
try {
return handlerMethodArgumentResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private void handleReturnValue(HandlerMethodReturnValueHandler returnValueHandler, Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) {
try {
returnValueHandler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static class ServiceRequestResponseBodyMethodProcessor extends RequestResponseBodyMethodProcessor {
public ServiceRequestResponseBodyMethodProcessor(List<HttpMessageConverter<?>> converters) {
super(converters);
}
@Override
protected boolean checkRequired(MethodParameter parameter) {
return true;
}
}
}
EnableControlless
做成了一个Springboot starter形式的jar包,所以加了个注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(RequestMappingHandlerAdapterConfig.class)
public @interface EnableControlless {
}
测试
@SpringBootApplication
@EnableControlless
public class ControllessTestApplication {
public static void main(String[] args) {
SpringApplication.run(ControllessTestApplication.class, args);
}
}
public interface ITestService {
String call(String name);
Map<String, Object> haha(Map<String, Object> map);
Map<String, Object> haha2(Map<String, Object> map, String name);
}
/**
* Created on 2021/5/26.
*
* @author lan
* @since 1.0
*/
@Service
public class TestService implements ITestService {
@Override
public String call(String name) {
System.out.println("name: " + name);
return "call me baby: " + name;
}
@Override
public Map<String, Object> haha(Map<String, Object> map) {
System.out.println(map);
map.put("time", LocalDateTime.now().toString());
return map;
}
@Override
public Map<String, Object> haha2(Map<String, Object> map, String name) {
System.out.println("name: " + name + ", map: " + map);
map.put("name", name);
return map;
}
}
http 请求
GET http://localhost:8080/TestService/call?name=21
###
GET http://localhost:8080/TestService/haha
Content-Type: application/json
{
"author": "lanicc"
}
###
GET http://localhost:8080/TestService/haha2?name=lanicc
Content-Type: application/json
{
"author": "lanicc",
"call": 1111
}