现在工作中有这样一个需求,需要我把 c#的一个项目里的接口都转成 java 实现。这些接口都有共同的请求规则:/{controller}/{action}/{apiVersion}/{userId}/{clientName},举例子比如: http://127.0.0.1:8080/home/index/6.0.0/0/Any. 前面两个参数是 controller 名和方法名,这个我都可以在 @RequestMapping 里写死,但是后面的 apiVersion 、userId 、clientName 这些参数,我不可能在每个 controller 方法的注解上都写上占位符然后用 @PathVariable 获取吧,太 low 了,后期想统一维护都没法维护,而且这些参数要求如果 url 里没有的话要赋默认值。所以问问大佬们,springboot 中如何有没有更好的实现方式呢?无论是拦截器、aop,想来想去都没有太明确的思路。
原 c#项目里是用路由实现的,这样配置后都会自动去找 controller 里的方法,并且给方法入参赋(默认)值:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{apiVersion}/{userId}/{clientName}",
defaults: new
{
controller = "Home",
action = "Index",
apiVersion = "6.0.0",
userId = "0",
clientName = ClientNames.Any
}
});
我现在写出来只能是这样的,但我不能 200 多个接口都这样写吧:
@RestController
@RequestMapping("/home")
public class HomePageController {
@RequestMapping("/index/{apiVersion}/{userId}/{clientName}")
public IndexResponse index(@PathVariable("apiVersion") String apiVersion,
@PathVariable("userId") String userId,
@PathVariable("clientName") String clientName) {
return null;
}
}
真有大佬能提供解决思路,我愿意有偿哈~多谢了
1
taogen 2021-05-26 11:04:09 +08:00
我觉得这样写可以啊,一个 action 不就是一个方法吗。难道 C# 中不用写 200 多个方法(接口)?还是你觉得每个接口都加一个 @RequestMapping 很麻烦?
|
2
agzou 2021-05-26 11:06:02 +08:00
切面加自定义注解实现
|
3
Aliberter OP @taogen 不是,我是觉得每一个方法都要写那三个 @PathVariable 麻烦,本来就是共性的东西,所以想问问怎么实现比较优雅
|
4
actar 2021-05-26 11:10:12 +08:00 5
可以通过转发实现
@RestController public class DefaultController { @RequestMapping("/{controller}/{action}/{apiVersion}/{userId}/{clientName}") public void index(@PathVariable String controller, @PathVariable String action, @PathVariable String apiVersion, @PathVariable String userId, @PathVariable String clientName, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("apiVersion", apiVersion); request.setAttribute("userId", userId); request.setAttribute("clientName", clientName); request.getRequestDispatcher(String.format("/%s/%s", controller, action)).forward(request, response); } @RequestMapping("/home/index") public String hello(HttpServletRequest request) { System.out.println(request.getAttribute("apiVersion")); System.out.println(request.getAttribute("userId")); System.out.println(request.getAttribute("clientName")); return "Hello World!"; } } |
5
TicSmtc 2021-05-26 11:10:52 +08:00
自己解析 url 然后反射?
|
6
TicSmtc 2021-05-26 11:12:28 +08:00
4 楼说的这个法子貌似挺好
|
7
timethinker 2021-05-26 11:13:17 +08:00 2
这里的问题就是把一些原本更适合放在 Header 中的参数放到了 URL 上。
如果楼主确实需要一种解决方案,我个人的做法可能就是写一个 Filter,然后对 Request 进行包装( HttpServletRequestWrapper )并重写 getRequestURI()方法,相当于 rewrite,把这些 URL 路径参数转移到一个 ThreadLocal 上(或者 Header,总之让它存到另一个地方),然后就可以比较干净的来写 Controller 了。 |
8
taogen 2021-05-26 11:25:51 +08:00 1
@Aliberter #3
我觉得想办法去掉 @PathVariable 没必要,可能有很优雅的方法做到。但是会增加代码的复杂度,增加了一层 HTTP URL 到 controller URL 的转换关系。 另外,我觉得可以这样实现,我没有验证,只是提供一个思路。 1 )写一个 filter 。 2 )在 filter 中 forward 请求,把 URL 中的参数放到 request 中。 3 )写一个实体类 BaseParam 封装 apiVersion, userId, 和 clientName,controller 接口中用 @ModelAttribute BaseParam baseParam 接收参数。 |
9
rd554259440 2021-05-26 11:27:55 +08:00
楼上答的不是想要的吧..........用对象接收,把参数都放对象里,不就可以只写一个到处使用了........
|
10
xiangyuecn 2021-05-26 11:28:26 +08:00 2
脱离框架来思考,所有功能都异常简单,非常容易移植
不然去研究 xx 框架有没有 xx 功能,如果以前会,那还好,不会?学习成本比自己手撸一个框架还高 就你这个事,按我的脑回路 优先想到的就是写一个静态的类,每个参数都提供一个静态 get 方法,直接取当前请求上下文中的 url,提取对应变量和默认值,几十行代码半小时搞定,研究 spring 半天不一定搞得定,毕竟人家写的东西代码又多又看不懂😅 |
11
agzou 2021-05-26 11:30:21 +08:00 1
@Aspect
@Component @RestController public class Demo { @RequestMapping() public void test() { //获取参数 ParamsHolder.getApiVersion(); } @Pointcut(value = "@annotation(org.springframework.web.bind.annotation.RequestMapping)") public void pointCut() { } @Around("pointCut()") public Object process(ProceedingJoinPoint pjp) throws Throwable { try { ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest req = servletRequestAttributes.getRequest(); ParamsHolder.setApiVersion(getApiVersion(req)); return pjp.proceed(); } finally { ParamsHolder.removeApiVersion(); } } private String getApiVersion(HttpServletRequest request) { // TODO: 2021/5/26 获取需要的参数 return null; } public static class ParamsHolder { private static final ThreadLocal<String> API_VERSION = new ThreadLocal<>(); public static String getApiVersion() { return API_VERSION.get(); } private static void removeApiVersion() { API_VERSION.remove(); } private static void setApiVersion(String apiVersion) { API_VERSION.set(apiVersion); } } } |
12
huifer 2021-05-26 12:38:22 +08:00
自己写 url 解析写 aop,写拦截器你在自己做类型转换等你写完这个就相当于实现了 spring-mvc 中的路由解析只是没有注解.
|
13
huifer 2021-05-26 13:15:15 +08:00 1
@Aliberter
解决方案为开启一个独立的 servlet,具体在 springboot 中注入方式如下: ``` @Component @ComponentScan("com.example.demo.*") public class Beans { @Autowired private ApplicationContext context; @Bean public ServletRegistrationBean viewRedisServlet() { ServletRegistrationBean<Servlet> servletServletRegistrationBean = new ServletRegistrationBean<>(); CustomerServlet servlet = new CustomerServlet(); servlet.setContext(context); servletServletRegistrationBean.setServlet(servlet); return servletServletRegistrationBean; } } ``` 第二步编写 servlet 具体代码如下: ``` @WebServlet public class CustomerServlet extends HttpServlet { Gson gson = new Gson(); private ApplicationContext context; public ApplicationContext getContext() { return context; } public void setContext(ApplicationContext context) { this.context = context; } @SneakyThrows @Override protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { // 从请求头中获取一个标记,用于确认是需要进行处理的 String c = req.getHeader("is_c"); if (Boolean.valueOf(c)) { String contextPath = req.getContextPath(); String servletPath = req.getServletPath(); // /{controller}/{action}/{apiVersion}/{userId}/{clientName} String requestURI = req.getRequestURI(); String[] split = requestURI.split("/"); String controller = null; String action = null; String apiVersion = null; String userId = null; String clientName = null; if (split.length == 6) { controller = split[1]; action = split[2]; apiVersion = split[3]; userId = split[4]; clientName = split[5]; } // 通过 spring 上下文去搜索 controller + actiron 对应的方法 ApplicationContext tuUse = this.context; if (StringUtils.hasText(controller)) { // 找到实例 Object bean = tuUse.getBean(controller); // 找到执行方法 Method[] methods = bean.getClass().getDeclaredMethods(); Method toCall = null; for (Method method : methods) { boolean equals = method.getName().equals(action); if (equals) { toCall = method; break; } } // 获取方法参数类型, 你需要做转换 Class<?>[] types = toCall.getParameterTypes(); // 转换后进行参数使用调用方法 Object invoke = toCall.invoke(bean, apiVersion, userId, clientName); resp.setContentType("application/json; charset=UTF-8"); resp.getWriter().write(gson.toJson(invoke)); } System.out.println(contextPath); } } } ``` 上述代代码处理流程: 1. 判断是需要进行处理的 2. 将 url 中的 /{controller}/{action}/{apiVersion}/{userId}/{clientName}参数提取 3. 通过成员变量 context 在 spring 中根据名字获取 bean 实例,名字是 controller,通过 spring 中 Component 注解的 value 进行赋值 4. 在 bean 实例种搜索 action 对应的方法,这里要求方法名称和 action 强对应。 5. 将上一步得到的方法提取方法参数,将 url 参数进行类型转换。 6. 反射执行 7. response 返回 其他代码如下: ``` @Data public class IndexResponse { private int code; private Object data; } ``` ``` @Service(value = "home") public class HomePageController { public IndexResponse index( String apiVersion, String userId, String clientName) { IndexResponse response = new IndexResponse(); response.setCode(1); response.setData(apiVersion + "-" + userId + "-" + clientName); return response; } } ``` 测试用例如下: GET http://localhost:8080/home/index/6.0.0/0/Any. HTTP/1.1 200 Content-Type: application/json;charset=UTF-8 Content-Length: 32 Date: Wed, 26 May 2021 05:14:26 GMT Keep-Alive: timeout=60 Connection: keep-alive { "code": 1, "data": "6.0.0-0-Any." } |
14
ikas 2021-05-26 13:26:19 +08:00 4
......................................
了解下? @PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute Pet pet) { // method logic... } |
15
zliea 2021-05-26 13:35:59 +08:00
Map<String, String> pathVariableRequestMap = (Map<String, String>) nativeWebRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
|
16
FreeEx 2021-05-26 14:09:53 +08:00
这个接口设计就很拉跨,安卓看了沉默,iOS 看了流泪,前端出来骂街。
|
17
zliea 2021-05-26 14:25:16 +08:00 1
详细说一下
自定义一个注解,实现 HandlerMethodArgumentResolver 的 Bean,在 @EnableWebMvc 中配置并注入这个 Bean 。 然后在 resolveArgument 使用 Map<String, String> pathVariableRequestMap = (Map<String, String>) nativeWebRequest.getAttribute(HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST); 获取所有 url 参数,进行通用处理,如果接口需要这些数据的,可以返回一个类,里边包含这些通用参数。 不需要参数的,可以在方法上加入这个注解。需要参数的,在方法参数上加入注解与返回类。 |
22
jorneyr 2021-05-26 16:56:49 +08:00
这不就是另一个 ServletDispatcher 吗?用拦截器然后再处理吧。
|
23
bringyou 2021-05-27 18:18:03 +08:00
可以用 #14 的 model attribute
如果路径上的值跟变量名一样,可以省略 @PathVariable 的括号 |
24
MarioLuo 2021-05-28 07:45:43 +08:00 via Android 1
|