Using SpringBoot I managed to get the list of all controllers dynamically (in a test) using RequestMappingHandlerMapping, but I cannot check if the controller uses the #RequestHeader("language") or not. Is there a way to retrieve this information?
I don't think it's possible from RequestMappingHandlerMapping.
Thanks.
public void randomApi(#PathVariable("user") String user,
#RequestHeader("language") String language){...}
RequestMappingHandlerMapping represents all controller methods, you have to get a particular controller method that you are interested from it first. The easiest way to do it is first give a name to the controller method such as GetRandomApi :
#GetMapping(name = "GetRandomApi" , value= "/random")
public void randomApi(#PathVariable("user") String user, #RequestHeader("language") String language){
}
and then get the controller method by this name :
HandlerMethod hm = mapping.getHandlerMethodsForMappingName("GetRandomApi").get(0);
Please note HandlerMethod represents a controller method and I assume you only has one controller method with this name.
To check if this controller method has a parameter which is annotated with #RequestHeader , you can do something likes:
for( MethodParameter param : hm.getMethodParameters()){
RequestHeader requestHeader = param.getParameterAnnotation(RequestHeader.class);
if(requestHeader != null) {
System.out.println(String.format("parameter index %s is annotated with #RequestHeader with the value %s",
param.getParameterIndex(),
requestHeader.value()));
}
}
Related
I have big legacy project with a lot of code and logic.
I have many similar methods in my controller:
public void someEndpoint(
#RequestHeader("flowId") String flowId,
#RequestHeader("someAnotherParam") String someAnotherParam,
#RequestHeader("customerId") String customerId
) {
//pass all arguments to services
}
Every controller method has this three arguments.
This three arguments are passed to another services, and next to another services and another services as method argument.
Whole code is a little messy from this reason.These three arguments are everywhere.
Can I write something like a provider for this three parameter? Some service like:
#Service
class RequestContextProvider {
public RequestContext getRequestContext() {
//some logic
}
}
class RequestContext {
String flowId,
String someAnotherParam,
String customerId
}
And how to do that using spring?
You can use RequestContextHolder class as below:
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes)requestAttributes).getRequest();
String flowId = request.getHeader("flowId");
#RequestMapping("/accounts")
public class controller {
#GetMapping("/get/{id}")
public final ResponseEntity<?> getHandler(){
}
#PostMapping(value = "/create")
public final ResponseEntity<?> createHandler(){
/*
trying to use some spring library methods to get the url string of
'/accounts/get/{id}' instead of manually hard coding it
*/
}
}
This is the mock code, now I am in createHandler, after finishing creating something, then I want to return a header including an URL string, but I don't want to manually concat this URL string ('/accounts/get/{id}') which is the end point of method getHandler(), so I am wondering if there is a method to use to achieve that? I know request.getRequestURI(), but that is only for the URI in the current context.
More explanation: if there is some library or framework with the implementation of route:
Routes.Accounts.get(1234)
which return the URL for the accounts get
/api/accounts/1234
The idea is, that you don't need to specify get or create (verbs are a big no-no in REST).
Imagine this:
#RequestMapping("/accounts")
public class controller {
#GetMapping("/{id}")
public final ResponseEntity<?> getHandler(#PathVariable("id") String id) {
//just to illustrate
return complicatedHandlerCalculation(id).asResponse();
}
#PostMapping
public final ResponseEntity<?> createHandler() {
//return a 204 Response, containing the URI from getHandler, with {id} resolved to the id from your database (or wherever).
}
}
This would be accessible like HTTP-GET: /api/accounts/1 and HTTP-POST: /api/accounts, the latter would return an URI for /api/accounts/2 (what can be gotten with HTTP-GET or updated/modified with HTTP-PUT)
To resolve this URI, you could use reflection and evaluate the annotations on the corresponding class/methods like Jersey does.
A Spring equivalent could be:
// Controller requestMapping
String controllerMapping = this.getClass().getAnnotation(RequestMapping.class).value()[0];
and
//Method requestMapping
String methodMapping = new Object(){}.getClass().getEnclosingMethod().getAnnotation(GetMapping.class).value()[0];
taken from How do i get the requestmapping value in the controller?
I'm hoping this isn't too simple of a question. I'm new to the java web services world and cant seem to get access to my PathVariables in my controller. I'm using STS and it's not complaining that my syntax is wrong.
So more important than the correct answer, I'd really like to know why this isn't working.
Here's some example code that I cant get to work:
#RestController
public class TestController {
#RequestMapping("/example")
public String doAThing(
#PathVariable String test
) throws MessagingException {
return "Your variable is " + test;
}
}
If I run a curl against it like so:
curl http://localhost:8080/example?test=foo
I receive the following response:
{"timestamp":1452414817725,"status":500,"error":"Internal Server
Error","exception":"org.springframework.web.bind.MissingPathVariableException","message":"Missing
URI template variable 'test' for method parameter of type
String","path":"/example"}
I know that I have everything else wired up correctly, other controllers work.
I feel like I must be missing some fundamental principal here.
Thanks in advance.
Spring support different ways how to map stuff from the url to method parameters: request parameters and path variables
Request Parameters are taken from url-query parameters (and request body, for example in http-POST requests). The annotation to mark the java method parameter that should take its value from a request parameter is #RequestParam
Path Variables (somtimes called path templates) are parts of the url-path. The annotation to mark the java method parameter that should take its value from a request parameter is #PathVariable
Have a look at this answer of mine, for an example an links to the Spring Reference.
So what your problem is: you want to read a Request Parameter (from the url-query part), but used the annotation for the Path Variables. So you have to use #RequestParam instead of #PathVariable:
#RestController
public class TestController {
#RequestMapping("/example")
public String doAThing(#RequestParam("test") String test) throws MessagingException {
return "Your variable is " + test;
}
}
If you are using path variable, then it has to be part of the URI. As you have not mentioned in the URI but used in the method arguments, spring tries to find out and assign this value from the path URI. But this path variable is not there in the path URI , therefore throwing MissingPathVariableException.
This should work.
#RestController
public class TestController {
#RequestMapping("/example/{test}")
public String doAThing(
#PathVariable String test
) throws MessagingException {
return "Your variable is " + test;
}
}
And your curl request would be like
curl http://localhost:8080/example/foo
//here the foo can be replace with other string values
The reason why it's not working is that there are two ways to pass parameters to a REST API implementation using RestController. One is the PathVariable, the other is RequestParam. Both of them need names to be specified in the RequestMapping annotation.
Check out this excellent resource that explains RequestMapping in detail
Try this for your solution.
#RequestMapping("/example/{test}", method= RequestMethod.GET)
public String doAThing(
#PathVariable("test") String test
) throws MessagingException {
return "Your variable is " + test;
}
The solution for me was:
#RestController
#RequestMapping("/products")
#EnableTransactionManagement
public class ProductController {
#RequestMapping(method = RequestMethod.POST)
public Product save(#RequestBody Product product) {
Product result = productService.save(product);
return result;
}
}
On Java MVC Controller how to get the value of annotation #RequestMapping("/getThisValueFromOtherClass")? I know we can extract this by using java reflections but is there any other way? Thank you.
#RequestMapping("/getThisString")
public class MyController{}
If the purpose is just to avoid changing the url at every place, I will suggest define a string constant in some class and instead of using hard coded string in request mapping use that constant every where.
In future if u want tp\o change the url, simple update the constant value at one place
final String constUrl = "/myurl";
#RequestMapping(value=constUrl)
you can make the constant static, if defining in another class
The value of the annotation can be read programmatically:
#RequestMapping("/endpoints")
public ResponseEntity<String> getPath() {
String path = getClass().getAnnotation(RequestMapping.class).value()[0];
return new ResponseEntity<String>(path, HttpStatus.OK);
}
To obtain the path, you should pass the Request i.e. HttpServletRequest as a parameter to your handler method.
#RequestMapping(value={"/getThisString"}, method=RequestMethod.GET)
public String handlerMethod (Model model, HttpServletRequest request) throws Exception {
String getThatString = request.getServletPath();
....
}
Reference:
HttpServletRequest
In your case if an URI pattern “/getThisString” is requested, it will map to this MyController, and handle the request with method where #RequestMapping(method = RequestMethod.GET) is declared.
You can refer this tutorial #RequestMapping example
Hope it helps.
In Spring MVC, it is easy to bind request parameter to method paramaters handling the request. I just use #RequestParameter("name"). But can I do the same with request attribute? Currently, when I want to access request attribute, I have to do following:
MyClass obj = (MyClass) request.getAttribute("attr_name");
But I really would like to use something like this instead:
#RequestAttribute("attr_name") MyClass obj
Unfortunately, it doesn't work this way. Can I somehow extend Spring functionality and add my own "binders"?
EDIT (what I'm trying to achieve): I store currently logged user inside request attribute. So whenever I want to access currently logged user (which is pretty much inside every method), I have to write this extra line user = (User) request.getAttribute("user");. I would like to make it as short as possible, preferably inject it as a method parameter. Or if you know another way how to pass something across interceptors and controllers, I would be happy to hear it.
Well, I finally understood a little bit how models work and what is #ModelAttribute for. Here is my solution.
#Controller
class MyController
{
#ModelAttribute("user")
public User getUser(HttpServletRequest request)
{
return (User) request.getAttribute("user");
}
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String HandleSomeUrl(#ModelAttribute("user") User user)
{
// ... do some stuff
}
}
The getUser() method marked with #ModelAttribute annotation will automatically populate all User user parameters marked with #ModelAttribute. So when the HandleSomeUrl method is called, the call looks something like MyController.HandleSomeUrl(MyController.getUser(request)). At least this is how I imagine it. Cool thing is that user is also accessible from the JSP view without any further effort.
This solves exactly my problem however I do have further questions. Is there a common place where I can put those #ModelAttribute methods so they were common for all my controllers? Can I somehow add model attribute from the inside of the preHandle() method of an Interceptor?
Use (as of Spring 4.3) #RequestAttribute:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute User user) {
// ... do some stuff
}
or if the request attribute name does not match the method parameter name:
#RequestMapping(value = "someurl", method = RequestMethod.GET)
public String handleSomeUrl(#RequestAttribute(name="userAttributeName") User user) {
// ... do some stuff
}
I think what you are looking for is:
#ModelAttribute("attr_name") MyClass obj
You can use that in the parameters for a method in your controller.
Here is a link a to question with details on it What is #ModelAttribute in Spring MVC?
That question links to the Spring Documentation with some examples of using it too. You can see that here
Update
I'm not sure how you are setting up your pages, but you can add the user as a Model Attribute a couple different ways. I setup a simple example below here.
#RequestMapping(value = "/account", method = RequestMethod.GET)
public ModelAndView displayAccountPage() {
User user = new User(); //most likely you've done some kind of login step this is just for simplicity
return new ModelAndView("account", "user", user); //return view, model attribute name, model attribute
}
Then when the user submits a request, Spring will bind the user attribute to the User object in the method parameters.
#RequestMapping(value = "/account/delivery", method = RequestMethod.POST)
public ModelAndView updateDeliverySchedule(#ModelAttribute("user") User user) {
user = accountService.updateDeliverySchedule(user); //do something with the user
return new ModelAndView("account", "user", user);
}
Not the most elegant, but works at least...
#Controller
public class YourController {
#RequestMapping("/xyz")
public ModelAndView handle(
#Value("#{request.getAttribute('key')}") SomeClass obj) {
...
return new ModelAndView(...);
}
}
Source : http://blog.crisp.se/tag/requestattribute
From spring 3.2 it can be done even nicer by using Springs ControllerAdvice annotation.
This then would allow you to have an advice which adds the #ModelAttributes in a separate class, which is then applied to all your controllers.
For completeness, it is also possible to actually make the #RequestAttribute("attr-name") as is.
(below modified from this article to suit our demands)
First, we have to define the annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.PARAMETER)
public #interface RequestAttribute {
String value();
}
Then we need a [WebArgumentResolver] to handle what needs to be done when the attribute is being bound
public class RequestAttributeWebArgumentResolver implements WebArgumentResolver {
public Object resolveArgument(MethodParameter methodParameter, NativeWebRequest nativeWebRequest) throws Exception {
// Get the annotation
RequestAttribute requestAttributeAnnotation = methodParameter.getParameterAnnotation(RequestAttribute.class);
if(requestAttributeAnnotation != null) {
HttpServletRequest request = (HttpServletRequest) nativeWebRequest.getNativeRequest();
return request.getAttribute(requestAttributeAnnotation.value);
}
return UNRESOLVED;
}
}
Now all we need is to add this customresolver to the config to resolve it:
<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
<property name="customArgumentResolver">
<bean class="com.sergialmar.customresolver.web.support.CustomWebArgumentResolver"/>
</property>
</bean>
And we're done!
Yes, you can add your own 'binders' to the request attribute - see spring-mvc-3-showcase, or use #Peter Szanto's solution.
Alternatively, bind it as a ModelAttribute, as recommended in other answers.
As it's the logged-in user that you want to pass into your controller, you may want to consider Spring Security. Then you can just have the Principle injected into your method:
#RequestMapping("/xyz")
public String index(Principal principle) {
return "Hello, " + principle.getName() + "!";
}
In Spring WebMVC 4.x, it prefer implements HandlerMethodArgumentResolver
#Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.getParameterAnnotation(RequestAttribute.class) != null;
}
#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
return webRequest.getAttribute(parameter.getParameterAnnotation(RequestAttribute.class).value(), NativeWebRequest.SCOPE_REQUEST);
}
}
Then register it in RequestMappingHandlerAdapter