Возможно ли динамически установить RequestMappings в Spring MVC?

Я использую Spring MVC в течение трех месяцев. Я рассматривал хороший способ динамического добавления RequestMapping. Это связано с необходимостью поставить компоненты контроллера в библиотеку, а затем добавить их динамически. Во всяком случае, единственный способ, о котором я могу думать, - объявить контроллер следующим образом:

@Controller
@RequestMapping("/mypage")
public class MyController {

@RequestMapping(method = RequestMethod.GET)
    public ModelAndView mainHandler(HttpServletRequest req) {
        return handleTheRest(req);
    }

}

Это не хорошо, потому что в основном я не использую Spring. Тогда я не могу использовать привязку формы, аннотации и т.д. Я бы хотел добавить динамические запросы requestMappings к методам классов, которые можно было бы аннотировать, как обычные MVC-контроллеры, с автообновлением, чтобы я мог избежать обработки HttpServletRequest вручную.

Любые идеи? }

Ответ 1

Spring MVC выполняет сопоставления URL-адресов с использованием реализаций интерфейса HandlerMapping. Обычно используемыми из коробки являются реализации по умолчанию, а именно SimpleUrlHandlerMapping, BeanNameUrlHandlerMapping и DefaultAnnotationHandlerMapping.

Если вы хотите реализовать свой собственный механизм сопоставления, это довольно просто сделать - просто реализуйте этот интерфейс (или, возможно, более вероятно, расширьте AbstractUrlHandlerMapping), объявите класс как bean в вашем контексте и с сообщением DispatcherServlet, когда запрос должен быть сопоставлен.

Обратите внимание, что вы можете иметь столько реализаций HandlerMapping, сколько хотите в одном контексте. С ними будут проводиться консультации по очереди, пока один из них не будет соответствовать.

Ответ 2

Я долгое время пытался заставить это работать, но, наконец, удалось найти решение, которое возвращает ResponseEntity вместо старого ModelAndView. Это решение также имеет дополнительное преимущество, позволяющее избежать явного взаимодействия с Application Context.

Служба конечных точек

@Service
public class EndpointService {

  @Autowired
  private QueryController queryController;

  @Autowired
  private RequestMappingHandlerMapping requestMappingHandlerMapping;

  public void addMapping(String urlPath) throws NoSuchMethodException {

    RequestMappingInfo requestMappingInfo = RequestMappingInfo
            .paths(urlPath)
            .methods(RequestMethod.GET)
            .produces(MediaType.APPLICATION_JSON_VALUE)
            .build();

    requestMappingHandlerMapping.
            registerMapping(requestMappingInfo, queryController,
                    QueryController.class.getDeclaredMethod("handleRequests")
            );
  }

}

Контроллер для обработки вновь отображенных запросов

@Controller
public class QueryController {

  public ResponseEntity<String> handleRequests() throws Exception {

    //Do clever stuff here

    return new ResponseEntity<>(HttpStatus.OK);
  }

}

Ответ 3

Я знаю, что это действительно старо, но я решил, что я подброшу это, если у кого-то еще будет такой же грубый опыт, который я пытался сделать для этой работы. В итоге я воспользовался двумя функциями Spring: возможность динамического регистрации beans после запуска контекста и метода afterPropertiesSet() объекта RequestMappingHandlerMapping.

Когда RequestMappingHandlerMapping инициализируется, он сканирует контекст и создает карту всех @RequestMapping, которые должны обслуживать (предположительно по соображениям производительности). Если вы динамически зарегистрируете beans, аннотированный с помощью @Controller, их не будут отображать. Чтобы перезапустить это сканирование, вам просто нужно вызвать afterPropertiesSet() после добавления beans.

В моем конкретном случае использования я создал новые объекты @Controller в отдельном контексте Spring и должен был связать их в моем контексте WebMvc. Детали того, как объекты не имеют значения для этого, все, что вам нужно, это ссылка на объект:

//register all @Controller beans from separateContext into webappContext
separateContext.getBeansWithAnnotation(Controller.class)
   .forEach((k, v) -> webappContext.getBeanFactory().registerSingleton(k, v));

