I am facing some difficulties when trying to create a certain url for #RequestMapping in a spring controller.
First of all, because of the framework the project uses, there is a controller method mapped to the following url: "/{pageLabelOrId}"
Second, I have another controller method mapped to "/{pageName}.html". This works fine, meaning that if I try to access from browser "www.applicationUrl/something.html" this url if captured by the second method as is intended.
Now here is my problem. I must handle a somehow different but also similar url in a distinct method as follows: "/something-{parameter1}_{parameter2}_{parameter3}"
Trying to access "www.applicationUrl/something-1_2_3" will trigger the first controller: "/{pageLabelOrId}" instead of the desired one.
Handling this kind of url("/something-1_2_3") is a requirement and I cannot change it to something like "/something/{param1}/{param2}/{param3}" which I am sure it will work.
I have observed however, that writing a controller method mapped to "/something-{param}" will work and will capture my three parameters in one PathVariable(like "1_2_3") that I can parse afterwards by "_".
Does anyone have any idea of why does spring has this behavior and if I can somehow make it to work using three different path variables?
Spring 3.1+ uses a RequestMappingHandlerMapping to create RequestMappingInfo objects which map your controller handler methods to their corresponding #RequestMapping paths. When the DispatcherServlet receives a request, it uses the RequestMappingHandlerMapping again to determine which handler method should be used.
The way it is currently implemented, it finds all the registered RequestMappingInfo objects that match the request and then sorts them based on a number of rules, basically those defined in AntPathMatcher and AntPatternComparator.
You'll have to configure your RequestMappingHandlerMapping bean to use a custom PathMatcher with your own rules for comparing.
How you do this depends on how you are doing your configuration. If you use a #Configuration class, you can do
#Configuration
public class WebConfig extends WebMvcConfigurationSupport {
...
#Override
public RequestMappingHandlerMapping requestMappingHandlerMapping() {
RequestMappingHandlerMapping mapping = super.requestMappingHandlerMapping();
mapping.setPathMatcher(pathMatcher()); // some PathMatcher bean
return mapping;
}
...
}
The RequestMappingInfo objects created by this RequestMappingHandlerMapping will internally use this PathMatcher. They will be sorted based on its Comparator.
Related
I am trying to overload a #RestController class's method which has the same exact endpoint (url).
However, when I do, I get the following error:
Error creating bean with name 'requestMappingHandlerMapping' defined
in class path resource
[org/springframework/boot/autoconfigure/web/servlet/WebMvcAutoConfiguration$EnableWebMvcConfiguration.class]:
Invocation of init method failed; nested exception is
java.lang.IllegalStateException: Ambiguous mapping. Cannot map
'helloWorldController' method
com.rest.webservices.restfulwebservices.helloworld.HelloWorldController#helloWorldInternationalized(Locale)
to {GET [/hello-world-internationalized]}: There is already
'helloWorldController' bean method
If I change the endpoint (url) to something else, error goes away. In the code below, you can see I use the "hello-world-internationalized" string as endpoint value for both methods which gives me an error about the bean method.
#RestController
public class HelloWorldController {
#Autowired
private MessageSource messageSource;
#GetMapping(path="/hello-world-internationalized")
public String helloWorldInternationalized(#RequestHeader(name="Accept-Language", required=false) Locale locale) {
return messageSource.getMessage("good.morning.message", null, locale);
}
//other version of the method
#GetMapping(path="/hello-world-internationalized")
public String helloWorldInternationalizedVersion2() {
return messageSource.getMessage("good.morning.message", null, LocaleContextHolder.getLocale());
}
}
In Spring Boot #RestController class, is it not possible to overload a method and use the same url for both?
I'd appreciate any comment. Your comments will help me understand this scenario better.
Thank you.
Yes and no. What you currently have is 2 get methods mapped to the same URL, there is no differentiator in the mapping for Spring to determine which one to use when a request comes in.
As you are using a request header in your second one, you could use the headers element on the #GetMapping to add an additional mapping info.
#GetMapping(path="/hello-world-internationalized", headers="Accept-Language")
Now if that specific header is present the method will now be called.
It is important that the mapping information (or metadata) needs to be unique for each method, if the information is the same you will get an error.
You can't do that.
The integration of web framework and spring ioc in spring mvc is accomplished by monitoring the creation of servlet context through ContextLoaderListener and then loading the parent container. Then, by configuring a servlet object DispatcherServlet, the specific sub-container is loaded when DispatcherServlet is initialized.
So,overloading cannot be implemented with the same url。
You can implement overloading in Service.
I have a #RestController that has a #RequestMapping method. It takes one parameter that is an instance of a class, EmailTemplate:
public List<Message> sendEmail(EmailTemplate emailTemplate) {
return emailService.sendEmail(emailTemplate);
}
I would like to require that two String properties of emailTemplate not contain null or only whitespace. However, it should not be required in general. In other words, it should be required when passing to sendEmail but not necessarily in other places where the code might instantiate the class. Does Spring provide an annotated way to accomplish this?
Yes. Spring support Bean Validation. And Bean Validation allows setting groups on validation constraints.
So, just add constraints to the two fields with a group like SendEmailGroup.class, and annotate the EmailTemplate argument of sendEmail() with #Validated(SendEmailGroup.class).
I want to retrieve a list of all handler methods in my Spring controllers. I could check all classes one by one, but that way requires too much time.
Starting from version 3.1.SOMETHING, Spring offers RequestMappingHandlerMapping bean. That class has a method that returns a Map with the info you want: getHandlerMethods(). This map contains info about the #RequestMapping annotation in its keys, and about the method in the controller that matches the mapping in its values.
To use it, you simply autowire a RequestMappingHandlerMapping instance in any bean of your Spring MVC application:
#Configuration
public class MyConfig {
#Autowire
RequestMappingHandlerMapping mappings;
#PostConstruct // It could also be a #Bean getter, actually any method you want
void init() {
for (Entry<RequestMappingInfo, HandlerMethod> entry : this.mappings.getHandlerMethods().entrySet()) {
// do something useful with the actual mapping
}
}
}
Not specific to your question, but RequestMappingHandlerMapping also offers uselful information about interceptors, content negotiator manager, url mapping configuration, etc.
You can harness the power of reflection to get your hands on a list of all #RequestMapping annotated methods within a certain package. Using Google's reflections library this could look as follows:
Reflections reflections = new Reflections("my.project.prefix");
Set<Method> handlerMethods = reflections.getMethodsAnnotatedWith(org.springframework.web.bind.annotation.RequestMapping.class)
I am looking into Dispatcher Servlet code. Here i found that dispatcher servlet uses HandlerMapping to select the handler for the request. Also, RequestMappingHandlerMapping is used as an implementation for HandlerMapping. Now, isHandlerMethod of RequestMappingHandlerMapping returns true if the bean under consideration has either #Controller or #RequestMapping annotation. If certain bean has only #RequestMapping annotation applied at class level would it still be considered as Handler?.
Any Help would be greatly appreciated.
The #RequestMapping and #Controller annotation have different meanings. The request mapping is used to decide, which class / method is used to handle a request to a speciffic URL. If you look at the source of the #Controller adnotation you will find that it is annotated with #Component itself. This way it can be used to set up the component scan that will register an instance of the class as a bean.
I'm quessing that, since those annotations are usually used together, it's done so that a minute performance gain can be achieved. Also, you could declare your controllers differently, either by java config or xml.
Edit:
I have done a quick prototype with a controller bean declared in java config, without the #Controller annotation. The answer is yes, the method will be used to handle a request, even if the class is not annotated.
I have several controllers in a spring mvc application. They are regular beans that inherit from MultiActionController. They also have a custom MethodNameResolver that inspects a certain request parameter.
Now I am trying to use a new controller - a pojo with #Controller annotation. I am using #RequestMapping to resolve methods.
I am not sure if I understand this correctly, but as explained here in the spring reference, it is possible to use #RequestMapping with various filters (e.g. GET vs POST) without specifying a path, and then if a url applies to several methods then Spring falls back to InternalPathMethodNameResolver to decide which method to invoke.
How can I tell Spring to fall back to my custom MethodNameResolver? Is it enough to inject the resolver to my pojo controller?
(my controller doesn't inherit from any Spring specific class)
I guess you need to declare AnnotationMethodHandlerAdapter bean and set its methodNameResolver property.