简体   繁体   中英

Spring service locator without autowiring it

Having this code:

public class ClassA {

    private InterfaceB interfaceB;

    private int a

    private int b;

    public ClassA(int a, int b) {
       this.a = a;
       this.b = b;
    }
}

There could be several objects of ClassA in my application, created on demand at runtime. But all of them should use the same concrete implementation of InterfaceB class (there are several implementations of InterfaceB but only one is used at runtime depending on the platform used). And there should be only one InterfaceB object in the application (singleton class).

I can not autowired interfaceB as ClassA objects are created at runtime when a and b constructor parameters are known.

How could I use Spring Framework to implement the service locator pattern here? My plan is to instantiate the service locator in ClassA and use it to get the InterfaceB object.

You could create an additional class that will be creating your ClassA and it will be holding a reference to an interfaceB . For example:

@Component
public class ClassAFactory {

    @Autowired
    private InterfaceB interfaceB;

    public ClassA create(int a, int b) {
       return new ClassA(a, b, interfaceB);
    }
}

In this case you have to extend the ClassA to pass an interfaceB . Then somewhere in your code you could:

@Autowired
private ClassAFactory factory ;

...

ClassA classA = factory.create(1,1); 

I don't think you need a service locator pattern, in the modern spring driven applications it usually not required anymore.

I'll try to address all your statements from the Spring Framework's integration standpoint:

There could be several objects of ClassA in my application, created on demand at runtime.

Spring is a runtime framework, so everything is created in runtime anyway. If you need many objects created by demand you can declare ClassA as a spring bean with scope prototype. Other beans could have this prototype bean injected. Another possible approach if you know which instances will be created during the application startup is to define many beans of the same type and use spring's qualifier feature to differentiate between them during the injection.

But all of them should use the same concrete implementation of InterfaceB class (there are several implementations of InterfaceB but only one is used at runtime depending on the platform used).

This means that InterfaceB can be a regular singleton, however, given different implementations you can define something like this:

@Configuration 
public class MyConfiguration {

    @Bean
    @ConditionalOnProperty(name="myprop", havingValue="true")
    public InterfaceB interfaceBImpl1() {
        return new InterfaceBImpl1();
    }

    @Bean
    @ConditionalOnProperty(name="myprop", havingValue="false")
    public InterfaceB interfaceBImpl2() {
        return new InterfaceBImpl2();
    }
}

I can not autowired interfaceB as ClassA objects are created at runtime when a and b constructor parameters are known.

Actually you can, no issues with that. Define the bean of classA as a prototype.

Check also @Lazy annotation of spring in case you want to initialize the instance of that classA upon the first invocation only.


public class ClassA {
  /// fields ///

  public ClassA(InterfaceB intefaceB, int a, int b) {
    this.intefaceB = intefaceB;
    this.a = a;
    this.b = b;
  }
}
@Configuration 
class MyConfig {

   @Bean
   @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
   public ClassA classA(InterfaceB bImpl, int a, int b) {
      return new ClassA(bImpl, int a, int b);
   }
}

Update 1

Based on OP's comments:

Here is the working example:

Add the following dependency in pom.xml:

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>

It contains only interfaces and has not transitive dependencies

Then based on your use case explained in comment:


public class InterfaceB {
}

public class ClassA {
    private final InterfaceB interfaceB;

    public ClassA(InterfaceB interfaceB) {
        this.interfaceB = interfaceB;
    }

    public void doSomething() {
        System.out.println("Doing something on instance: [ " + this + " ]. The interface B instance is [ "+ interfaceB + " ]");
    }
}


public class ServiceA {

    private final List<ClassA> classAList;
    private Provider<ClassA> classAProvider;

    public ServiceA(Provider<ClassA> classAProvider) {
        this.classAProvider = classAProvider;
        this.classAList = new ArrayList<>();
    }

    public void addNewObject() {
        ClassA newObj = classAProvider.get();
        classAList.add(newObj);
    }

    public void doWithAllElementsInList() {
        classAList.forEach(ClassA::doSomething);
    }
}

Spring configuration looks like this:

public class SingletonWithPrototypesConfig {

    @Bean
    public ServiceA serviceA(Provider<ClassA> classAProvider) {
        return new ServiceA(classAProvider);
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public ClassA classA(InterfaceB interfaceB) {
        return new ClassA(interfaceB);
    }

    @Bean
    public InterfaceB interfaceB() {
        return new InterfaceB();
    }
}

And the main class that gets the service A from the application context (in your case it should be probably a controller or any other business flow):

public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SingletonWithPrototypesConfig.class);
        ServiceA serviceA = ctx.getBean(ServiceA.class);
        serviceA.doWithAllElementsInList(); // won't print anything, 0 elements in the list
        System.out.println("---------");
        serviceA.addNewObject();
        serviceA.addNewObject();
        serviceA.doWithAllElementsInList();
    }

In the last print note that ClassA instances are different, the interfaceB is the same shared instance though.

Sidenote: the Provider is something integrated with spring already, it resides in javax.inject.Provider of that new jar.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM