How to provide a #ControllerAdvice conditional on bean? - java

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.

Related

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;
}

Spring inherited #Component with constructor arguments

I have a service which needs to create Agents on the runtime. Agents inherit from a base Agent class. I would like to use the Autowired ability of spring instead of doing my own dependency injections.
But I am running into this issue, even though I am marking the component as scope=prototype, and even #Lazy to prevent anything from happening at compile-time.
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 0 of constructor in com.my.project.AgentType1 required a bean of type 'com.my.project.POJO' that could not be found.
This is the service that tries to create the agents:
#Service
public class ProjectMain {
#Autowired
ApplicationContext context;
List<IAgent> agents = new ArrayList<>();
void SetupAgents(List<POJO> agentPojos) {
for(POJO agentPojo: agentPojos) {
IAgent agent = AgentFactory.CreateAgent(agentPojo, context);
agents.add(agent);
}
}
}
This is the factory class, not marked as #Component etc. It uses the context passed to it to create the child class beans. It tries to pass the constructor argument via the getBean method.
public class AgentFactory {
public static IAgent CreateAgent(POJO agentPojo, ApplicationContext context) {
if (agentPojo.type.equals("AgentType1")) {
return context.getBean(AgentType1.class, agentPojo);
} else {
return context.getBean(AgentType2.class, agentPojo);
}
}
}
This is a custom annotation which I found is needed for inheritance scenarios.
#Target({ ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#Component
#Inherited
#Lazy
#Scope("prototype")
public #interface AgentAnnotation {}
These are the base and child agent classes, which need a custom data structure called POJO to work.
#AgentAnnotation
public class BaseAgent implements IAgent {
#Autowired
Environment env;
public BaseAgent(POJO agentPojo, String someotherdata) {
}
}
public class AgentType1 extends BaseAgent {
public AgentType1(POJO agentPojo) {
super(agentPojo, "mydata1");
...
}
}
public class AgentType2 extends BaseAgent {
public AgentType2(POJO agentPojo) {
super(agentPojo, "mydata2");
...
}
}
This is the starter app.
#ComponentScan(basePackages = "com.my.project", includeFilters = #ComponentScan.Filter(AgentAnnotation.class))
#EnableScheduling
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I also tried the configuration approach:
#Configuration
public class BaseAgentConfig {
#Bean
#Scope("prototype")
public AgentType1 agentType1(POJO agentPojo) {
return new AgentType1(agentPojo);
}
#Bean
#Scope("prototype")
public AgentType2 agentType2(POJO agentPojo) {
return new AgentType2(agentPojo);
}
}
In this case, I removed the #AgentAnnotation from the baseAgent class as we are now instantiating through this config. Also removed the ComponentScan line from the main App.
This time around, the #Autowired doesn't work. All Autowired references in the baseAgent class are null.
Please advise on the best approach to solve this error. Thanks.
Found the issue and solution.
Basically, I was expecting child classes to inherit #Component and #Scope, which it doesn't.
So essentially, I need to annotate each child class with #Component and #Scope("prototype").
The other problem was that I was expecting Autowired items in the constructor, which was too early. Adding a #PostConstruct addressed that issue.
So I ended up deleting the custom annotation and the configuration class and making the changes I just described.

AOP with Spring controller Function using customer Annotations

