一, 接口开关功能
1, 可配置化, 依赖配置中心
2, 接口访问权限可控
3,springmvc 不会扫描到, 即不会直接的将接口暴露出去
二, 接口开关使用场景
和业务没什么关系, 主要方便查询系统中的一些状态信息. 比如系统的配置信息, 中间件的状态信息. 这就需要写一些特定的接口, 不能对外直接暴露出去(即不能被 springmvc 扫描到, 不能被 swagger 扫描到).
三, SimpleUrlHandlerMapping 官方解释
SimpleUrlHandlerMapping 实现 HandlerMapping 接口以从 URL 映射到请求处理程序 bean.
支持映射到 bean 实例和映射到 bean 名称; 后者是非单身处理程序所必需的.
"urlMap" 属性适合用 bean 引用填充处理程序映射, 例如通过 xml bean 定义中的 map 元素.
可以通过 "mappings" 属性以 java.util.Properties 类接受的形式设置 bean 名称的映射, 如下所示:/welcome.html=ticketController /show.HTML=ticketController 语法为 PATH = HANDLER_BEAN_NAME.
如果路径不以斜杠开头, 则前置一个. 支持直接匹配 (给定 "/ test" -> 注册 "/ test")和 "*" 模式匹配 (给定 "/ test" -> 注册 "/ t *").
四, 接口开关实现
就像 SimpleUrlHandlerMapping javadoc 中描述的那样, 其执行原理简单理解就是根据 URL 寻找对应的 Handler. 借助这种思想, 我们在 Handler 中再借助 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 来帮助我们完成 URL 的转发. 这样做的好处是不需要直接暴露的接口开发规则只需要稍作修改, 接下来将详细介绍一下.
请求转发流程如下
想法是好的, 如何实现这一套流程呢? 首先要解决以下问题.
1, 定义的接口不能被 springmvc 扫描到.
2, 接口定义还是要按照 @RequestMaping 规则方式编写, 这样才能减少开发量并且能被 RequestMappingHandlerMapping 处理.
3, 如何自动注册 url->handler 到 SimpleUrlHandlerMapping 中去.
对于上面需要实现的, 首先要了解一些 springmvc 相关源码.
RequestMappingHandlerMapping 初始化 method mapping
- /**
- * Scan beans in the ApplicationContext, detect and register handler methods.
- * @see #isHandler(Class)
- * @see #getMappingForMethod(Method, Class)
- * @see #handlerMethodsInitialized(Map)
- */
- protected void initHandlerMethods() {
- if (logger.isDebugEnabled()) {
- logger.debug("Looking for request mappings in application context:" + getApplicationContext());
- }
- String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ?
- BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) :
- getApplicationContext().getBeanNamesForType(Object.class));
- for (String beanName : beanNames) {
- if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
- Class<?> beanType = null;
- try {
- beanType = getApplicationContext().getType(beanName);
- }
- catch (Throwable ex) {
- // An unresolvable bean type, probably from a lazy bean - let's ignore it.
- if (logger.isDebugEnabled()) {
- logger.debug("Could not resolve target class for bean with name'" + beanName + "'", ex);
- }
- }
- if (beanType != null && isHandler(beanType)) {
- detectHandlerMethods(beanName);
- }
- }
- }
- handlerMethodsInitialized(getHandlerMethods());
- }
isHandler 方法[判断方法是不是一个具体 handler] 逻辑如下
- protected boolean isHandler(Class<?> beanType) {
- return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
- AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
- }
所以我们定义的开关接口为了不被 springmvc 扫描到, 直接去掉类注释上的 @Controller 注解和 @RequestMapping 注解就好了, 如下.
- @Component
- @ResponseBody
- public class CommonsStateController {
- @GetMapping("/url1")
- public String handleUrl1() {
- return null;
- }
- @GetMapping("/url2")
- public String handleUrl2() {
- return null;
- }
- }
按照如上的定义, url -> handler(/message/state/* -> CommonsStateController )形式已经出来了, 但是还缺少父类路径 /message/state/ 以及 如何让 RequestMappingHandlerMapping 识别 CommonsStateController 这个 handler 中的所有子 handler.
抽象 Handler 以及自定义 RequestMappingHandlerMapping
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.beans.factory.InitializingBean;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.web.servlet.HandlerExecutionChain;
- import org.springframework.Web.servlet.ModelAndView;
- import org.springframework.Web.servlet.mvc.AbstractController;
- import org.springframework.Web.servlet.mvc.method.RequestMappingInfo;
- import org.springframework.Web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
- import org.springframework.Web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.lang.reflect.Method;
- import java.util.Objects;
- /**
- * @author hujunzheng
- * @create 2018-08-10 12:53
- **/
- public abstract class BaseController extends AbstractController implements InitializingBean {
- private RequestMappingHandlerMapping handlerMapping = new BaseRequestMappingHandlerMapping();
- @Autowired
- private RequestMappingHandlerAdapter handlerAdapter;
- @Override
- protected ModelAndView handleRequestInternal(HttpServletRequest request, HttpServletResponse response) throws Exception {
- HandlerExecutionChain mappedHandler = handlerMapping.getHandler(request);
- return handlerAdapter.handle(request, response, mappedHandler.getHandler());
- }
- @Override
- public void afterPropertiesSet() {
- handlerMapping.afterPropertiesSet();
- }
- private class BaseRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
- // 初始化子 handler mapping
- @Override
- protected void initHandlerMethods() {
- detectHandlerMethods(BaseController.this);
- }
- // 合并父路径和子 handler 路径
- @Override
- protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
- RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
- if (!Objects.isNull(info) && StringUtils.isNotBlank(getBasePath())) {
- info = RequestMappingInfo
- .paths(getBasePath())
- .build()
- .combine(info);
- }
- return info;
- }
- }
- // 开关接口定义父路径
- public abstract String getBasePath();
- }
所有开关接口 handler 都继承这个 BaseController 抽象类, 在对象初始时创建所有的子 handler mapping.SimpleUrlHandlerMapping 最终会调用开关接口的 handleRequestInternal 方法, 方法内部通过 RequestMappingHandlerMapping 和 RequestMappingHandlerAdapter 将请求转发到具体的子 handler.
- @Component
- @ResponseBody
- public class CommonsStateController extends BaseController {
- @GetMapping("/url1")
- public String handleUrl1() {
- return null;
- }
- @GetMapping("/url2")
- public String handleUrl2() {
- return null;
- }
- }
自动注册 url-handler 到 SimpleUrlHandlerMapping
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.util.CollectionUtils;
- import org.springframework.Web.servlet.handler.SimpleUrlHandlerMapping;
- import java.util.HashMap;
- import java.util.List;
- import java.util.Map;
- /**
- * @author hujunzheng
- * @create 2018-08-10 13:57
- **/
- public class EnhanceSimpleUrlHandlerMapping extends SimpleUrlHandlerMapping {
- public EnhanceSimpleUrlHandlerMapping(List<BaseController> controllers) {
- if (CollectionUtils.isEmpty(controllers)) {//NOSONAR
- return;
- }
- Map<String, BaseController> urlMappings = new HashMap<>();
- controllers.forEach(controller -> {
- String basePath = controller.getBasePath();
- if (StringUtils.isNotBlank(basePath)) {
- if (!basePath.endsWith("/*")) {
- basePath = basePath + "/*";
- }
- urlMappings.put(basePath, controller);
- }
- });
- this.setUrlMap(urlMappings);
- }
- }
获取 BaseController 父路径, 末尾加上'/*', 然后将 url -> handler 关系注册到 SimpleUrlHandlerMapping 的 urlMap 中去. 这样只要请求路径是 父路径 /* 的模式都会被 SimpleUrlHandlerMapping 处理并转发给对应的 handler(BaseController), 然后在转发给具体的子 handler.
接口开关逻辑
- import com.cmos.wmhopenapi.service.config.LimitConstants;
- import com.google.common.collect.Lists;
- import org.apache.commons.lang3.StringUtils;
- import org.springframework.Web.servlet.handler.HandlerInterceptorAdapter;
- import org.springframework.Web.servlet.handler.SimpleUrlHandlerMapping;
- import javax.servlet.http.HttpServletRequest;
- import javax.servlet.http.HttpServletResponse;
- import java.util.stream.Collectors;
- /**
- * @author hujunzheng
- * @create 2018-08-10 15:17
- **/
- public class UrlHandlerInterceptor extends HandlerInterceptorAdapter {
- private SimpleUrlHandlerMapping mapping;
- private LimitConstants limitConstants;
- public UrlHandlerInterceptor(SimpleUrlHandlerMapping mapping, LimitConstants limitConstants) {
- this.mapping = mapping;
- this.limitConstants = limitConstants;
- }
- @Override
- public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
- String lookupUrl = mapping.getUrlPathHelper().getLookupPathForRequest(request);
- String urllimits = limitConstants.getUrllimits();
- if (StringUtils.isNotBlank(urllimits)) {
- for (String urllimit : Lists.newArrayList(urllimits.split(","))
- .stream()
- .map(value -> value.trim())
- .collect(Collectors.toList())) {
- if (mapping.getPathMatcher().match(urllimit, lookupUrl)) {
- return false;
- }
- }
- }
- return true;
- }
- }
基本思路就是通过 UrlPathHelper 获取到 request 的 lookupUrl(例如 /message/state/url1) , 然后获取到配置中心配置的 patter path(例如 message/state/*), 最后通过 AntPathMatcher 进行二者之间的匹配, 如果成功则禁止接口访问.
五, 接口开关配置
- @Bean
- public SimpleUrlHandlerMapping simpleUrlHandlerMapping(ObjectProvider<List<BaseController>> controllers, LimitConstants limitConstants) {
- SimpleUrlHandlerMapping mapping = new EnhanceSimpleUrlHandlerMapping(controllers.getIfAvailable());
- mapping.setOrder(Ordered.HIGHEST_PRECEDENCE + 1);
- mapping.setInterceptors(new UrlHandlerInterceptor(mapping, limitConstants));
- return mapping;
- }
创建自定义的 SimpleUrlHandlerMapping, 然后将类型为 BaseController 所有 handler 以构造参数的形式传给 SimpleUrlHandlerMapping, 并设置接口开关逻辑拦截器.
至此, 接口开关能力已经实现完毕. 再也不用在担心接口会直接暴露出去了, 可以通过配置随时更改接口的访问权限.
来源: https://www.cnblogs.com/hujunzheng/p/9902475.html