Spring MVC URI mapping configuration - java

I use WebApplicationInitializer to initialize Spring Dispatcher, this is how it was set.
public class MyApplicationInitializer implements WebApplicationInitializer{
#Override
public void onStartup(final ServletContext servletContext) throws ServletException {
final XmlWebApplicationContext appconfig = new XmlWebApplicationContext();
appconfig.setConfigLocation("/WEB-INF/app-servlet.xml");
final ServletRegistration.Dynamic dispatcher =
servletContext.addServlet("my_app", new DispatcherServlet(appconfig));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("/app/*.do");
System.out.println("Here I am ");
}
and I intention is to use controller class as namespace as
#Controller
#RequestMapping("app")
public class TestingController{
#RequestMapping("/{act}.do")
public ModelAndView getIndex(){ return new ModelAndView("/index.jsp"); }
}
it seems reasonable, but when I try to run this artifact from tomcat I got this error message.
Caused by: java.lang.IllegalArgumentException: Invalid <url-pattern> /app/*.do in servlet mapping
Question is why i can't define dispatching URI like this and if I want to achieve class URI + method URI for a dispatcher how should I set it?

It looks like your endpoints are formulated for struts. Spring does not support this. Probably because it is a non-standard REST technique.
The more RESTful way of doing this would be to design the URI like this:
/app/do/{action}
Or:
/app/do?action={action}
However, you can use regex PathVariables like this:
/app/{action:[A-Za-z\\d]+}.do
Then, within the method block evaluate the request path and redirect to different controllers conditionally based on pattern matching.
Here is the example I was able to get working:
package com.app.controller;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.servlet.ModelAndView;
#Controller
#RequestMapping(value = "/test")
public class TestController
{
#RequestMapping(value = "/{action:[A-Za-z\\d]+}.do")
public #ResponseBody
ModelAndView getDo(#PathVariable String action)
{
return new ModelAndView("redirect:/test/action/" + action);
}
#RequestMapping(value = "/action/a")
public #ResponseBody
ModelAndView getAction()
{
return new ModelAndView("/index.jsp");
}
}
The getAction method could be in another controller if you wanted it to, but by using regex and redirect I think you can accomplish what you are trying to do.

Related

Value cannot injected into service class spring boot

I already try to search through stackoverflow, and I don't think I find the solution I want...
Also I try to use answer https://stackoverflow.com/questions/45970442/spring-boot-value-returning-null and still doesn't work...
Here is my controller class
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
GiftCardServiceImpl giftCardService = new GiftCardServiceImpl("");
return giftCardService.activate(request);
}
}
And here is my service class
package com.vincent.springoauth.service;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
#Service
#Log4j2
public class GiftCardServiceImpl {
private final String baseEndpoint;
public GiftCardServiceImpl( #Value("${webserviceurl}")String baseEndpoint){
this.baseEndpoint = baseEndpoint;
}
public String accessToken() {
log.info("Access oauth token url address: " + baseEndpoint);
// will be use that base endpoint to manipulate stuff later
return "abcdefg";
}
public GiftCardResponse activate(GiftCardRequest request) {
log.info("Calling token ...");
accessToken();
log.info("Incomm Pre Auth Service");
// Generate preAuth request;
//RetailTransactionGenericRequestWrapper retailTransactionGenericRequest = buildRequest(request);
//log.info("RetailTransactionGenericRequest: " + retailTransactionGenericRequest);
GiftCardResponse response = GiftCardResponse.builder().responseCode("0").responseMessage("Success").build();
return response;
}
}
And in my application.properties I have following line webserviceurl=https://localhost/giftcard
The issue that in my service class the webserviceurl return null. How can I fix this?
In your controller you are creating your own instance of the service and so Spring is unaware of it and cannot inject the value. Your service class is annotated as a Service so Spring will create an instance, which will have the value injected but that is not the instance that your controller is using.
Instead you should declare that service as an instance variable in your controller and use either Autowire annotation on that instance variable or use constructor autowiring to ensure that the bean created by Spring is the one that is used by your controller.
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
private GiftCardServiceImpl giftCardService;
#Autowired
public GiftCardController(GiftCardServiceImpl giftCardService) {
this.giftCardService = giftCardService;
}
#Value annotation uses for injecting values into fields in Spring-managed beans. In your example, you create GiftCardServiceImpl on your own and Spring cannot control the creation and inject the webserviceurl value from application.properties. You can change GiftCardController to allow Spring to do this.
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
private final GiftCardServiceImpl giftCardService;
public GiftCardController(GiftCardServiceImpl giftCardService) {
this.giftCardService = giftCardService;
}
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
return giftCardService.activate(request);
}
}
Try to inject GiftCardServiceImpl via Spring DI like in the example below
package com.vincent.springoauth.controller;
import com.vincent.springoauth.model.GiftCardRequest;
import com.vincent.springoauth.model.GiftCardResponse;
import com.vincent.springoauth.service.InCommGiftCardServiceImpl;
import org.springframework.web.bind.annotation.*;
#RestController
#RequestMapping("/api/gift-card")
public class GiftCardController{
#Autowired
private GiftCardServiceImpl giftCardService;
#PostMapping("/activate")
public #ResponseBody
GiftCardResponse activate(GiftCardRequest request) {
return giftCardService.activate(request);
}
}
The #Value annotation only works for Spring Beans, when you create the class via simple new keyword. Spring doesn't catch that should inject property in the constructor.

My "/" requestMapping sent me a WhiteLabel "/" error

I want to simply map my home page with a Spring Controller like this :
package com.douineau.testspringboot.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
#Controller
#RequestMapping("/")
public class HomeController {
#GetMapping
// #ResponseBody
public String home() {
return "index";
}
}
I also have index.html in src/main/webapp folder.
But the application does not recognize the mapping, whereas if comment this all, the app recognizes that it is my home page.
What am I missing?
You mention you placed the index.html in your src/main/webapp but as I remember spring boot default configuration, it should be under src/main/resources/templates if it should be handled through your #Controller.
Everything in webapp is exposed "as-is" for eg. assets
You can change #Controller to #RestController.
Your code should be like this.
#RestController
public class HomeController {
#RequestMapping(value = "/", method = RequestMethod.GET)
public String home() {
return "index";
}
}
or
#RestController
#RequestMapping(value = "/")
public class HomeController {
#GetMapping("")
public String home() {
return "index";
}
}

Immutable spring request scoped beans

I'd like to be able to instantiate a request-scoped bean which is also immutable by using constructor parameters.
Something like the following (which of course doesn't work):
RequestContextFactory.java
package org.springframework.samples.mvc.requestscope;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.annotation.RequestScope;
#Configuration
public class RequestContextFactory {
#Bean
#RequestScope
public RequestContext getRequestContext(TestBean bean) {
return new RequestContext(bean);
}
}
MyController.java
package org.springframework.samples.mvc.requestscope;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
#Controller
public class MyController {
#Autowired
RequestContextFactory requestContextFactory;
#RequestMapping("/my")
public #ResponseBody String simple(TestBean bean) {
RequestContext requestContext = requestContextFactory.getRequestContext(bean);
return "Hello world!";
}
}
Spring complains that it cannot autowire a TestBean bean to create RequestContext.
How can I achieve immutability of a request-scoped bean which needs constructor parameters only known in the controller?
I'd like to be able to inject RequestContext into other beans (either request scope or other scopes). Is this an antipattern? Should something like RequestContext (or any other object with request lifecycle) be in the signature of all call hierarchy under the controller?
Note:
I thought of as a solution like for example having a RequestContext with default constructor and an init(...) method which can be called only once (would throw the second time). I don't like it.

How exclude some paths from #GetMapping spring annotation?

I use Spring Boot and my main controller handles all top urls:
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
#Controller
public class IndexController {
#GetMapping({"/*"})
public ResponseEntity<String> handleIndex() {
// ...
}
}
And for some reasons I want exclude path /exclude_path from handling of my controller.
Something like this:
#GetMapping(include={"/*"}, exclude={"/exclude_path"})
Could you please answer how I may do this?
Firstly, /* is a bad practice. If at all you want to do it, your best option would be to introduce an Aspect for your controller. And filter it out there.
Something like this will do:
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMapping() {}
#Pointcut("within(blah.blah.package.controller.*)")
public void myController() {}
#Around("requestMapping() && myController()")
public Object logAround(ProceedingJoinPoint joinPoint, RequestMapping requestMapping) throws Throwable {
String url = requestMapping.value();
// check the url and based on that do
// joinPoint.proceed();
}
You should remove “/*” as it will except all the requests. Provide only the path you want so you don’t need /excludepath.

How to map all calls to *.html in a springboot controller?

I am trying to serve dynamic html pages with Thymeleaf template and springboot api. This is the scenario that I want to achieve.
When someone makes the following request: hostname/client then the application would return a Json object on the other hand if someone makes this request: hostname/client.html, this request is catch in a different controller so that I can manipulate the view that will be returned.
Client Controller
This class is working as expected, it is returning a Json Object
#RestController
public class ClientController {
#Autowired
public ClientService clientServiceImp;
#RequestMapping("/client")
public Client get(#RequestParam(value="name", defaultValue="World") String name){
return clientServiceImp.getClient(name);
}
}
Home Controller
This class's method does not map calls to *.html
#Controller
public class HomeController {
#RequestMapping(value={"/*.html"}, produces="text/html")
public String getIndex(Model model, HttpServletRequest request){
// I will set here the thymeleaf fragment location based on the resource requested.
return "index";
}
*This is the error I am receiving after calling hostname/client.html
Whitelabel Error Page
*This application has no explicit mapping for /error, so you are seeing this as a fallback.
Mon Jan 25 16:04:56 BRST 2016
There was an unexpected error (type=Not Acceptable, status=406).
Could not find acceptable representation**
Springboot basic configuration
#SpringBootApplication(scanBasePackages = {"com.serviceira"})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
It is important to point that I did not set any other configuration for the application. Thanks in advance for the help.
The reason ClientController is working properly is because you marked it as a #RestController, you are telling Spring to write the response to the html page directly.
However, your HomeController is not finding the mapping because you have not set up the servlet mapping yet.
If you are in Java config:
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
public class WebAppInitializer implements WebApplicationInitializer {
#Override
public void onStartup(ServletContext servletContext) throws ServletException {
WebApplicationContext context = getContext();
servletContext.addListener(new ContextLoaderListener(context));
ServletRegistration.Dynamic dispatcher = servletContext.addServlet("DispatcherServlet", new DispatcherServlet(context));
dispatcher.setLoadOnStartup(1);
dispatcher.addMapping("*.html"); // HERE YOU ARE SETTING THE .html mapping
}
private AnnotationConfigWebApplicationContext getContext() {
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.setConfigLocation("your package name here");
return context;
}
}
If you are using XML, besides setting up your servelt, you need a mapping like this:
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/config/servlet-config.xml</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>*.html</url-pattern>
</servlet-mapping>
Problem Solved
I solved the problem by adding different "produces" argument for for each controller's RequestMapping annotation, besides to change the value from the getIndex from /.html to .html.
HomeController
#RequestMapping(value={"*.html"}, produces="text/html")
ClientController
#RequestMapping(value="/client" produces="application/json")
Now, every request that contains *.html is received and treated by the getIndex method from the HomeController class and the others are handled by its own controller. For example:
request: GET: localhost/client
Controller
#RequestMapping(value="/client" produces="application/json")
public Client get(#RequestParam(value="name", defaultValue="World") String name){
return clientServiceImp.getClient(name);
}
request: GET: localhost/client.html
Controller
#RequestMapping(value={"*.html"}, produces="text/html")
public String getIndex(Model model, HttpServletRequest request){
// I will set here the thymeleaf fragment location based on the resource requested.
return "index";
}

Categories