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