Can someone explain why replacement of RestController annotation with Component has no any visible effect in my case?
My controller:
#RestController
#RequestMapping("/api/employees")
public class EmployeeController {
#Autowired
EmployeeService employeeService;
#PostMapping("")
public Employee saveEmployee(#Valid #RequestBody Employee employee) {
return employeeService.save(employee);
}
...
This works in the same way:
#Component
#ResponseBody
#RequestMapping("/api/employees")
public class EmployeeController {
...
The not-in-depth description of individual annotations would be:
#RequestMapping registers your class for servlet mapping.
#Component registers your class for dependency injection.
#ResponseBody will add the return value of the method to the HTTP response body.
1st case - #RestController registers your class for DI and adds #ResponseBody annotation to it, #RequestMapping registers class for servlet mapping.
2nd case - #Component registers your class for DI and you added a manual #ResponseBody annotation to it, #RequestMapping registers class for servlet mapping.
Both of the cases above do the same thing, so that's why it 'just works'.
Related
As part of my university project I was asked to implement a simple spring-boot app(backend only) which can communicate with Postman through HTTP requests.
The project built in controller-service-repository architecture and conatins only 1 Entity(Post object with string content) and 2 end-points(create new post, get all posts).
I know there is a few ways to configure beans in spring-boot:
with an external XML file.
With #Configuration annotation & #Bean annotation
With #Component annotation(#RestController,#Service, #JpaRepository)
The 3rd way working great but i was asked to implement the 2nd way and I'm really struggling to get this working.
Im getting:
ServletException: Circular view path [post]: would dispatch back to the current handler URL [/post] again. Check your ViewResolver setup!
Tried to explore about this exception and i did manage to "solve" it by adding this maven dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
<version>2.5.2</version>
</dependency>
which led to:
"org.thymeleaf.exceptions.TemplateInputException: Error resolving template [post], template might not exist or might not be accessible by any of the configured Template Resolvers"
what am i doing wrong ?
Configuration class:
#Configuration
#EnableJpaRepositories(basePackages = {
"com.example.microblog.post.domain.repository"
})
public class ApplicationBeans {
#Bean
public PostController postController(PostService postService){
return new PostController(postService);
}
#Bean
public PostService postService(){
return new PostService();
}
}
Controller class:
#AllArgsConstructor
#RequestMapping(path = "post")
public class PostController {
#Autowired
private PostService service;
#CrossOrigin(origins = "http://localhost:4200")
#PostMapping("")
public PostEntity create(#RequestBody PostDto dto) {
return service.create(dto);
}
#GetMapping("/all")
#CrossOrigin(origins = "http://localhost:4200")
public List<PostEntity> getAll() {
return service.getAll();
}
}
Service Class:
#Transactional
public class PostService {
#Autowired
private PostRepository PostRepository;
public PostEntity create(PostDto dto){
PostEntity newPost = new PostEntity(dto.getContent());
return PostRepository.save(newPost);
}
public List<PostEntity> getAll(){
return PostRepository.findAll();
}
Repository class:
public interface PostRepository extends JpaRepository<PostEntity,Long> {}
For second approach, when you create a Bean, try not to have #Component/#Controller ... on the class for which you create the bean
#Configuration
public class AppConfig {
#Bean
public TransferService transferService() {
return new TransferServiceImpl();
}
}
You can continue to autowire them like in third approach, try not to keep beans of same name
Your error points in the direction, that your Controller has difficulties to resolve the answer of your Service to a valid JSON response.
Note that #RestController is just a convenient way to add #Controller and #ResponseBody annotation. When you just add a #Bean annotation you are not adding either #Controller or #ResponseBody.
If you want to use the Controller class without using these Annotations you need to implement the functionality that these classes provide.
However I really see no way, why option 2 would be used for a Controller class. If you want to use it for a #Service class (which is doing the same as #Component) you can use the approach that Ravi suggested.
I want to call a common service for all the controllers to get a commmon ModelAndView object with some common objects inside it.
So I created a superclass for all the controllers- BaseController, and I am initiating the common model object inside the constructor of BaseController by calling the a method, initCommonData which uses a #Autowired bean CommonDataService, which is not present at the construction time of object and returns null, so what I should do to get #autowired dependency inside constructor.
FYI - I am using this common servie and common data to get some commod data which will be used on each jsp in header and footer of the site.
So if there is some another way of doing this without calling the common service in each controller method, in each controller please suggest.
Here is my code -
BaseController
#Controller
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
public BaseController() {
this.initCommonData();
}
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}
subclass controller -
#Controller
public class HomeController extends BaseController {
#Autowired
CategoryService categoryService;
#Autowired
CompanyService companyService;
#RequestMapping(value = { "", "/", "home" })
public ModelAndView homePage() {
model.setViewName("home");
.
.
.
model.addObject("name", value);
model.addObject("name2", value2);
return model;
}
CommonServiceClass -
#Service
public class CommonDataService {
#Autowired
CompanyService companyService;
#Autowired
CategoryService categoryService;
#Cacheable
public List<Category> getHeaderData(){
return categoryService.getTopCategoryList();
}
#Cacheable
public List<Company> getFooterData(){
return companyService.getTopCompanyList();
}
Please suggest if there is any other good way of doing this, getting common data from server to jsp.
Whatever #Andreas has suggested is the best solution i.e., mark your BaseController as abstract and use #Postconstruct. This makes perfect scense because BaseController itself does not own any url mappings in your case, so do not mark it as #Controller
Because of any reason, if you are looking for other options, you can consider marking your BaseController as #Component and use #Postconstruct for initCommonData so that this method will be called automatically once the BaseController bean has been loaded by the spring container:
#Component
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
#Postconstruct
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}
}
First, remove #Controller from your base class. You might even make the class abstract to help indicate/document that it must be subclassed.
Next, don't call initCommonData() from the constructor. Spring cannot inject field values until after the object is created, so there is no way for Spring to wire in commonDataService before constructor completes.
Instead, annotate initCommonData() with #PostConstruct:
public class BaseController {
#Autowired
private CommonDataService commonDataService;
protected ModelAndView model;
#PostConstruct
public void initCommonData(){
this.model = new ModelAndView();
this.model.addObject("headerData",commonDataService.getHeaderData());
this.model.addObject("footerData",commonDataService.getFooterData());
}
#Service and #Controller annotations are used for automatic bean detection using classpath scan in Spring framework.
So I tried below four use cases but I am bit confused with case 4 as it gives me 404 error.
use case 1: #Controller & class level #RequestMapping
#Controller
#RequestMapping(value = "/home")
public class MyController
{
...
}
Result:
http://localhost:8080/MyApp/home/helloWorld/va ---> Hello va
use case 2: #Service & class level #RequestMapping
#Service
#RequestMapping(value = "/home")
public class MyController
{
...
}
Result:
http://localhost:8080/MyApp/home/helloWorld/va ---> Hello va
use case 3: #Controller & no class level #RequestMapping
#Controller
public class MyController
{
...
}
Result:
http://localhost:8080/MyApp/helloWorld/va ---> Hello va
use case 4: #Service & no class level #RequestMapping
#Service
public class MyController
{
...
}
Result:
http://localhost:8080/MyApp/helloWorld/va ---> error 404
code:
#Service
#RequestMapping(value = "/home")
public class MyController
{
#RequestMapping(value = "/helloWorld/{Name}", method = RequestMethod.GET)
public #ResponseBody String HelloWorld(#PathVariable("Name") String name)
{
return "Hello "+name;
}
}
So in short when using #Service, if I dont use #RequestMapping at class level am getting 404 error.
A bean is considered a request handler if it has either #Controller or #RequestMapping at class level.
One of my classes deals with HttpServletRequest and is a component like this:
#Component
public class AuthorizationService {
#Autowired
HttpServletRequest request;
public Boolean authorize(Integer fetId) {
...play with headers, db and other stuff...
}
and is used somewhere else like this
public class CarRestController {
#Autowired
CarService service;
#Autowired
AuthorizationService authorizer;
#RequestMapping(method = RequestMethod.GET)
public List<Car> get()throws Exception {
authorizer.authorize(666);
...
return cars;
}
My worry is that since AuthorizationService is a #component, it will be a singleton by default and therefore there can only be one request that will be swapped by newer ones coming as it is processed.
Should I do this to solve the problem?
#Component
#Scope("prototype")
public class AuthorizationService {
Many Thanks
Remove the #Scope on the service and don't worry, your controller is also a singleton (because it's managed by spring).
BTW: you are missing a #Controller on your controller
Some reading:
Spring framework reference: The IoC container
Must Spring MVC Classes be Thread-Safe
Spring takes care of request scoped object like HttpServletRequest automatically. Remove #Scope and you should be fine.
Does it make sense to have an annotated (#Controller) Abstract class in Spring MVC driven container, basically would like to place most of the reusable methods such as exception handlers in the Abstract class and extend that with the base class, so that don't have to repeat the same boilerplate code. For example.
Abstract Controller Class:
#Controller
abstract class AbstractExternalController {
#ExceptionHandler(NoSuchRequestHandlingMethodException.class)
#ResponseStatus(value = HttpStatus.NOT_FOUND)
public #ResponseBody ResponseModel handleNotFoundException() {
final ResponseModel response = new ErrorModel();
response.setStatus("404");
response.setMessage("Resource Not Found");
return response;
}
...
}
Base Controller Class
#Controller
class ExternalControllerXXX extends AbstractExternalController {
...
}
It is unnecessary to annotate your AbstractExternalController class with the #Controller anntation, although, leaving it there will not break anything. Regardless of whether or not you have the #Controller annotation, you certainly can have the method annotations, and they will work. Your ExternalControllerXXX extending it will be added to the application context (because it is annotated with a streotype annotation), and the #ExceptionHandler and #ResponseStatus annotations will be honored.