I have a use case where I want to intercept the Spring controller functions that are annotated with my Custom annotation. Let's call my annotation #CustomerAnnotation.
I have a controller MyController
public class MyController extends Controller {
#CustomerAnnotation
#RequestMappint("/test")
public void test() {
// SOME CODE
}
My AspectJ class:
#Aspect
#Component
public class CustomImpl implements CustomAspect {
#Around("#annotation(CustomerAnnotation)")
#Override
public Object testAnnotation(ProceedingJoinPoint proceedingJoinPoint) {
System.out.println("Inside annotaion");
try {
Object returnObj = proceedingJoinPoint.proceed();
return returnObj;
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
return null;
}
};
}
Bu whenever I call my API i get the following exception :
The mapped controller method class 'com.controller.MyController' is not an instance of the actual controller bean instance 'com.sun.proxy.$Proxy154'. If the controller requires proxying (e.g. due to #Transactional), please use class-based proxying.
HandlerMethod details:
My CustomerAnnotation class:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface CustomerAnnotation {
}
Any solutions ?
I have tried using the same logic on my service functions and it works there.
Update: When I add #EnableAspectJAutoProxy(proxyTargetClass = true) it gives following exception
Caused by: org.springframework.aop.framework.AopConfigException: Could not generate CGLIB subclass of class
Please help
If you are using JavaConfig use "#EnableAspectJAutoProxy" from "import org.springframework.context.annotation.EnableAspectJAutoProxy" at class level in your configuration class.

How can I define RestController manually in Spring?

I've a lot of RestController class that annotated with #RestController and works correctly. but in a situation I have to add one of them manually. I think I can define a bean in Spring configuration class, so I can define a RestService, but how?
For example :
#Configuration
public class Config ..... {
............
#RestController
public MyRestService myRestService() {
if(shouldUseTypeA){
return new MyRestService<TypeA>(myParams);
}else{
return new MyRestService<TypeB>(myParams);
}
}
}
If shouldUseTypeA is something you know before the application starts, use it as a Spring profile and instantiate right controller according to the activated profile.
#Configuration
public class Config ..... {
// Type A
#Profile("shouldUseTypeA")
#RestController
public class TypeAService extends MyRestService<TypeA>(myParams){}
// Otherwise type B
#Profile("!shouldUseTypeA")
#RestController
public class TypeBService extends MyRestService<TypeB>(myParams){}
}

How do I express a dependency on a bean defined in an imported configuration in Spring?

I recently started working at a place that uses Java configuration for Spring as opposed to XML and so far I'm loving it.
My question is the following:
If we have a #Configuration annotated class A that imports another #Configuration annotated class B, what is the proper, type-safe way for a bean defined in A to depend on a bean defined in B.
Here's an example I saw in a blog (https://blog.codecentric.de/en/2012/07/spring-dependency-injection-styles-why-i-love-java-based-configuration/):
#Configuration
public class PartnerConfig {
#Bean
public PartnerService partnerService() {
return new PartnerServiceImpl();
}
}
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Autowired
private PartnerConfig partnerConfig;
#Bean
public CashingService cashingService() {
return new CashingServiceImpl(partnerConfig.partnerService());
}
}
As a second part to my question, if I was to do the above, would Spring interpret as a bean dependency? That is, when I do
partnerConfig.partnerService()
in the example above, am I getting Spring to fetch me the partnerService bean, or am I just calling a regular java method and creating a new instance of the PartherService (which is NOT what I want, since the bean should be a singleton) ?
EDIT:
It has been suggested to use a #Qualifier. Would this work?
#Configuration
public class PartnerConfig {
#Bean
#MyCustomQualifier
public PartnerService partnerService() {
return new PartnerServiceImpl();
}
}
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Bean
public CashingService cashingService(#MyCustomQualifier PartnerService partnerService) {
return new CashingServiceImpl(partnerService);
}
}
I recommend giving the docs a read: http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/context/annotation/Bean.html
Refer to the section:
#Bean Methods in #Configuration Classes
This sums it up very well.
Typically, #Bean methods are declared within #Configuration classes. In this case, bean methods may reference other #Bean methods in the same class by calling them directly. This ensures that references between beans are strongly typed and navigable.
Also take a look at: http://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html
Section:
Composing #Configuration classes
Just add the dependency as an argument to the #Bean annotated method and remove the autowiring of the configuration.
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Bean
public CashingService cashingService(PartnerService partnerService) {
return new CashingServiceImpl(partnerService);
}
}
or simply autowire the PartnerService instead of the configuration.
#Configuration
#Import(PartnerConfig.class)
public class CashingConfig {
#Autowire
private PartnerService partnerService;
#Bean
public CashingService cashingService() {
return new CashingServiceImpl(partnerService);
}
}

Categories