在Spring Boot项目开发过程中,对于接口API发布URL访问路径,一般都是在类上标识@RestController或者@Controller注解,然后在方法上标识@RequestMapping相关注解,比如:@PostMapping、@GetMapping注解,通过设置注解属性,发布URL。在某些场景下,我觉得这样发布URL太麻烦了,不适用,有没有什么其他方法自由发布定义的接口呢?答案是肯定的。
按照上面的描述,我们先看一下一般常用的开发代码:
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.Map; @RestController public class TestController { @RequestMapping("/test/url") public String test(@RequestParam String name, @RequestBody Mapmap) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("hello, ").append(name).append(", receive param:"); for (Map.Entry entry : map.entrySet()) { stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue()); } return stringBuilder.toString(); } }
测试效果:
参考步骤二的测试截图效果,我们自定义发布一个URL。
org.springframework.boot spring-boot-starter-web org.projectlombok lombok
去掉@RestController和@RequestMapping相关注解,示例代码如下:
import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.ResponseBody; import java.util.Map; // @RestController @Component public class TestController { //@RequestMapping("/test/url") @ResponseBody // 注意:此注解需要添加,不能少 public String test(/*@RequestParam*/ String name,/* @RequestBody*/ Mapmap) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收 StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("hello, ").append(name).append(", receive param:"); for (Map.Entry entry : map.entrySet()) { stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue()); } return stringBuilder.toString(); } }
参考代码如下:
import lombok.SneakyThrows; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.util.Map; import java.util.concurrent.atomic.AtomicBoolean; /** * 注册一个web容器初始化以后的事件监听,注册自定义URL */ @Component public class CustomRegisterUrl implements ApplicationListener{ /** * 标识事件监听器是否已经注册,避免重复注册 */ private volatile AtomicBoolean flag = new AtomicBoolean(false); /** * 需要发布的地址 */ public static final String CUSTOM_URL = "/test/url"; @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; @Autowired private TestController testController; @SneakyThrows @Override public void onApplicationEvent(WebServerInitializedEvent event) { if (flag.compareAndSet(false, true)) { // 构建请求映射对象 RequestMappingInfo requestMappingInfo = RequestMappingInfo .paths(CUSTOM_URL) // 请求URL .methods(RequestMethod.POST, RequestMethod.GET) // 请求方法,可以指定多个 .build(); // 发布url,同时指定执行该请求url的具体类变量的的具体方法 requestMappingHandlerMapping.registerMapping(requestMappingInfo, testController, testController.getClass().getMethod("test", String.class, Map.class)); } } }
同样请求:http://localhost:8080/test/url?name=jack
可以看到,此时请求效果并不是正常的,存在参数丢失,怎么办呢?
注意:如果请求出现如下错误:
java.lang.IllegalArgumentException: Expected lookupPath in request attribute "org.springframework.web.util.UrlPathHelper.PATH".
可以在application.yaml文件中添加如下内容:
spring: mvc: pathmatch: matching-strategy: ant_path_matcher
为了实现参数可以正常解析,同时方便增加自定义处理逻辑,我们可以增加一个统一的请求处理器,参考示例:
import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.ResponseBody; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.lang.reflect.Method; import java.util.Map; @Component public class CustomHandlerUrl { public static final Method HANDLE_CUSTOM_URL_METHOD; private static final ObjectMapper OBJECTMAPPER = new ObjectMapper(); @Autowired private TestController testController; static { // 提前准备好参数对象 Method tempMethod = null; try { tempMethod = CustomHandlerUrl.class.getMethod("handlerCustomUrl", HttpServletRequest.class, HttpServletResponse.class); } catch (NoSuchMethodException e) { e.printStackTrace(); } HANDLE_CUSTOM_URL_METHOD = tempMethod; } @ResponseBody /** * 拦截自定义请求的url,可以做成统一的处理器,这里我只做简单实现,专门处理test */ public Object handlerCustomUrl(HttpServletRequest request, HttpServletResponse response) throws IOException { // 获取参数 get方式请求参数 String name = request.getParameter("name"); // 获取 post方式请求参数 Mapmap = OBJECTMAPPER.readValue(request.getInputStream(), Map.class); // 执行业务方法 String result = testController.test(name, map); return result; } }
修改事件监听逻辑,此时注册URL时,绑定统一处理器就行了。
示例代码:
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.web.context.WebServerInitializedEvent; import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.mvc.method.RequestMappingInfo; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import java.util.concurrent.atomic.AtomicBoolean; /** * 注册一个web容器初始化以后的事件监听,注册自定义URL */ @Component public class CustomRegisterUrl implements ApplicationListener{ /** * 标识事件监听器是否已经注册,避免重复注册 */ private volatile AtomicBoolean flag = new AtomicBoolean(false); /** * 需要发布的地址 */ public static final String CUSTOM_URL = "/test/url"; @Autowired private RequestMappingHandlerMapping requestMappingHandlerMapping; @Autowired private CustomHandlerUrl customHandlerUrl; @Override public void onApplicationEvent(WebServerInitializedEvent event) { if (flag.compareAndSet(false, true)) { // 构建请求映射对象 RequestMappingInfo requestMappingInfo = RequestMappingInfo .paths(CUSTOM_URL) // 请求URL .methods(RequestMethod.POST, RequestMethod.GET) // 请求方法,可以指定多个 .build(); // 发布url,指定一下url的处理器 requestMappingHandlerMapping.registerMapping(requestMappingInfo, customHandlerUrl, CustomHandlerUrl.HANDLE_CUSTOM_URL_METHOD); } } }
此时,请求可以发现,效果和使用@RestController+@RequestMapping注解就一样了。
自定义发布URL路径一般情况下很少使用,不过针对特殊url的处理,以及自定义rpc框架发布url时,选择这样处理好了,可以达到出其不意的效果。
上一篇:Spring Boot 配置文件