Spring: Inject Any alternative - java

The project I'm working on is a spring boot one and I need to migrate, from an EJB project, multiple implementations of the same interface.
There's a job that needs to use all of this instances and we would like to use a flexible implementations like the one supported in java ejbs:
#Inject
private #Any
Instance<FleetAnalyzerProvider> fleetAnalyzerProviders;
In the source project, it is used a provider builder:
private void initializeProvider(String type) {
try {
final DeviceTypesProviderBuilder providerBuilder = new DeviceTypesProviderBuilder(type);
final Instance qualifiedInstance = this.fleetAnalyzerProviders.select(providerBuilder);
this.analyzerProvider = (FleetAnalyzerProvider) qualifiedInstance.get();
} catch (final Exception e) {
this.logger.error("[initializeProvider] Failed for type [{}] due to: {}", type, e);
}
}
where DeviceTypesProviderBuilder is:
public class DeviceTypesProviderBuilder extends AnnotationLiteral<DeviceTypes> implements DeviceTypes {
private final Type type;
public DeviceTypesProviderBuilder(String type) {
this.type = DeviceTypes.Type.getEnum(type);
}
#Override
public Type value() {
return type;
}
}
And DeviceTypes:
#Qualifier
#Retention(RUNTIME)
#Target({METHOD, PARAMETER, FIELD, TYPE})
public #interface DeviceTypes {
Type value();
enum Type {
...
}
}
Is there a similar option in spring?
I saw a post (Autowire reference beans into list by type) with a syntax like this:
#Autowired
private FleetAnalyzerProvider[] fleetAnalyzerProviders;
But then how can I identify the correct provider?
The examples in the linked spring documentations are too simple and does not explicitly explain this particular possibility.

Related

javaslang List.of() on cdi Instance

I have multiple class with a Qualifier that I created:
#ServiceComponent(restPath = "/trucks")
public class TruckService {
}
#ServiceComponent(restPath = "/cars")
public class CarService {
}
here is the Qualifier (not important for the question)
#Qualifier
#Retention(RetentionPolicy.RUNTIME)
#Target({TYPE, FIELD})
public #interface ServiceComponent {
public boolean exposeAsRest() default true;
#Nonbinding public String restPath() default "";
#Nonbinding public String restGetPrefix() default "get,find,all";
#Nonbinding public String restPostPrefix() default "create,new,post";
}
in another class, I inject those instance using javax.enterprise.inject.Instance<>
class SomeConfigurationClasss {
#Inject
#ServiceComponent()
Instance<Object> _restComponents;
#Override
public void iterate() throws Exception {
//iterate
for(Object obj : _restComponents){
somefuncion(obj);
}
//List.of(_restComponents)
//.flatMap(obj -> somefuncion(obj));
}
}
if I execute the "normal" iteration (for...) I get the Object (TruckService or CarService) given as parameter to the somefunction().
but if I use javaslang's List.of(...) I get the Instance itself. Which I think it's the expected behavior
Is there a possibility to use List.of on a Instance that can contain one or multiple bean (depending on the injection binding). (I already try to call iterator(), select() on the Instance)
Instance<T> extends Iterable<T> so you should use List#ofAll(Iterable)

Can a CDI Bean's method return a managed Bean it created?

