Search code examples
javadependency-injectionfactorypicocontainer

How do I get PicoContainer to start/stop/dispose a component injected by a factory?


I have a PicoContainer which caches all components. Since it caches all components, I expect it to call start, stop and dispose at the appropriate points in the container lifecycle.

However, I'm finding that if I construct a component using a FactoryInjector, these methods don't get called at all, despite that component also being cached.

Take the following example:

import java.lang.reflect.Type;

import org.picocontainer.Characteristics;
import org.picocontainer.DefaultPicoContainer;
import org.picocontainer.Disposable;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoContainer;
import org.picocontainer.Startable;
import org.picocontainer.injectors.FactoryInjector;

public class PicoContainerFactoryTest {
    public static void main(String[] args) {
        MutablePicoContainer container =
            new DefaultPicoContainer().as(Characteristics.CACHE);
        try {
            System.out.println("Adding components...");
            container.addComponent(InstanceService.class,
                                   new InstanceServiceImpl());
            container.addComponent(ConstructedService.class,
                                   ConstructedServiceImpl.class);
            container.addAdapter(
                new FactoryConstructedServiceAdapter());

            System.out.println("Starting...");
            container.start();

            // Even this doesn't trigger it. :(
            //container.getComponent(FactoryConstructedService.class);

            System.out.println("Stopping...");
            container.stop();
        }
        finally
        {
            System.out.println("Disposing...");
            container.dispose();
        }
    }


    public interface InstanceService
        extends Startable, Disposable {}
    public interface ConstructedService
        extends Startable, Disposable {}
    public interface FactoryConstructedService
        extends Startable, Disposable {}

    private static class InstanceServiceImpl extends Impl
            implements InstanceService {
        public InstanceServiceImpl() {
            super("InstanceServiceImpl");
        }
    }

    public static class ConstructedServiceImpl extends Impl
            implements ConstructedService {
        public ConstructedServiceImpl() {
            super("ConstructedServiceImpl");
        }
    }

    private static class FactoryConstructedServiceAdapter
            extends FactoryInjector<FactoryConstructedService> {

        public FactoryConstructedServiceAdapter() {
            super(FactoryConstructedService.class);
        }

        @Override
        public FactoryConstructedService getComponentInstance(
            PicoContainer picoContainer, Type type) {
            return new FactoryConstructedServiceImpl();
        }

        private static class FactoryConstructedServiceImpl extends Impl
            implements FactoryConstructedService {
            public FactoryConstructedServiceImpl() {
                super("FactoryConstructedServiceImpl");
            }
        }
    }

    public static class Impl implements Startable, Disposable {
        private final String name;

        public Impl(String name) {
            this.name = name;
            System.out.println("  " + name + "#<init>");
        }

        @Override
        public void start() {
            System.out.println("  " + name + "#start");
        }

        @Override
        public void stop() {
            System.out.println("  " + name + "#stop");
        }

        @Override
        public void dispose() {
            System.out.println("  " + name + "#dispose");
        }
    }
}

The output of running this is as follows:

Adding components...
  InstanceServiceImpl#<init>
Starting...
  ConstructedServiceImpl#<init>
  InstanceServiceImpl#start
  ConstructedServiceImpl#start
Stopping...
  ConstructedServiceImpl#stop
  InstanceServiceImpl#stop
Disposing...
  ConstructedServiceImpl#dispose
  InstanceServiceImpl#dispose

So on start(), the component I created and injected as an instance is started. The component I injected via constructor injection gets constructed and then started. But nothing is seen from the component I injected via the factory.

As far as the documentation goes, the Javadoc for FactoryInjector shows #start, #stop and #dispose methods, which appear to be intended for the factory itself to do its own lifecycle stuff, not for the components the factory spins out.

A quick look at the source shows that the adapter implementing ComponentLifecycle will have its methods called, but it isn't immediately clear how to hook it in. If I look at the other implementing classes, practically everything seems to delegate to something else, making it difficult to figure out what is really happening.

What is the proper way to do this? Is there even a proper way to do this?


Solution

  • FactoryConstructedServiceAdapter should implement LifecycleStrategy and have

    @Override
    public boolean hasLifecycle(Class<?> type) {
        return true;
    }
    

    Basically, that's all, factory will be included in the standard lifecycle and can manage the components AND the actual instantiation for FactoryConstructedServiceImpl will be called (if you don't need lifecycle on a component provided by your factory and just wonder why it's not instantiated, keep in mind that factories are lazy and you don't see "FactoryConstructedServiceImpl#init" in log until you actually wire or request the component).

    Take InstanceAdapter if you need a big example.