//find all RequestMappingHandlerMappings in webappContext and refresh them
webappContext.getBeansOfType(RequestMappingHandlerMapping.class)
   .forEach((k, v) -> v.afterPropertiesSet());

Например, вы также можете сделать это:

//class annotated with @Controller
MyController controller = new MyController

//register new controller object
webappContext.getBeanFactory().registerSingleton("myController", controller);

//find all RequestMappingHandlerMappings in webappContext and refresh them
webappContext.getBeansOfType(RequestMappingHandlerMapping.class)
   .forEach((k, v) -> v.afterPropertiesSet());

Ответ 4

Посмотрите мое решение. Он не создает динамический @RequestMapping в вашем коде, но предоставляет HandlerMapping и Controller, который обрабатывает весь запрос. Если вы запустите это приложение, вы получите приветственное мировое сообщение в json.

Класс приложения:

@SpringBootApplication
public class Application {
  public static void main(String[] args) {
    SpringApplication.run(Application.class, args);
  }

  @Bean
  public MyCustomHandler myCustomHandler(MyCustomController myCustomController) {
    MyCustomHandler myCustomHandler = new MyCustomHandler(myCustomController);
    myCustomHandler.setOrder(Ordered.HIGHEST_PRECEDENCE);
    return myCustomHandler;
  }
}

MyCustomController

@Component
public class MyCustomController extends AbstractController {

  @Override
  protected ModelAndView handleRequestInternal(HttpServletRequest request,
      HttpServletResponse response) throws Exception {
    response.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE);
    response.getWriter().println("{\"hello\":\"world\"}");
    return null;
  }
}

MyCustomHandler

public class MyCustomHandler extends AbstractHandlerMapping {

  private MyCustomController myCustomController;

  public MyCustomHandler(MyCustomController myCustomController) {
    this.myCustomController = myCustomController;
  }

  @Override
  protected Object getHandlerInternal(HttpServletRequest request) throws Exception {
    return myCustomController;
  }
}

https://github.com/nowszy94/spring-mvc-dynamic-controller

Ответ 5

Следующая конструкция настраивает и реализует методы-обработчики в одном классе.

Это сочетание динамического и статического сопоставления - можно использовать все аннотации MVC, такие как @RequestParam, @PathVariable, @RequestBody и т.д.

Аннотация @RestController создает компонент и добавляет @ResponseBody к каждому методу обработчика.

@RestController
public class MyController {

    @Inject
    private RequestMappingHandlerMapping handlerMapping;

    /***
     * Register controller methods to various URLs.
     */
    @PostConstruct
    public void init() throws NoSuchMethodException {

        /**
         * When "GET /simpleHandler" is called, invoke, parametrizedHandler(String,
         * HttpServletRequest) method.
         */
        handlerMapping.registerMapping(
                RequestMappingInfo.paths("/simpleHandler").methods(RequestMethod.GET)
                .produces(MediaType.APPLICATION_JSON_VALUE).build(),
                this,
                // Method to be executed when above conditions apply, i.e.: when HTTP
                // method and URL are called)
                MyController.class.getDeclaredMethod("simpleHandler"));

        /**
         * When "GET /x/y/z/parametrizedHandler" is called invoke
         * parametrizedHandler(String, HttpServletRequest) method.
         */
        handlerMapping.registerMapping(
                RequestMappingInfo.paths("/x/y/z/parametrizedHandler").methods(RequestMethod.GET)
                .produces(MediaType.APPLICATION_JSON_VALUE).build(),
                this,
                // Method to be executed when above conditions apply, i.e.: when HTTP
                // method and URL are called)
                MyController.class.getDeclaredMethod("parametrizedHandler", String.class, HttpServletRequest.class));
    }

    // GET /simpleHandler
    public List<String> simpleHandler() {
        return Arrays.asList("simpleHandler called");
    }

    // GET /x/y/z/parametrizedHandler
    public ResponseEntity<List<String>> parametrizedHandler(
            @RequestParam(value = "param1", required = false) String param1,
            HttpServletRequest servletRequest) {
        return ResponseEntity.ok(Arrays.asList("parametrizedHandler called", param1));
    }
}