i have the following setup:
#Applicationscoped
#Transactional(txtype.Requires_new)
Public class querybean {
#Inject ExternalSysrltem externalSystemProxy;
Public Handle gethandleByKey(String key) {
return new Handle(/*do external Systems Query, returns an ExternalHandle Object*/)
}
Public static class Handle {
ExternalHandle eh;
/*protected so that User of class cannot Instantiate it otherwise that by getHandleByKey()*/
Protected Handle(ExternalHandle arg) {
This.eh = arg;
}
Public String getHandleInfo() {
Return This.eh.getName() + "/" + this.eh.getState()..;
/*generally wrap the ExternallHandle with businesslogic to hide direct access to the complex ExternalService's Interface*/
}
}
}
Can I get Handle to be a Managed Bean that can be annotated with #Transactional and still create it in the getHandleByKey Method at Runtime by querying the external System?
A static inner class can be a bean according the the spec.
In your example it is not a bean due to its constructor.
As said in comments you could use a producer, but a produced bean can't be intercepted (with #Transaction here)
If you want to keep your pattern, you'll have to create a very complex extension since it should work at low level to ensure interceptor will be activated.
I suggest that you go for something simpler by deporting your ExternalHandle resolution in Handle Bean, allowing you to use a String to construct it.
First create a qualifier with a non binding member to transmit information to your constructor.
#Target({TYPE, METHOD, PARAMETER, FIELD})
#Retention(RUNTIME)
#Documented
#Qualifier
public #interface Keyed {
#Nonbinding
String key();
}
Then create a literal for your annotation to allow creation of an annotation instance with a given key value.
public class KeyedLiteral extends AnnotationLiteral<Keyed> implements Keyed {
private final String key;
public KeyedLiteral(String key) {
this.key = key;
}
#Override
public String key() {
return key;
}
}
Using programmatic lookup and InjectionPoint to transmit your key value. Your code will be like:
#Applicationscoped
#Transactional(txtype.Requires_new)
Public class querybean {
#Inject
#Any
Instance<Handle> handles;
Public Handle gethandleByKey(String key) {
return instances.select(new KeyedLiteral(key)).get()
}
#Dependent
#Transactional
#Keyed("") //enforce the presence of the annotation for the constructor
Public static class Handle {
ExternalHandle eh;
// needed to make the bean proxyable (mandatory for the interceptor bound))
Protected Handle() {}
#Inject
Protected Handle(InjectionPoint ip, ExternalSysrltem externalSystem) {
String key=ip.getAnnotated().getAnnotation(Keyed.class).key();
eh = /*do external Systems Query, returns an ExternalHandle Object from key and externalSystem*/
}
Public String getHandleInfo() {
Return This.eh.getName() + "/" + this.eh.getState()..;
/*generally wrap the ExternallHandle with businesslogic to hide direct access to the complex ExternalService's Interface*/
}
}
}

How to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class

I am trying to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class (not the class I want to inject), however the problem I've got is that the code I'm using to lookup the correct bean always returns with null.
The beans I want to inject are annotated with a custom annotation called #CQRSCommandHandler which contains the name of a class being used as a qualifier and the beans also implement an interface called CommandHandler. The classes I'm using qualifier implement the interface Command.
Based on my somewhat limited knowledge of CDI, I believe that in order to programmatically lookup the correct bean which has been qualified with the #CQRSCommandHandler annotation, I need to extend AnnotationLiteral and I can then use Instance to select the bean.
The code for the #CQRSCommandHandler annotation is as follows:
#Qualifier
#Documented
#Retention(value= RetentionPolicy.RUNTIME)
public #interface CQRSCommandHandler {
Class<? extends Command> command();
}
The code for extending AnnotationLiteral is as follows:
public class CQRSCommandHandlerQualifier extends AnnotationLiteral<CQRSCommandHandler> implements CQRSCommandHandler {
private static final long serialVersionUID = 1L;
private final Class<? extends Command> command;
public CQRSCommandHandlerQualifier(Class<? extends Command> command) {
this.command = command;
}
#Override
public Class<? extends Command> command() {
return command;
}
}
The code I'm using to lookup the correct bean using CDI is as follows:
#Inject
#Any
private Instance<CommandHandler> commandHandlerInstance;
private CommandHandler findCommandHandlerFor(Command command) {
CommandHandler commandHandler = commandHandlerInstance.select(new CQRSCommandHandlerQualifier(command.getClass())).get(); //This always returns null
return commandHandler;
}
Despite many hours of google searching I can't work out why commandHandlerInstance.select(new CQRSCommandHandlerQualifier(command.getClass())).get(); does not return an instance of a bean which has been annotated with #CQRSCommandHandler (command = MyCommand.class) where the bean implements the CommandHandler interface and MyCommand.class implements the interface Command.
Is this the correct way to programmatically lookup and inject a CDI managed bean where the qualifier contains the name of a class? If so, where am I going wrong with the above code? If not, what is the best way to achieve the same end result?
Update
The following code is an example implementation of a bean that I'm trying to lookup:
#CQRSCommandHandler(command = CreateToDoItemCommand.class)
public class CreateToDoItemCommandHandler implements CommandHandler {
#Override
public <R> Object handle(Command command) {
System.out.println("This is the CreateToDoItemCommandHandler");
return null;
}
}
The following code is the interface for CommandHandler:
public interface CommandHandler {
public <R> Object handle(Command command);
}
The following code is an example of the class I'm using as a parameter in the qualifier:
public class CreateToDoItemCommand implements Command {
private String todoId;
private String description;
public CreateToDoItemCommand(String todoId, String description) {
this.todoId = todoId;
this.description = description;
}
public String getTodoId() {
return todoId;
}
public String getDescription() {
return description;
}
}
I've stepped through the code in Eclipse and it seems that the Instance object of commandHandlerInstance is null.
Update 2
As suggested by #redge I've separate each step of the instantiation onto a separate line as follows:
private CommandHandler findCommandHandlerFor(Command command) {
CQRSCommandHandlerQualifier qualifier = new CQRSCommandHandlerQualifier(command.getClass());
Instance<CommandHandler> instance = commandHandlerInstance.select(qualifier);
CommandHandler commandHandler = instance.get();
return commandHandler;
}
The issue seems to be with this line of code Instance<CommandHandler> instance = commandHandlerInstance.select(qualifier); where NullPointerException is thrown presumably because the Instance object commandHandlerInstance is null
I'm running this code on GlashFish 4 which ships with Weld 2.0.0 SP1, but I've also just tried running the same code on GlashFish 4.1 and have installed Weld version 2.2.10.SP1 which is the latest from Maven Central but the same issue occurs.
You have GlassFish 4.1. I doubt you have a beans.xml file, which if you do should be marked as bean-discovery-mode="all" based on your current setup. If you don't, or you use bean-discovery-mode="annotated" then you'll need to add a bean defining annotation to each of your commands, e.g. #ApplicationScoped for each command so that they can be resolved.

