Nested #RestController (inner class) - any side effects? - java

I have a class annotated with #RestController. Inside that class I have an inner class, which is again annotated with #RestController. Is this fine or are there any unintended side-effects with regards bean creation (using Spring)?
#RestController
#RequestMapping("/api/v1/internal")
public class ClientController {
#GetMapping("/clients/{id}")
public ClientDTO.OutDetail findOne(#PathVariable String id) {
return clientService.findOne(id, ClientDTO.OutDetail.class);
}
#RestController
#RequestMapping("/api/v1/external")
public class ExternalApi {
#GetMapping("/clients/{id}")
public ClientDTO.OutDetailExt findOne(#PathVariable String id) {
return clientService.findOne(id, ClientDTO.OutDetailExt.class);
}
}
}

You can do this. First remove public from inner class and now your URL will be http://ip:port/appName/api/v1/external/clients/{id}. But my suggestion is that please create different RestController class so you can easily track it.

Related

Springboot: Compilation error while autowiring the parent class

All,
I am trying to autowire a parent class which has a child class with additional methods. As far as I know, Spring will inject an instance of a child class to a parent class reference. To be sure, I even added a qualifier, but I am getting a compilation error. Please advice on how to resolve this.
Here is the code
Parent class:
#Service
Class Student {
void doSomething (int i) {
}
}
Child class:
#Service
#Qualifier("childclass")
Class ScholarStudent extends Student {
void doAnotherThing (int i) {
}
}
Controller:
#RestController
Class StudentController {
#Autowired
#Qualifier("childclass")
Student student;
#GetMapping(value = "/students/{studentId}")
public restCallFromFE (#PathVariable int studentId) {
student.doAnotherThing (studentId);
}
}
I get a compilation error here stating
The method doAnotherThing() is undefined for the type Student.
(Will try to attach an image here)
I can understand this is because the method is not defined in the parent class. But how do I resolve this with autowiring?
Thanks in advance to everyone who responds.
You cannot call the method of a child class from the reference to a base class that's not declared in the base class in any way.
To fix this you'll have to make your Student an interface or an abstract class that declares all the methods the child classes will have (and maybe implements some of them itself). If you don't need all of those methods in a single Student interface, just split it into multiple interfaces and autowire those which have the methods required for the concrete usage.
Try this:
#RestController
Class StudentController {
#Autowired
#Qualifier("childclass")
Student student;
#GetMapping(value = "/students/{studentId}")
public restCallFromFE(#PathVariable int studentId) {
if (student instanceof ScholarStudent) {
var scholar = (ScholarStudent)student;
scholar.doAnotherThing(studentId);
}
}
}
Note that you may want to consider looking at polymorphic methods (eg: overriding doSomething(int i) instead of creating a separate method) if you don't want consumers to know the specialised class type.

Inject multiple beans of the same type and automatically select between them based on generic type

I have two (more in the future) implementations of ImportantService – VeryImportantService and LessImportantService:
public interface ImportantService<T extends ImportantRequest> {}
#Service
public class VeryImportantService implements ImportantService<VeryImportantRequest> {}
#Service
public class LessImportantService implements ImportantService<LessImportantRequest> {}
And then I have a controller, in which I want to inject all of the implementations of ImportantService:
#RequiredArgsConstructor
#RestController
#RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
#PostMapping
public ResponseEntity<ImportantResponse> create(#RequestBody #Valid T request) {
// very important code here
}
}
Obviously, such king of injecting fails:
UnsatisfiedDependencyException: Error creating bean with name 'importantController' defined in file ...
...
Consider marking one of the beans as #Primary, updating the consumer to accept multiple beans, or using #Qualifier to identify the bean that should be consumed
What I want is:
Inject all of the implementations of ImportantService, and then, based on the T automatically select required bean. I know I can add method to ImportantService, which returns the type that implementation works with and then inject ImportantService as List<ImportantService> importantServices and then filter like this:
importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.ifPresent(importantService -> importantService.doImportantJob(request));
BUT! I have hundreds of services to refactor like this and I really don't want to write additional logic to controllers.
I know about #Conditional annotation and Condition interface, but AFAIK there's no way to make them do what I want.
Why not implement the proxy pattern?
example:
#Service
#Primary
#RequiredArgsConstructor
public class ImportantServiceProxy implements ImportantService<T extends ImportantRequest> {
private final List<ImportantService> importantServices;
private ImportantService getImportantService(ImportantRequest request){
return this.importantServices.stream()
.filter(importantService -> importantService.getType().equals(request.getClass()))
.findFirst()
.get();
}
public void doImportantJob(ImportantRequest request){
this.getImportantService(request).doImportantJob(request);
}
}
Then in your controller you can call the function without check the type.
#RequiredArgsConstructor
#RestController
#RequestMapping("/api/important")
public class ImportantController<T extends ImportantRequest> {
private final ImportantService<T> importantService;
#PostMapping
public ResponseEntity<ImportantResponse> create(#RequestBody #Valid T request) {
importantService.doImportantJob(request);
}
}
what you want is a list of beans which are of type ImportantService
so you have to declare a variable like this.
final List<ImportantService> importantServices;
demoController(List<ImportantService> importantServices) {
this.importantServices = importantServices;
}

How to set a different base url for each controller

