一、前言
在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 Map<String, Object> map) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("hello, ").append(name).append(", receive param:");
for (Map.Entry<String, Object> entry : map.entrySet()) {
stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue());
}
return stringBuilder.toString();
}
}
测试效果:
三、自定义URL发布逻辑
参考步骤二的测试截图效果,我们自定义发布一个URL。
1. 新建一个spring boot项目,导入相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
2. 修改Controller实现类代码
去掉@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*/ Map<String, Object> map) { // 这里只是方便测试,实际情况下,请勿使用Map作为参数接收
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("hello, ").append(name).append(", receive param:");
for (Map.Entry<String, Object> entry : map.entrySet()) {
stringBuilder.append("\n").append("key: ").append(entry.getKey()).append("--> value: ").append(entry.getValue());
}
return stringBuilder.toString();
}
}
3. 自定义一个事件监听,实现URL发布功能
参考代码如下:
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<WebServerInitializedEvent> {
/**
* 标识事件监听器是否已经注册,避免重复注册
*/
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));
}
}
}
4. 测试效果
同样请求: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
5. 增加统一请求处理器
为了实现参数可以正常解析,同时方便增加自定义处理逻辑,我们可以增加一个统一的请求处理器
,参考示例:
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方式请求参数
Map<String, Object> map = OBJECTMAPPER.readValue(request.getInputStream(), Map.class);
// 执行业务方法
String result = testController.test(name, map);
return result;
}
}
6. 修改事件监听逻辑
修改事件监听逻辑,此时注册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<WebServerInitializedEvent> {
/**
* 标识事件监听器是否已经注册,避免重复注册
*/
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);
}
}
}
7. 重新测试
此时,请求可以发现,效果和使用@RestController
+@RequestMapping
注解就一样了。
四、写在最后
自定义发布URL路径一般情况下很少使用,不过针对特殊url的处理,以及自定义rpc框架发布url时,选择这样处理好了,可以达到出其不意的效果。