Spring: how to get an applicable generic bean instance by its type argument(s)?

I'm using Spring 4.1.2 and I have the following code:
public class Foo {
}
public class Bar {
}
public interface Service<T> {
}
#Service("fooService")
public class FooServiceImpl implements Service<Foo> {
}
#Service("barService")
public class BarServiceImpl implements Service<Bar> {
}
I know that Spring 4 can inject generic bean instances like the following:
#Autowired
private Service<Foo> service; // works fine
But I need to obtain them in a static way like the following:
Service<Foo> service = getService(getContext(), Foo.class);
...
public static <T> Service<T> getService(ApplicationContext context,
Class<T> objectClass) {
...
}
I tried to use ApplicationContext.getBeansOfType(Service.class) but it returns all available bean instances (fooService and barService). So I need to pass type arguments somehow.
Is there any way to do this? Something like this:
#SupressWarnings("unchecked")
Service<Foo> service = applicationContext.getGenericBean(
Service.class, // bean class
Foo.class // type arguments
// ...
);
Getting generic beans programmatically from the application context:
String[] beanNames = applicationContext.getBeanNamesForType(ResolvableType.forType(new ParameterizedTypeReference<String>() {}));
if (beanNames.length > 0) {
String bean = (String) applicationContext.getBean(beanNames[0]);
}
Or check this answer if you're interested in a mechanism to handle generic objects with particular handler.

Conditional injection of bean

I want to have inject a bean based on a String parameter passed from client.
public interface Report {
generateFile();
}
public class ExcelReport extends Report {
//implementation for generateFile
}
public class CSVReport extends Report {
//implementation for generateFile
}
class MyController{
Report report;
public HttpResponse getReport() {
}
}
I want report instance to be injected based on the parameter passed. Any help would be greatly appretiated. Thanks in advance
Use Factory method pattern:
public enum ReportType {EXCEL, CSV};
#Service
public class ReportFactory {
#Resource
private ExcelReport excelReport;
#Resource
private CSVReport csvReport
public Report forType(ReportType type) {
switch(type) {
case EXCEL: return excelReport;
case CSV: return csvReport;
default:
throw new IllegalArgumentException(type);
}
}
}
The report type enum can be created by Spring when you call your controller with ?type=CSV:
class MyController{
#Resource
private ReportFactory reportFactory;
public HttpResponse getReport(#RequestParam("type") ReportType type){
reportFactory.forType(type);
}
}
However ReportFactory is pretty clumsy and requires modification every time you add new report type. If the report types list if fixed it is fine. But if you plan to add more and more types, this is a more robust implementation:
public interface Report {
void generateFile();
boolean supports(ReportType type);
}
public class ExcelReport extends Report {
publiv boolean support(ReportType type) {
return type == ReportType.EXCEL;
}
//...
}
#Service
public class ReportFactory {
#Resource
private List<Report> reports;
public Report forType(ReportType type) {
for(Report report: reports) {
if(report.supports(type)) {
return report;
}
}
throw new IllegalArgumentException("Unsupported type: " + type);
}
}
With this implementation adding new report type is as simple as adding new bean implementing Report and a new ReportType enum value. You could get away without the enum and using strings (maybe even bean names), however I found strongly typing beneficial.
Last thought: Report name is a bit unfortunate. Report class represents (stateless?) encapsulation of some logic (Strategy pattern), whereas the name suggests it encapsulates value (data). I would suggest ReportGenerator or such.

Categories