Возможно ли динамически установить 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));
}
}