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

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/

Related

Dynamically choose dependency without if else

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();

How to dynamically inject different implementation for the same Interface using spring

I have one interface and two implementations for this interface
Interface definition:
public interface DoSomething {}
Two implementations:
public ImplementationOne implements DoSomething{}
public ImplementationTwo implements DoSomething{}
Then inside another class, I want to get a different implementaion (either ImplementationOne or ImplementationTwo) based on the condition, how can I do that using Spring?
Something like..
Public ServiceManager {
Private DoSomething doSomething = null;
Public void do() {
If (condition1) {
doSomething = new ImplementationOne();
} else {
doSomething = new ImplementationTwo();
}
}
}
You should definitely auto wire ApplicationContext type using #Autowire annotation. Then if you did it like this:
#Autowire
ApplicationContext context
Then you should get your desired bean like this:
context.getBean(yourDesiredType.class)
Like that you can get any bean you want to be placed under any matching type according to your example.
Another option to consider is have a configuration bean - for example -
#Configuration
public class EntityRepositoryConfiguration {
private Map<Entity, EntityRepository> entityEntityRepositoryMap = new HashMap<>();
protected EntityRepositoryConfiguration() {
entityEntityRepositoryMap.put(Entity.Book, new BookRepository());
}
#Bean
public EntityRepository getByEntityType(Entity entity) {
return entityEntityRepositoryMap.get(entity);
}
}
And then inject the configuration bean to your other beans and use the getEntityType method (for example) to get beans injected.

Spring MVC multiple controllers of the same class

I want to have one controller class, but 4 instances of it, each of instance will have own datasource and controller path, everything else (methods, validations rules, views names) will be the same;
So i need something like this :
class MyController{
private MyService service;
#RequestMapping("somework")
public String handleRequest(){
........
}
....................
}
Configuration class :
#Configuration
#EnableWebMvc
public class AppConfiguration {
#Controller // assuming it exists to get the
#RequestMapping('con1') // desired result
MyController controller1(){
MyController con = new MyController();
con.setService(service1Bean);
return con;
}
#Controller // assuming it exists to get the
#RequestMapping('con2') // desired result
MyController controller2(){
MyController con = new MyController();
con.setService(service2Bean);
return con;
}
...............................
}
No, you can't do this.
First, annotations are a set in stone at compile time. They are constant meta data that you cannot modify. So even though, they are accessible at run time through reflection, you cannot modify them.
Second, the #Controller annotation call only be used to annotate types. You cannot use it on a method. There is no corresponding annotation in Spring MVC that does what you want in your example. (You could always write your own.)
Finally, the Spring MVC stack registers your #Controller beans' methods as handlers mapping them to the various URL patterns you provide. If it tries to register a pattern that has already been registered, it fails because duplicate mappings are not allowed.
Consider refactoring. Create a #Controller class for each path you want but move the logic to a #Service bean which you can customize to use whatever data source you need.
You may achieve what you want by implementing an abstract superclass of
your controller, with constructor parameters for your service.
Then you should write derive your controllers from the abstract superclass,
with a constructor, where you inject your concrete service implementation:
public abstract class MyBaseController {
private MyService service;
public MyBaseController(final MyService service) {
this.service = service;
}
...
#RequestMapping("method1")
public ... method1( ... ) {
...
}
}
#Controller
#RequestMapping("con1")
public MyController1 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con1") final MyService service) {
super(service);
}
}
#Controller
#RequestMapping("con2")
public MyController2 extends MyBaseController {
#Autowired
public MyController1(#Qualifier("con2") final MyService service) {
super(service);
}
}
#Configuration
public class MyConfiguration {
#Bean(name = "con1")
public MyService serviceCon1() {
return ...;
}
#Bean(name = "con2")
public MyService serviceCon2() {
return ...;
}
}

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.

Spring #Bean configs and java polymorphism

I've been making increasingly heavy use of the new #Bean configuration style in Spring 3, as a more type-safe alternative to XML bean definition files. Occasionally, though, this type-safety can prevent you do what should be valid things, due to a combination of Java's lack of type expressiveness, and Spring scoped proxies.
A full unit test which demonstrates the problem is below, but briefly put I have a class ServiceBean, which implements interfaces ServiceA and ServiceB. This bean is a scoped proxy (session-scoped in this case). I also have beans ClientA and ClientB, which are injected with objects of type ServiceA and ServiceB respectively.
In Spring XML config, there's no problem with this. Spring generates a JDK-proxy for the ServiceBean, which implements both interfaces, and both are injected into the client beans. It's all reflective, and the types are fine at runtime.
Try this in #Bean-style, though, and you have problems. Here's the demonstrative test.
Firstly, the services:
public interface ServiceA {}
public interface ServiceB {}
public class ServiceBean implements ServiceA, ServiceB {}
Now, the clients:
public class ClientA {
public ClientA(ServiceA service) {}
}
public class ClientB {
public ClientB(ServiceB service) {}
}
Now, the Spring bean definitions:
#Configuration
public class ScopedProxyConfig {
#Bean #Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public ServiceBean services() {
return new ServiceBean();
}
#Bean
public ClientA clientA() {
return new ClientA(services());
}
#Bean
public ClientB clientB() {
return new ClientB(services());
}
}
And finally, the unit test and support context:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration
public class ScopedProxyTest {
private #Resource ClientA clientA;
private #Resource ClientB clientB;
public #Test void test() {
assertThat(clientA, is(notNullValue()));
assertThat(clientB, is(notNullValue()));
}
}
<beans>
<context:annotation-config/>
<bean class="test.ScopedProxyConfig"/>
</beans>
(XML namespaces omitted for clarity).
This all compiles nicely. Run the test, though, and you get a type casting runtime exception:
Caused by: java.lang.ClassCastException: $Proxy11 cannot be cast to test.ServiceBean
at test.ScopedProxyConfig$$EnhancerByCGLIB$$d293ecc3.services()
at test.ScopedProxyConfig.clientA(ScopedProxyConfig.java:26)
It's not clear to me exactly what this is telling me, but it appears to be a clash between the JDK proxy (which implements ServiceA and ServiceB) and the ServiceBean object.
I've tried getting clever with generics:
#Bean #Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public <T extends ServiceA & ServiceB> T services() {
return (T)new ServiceBean();
}
But that doesn't even compile.
This isn't an especially exotic situation, I think, and I've run into it a few times before. In the past, the workaround has been to use TARGET_CLASS proxying instead of interface proxying, but that's not an option for me here.
Can anyone figure out how to make this work?
I think you'll have to go for a more interface-based solution:
create an interface ServiceC:
public interface ServiceC extends ServiceA, ServiceB {}
and let ServiceBean implement that interface
public class ServiceBean implements ServiceC{}
And in your ScopedProxyConfig:
#Bean #Scope(value=WebApplicationContext.SCOPE_SESSION,
proxyMode=ScopedProxyMode.INTERFACES)
public ServiceC services() {
return new ServiceBean();
}
Consistent use of interfaces should let Spring work with JDK proxies.
This one at least compiles, perhaps it will work:
#Bean #Scope(value=WebApplicationContext.SCOPE_SESSION, proxyMode=ScopedProxyMode.INTERFACES)
public <T extends ServiceA & ServiceB> T services() {
return (T)new ServiceBean();
}
#Bean
public ClientA clientA() {
return new ClientA(this.<ServiceBean>services());
}
#Bean
public ClientB clientB() {
return new ClientB(this.<ServiceBean>services());
}

Categories

Resources