Dynamically choose dependency without if else - java

How can i choose dependency without if else condition.
Suppose i have a interface:
public interface A{
String doSomething(String req);
}
there are two service implements A:
#Component
public class FirstImpl implements A{
#override
String doSomething(String req){
return "something";
}
}
and:
#Component
public class SecondImpl implements A{
#override
String doSomething(String req){
return "something";
}
}
Now i create a AdapterService class:
#Component
public class AdapterService{
#Autowired
private FirstImpl first;
#Autowired
private SecondImpl second;
public getService(String name){
if("name".equals("First")){
return first;
}else if("name".equals("Second")){
return second;
}
}
}
Now call Implementation:
#Service
public class BusinessService{
#Autowired
private AdapterService adapterService;
void doSomething(String name,String req){
return adapterService.getService(name).doSomething();
}
}
Now if i need to create another class which implements A then need to add condition in ServiceAdapter class. Like "Third".equals(name) return another injected service. For every new service there need to add another if else condition and inject corresponding service. How can i avoid this Adapter class. Spring dynamically choose depenedency from name.

If you have access to applicationContext object, you can call
applicationContext.getBean(name)
and totally avoid the ServiceAdapter class. Only thing is you need to have those beans in the container.
Try with this:
#Component
public class AdapterService{
#Autowired
private ApplicationContext applicationContext;
public A getService(String name){
return applicationContext.getBean(name,A.class);
}
}

Here the java SPI, Service Provider Interface, see here, would do just as well, as trying to use the Spring hammer.
For an interface x.y.z.A there is a discovery mechanism of implementing classes using the java SPI.
You can have several jars.
They have a text file META-INF/services/x.y.z.A with implementing class(es) on a line not starting with #.
As you might not want to instantiate a object of the class before it is selected by name you would either use a runtime annotation on the class, or have the SPI on a
factory AFactory with minor construction overhead, creating an A.
ServiceLoader<Dictionary> loader = ServiceLoader.load(A.class);
Iterator<A> dictionaries = loader.iterator();

Related

How to get Service from another class?

I'm creating telegram bot with Spring-Boot. I have AscractState class:
public abstract class AbstractState {
boolean isInputIndeed = Boolean.FALSE;
public abstract void handleInput(BotContext context);
//another parts
}
And there is extend which is
#Slf4j
public class AgeInputState extends AbstractState {
#Autowired
ClientService clientService;
public AgeInputState(boolean isInputIndeed) {
super(isInputIndeed, State.AGE_INPUT);
}
#Override
public void handleInput(BotContext context) {
context.getClient().setAge(Integer.parseInt(context.getInput()));
clientService.updateUser(context.getClient());
}
}
But i have touble with ClientService. Which annotations on class i need to add for autowiring this fiels?
Since this class has a constructor which only accepts a boolean, I assume you're needing to make lots of them.
Spring won't know you're wanting to load these as spring beans if you call this constructor directly. So creating these through a factory of some sort would be one way to go. Something like:
#Configuration
public class AgeInputStateFactory {
private #Autowired ClientService clientService;
#Bean
#Scope("prototype") // Makes a new one each time...
public AgeInputState create(final boolean isInputIndeed) {
return new AgeInputState(this.clientService, isInputIndeed);
}
}
Along with a newly designed AgeInputState constructor which also takes the ClientService field.
public class AgeInputState extends AbstractState {
private final ClientService clientService;
// Package private constructor so that no one outside
// of this package will call it. This means you can
// (try your best to) limit the construction to the
// factory class.
AgeInputState(final ClientService clientService,
final boolean isInputIndeed) {
super(isInputIndeed, State.AGE_INPUT);
this.clientService = clientService;
}
}
And then all you would do is wherever you need to create these AgeInputState Objects, you would #Autowire the AgeInputStateFactory instance, and call the create method whenever you need one.

is it possible to create multiple spring beans (from different interfaces) with the same name?