Adding the line spring.data.rest.basePath=/api to my application.properties file makes it so every endpoint starts with /api.
On top of that, I want each controller to "increment" this url. For example, assume I have 2 different controllers, CustomerController and ProviderController.
If I define in both this function:
//CustomerController
#Autowired
private CustomerService service;
#GetMapping("/getById/{id}")
public Customer findCustomerById(#PathVariable int id) {
return service.getCustomerById(id);
}
//ProviderController
#Autowired
private ProviderService service;
#GetMapping("/getById/{id}")
public Provider findProviderById(#PathVariable int id) {
return service.getProviderById(id);
}
I want the first one to be /api/customer/getById/{id} and the second /api/provider/getById/{id}.
Is there any way to achieve this without manually having to type it on each annotation?
Thank you.
Yes, you can extract the common part of the path and put it into #RequestMapping on your controller:
#RestController
#RequestMapping("/api/customer")
public class CustomerController {
// ...
#GetMapping("/getById/{id}")
public Customer findCustomerById(#PathVariable int id) {
return service.getCustomerById(id);
}
}
and
#RestController
#RequestMapping("/api/provider")
public class ProviderController {
// ...
#GetMapping("/getById/{id}")
public Provider findProviderById(#PathVariable int id) {
return service.getProviderById(id);
}
}
You can use the #RequestMapping("/example/url") Annotation on your controller.
#Controller
#RequestMapping("/url")
class HomeController() {}

Parameter 3 of constructor in required a bean of type 'package' that could not be found

I have call method which is defined in NoteService interface, the implementation of this method is in NoteImpl class. I am trying to access this method from Refresh class, but I am getting this error
Parameter 3 of constructor in com.new.macro.rest.Refresh required a bean of type 'com.new.macro.unity.processorService' that could not be found.
Action:Consider defining a bean of type 'com.new.macro.unity.NoteService' in your configuration.
I need help resolving this error.
Here is my Refresh class from where I try to access call method from NoteImpl class
package com.new.macro.rest;
#Component
public class Refresh implements BaseService {
private final NoteService<Inbuild> iNoteService;
public Refresh(final NoteService iNoteService) {
this.iNoteService = iNoteService;
}
#PUT
public String firstRefresh() {
iNoteService.call(Types);
return RefreshStatus.STARTED.toJsonResponse();
}
Here is NoteImpl class with call method functionality
#Configuration
public abstract class NoteImpl implements NoteService{
private static final Logger LOGGER = LogManager.getLogger(NoteImpl.class);
private final RestTemplate restTemplate;
private final String Url;
public NoteImpl( RestTemplate restTemplate,#Value("${url}") String Url){
this.restTemplate = restTemplate;
this.Url = Url;
}
public void call(Set<Inbuild> Types, String Url) {
Set<String> results = new HashSet<>();
\\ Remaining functionality
}
}
Here is the interface
package com.new.macro.unity;
import java.util.Set;
public interface NoteService<T> {
void call(Set<? extends T> Types);
}
Add #Autowired annotation on head of constructor:
#Autowired
public Refresh(final NoteService iNoteService) {
this.iNoteService = iNoteService;
}
Then you will get a bean from NoteService.
Assuming all these classes are classpath scanned:
- NoteImpl shouldn't be abstract
- NoteImpl should be annotated as #Component instead of #Configuration. Configuration classes are the ones that create beans
To fix this, don't keep NoteImpl as abstract class - abstract classes can't be instantiated. That should resolve the problem. Though #Configuration will work(as it's meta annotated with #Component), #Service makes sense here.
Also, you don't need #Autowired at constructor injection - that is optional. (So the other answer won't solve the problem)

How to provide a #ControllerAdvice conditional on bean?

I'm creating a framework that I'd like to reuse in all of my future projects. If the sub project does not define a #ControllerAdvice, I want my framework to automatically initialize a default Advice for exception handling.
public class ExHandler implements IAdvice {
ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.NOT_FOUND)
#ResponseBody
public ErrorDTO default(xception e) {
return new ErrorDTO();
}
}
I tried as follows, but does not work:
#Configuration
static class MyConfig {
#ControllerAdvice
#ConditionalOnMissingBean(IAdvice.class)
static class AdviceExHandler extends ExHandler {
}
}
In Sub project:
#ControllerAdvice
public class SubHandler extends ExHandler {
}
Result: It works. BUT: if the subproject does not define the ExHandler, the bean is not initialized at all! But why?
Sidenote: I'm trying to prevent mutliple ControllerAdvice because error handling depends on the order of methods inside the exception handler. Thus I don't want to mess the order by introducing multiple classes.
You may use #ConditionalOnMissingBean(annotation = ControllerAdvice.class) to configure condition on missing bean with ControllerAdvice annotation.
#ControllerAdvice
public abstract class FrameworkAdvice {
...
}
And conditionally configure it:
#Configuration
#ConditionalOnMissingBean(annotation = ControllerAdvice.class)
public class FrameworkAdviceConfig {
#Bean
public FrameworkAdvice frameworkAdvice() {
return new FrameworkAdvice() {
};
}
}
And if there is another controller advice in project, it will be used instead.
#ControllerAdvice
public class CustomAdvice {
...
}
You could use it without wrappers. Just declare #ControllerAdvice annotated class as following in Kotlin:
#ControllerAdvice
#ConditionalOnMissingBean(annotation = [ControllerAdvice::class])
class ExceptionHandler {
#ResponseStatus(HttpStatus.EXPECTATION_FAILED)
#ResponseBody
#ExceptionHandler
fun handleProcessingException(e: Exception): ErrorDto {
return ErrorDto()
}
}
And just declare it in spring.factories file if you're doing it for starter:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
ru.raiffeisen.ecommerce.controller.ExceptionHandler
That's all. If there is no #ControllerAdvice annotated classes, then will be used class from configuration.

Categories