将@Service直接暴露为http接口

将@Service直接暴露为http接口

本人目前在做公司的基础服务开发,一般是将具有基础能力的jar包提供给公司的其他部门做开发使用,
我们提供的基础能力包只会有service层,不会包含接入层(http controller),但是如果只是service包的话,
我们团队的测试同学就不太方便测试,所以写了个小工具,可以将service直接暴露为http接口。
仅仅是作为测试使用,并不支持用于生产环境哦。

思路

  1. 扫描service接口及方法
  2. 将service方法注册到spring mvc requestMapping中,交由spring mvc管理
  3. 解决参数映射、结果映射的问题

实现

按照上面的思路,开始一个个解决

扫描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
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

项目地址

https://github.com/lanicc/mvc-controller-less

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
如果Dubbo接口没有暴露给外部,那么只能在同一个进程内部使用Dubbo内部调用的方式来调用该接口。下面是一个示例代码: 1. 首先在服务提供者端,定义一个接口: ``` public interface InternalService { void doSomething(); } ``` 2. 在服务提供者端,实现该接口: ``` public class InternalServiceImpl implements InternalService { @Override public void doSomething() { // do something } } ``` 3. 在服务提供者端,将该实现类注册成Dubbo服务: ``` @Service public class DemoServiceImpl implements DemoService { @Autowired private InternalServiceImpl internalService; @Override public void demo() { // 调用内部接口 internalService.doSomething(); } } ``` 4. 在服务消费者端,使用Dubbo内部调用的方式来调用该接口: ``` @Service public class DemoConsumerService { @Reference private InternalService internalService; public void demo() { // 调用内部接口 internalService.doSomething(); } } ``` 在上述示例代码中,服务提供者将InternalServiceImpl实现类注册成Dubbo服务,并在DemoServiceImpl实现类中使用@Autowired注解注入该实现类的实例,然后在demo()方法中调用InternalService的doSomething()方法。服务消费者则使用@Reference注解来注入InternalService实例,并在DemoConsumerService类中调用doSomething()方法。由于服务提供者和服务消费者在同一个进程中,因此Dubbo内部调用可以直接调用接口的实现类。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

lanicc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值