Using a strictly annotation-based/Java-only Spring MVC 3.2.2 configuration, I'm trying to create a controller method with a custom class (I'll call it Context) as a parameter. I want to have Context constructed with knowledge of the current HttpServletRequest and then passed along to the controller method. In essence, I want to create my own custom wrapper around the request object before it's sent to the controller. e.g. I want to accomplish this:
#Controller
#RequestMapping(value = "/")
public class MainController {
#RequestMapping(value = "/")
public #ResponseBody
String process(HttpServletRequest request) {
Context context = new Context(request);
...
}
}
automatically like this:
#Controller
#RequestMapping(value = "/")
public class MainController {
#RequestMapping(value = "/")
public #ResponseBody
String process(Context context) {
...
}
}
Is this possible? I looked into implementing a HandlerMethodArgumentResolver as a #Bean in my WebMvcConfigurerAdapter but I don't think that's the correct route to take. I've tried adding #AutoWired to Context (as a #Bean) to no avail as well. I imagine there's WebMvcConfigurerAdapter or possibly in a AbstractAnnotationConfigDispatcherServletInitializer?
HandlerMethodArgumentResolver and WebMvcConfigurerAdapter is certainly a right way to achieve your goal.
In order to register custom argument resolver you need to make your #Configuration extend WebMvcConfigurerAdapter and override its addArgumentResolver() method.
Related
For a user registration form (registration.html) I created a view controller through:
#Configuration
#EnableWebMvc
public class MvcConfig extends WebMvcConfigurerAdapter {
#Override
public void addViewControllers(final ViewControllerRegistry registry) {
super.addViewControllers(registry);
registry.addViewController("/user/registration").setViewName("registration");
}
This works fine, and if I go to /user/registration (i.e. GET), I can see the registration form.
However if I now want to create a controller for POST requests at the same uri through:
#Controller
public class RegistrationController {
#RequestMapping(value = "/user/registration", method = RequestMethod.POST)
#ResponseBody
public GenericResponse registerUserAccount(#Valid final UserDto accountDto, final HttpServletRequest request) {
// some code
}
}
I get an error message at the /user/registration uri saying:
Request method 'GET' not supported
So it seems that my post controller is somehow overriding the GET controller which was working before. Why is that? Is it possible to make the two work together or do I have to write my own GET controller in the same way as my post controller?
spring mvc
#ModelAttribute("classname"),
How to make the argument "classname" a dynamic one ?
Whatever comes from view can get appended there.
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> { ... }
Can Spring MVC bind HTTP headers to Java classes?
I've got three headers, and I'd like to marshall them into a POJO, much like you'd do with a form or a request body.
I can see two ways that you could achieve this with Spring and request or prototype scoped beans.
It is worth first being clear on the different scopes of beans and how Spring creates proxies for different scopes if you are not already.
The first method uses Spring Expression Language to directly reference the current HttpServletRequest instance.
#Component
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyClass
{
#Value({#request.getHeader('headerName')})
private String myHeaderValue;
public String getMyHeaderValue()
{
return myHeaderValue;
}
}
An alternative is to simply inject the current HttpServletRequest as a constructor parameter:
#Component
#Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class MyClass
{
private String myHeaderValue;
#Autowired
public MyClass(HttpServletRequest httpServletRequest)
{
this.myHeaderValue = httpServletRequest.getHeader("headerValue");
}
public String getMyHeaderValue()
{
return this.myHeaderValue;
}
}
You can then inject this bean into your Controller or Service beans as needed:
#Controller
public class MyController
{
#Autowired
private MyClass myClass;
}
Either method should let you achieve what you want, you can pick which best suits your requirements and preferences.
Can I do the following in Spring MVC
Suppose I have the Base GenericController as follows with one request mapping "/list"
#Controller
public class GenericController<T>{
#RequestMapping(method = RequestMethod.GET, value = "/list")
public #ResponseBody List<T> getMyPage(){
// returns list of T
}
}
Below are my two controllers
#Controller(value = "/page1")
public class Page1Controller extends GenericController<Page1>{
}
#Controller(value = "/page2")
public class Page2Controller extends GenericController<Page2>{
}
Now will i be able to access the url "/page1/list" and "/page2/list" where first goes to Page1Controller and second goes to Page2Controller.
That's not possible and was already rejected, see SPR-10089. I think this would be somewhat confusing and in addition, it is very unlikely that those method behave exactly the same besides the different mapping.
But you can use delegation instead:
public class BaseController<T> {
public List<T> getPageList(){
// returns list of T
}
}
#Controller(value = "/page1")
public class Page1Controller extends BaseController<Page1>{
#RequestMapping(method = RequestMethod.GET, value = "/list")
public #ResponseBody List<Page1> getMyPage() {
return super.getPageList();
}
}
#Controller(value = "/page2")
public class Page2Controller extends BaseController<Page2>{
#RequestMapping(method = RequestMethod.GET, value = "/list")
public #ResponseBody List<Page2> getMyPage() {
return super.getPageList();
}
}
For those looking for something similar with Spring Framework 4.x, the class hierarchy provided by the OP is possible. A sample application is available on Github. It allows users to view a list of books or a list of magazines as JSON.
I have the following:
#Controller
#RequestMapping("/admin")
public class AdminController extends BaseHtmlController{
#Autowired
protected DeviceCustomerMap deviceCustomerMap;
#Autowired
protected CustomerDao customerDao;
String layout = "template/admin";
#RequestMapping(value="/login", method = RequestMethod.GET)
public String login(ModelMap model) {
model.addAttribute("meta", meta);
String view = "login";
return view;
}
}
public class AdminCustomerController extends AdminController{
#RequestMapping(value="/customer/mapping", method = RequestMethod.GET)
public String customerMapping(ModelMap model, #RequestParam(required=false) boolean refresh) throws Exception {
if (refresh){
deviceCustomerMap.initCustomerUrlMap();
}
model.addAttribute("meta", meta);
model.addAttribute("view", "customer/mapping");
model.addAttribute("customers", deviceCustomerMap.getCustomerMap());
return layout;
}
}
However, the extended controller doesn't resolve the requests, but when they're in the base controller, they're resolved just fine, I've poked around several threads but couldn't find a solution, any idea?
Is the problem that you are able to get a response when executing a request to the /admin/login resource, but not to /admin/customer/mapping resource, unless you move the customerMapping() method to the AdminController class?
The solution is to annotate the AdminCustomerController class with the #Controller annotation. Without a stereotype annotation (and appropriate component scanning), Spring will not recognise the class as a Spring bean.