Search code examples
javaoopserviceloader

Loading services with different fields using ServiceLoader in Java


I am trying to use Service loader to load all the implementations of an Interface. However I am not unable get it working when Service implementation has private fields.

Here are my service and implementation setup.

public interface OrderProcessor {
  
    void process()
            
}

And in implentation, I need this field to be initialised.

public class OrderProcessorImpl implements OrderProcessor {

    private final int maxOrders;
    

    public OrderStreamProcessorImpl(int maxOrders) {
        this.maxOrders = maxOrders;
    }


    @Override
    public void process() throws IOException {
       // Logic which involves maxOrders.

    }
}

Here's how I am trying to load this service using ServiceLoaderAPI.

public OrderProcessor createProcessor(int maxOrders) {
        var factories =
                ServiceLoader.load(OrderProcessor.class);

        if(factories.hasNext()){
            return factories.next();
        } else {
            throw new IllegalStateException("No OrderProcessor found");
        }

    }

I also tried using

public static OrderProcessorImpl provider()

but this doesnt work with params.

How do I solve this issue ? Or ServiceLoader API is not the right option in this use case?

Thanks


Solution

  • You have missed out a factory interface which enables the service to create instances of an order processor. See details of ServiceLoader.

    You would need two interfaces defining the service, for the factory and a processor. The ServiceLoader will provide instances of all registered Factory implementations:

    public interface OrderProcessor {
        void process() throws IOException;
    }
    
    public interface OrderProcessorFactory {
        OrderProcessor createProcessor(int maxOrders);
    }
    

    Then you can implement 2 classes supporting the factory and processor:

    public class OrderProcessorFactoryImpl implements OrderProcessorFactory {
    
        public OrderProcessorFactoryImpl() {
            System.out.println("OrderProcessorFactoryImpl()");
        }
    
        public OrderProcessor createProcessor(int maxOrders)
        {
            System.out.println("OrderProcessorFactoryImpl.createProcessor("+maxOrders+")");
            return new OrderProcessorImpl(maxOrders);
        }
    }
    
    public class OrderProcessorImpl implements OrderProcessor {
    
        private final int maxOrders;
    
        public OrderProcessorImpl(int maxOrders) {
            System.out.println("OrderProcessorImpl("+maxOrders+")");
            this.maxOrders = maxOrders;
        }
    
        @Override
        public void process() throws IOException {
            System.out.println("OrderProcessorImpl.process() where maxOrders is "+maxOrders);
        }
    }
    

    Add a file META-INF/services/packagenameof.theinterface.OrderProcessorFactory to register your implementation which contains one line:

    yourpackage.OrderProcessorFactoryImpl
    

    Then you can lookup a factory and instantiate an order processor:

    ServiceLoader<OrderProcessorFactory> loader = ServiceLoader.load(OrderProcessorFactory.class);
    System.out.println("loader="+loader);
    
    for (OrderProcessorFactory factory : loader) {
        OrderProcessor max999 = factory.createProcessor(999);
        System.out.println("max999="+max999);
        if (max999 != null)
            max999.process();
    }
    

    Should print:

    loader=java.util.ServiceLoader[stackoverflow.service.OrderFactory]
    OrderFactoryImpl()
    OrderFactoryImpl.createProcessor(999)
    OrderProcessorImpl(999)
    max999=yourpackage.OrderProcessorImpl@7699a589
    OrderProcessorImpl.process maxOrders=999