Dynamic Command Class in Annotated Controller - java

As of Spring MVC 3, AbstractCommandController is deprecated so you can no longer specify the command class in setCommandClass(). Instead you hard-code the command class in the parameter list of a request handler. For example,
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request, #ModelAttribute("employee") Employee employee)
My problem is that I'm developing a generic page that allows the user to edit a generic bean, so the command class isn't known until the run-time. If the variable beanClass holds the command class, with AbstractCommandController, you would simply do the following,
setCommandClass(beanClass)
Since I can't declare the command object as a method parameter, is there any way to have Spring bind request parameters to a generic bean in the body of the request handler?

Instantiation of the command object is the only place where Spring needs to know a command class. However, you can override it with #ModelAttribute-annotated method:
#RequestMapping(method = RequestMethod.POST)
public void show(HttpServletRequest request,
#ModelAttribute("objectToShow") Object objectToShow)
{
...
}
#ModelAttribute("objectToShow")
public Object createCommandObject() {
return getCommandClass().newInstance();
}
By the way, Spring also works fine with the real generics:
public abstract class GenericController<T> {
#RequestMapping("/edit")
public ModelAndView edit(#ModelAttribute("t") T t) { ... }
}
#Controller #RequestMapping("/foo")
public class FooController extends GenericController<Foo> { ... }

Related

How to pass an argument to a Spring Boot REST Controller?

I am trying to pass an argument to my #RESTController Spring Boot class.
In the #POSTMapping method I want to use a method of a self defined Java class for processing the received body and returning a response.
The Spring application is launched in Application.java. The Controller-Object seems to get created implicitly.
I already tried adding a constructor to my RESTController class. But I couldn't find a way to call that constructor with an argument.
// Application.java
public static void main (String[] args) {
SpringApplication.run(Application.class, args);
}
//ConnectorService.java
#RestController
public class ConnectorService {
private Solveable solver;
public ConnectorService() {}
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestBody Test test) {
return solver.solve(test);
}
}
Even though i could define a second constructor, i didn't find any way to call it with my Object.
Use #RequestParam annotation to pass an argument
You can pass parameter with #RequestParam annotation like this:
#CrossOrigin(origins = "http://localhost:3000")
#PostMapping(path = "/maze")
public Solution Test(#RequestParam("paramName") String param, #RequestBody Test test) {
return solver.solve(test);
}
And you can put it with http request:
http://localhost:3000/maze?paramName=someValue
Assuming that you have POST request, there may be different ways to build this request, depending on the API testing tools you use.
#RestController follows the same rules for dependency injection as any other #Component in Spring framework.
If you have a single constructor, Spring will try to „inject” the parameters while instantiating the controller.
You need to register your dependency as a Spring bean.
It seems that you are new to Spring and you are starting with advanced topics like Spring Boot and rest controllers. Please find some time to read about the basics.
Yo can create a Bean configuration file to initialize your objects like:
#Configuration
#ComponentScan("com.xxx.xxx") // the base package you want to scan
public class Config {
#Bean
//where Solveable is a class and is annotated with an Spring's annotation
public Solveable solveable() {
return new Solveable();
}
}
And use the #Autowired annotation to inject the object in:
#Autowired
public ConnectorService (Solveable solveable) {
this.solver = solveable;
}
This last block will initialize or pass(what you want) the object to the ConnectorService class.

Bind Principal to MyCustomUser class as method argument in controller

I know you can get a username easily in a Spring controller by including Principal as a method argument like:
#GetMapping("/username")
#ResponseBody
public String currentUserName(Principal principal) {
return principal.getName();
}
But I am ultimately going to want access to members of a MyCustomUser class that I instantiate from a repository with a findBy method. I can put a helper method in the Controller to do the lookup and return the user based on principal.getName(), but can I go a step further and bind to MyCustomUser directly, like
#GetMapping("/stuff")
#ResponseBody
public String stuff(MyCustomUser user) {
return user.thing();
}
I was looking into creating a converter like (Ref):
#Component
public class PrincipalToMyCustomUserConverter implements Converter<Principal, MyCustomUser> {
private MyCustomUserRepository userRepository;
public PrincipalToApplicationUserConverter(MyCustomUserRepository userRepository) {
this.userRepository = userRepository;
}
#Override
public MyCustomUser convert(Principal source) {
return this.userRepository.findByUsername(source.getName());
}
}
But I don't know if that's an appropriate way to grab the repository, and I don't know how to pass the repository when registering the converter (Ref).
You're correct in that the converter you're proposing is not appropriate. Your converter can convert from an object of type Principal to an object of type MyCustomUser, however, there is no *Principal* by which to convert. The magic behind the principal injection is that Spring actually gets this from the SecurityContextHolder, it is not deserialized from request...though fields present in the request allow Spring to create the Principal. If you truly want to inject MyCustomUser, use a ModelAttribute. ModelAttributes are available to all of your Spring controller methods.
I generally like to keep stuff like this in it's own class, so I would define a class that held this and other #ControllerAdvice in one place, something like this:
#ControllerAdvice
public class SomeControllerAdvice {
#Autowired
private MyCustomUserRepository myCustomUserRepository;
#ModelAttribute
public MyCustomUser getUser(Principal principal) {
return myCustomUserRepository.findByUsername(principal.getName());
}
}
The above should suffice to make MyCustomUser available to all methods. I would note that you probably want a little error handling here, like skip over if principal is null and whatnot, also have your findByUsername method return an Optional so your can address empty returns.
see:
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ModelAttribute.html
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/ControllerAdvice.html

How to map #requestBody to a specific initBinder method?

I have code that looks like this:
Controller
public String doStuff(#Valid #RequestBody MyClass obj, BindingResult result) {
...
}
Validation
#InitBinder
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator());
}
The problem is that I don't know how to "hook up" the obj param with the specific initBinder method I need to run to automagically validate obj with my custom Validator class. I need some way to have a specific #InitBinder method run after the obj param is bound to the request body.
Also, I have to use #RequestBody since the controller method is taking in a JSON payload in the request body.
I think it should be sufficient to have the #InitBinder method in the same Controller class as your doStuff() method.
Alternatively, you can also create a custom annotation for MyValidator and apply it on the class level in MyClass instead of using #InitBinder. This also has the added benefit of being initiated and run on any method call where one of the arguments is #Valid MyClass obj.
If you only have a single parameter type, it is sufficient to have the InitBinder in the same controller class as rorschach mentioned.
Otherwise, you can specify the class name of the parameter (starting with lowercase letter) you want to bind to:
#InitBinder("myClass")
public void initBinder(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator());
}
public String doStuff(#Valid #RequestBody MyClass obj, BindingResult result) {
...
}
#InitBinder("myClass2")
public void initBinder2(WebDataBinder binder, HttpServletRequest request) {
binder.setValidator(new MyValidator2());
}
public String doStuff2(#Valid #RequestBody MyClass2 obj, BindingResult result) {
...
}
I'm not sure where exactly in the documentation it explains this, but the javadoc for InitBinder does say the following:
Specifying model attribute names or request parameter names here
restricts the init-binder method to those specific
attributes/parameters
So I guess by default the parameter name is the name of the class starting with a lowercase letter.

