Java 如何将自定义/动态请求映射到控制器(Spring)?

Java 如何将自定义/动态请求映射到控制器(Spring)?,java,spring,spring-boot,spring-mvc,Java,Spring,Spring Boot,Spring Mvc,如何根据存储库查找将自定义/dunamic请求映射到给定控制器 该用例是web平台中类似CMS的功能,其中存储在DB中的某些URL模式(“页面”)应由单独的控制器PageController.java处理。这些模式在编译时不一定已知,也可以在部署应用程序时添加和修改它们(因此,它不能由注释驱动) 我确实尝试将一个控制器映射到“**”(见下文),但由于以下两个原因,这不起作用:首先,所有其他请求都解析为同一个控制器方法(我曾希望它将“**”用作回退,然后先尝试其他方法),最后它还将所有对我的静态/

如何根据存储库查找将自定义/dunamic请求映射到给定控制器

该用例是web平台中类似CMS的功能,其中存储在DB中的某些URL模式(“页面”)应由单独的控制器
PageController.java
处理。这些模式在编译时不一定已知,也可以在部署应用程序时添加和修改它们(因此,它不能由注释驱动)

我确实尝试将一个控制器映射到“**”(见下文),但由于以下两个原因,这不起作用:首先,所有其他请求都解析为同一个控制器方法(我曾希望它将“**”用作回退,然后先尝试其他方法),最后它还将所有对我的静态/资产文件的请求解析为该控制器(导致不必要的404响应)

到目前为止,临时解决/修改上述方法的工作是将预定义的URL前缀映射到此控制器(例如,
/page/**
/info/**
/news/**
等),但这是一个不雅观的解决方案,为系统添加了任意限制,我现在试图消除这些限制

我目前正在使用Spring Boot 2.0。除了在常规的
@Controller
类(使用
@RequestMapping
-annotation)中对
**
进行简单映射之外,我还尝试通过以下方式配置
SimpleUrlHandlerMapping

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Inject
    private PageDao pageDao;

    @Bean
    public PageController pageController() {
        return new PageController();
    }

    @Bean
    public SimpleUrlHandlerMapping pageUrlHandlerMapping() {
        SimpleUrlHandlerMapping pageUrlHandlerMapping = new SimpleUrlHandlerMapping();
        PageController pageController = this.pageController();
        Map<String, Object> urlMap = this.pageDao.findAll().stream()
                .map(Page::getNormalizedSlug)
                .collect(Collectors.toMap(Function.identity(),
                        slug -> pageController, (existing, duplicate) -> existing));
        pageUrlHandlerMapping.setUrlMap(urlMap);
        pageUrlHandlerMapping.setOrder(Ordered.HIGHEST_PRECEDENCE); // <- Cannot be LOWEST_PRECEDENCE for some reason...
        return pageUrlHandlerMapping;
    }
}

public class PageController implements Controller {

    @Inject
    private PageService pageService;
    @Inject
    private DmaWebControllerAdvice controllerAdvice;

    @Override
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
        User user = null;
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof User) {
            user = (User) principal;
        }
        String path = request.getRequestURI();
        Page page = this.pageService.getByPath(path, user);
        if (page == null) {
            throw new NotFoundException();
        }
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("web/page");
        modelAndView.addObject("page", page);
        controllerAdvice.globalModelAttributes(modelAndView.getModel(), null);
        return modelAndView;
    }
}
@配置
公共类WebConfig实现WebMVCConfiguer{
@注入
私人网页;
@豆子
公共页控制器页控制器(){
返回新的PageController();
}
@豆子
公共SimpleUrlHandlerMapping页面UrlHandlerMapping(){
SimpleUrlHandlerMapping PagerlHandlerMapping=新的SimpleUrlHandlerMapping();
PageController PageController=this.PageController();
Map urlMap=this.pageDao.findAll().stream()
.map(第页::getNormalizedSlug)
.collect(Collectors.toMap(Function.identity()),
slug->pageController,(存在,重复)->存在);
setUrlMap(urlMap);

pageUrlHandlerMapping.setOrder(Ordered.HIGHEST_priority);//为什么不实现一个将模式解析为参数的“全覆盖”控制器,进行db查找,然后使用到特定控制器(信息、页面、新闻等)?似乎对于CMS来说,这个查找逻辑属于您的代码(例如服务层)。

最简单(但不是最好的)实现所需的方法是创建自定义
HandlerMapping
实现:

public class PageMapper implements HandlerMapping, Ordered {

    private HandlerMethod handlerMethod;        

    public CustomMapper(Object controller, Method method) {
        this.handlerMethod = new HandlerMethod(controller, method);
    }

    @Override
    public HandlerExecutionChain getHandler(HttpServletRequest httpServletRequest) throws Exception {
        return new HandlerExecutionChain(handlerMethod);
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; //you have to add the handler to the end
    }     
}
现在从
PageController
中删除
@Controller
注释,因为您不再需要自动检测它。之后,注册控制器并映射到配置:

@Configuration
public class AppWebConfig implements WebMvcConfigurer {

    @Bean
    public PageController pageController() {
        return new PageController();
    }

    @Bean
    public HandlerMapping pageMapping(PageController pageController) {
        Method method = BeanUtils.resolveSignature("getPage", PageController.class);
        return new PageMapping(pageController, method);
    }
}
现在,其他
HandlerMapping
实例无法识别的每个请求都将被发送到您的映射,从而发送到您的控制器。但是这种方法有明显的缺点。因为您的映射是映射链中的最后一个,所以您永远不会得到404错误。因此,您永远不会知道您的资源有什么问题(例如,如果其中一些丢失)

我更喜欢让应用程序通过前缀来区分路径(就像您已经做的那样),其中前缀是应用程序将要对页面执行的操作。例如,如果您需要显示或编辑页面:

@Controller
public class PageController {

    private final static String SHOW = "/show";
    private final static String EDIT = "/edit";

    @Inject
    private PageService pageService;

    GetMapping(value = SHOW + "/**")
    public String getPage(Model model, HttpServletRequest request, @CurrentUser User user) {
        String path = request.getRequestURI().substring(SHOW.length());
        Page page = this.pageService.getByPath(path, user);
        ...        
        model.addAttribute("page", page);
        return "web/page";
    }

    //the same for EDIT operation
}
@Controller
public class PageController {

    private final static String SHOW = "/show";
    private final static String EDIT = "/edit";

    @Inject
    private PageService pageService;

    GetMapping(value = SHOW + "/**")
    public String getPage(Model model, HttpServletRequest request, @CurrentUser User user) {
        String path = request.getRequestURI().substring(SHOW.length());
        Page page = this.pageService.getByPath(path, user);
        ...        
        model.addAttribute("page", page);
        return "web/page";
    }

    //the same for EDIT operation
}