I'm trying to create the following structure:
public interface A {string calculateA(){....}}
#Service("policy1")
public class APolicy1 implements A {
#Override
public String calculateA(){...}
}
#Service("policy2")
public class APolicy2 implements A {
#Override
public String calculateA(){...}
}
and another interface
public interface B{string calculateB(){....}}
#Service("policy1")
public class BPolicy1 implements B{
#Override
public String calculateB(){...}
}
#Service("policy2")
public class BPolicy2 implements B{
#Override
public String calculateB(){...}
}
and the classes that uses these beans:
#service
#Primary
public class Arouter implements A{
#Autowired
Map<String, A> AServices;
#Autowired
PolicyResolver policyResolver;
#Override
public String calculateA(){
String policy = policyResolver.getPolicy();
AServices.get(policy).CalculateA();
}
#service
#Primary
public class Brouter Implements B{
#Autowired
Map<String, B> AServices;
#Autowired
PolicyResolver policyResolver;
#Override
public String calculateB(){
String policy = policyResolver.getPolicy();
AServices.get(policy).CalculateB();
}
note: the idea here is to delegate to the correct business logic by some kind of policy
for example, if i'm running in policy a context and need to trigger Service B, then the router will call BPolicy2 method.
but I'm getting:
org.springframework.context.annotation.ConflictingBeanDefinitionException: Annotation-specified bean name 'policy1' for bean class [...apackage.APolicy1] conflicts with existing, non-compatible bean definition of same name and class [...bpackage.BPolicy1]
I would expect that since these are different bean types I would be able to give them the same name
edit: I have a solution: add a prefix to the beans the name and the routers will add the prefix to the policyResolver return value, but I'm it's less elegant
#qualifier annotation can be used to differentiate between different beans.
Here is a link with small example of #qualifier annotation usage in Spring :
https://memorynotfound.com/handling-multiple-autowire-dependencies-with-spring-qualifier/

Spring boot inject bean at runtime based on request endpoint/request param

I have an interface that has two implementations, and I'd like to conditionally inject either of the two implementations in a spring boot service.
The point is that the eligible implementation should be picked up based on the request message (JSON mapped to a POJO).
My searches leaded me to implement a FactoryBean to control selecting between those two implementations, and to keep the factory telling spring that the beans are not singleton (by returning false for the isSingleton method).
But if this is the right way, I am still not sure how to get the request message to check it and return the right bean.
Can you please tell me if I am on the right track for what I am trying to attain?
=============
UPDATE
I do not want to pollute my code and deal with managing the relation between my service and the dependencies' implementation in the service.
Considering that I will need to deal with more implementations in the future, I need my service to care only about its responsibility.
I need my service to have only one reference of the generic interface and deal with it in an abstracted way.
I need to find a spring-based way to choose the right implementation for each request based on a condition that is derived from the request itself, and inject it in the service.
One option is to inject both beans and conditionally pick the required bean. You can autowire classes implementing same interface into a Map.
Following example uses a factory class to hide the conditional check.
#Component("type1")
public class Type1 implements SomeInterface{}
#Component("type2")
public class Type2 implements SomeInterface{}
#Component
public class MyTypeFactory {
#Autowired
private Map<String, SomeInterface> typesMap;
public SomeInterface getInstance(String condition){
return typesMap.get(condition);
}
}
#Component
public class MyService {
#Autowired
private MyTypeFactory factory;
public void method(String input){
factory.getInstance(input).callRequiredMethod();
}
}
You could #Autowire both beans in the controller and decided based on the request which one to return.
Consider the below interface:
public interface MyInterface { ... }
Sample config:
#Configuration
public class MyConfig {
#Bean("first")
public MyInterface firstBean() { ... }
#Bean("second")
public MyInterface secondBean() { ... }
}
Sample controller:
#RestController
public class MyController {
#Autowire
#Qualifier("first")
public MyInterface first;
#Autowire
#Qualifier("second")
public MyInterface second;
#GetMapping
public MyInterface doStuff(#RequestBody body) {
if(shouldReturnFirst(body)){
return first;
} else {
return second;
}
}
}
Note that you should most likely not do it this way though, but have a single service, say MyService that should implement this logic for you.
#Component
public class MyService {
public MyInterface doStuff(body) {
if(shouldReturnFirst(body)){
// build your response here
} else {
// build your response here
}
}
}
And just delegate to the service from the controller
#GetMapping
public MyInterface doStuff(#RequestBody body) {
return myService.doStuff(body);
}
Spring has a concept of Conditional Bean...
Have a look here https://www.intertech.com/Blog/spring-4-conditional-bean-configuration/

Google Guice Assisted Inject object is null

I need to create objects with user defined data at runtime.TO do that i have used
google guice assisted inject.But when i run my test it throws null pointer exception.Please let me know where i made the mistake.
IArtifacts Interface
public interface IArtifacts {
MavenMetaDataXMLDTO getArtifactsVersions();
}
ArtifactsService.java
public class ArtifactsService implements IArtifacts {
private ProductProfile productProfile;
#Inject
public ArtifactsService(#Assisted ProductProfile productProfile){
System.out.println(productProfile.getArtifactManagementURL());
this.productProfile=productProfile;
}
#Override
public MavenMetaDataXMLDTO getArtifactsVersions() {
System.out.println(productProfile.getArtifactManagementURL());
return null;
}
}
ArtifactsFactory Interface
public interface ArtifactsFactory {
IArtifacts create(ProductProfile productProfile);
}
Module Class
#Override
protected void configure() {
install(new FactoryModuleBuilder().implement(IArtifacts.class,ArtifactsService.class).build(ArtifactsFactory.class));
}
TestArtifacts.java
public class TestArtifacts {
#Inject // this obj is null
private ArtifactsFactory artifactsFactory;
private IArtifacts s;
public TestArtifacts(){
}
public void getdata(){
//Pass custom data to factory
this.s=artifactsFactory.create(Products.QA.get());
System.out.println(s.getArtifactsVersions());
}
}
REST ENDPOINT
#GET
#Path("/test")
#Produces(MediaType.APPLICATION_JSON)
public String getartifacts(){
new TestArtifacts().getdata();
}
you created an Instance of the class TestArtifacts on your own in your Rest Endpoint class but all of your classes need to be created by the Guice Framework and not by you.
So how should the Guice Framework inject something into your class when you create them with new? You also need to inject the class TestArtifacts into your Rest Endpoint and your Rest Endpoint has to be created by Guice too.
Update:
Maybe this link will help you
https://sites.google.com/a/athaydes.com/renato-athaydes/posts/jersey_guice_rest_api
I was able to fix it adding following code snippet to below TestArtifacts.java class
TestArtifacts.java
private Injector injector=Guice.createInjector(new MYModule());//where implemented configuration
#Inject
private ArtifactsFactory artifactsFactory=injector.getInstance(ArtifactsFactory.class);

How to set mock object in proxy-based Spring bean?

I'm having problems trying to set the mock object in my wired bean in my testcase.
Here's my simplified problem:-
class SomeClassTest {
#Autowired
private SomeClass someClass;
#Test
public void testRun() {
Service service = mock(ServiceImpl.class);
when(service.doIt()).thenReturn("");
// this line fails with ClassCastException
((SomeClassImpl) someClass).setService(service);
assertEquals("bad", someClass.run());
}
}
interface SomeClass {
String run();
}
class SomeClassImpl implements SomeClass {
private Service service;
public void setService(Service service) {
this.service = service;
}
public String run() {
String value = service.doIt();
return StringUtils.isBlank(value) ? "bad" : "good";
}
}
interface Service {
String doIt();
}
class ServiceImpl implements Service {
public String doIt() {
return "bla";
}
}
In this example, I'm trying to test SomeClass by mocking out Service.doIt() so that I can test different conditions. The problem I'm facing is I'm not sure how exactly I should set the mock Service object in SomeClass. The only way I can think of is to downcast SomeClass into the concrete class to call setService(...), however, I'm getting a ClassCastException saying $Proxy incompatible with SomeClassImpl. I believe all my bean wirings are proxy-based because I'm using AOP to configure the transaction. I really do not want to expose setService(...) in SomeClass interface because it makes no sense to do so in my production code.
Is there a way for me to accomplish this?
Thanks.
You can use the #Resource annotation to get the implementation:
#Resource
private SomeClassImpl someClass;
...
someClass.setService(service);
...
Use additional interface for Service setter than.
or
Do not autowire Service but use 'new' operator in your test.

Categories

Resources