Как сопоставить пользовательские / динамические c запросы с контроллером (Spring)? - PullRequest
1 голос
/ 23 февраля 2020

Как можно сопоставить пользовательские / dunami c запросы к данному контроллеру на основе поиска в репозитории?

Вариант использования - это функция, подобная CMS, в веб-платформе, где определенные шаблоны URL («страницы»), хранящиеся в БД, должны обрабатываться отдельным контроллером PageController.java. Эти шаблоны не обязательно известны во время компиляции, и их также можно добавлять и изменять во время развертывания приложения (таким образом, оно не может управляться аннотациями).

Я пытался сопоставить контроллер с " ** "(см. ниже), но это не сработало по двум причинам: во-первых, все другие запросы были разрешены с использованием того же метода контроллера (я надеялся, что он будет использовать" ** "в качестве запасного варианта и сначала попробует другие), и он также заканчивал тем, что разрешал все запросы к моим статическим файлам / файлам активов на этот контроллер (что приводило к нежелательным 404 ответам).

@Controller
public class PageController {

    @Inject
    private PageService pageService;

    @RequestMapping(value = "**", method = RequestMethod.GET)
    public String getPage(Model model, HttpServletRequest request, @CurrentUser User user) {
        String path = request.getRequestURI();
        Page page = this.pageService.getByPath(path, user);
        if (page == null) {
            throw new NotFoundException();
        }
        model.addAttribute("page", page);
        return "web/page";
    }
}

Временное решение / модификация вышеуказанного метода до сих пор сопоставить предварительно определенные URL-префиксы с этим контроллером (например, /page/**, /info/**, /news/** et c), но это не элегантное решение, которое добавляет произвольные ограничения к системе, которые я сейчас пытаюсь устранить ,

В настоящее время я использую Spring Boot 2.0. В дополнение к наивному отображению в ** в обычном @Controller классе (с использованием @RequestMapping -аннотации) я также попытался настроить 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;
    }
}

Этот подход технически работает, но список страниц каким-то образом придется перезагружать в SimpleUrlHandlerMapping при каждом изменении одной из страниц (я не совсем уверен, как это сделать). Это также возможно перезаписывает некоторую конфигурацию Spring Boot по умолчанию, которую я в идеале хотел бы сохранить. Он также имеет некоторые недостатки по сравнению с разрешением контроллеров с использованием @Controller и @RequesMapping, поскольку в настоящее время я внедряю определенные данные во все представления, разрешенные таким образом (в основном данные модели, используемые в общем дизайне сайта, такие как меню, быстрые ссылки и т. 1029 *). В вышеупомянутой попытке мне пришлось установить их с помощью отдельного вызова на controllerAdvice-globalModelAttributes().

. То, что я ищу, - это решение, в котором мой репозиторий запрашивается для потенциальных совпадений страниц во время выполнения, и если это так. действительный, тогда запрос будет обработан соответствующим контроллером страниц. Является ли пользовательская реализация HandlerMapping способом сделать это? И если нет, то как мне решить эту проблему? И если сделать отдельное HandlerMapping для страниц, как мне добавить / зарегистрировать это в моей конфигурации без перезаписи по умолчанию, предоставляемой Spring?

Ответы [ 2 ]

1 голос
/ 23 февраля 2020

Самый простой (но не самый лучший) способ добиться того, что вам нужно, - создать собственную реализацию 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
    }     
}

Теперь удалите @Controller аннотацию из PageController, поскольку она вам не нужна быть обнаружены автоматически больше. После этого зарегистрируйте контроллер и сопоставьте с config:

@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. Поэтому вы никогда не узнаете о том, что что-то не так с вашими ресурсами (например, если некоторые из них отсутствуют).

Я бы предпочел, чтобы приложение различало guish путей по префиксу (так же, как вы уже это сделали) где префикс - операция, которую приложение собирается делать со страницей. Например, если вам нужно показать или отредактировать страницу:

@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
}
1 голос
/ 23 февраля 2020

Почему бы вам просто не внедрить универсальный контроллер, который анализирует ваши шаблоны в качестве параметра, выполняет поиск в БД, а затем использует forward для указания c контроллеров (info, page , новости et c.)? Похоже на CMS, эта поисковая логика c принадлежит вашему коду (например, сервисный уровень).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...