My question is, isn't an object created from the model in spring? So why does it give an error when it tries to inject in the following program?
#Controller
public class ContactController {
private final ContactService service;
private Model model;
public ContactController(ContactService service, Model model) {
this.service = service;
this.model = model;
}
#GetMapping("contact")
public String displayPage() {
model.addAttribute("contact", new Contact());
return "contact";
}
}
UPDATE:
but this works! It means that the bean is created.(Of course, after we delete the model field from the constructor and the class)
#GetMapping("contact")
public String displayPage(Model model) {
model.addAttribute("contact", new Contact());
return "contact";
}
error:
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 1 of constructor in com.isoft.controllers.ContactController required a bean of type 'org.springframework.ui.Model' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.ui.Model' in your configuration.
The model is not a dependency to your controller. You need to return a new model, when the method is called. Otherwise, different requests would all see the same model (race-conditions, security issues, and all other kinds of nasty problems)
#Controller
public class ContactController {
private final ContactService service;
public ContactController(final ContactService service) {
this.service = service;
}
#GetMapping("contact")
public String displayPage() {
final Model model = new Model();
model.addAttribute("contact", new Contact());
return "contact";
}
}
When defining the model as dependency in the constructor, a bean of that type (and name) is looked up in the application context. Only a single instance in injected.
However, the model is request-specific and therefore needs to be injected into the handler method itself. Think #Scope("request").
#GetMapping("contact")
public String displayPage(final Model model) {
model.addAttribute("contact", new Contact());
return "contact";
}
More details can be found in the Spring Web MVC framework docs: 17.3 Implementing Controllers
Related
Im struggling on how to implement a API when the methods in my controller are returning a ModelAndView. Most tutorials I can find are returning ResponseEntities. Should i be making seperate API controllers which strictly handle the API calls under a /api mapping? (which i believe isn't RESTFUL practice). Or is it possible to handle my API calls in the same controller, even when making use of ModelAndView?
My controller looks as following:
#RestController
#RequestMapping("/dish")
public class DishController {
private final DishRepository dishRepository;
public DishController(DishRepository dishRepository) {
this.dishRepository = dishRepository;
}
#GetMapping
public ModelAndView list() {
Iterable<Dish> dishes = this.dishRepository.findAll();
return new ModelAndView("dishes/list", "dishes", dishes);
}
#GetMapping("{id}")
public ModelAndView view(#PathVariable("id") Dish dish) {
return new ModelAndView("dishes/view", "dish", dish);
}
#GetMapping(params = "form")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public String createForm(#ModelAttribute Dish dish) {
return "dishes/form";
}
#ResponseStatus(HttpStatus.CREATED)
#PostMapping
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView create(#Valid Dish dish, BindingResult result,
RedirectAttributes redirect) {
if (result.hasErrors()) {
return new ModelAndView("dishes/form", "formErrors", result.getAllErrors());
}
dish = this.dishRepository.save(dish);
redirect.addFlashAttribute("globalMessage", "view.success");
return new ModelAndView("redirect:/d/{dish.id}", "dish.id", dish.getId());
}
#RequestMapping("foo")
public String foo() {
throw new RuntimeException("Expected exception in controller");
}
#ResponseStatus(HttpStatus.OK)
#GetMapping("delete/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView delete(#PathVariable("id") Long id) {
this.dishRepository.deleteById(id);
Iterable<Dish> dishes = this.dishRepository.findAll();
return new ModelAndView("dishes/list", "dishes", dishes);
}
#ResponseStatus(HttpStatus.OK)
#GetMapping("/modify/{id}")
#PreAuthorize("hasRole('ROLE_ADMIN')")
public ModelAndView modifyForm(#PathVariable("id") Dish dish) {
return new ModelAndView("dishes/form", "dish", dish);
}
You should not use Model and View in RestController. The main goal of RestController is to return data, not views. Take a look here for more details: Returning view from Spring MVC #RestController.
#RestController is a shorthand for writing #Controller and #ResponseBody, which you should only use if all methods are returning an object that should be treated as the response body (eg. JSON).
If you want to combine both REST endpoints and MVC endpoints within the same controller, you can annotate it with #Controller and individually annotate each method with #ResponseBody.
For example:
#Controller // Use #Controller in stead of #RestController
#RequestMapping("/dish")
public class DishController {
#GetMapping("/list")
public ModelAndView list() { /* ... */ }
#GetMapping
#ResponseBody // Use #ResponseBody for REST API methods
public List<Dish> findAll() { /* ... */ }
}
Alternatively, as you've mentioned, you can use multiple controllers:
#Controller
#RequestMapping("/dish")
public class DishViewController { /* ... */ }
#RestController
#RequestMapping("/api/dish")
public class DishAPIController { /* ... */ }
i want to know why #Value property injection works on classes with #Service annotation but not on classes with #Bean within #Configuration annotated class.
Works means that the property value is not null.
This value is also injected into two other service which i see during debugging in DefaultListableBeanFactory.doResolveDependency. But i dont see the bean WebserviceEndpoint.
Configuration
#Configuration
public class WebserviceConfig {
// do some configuration stuff
#Bean
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
}
Webservice interface
#WebService(targetNamespace = "http://de.example/", name = "IWebservice")
#SOAPBinding(parameterStyle = SOAPBinding.ParameterStyle.BARE)
public interface IWebserviceEndpoint {
#WebMethod
#WebResult(name = "response", targetNamespace = "http://de.example/", partName = "parameters")
public Response callWebservice(#WebParam(partName = "parameters", name = "request", targetNamespace = "http://de.example/") Request request) throws RequestFault;
}
Webservice class
public class WebserviceEndpoint implements IWebserviceEndpoint {
#Value("${value.from.property}")
private String propertyValue;
}
application.yml
value:
from:
property: property-value
When does the injection of #Value happen in this case.
Basically propertyValue is null because Spring injects value after bean's creation.
So when you do:
#Bean
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
Spring creates a new instance with propertyValue=null.
You can initialize your instance attribue with #ConfigurationProperties
#Bean
#ConfigurationProperties(prefix=...)
public IWebserviceEndpoint webserviceEndpoint() {
return new WebserviceEndpoint();
}
Note that propertyValue should have a setter.
You have several ways to solve this problem, usually it's good to centralize properties in one utils class.
#Component
public class Configs {
#Value("${propery}"
String property;
String getProperty(){
return property;
}
}
And then:
#Bean
#ConfigurationProperties(prefix=...)
public IWebserviceEndpoint webserviceEndpoint() {
WebserviceEndpoint we = new WebserviceEndpoint();
we.setProperty(configs.getProperty())
return we;
}
Again there are many many different ways to solve this problem
What should be the scope of Controller and RestController in a Spring application? The default behavior if being Singleton. But will not having single bean cross over the request/responses from multiple clients, as our Controller will call some other bean (say #Service) which handles user specific request (like fetching user details from a DB or from another REST/SOAP service).
You have two options here:
Option 1 - In your service classes, ensure that you do not save any request specific details in instance variables. Instead pass them around as method arguments.
Ex:
The below code will corrupt the value stored in userId if there are simultaneous requests.
#Service
public class SomeService {
String userId;
public void processRequest(String userId, String orderId) {
this.userId = userId;
// Some code
process(orderId);
}
public void process(String orderId) {
// code that uses orderId
}
}
While, the following code is safe.
#Service
public class SomeService {
private String userId;
public void processRequest(String userId, String orderId) {
// Some code
process(userId, orderId);
}
public void process(String userId, String orderId) {
// code that uses userId and orderId
}
}
Option 2:
You can save request specific data in request scoped beans and inject them in your singletons. Spring creates a proxy for the injected request scoped beans and proxies calls to the bean associated with the current request.
#RequestScoped
class UserInfo {
}
#Service
class UserService {
#Autowired
private UserInfo userInfo;
public void process(String orderId) {
// It is safe to invoke methods of userInfo here. The calls will be passed to the bean associated with the current request.
}
}
Assume I am creating a PrinterService class that has a AbstractPrinter object. AbstractPrinter is subclassed by classes such as HPPrinter, FilePrinter etc.
The exact kind of printer object to be used is mentioned in the RequestParam object passed to my Controller (it is a request attribute).
Is there any way I can inject the right kind of concrete printer class using Spring?
All the other dependencies are injected using #Autowired annotation. How to inject this one?
You can create and load a factory of AbstractPrinter objects during container startup as shown below and dynamically call the respective the AbstractPrinter's print() (or your own method) based on the input parameter (comes from controller) to the service.
In the below code for PrinterServiceImpl class, the main point is that all of the List<AbstractPrinter> will be injected by Spring container (depends upon how many implementation classes you provide like HPPrinter, etc..). Then you will load those beans into a Map during container startup with printerType as key.
#Controller
public class YourController {
#Autowired
private PrinterService printerService;
public X myMethod(#RequestParam("input") String input) {
printerService.myServiceMethod(input);
//return X
}
}
PrinterServiceImpl class:
public class PrinterServiceImpl implements PrinterService {
#Autowired
private List<AbstractPrinter> abstractPrinters;
private static final Map<String,AbstractPrinter> myPrinters = new HashMap<>();
#PostConstruct
public void loadPrinters() {
for(AbstractPrinter printer : abstractPrinters) {
myPrinters.put(printer.getPrinterType(), printer);
}
}
//Add your other Autowired dependencies here
#Override
public void myServiceMethod(String input){//get input from controller
AbstractPrinter abstractPrinter= myPrinters.get(input);
abstractPrinter.print();//dynamically calls print() depending on input
}
}
HPPrinter class:
#Component
public class HPPrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "HP";
}
#Override
public void print() {
// Your print code
}
}
FilePrinter class:
#Component
public class FilePrinter implements AbstractPrinter {
#Override
public String getPrinterType() {
return "FILE";
}
#Override
public void print() {
// Your print code
}
}
You could create a dedicated PrinterService instance per AbstractPrinter concrete class. For example you could achieve this using Spring configuration which follow the factory pattern:
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private HPPrinter hpPrinter;
#Autowired
private FilePrinter filePrinter;
#Bean
public PrinterService hpPrinterService() {
return new PrinterService(hpPrinter);
}
#Bean
public PrinterService filePrinterService() {
return new PrinterService(filePrinter);
}
public PrinterService findPrinterService(PrinterType type){
if (type == HP)
return hpPrinterService();
....
}
}
Then in your controller, inject PrinterServiceConfiguration then call findPrinterService with the right printer type.
Don't forget to add PrinterServiceConfiguration at your configuration #Import.
If the list of printer is dynamic you could switch to prototype bean :
#Configuration
public class PrinterServiceConfiguration {
#Autowired
private List<AbstractPrinter> printers;
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public PrinterService createPrinterService(PrinterType type){
return new PrinterService(findPrinterByType(type));
}
private Printer findPrinterByType(PrinterType type) {
// iterate over printers then return the printer that match type
// throw exception if no printer found
}
}
I made web service with spring mvc(version 4).
This service used token in http header for authorization.
I want to value in http header bind to field in model class auto.
Is it possible? How can I do?
(See below code and comment)
Controller
#Controller
#RequestMapping(value = "/order")
public class OrderController {
private static final Logger logger = LoggerFactory.getLogger(OrderController.class);
#Autowired
private OrderService orderService;
#RequestMapping(value = "/")
#ResponseBody
public List<Order> getAll() throws Exception {
// I want to remove two line below with auto binding (userToken field in model)
// in all controller using token value
String token = request.getHeader("X-Auth-Token"); // remove~
orderService.setUserToken(token); // remove~
orderService.getAllbyUser()
return items;
}
}
Model(Service)
#Service
public class OrderService {
//#Autowired - is it possible?
private String userToken;
public String setUserToken(String userToken)
{
this.userToken = userToken;
}
public List<Order> getAllbyUser() {
String userId = userMapper.getUserId(userToken);
List<Order> list = orderMapper.getAllbyUser(userId);
return list;
}
}
#Autowire is for Spring to inject beans one to another. If you want to inject a String to a bean you can with the org.springframework.beans.factory.annotation.Value annotation.
For example:
#Value("${user.token}")
private String userToken;
This will make Spring search of the user.token in the VM args and other places (which I don't remember and in some specific order).
But again, as said in my initial comment, from the code you show here it seems to be an error setting this field as it is context specific and the #Service (by default) indicates that the OrderService is a singleton.
In order to read a header value from request, you can use #RequestHeader("X-Auth-Token") in your controller, as shown below:
#RequestMapping(value = "/")
#ResponseBody
public List<Order> getAll(#RequestHeader("X-Auth-Token") String token) throws Exception {
orderService.setUserToken(token); // remove~
orderService.getAllbyUser()
return items;
}
Hope this helps you.