Spring PUT not mapping to method

It's strange that my PUT operations is not mapped to spring method, I've following classes...
#Controller
#RequestMapping(value = { "/**/applicationContext/abcde" })
public abstract class AbstractController {
// Exception handlers
}
This class is extended by another abstract class, as below
public abstract class AbstractAdapter extends AbstractController{}
Finally, above class is extended by below class
public class SpringAdapter extends AbstractAdapter implements MyInterface{}
And I've some put operations to be made which looks like below :-
#Override
#ResponseBody
#RequestMapping(method = { RequestMethod.PUT }, consumes = "application/x-www-form-urlencoded", produces = "application/xml", params = {
"param1=some_value", param2 })
public MyObject update(
#RequestParam(value = param2, required = true) #ContentType(value = Encoding.XML) MyObejct myObject, HttpServletRequest request) throws Exception{ }
But when I invoke put method then the request is not mapping to my update method.
Can anybody please help.
Your subclass isn't marked as a #Controller and therefor Spring (when it scans for controllers) doesn't find it.
Please see this answer
Basically, annotations aren't inherited by default.
You can use an annotation in the declaration of an annotation to mark it as inherited as is shown here
by using:
public #interface Inherited
but if you look at the controller source, this keyword is not used in the Controller implementation spring uses.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface Controller {
So I would assume #Controller is not inherited in your situation. I would first try out the inheritance with a simple example (class A extends class B , with just a simple unadorned method) to verify this.

Implementing dynamic menu for Spring MVC/AOP application

I wish to implement dynamically changeable menu (updating whenever annotated method or controller added) for my Spring MVC application.
What i want is to introduce new annotation (#RequestMenuMapping) which will go to #Controller beans and their methods (just like #RequestMapping works).
Heres is what i want, User class, producing menu like
Users
Index | List | Signup | Login
with following code:
#Controller
#RequestMapping("user")
#RequestMenuMapping("Users")
public class User {
#RequestMapping("")
#RequestMenuMapping("Index")
public String index(/* no model here - just show almost static page (yet with JSP checks for authority)*/) {
return "user/index.tile";
}
#RequestMapping("list")
#RequestMenuMapping("List")
public String list(Model model) {
model.addAttribute("userList",/* get userlist from DAO/Service */);
return "user/list.tile";
}
#RequestMapping("signup")
#RequestMenuMapping("Signup")
public String signup(Model model) {
model.addAttribute("user",/* create new UserModel instance to be populated by user via html form */);
return "user/signup.tile";
}
#RequestMapping("login")
#RequestMenuMapping("Login")
public String login(Model model) {
model.addAttribute("userCreds",/* create new UserCreds instance to be populated via html form with login and pssword*/);
return "user/login.tile";
}
}
I think that Spring AOP may help me to pointcut methods with #RequestMenuMapping annotation and via #AfterReturning add something representing web-site menu to model.
But this raises two questions:
How do i get Model instance in #AfterReturning advice method in case it is missing in adviced method (as in .index())?
How do i get all methods (as in java reflection Method) and classes (as in java reflection Class) annotated with #RequestMenuMapping in order to build complete menu index?
I think a better soultion would be a bean post processor to scan all controller classes for the #RequestMenuMapping and a HandlerInterceptor to add the menu items to every model map.
InterceptorDemo:
#Aspect
#Component
public class InterceptorDemo {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {
}
#Pointcut("#annotation(you.package.RequestMenuMapping)")
public void requestMenuMapping() {
}
#AfterReturning("requestMapping() && equestMenuMapping()")
public void checkServer(JoinPoint joinPoint,Object returnObj) throws Throwable {
Object[] args = joinPoint.getArgs();
Model m = (Model)args[0];
// use joinPoint get class or methd...
}
}
If you want to intercept Contoller with you own, you can wirte another pointcut and ProceedingJoinPoint object can get what you want.
Q1:
ModelAndView object create at org.springframework.web.servlet.DispatcherServlet.doDispatch()
// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
// Do we need view name translation?
if (mv != null && !mv.hasView()) {
mv.setViewName(getDefaultViewName(request));
}
So, you can intercept handle method after returing or override the method.
Q2:As far as i know, there are two ways getting annotation methods.
1.Use AOP:
You can declare a pointcut like this:
#Pointcut("#annotation(you.package.RequestMenuMapping)")
public void requestMenuMappingPountcut() {
}
2.Use reflection.
Class clazz = Class.forName(classStr);
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
if (method.isAnnotationPresent(RequestMapping.class)
&& method.isAnnotationPresent(RequestMenuMapping.class)) {
// do something
}
}

Categories

Resources