I am having some problems understanding the concept of component instantiation in iPOJO. I read this guide and I get the analogy to classes and objects but I still have some concrete problems and some conceptual problems that I hope someone can clarify
I thought I needed to create instances via iPOJO (@Instantiate or factories) only for service providers since they never use new because the impl is always hidden. However, I have some consumers @Component that I instantiate myself (say in a main() method where I call new on them directly). I made them @Component because they need to have things injected. I was assuming that the ipojo bytecode manipulation would make it so that when the objects were constructed, they would have their dependencies injected (I'm using mostly method injection with @Bind) but it seems that is not the case. Can someone clarify this to me please. Now it seems to me that for iPOJO to do any injection at all I need to always use one of the iPOJO instantiation techniques. The problem I have is that then the constructors I made in the consumer classes are not called.
This is a simplified example to illustrate my confusion
@Component(name="test")
public class MyFoo {
private List<External> externals; //injected
private Bar bar; //passed via constructor. Bar is *not* a @Component
public MyFoo(Bar otherBar) {
bar = otherBar;
externals = new ArrayList();
}
@Bind(aggregate=true)
public addExternal(External service) {
externals.add(service);
}
}
So, as can be seen here, I need to have all the providers of interface External
, but I also need a Bar
object that I pass when I construct the object using new MyFoo(someBar)
My problem is that if I need to pass a Bar
to the constructor then I need to use new; but If i use new, iPojo never invokes my injection method. On the other hand, if I use iPOJOs instantiation (say I add @Instantiate) then the injection does happen but the constructor is not invoked, so the bind throws a NPE because the list has not been created yet + bar will not be set. I know I can create the list inside the bind method, but my question is more conceptual.
iPojo works similar to other DI (dependency injection) frameworks such as Blueprint (OSGi), Spring, Guice, etc. That is, in order to allow DI to do it's job you have to let the container (iPojo) manage the lifecycle of the object you're interacting with. So, your inclination was correct: You have to use one of iPojo's instantiation techniques. If you choose to use new on your object, your code is then managing the lifecycle (and thus you will manually need to "inject" all parameters).
In the example, your constructor isn't invoked because out of the box iPojo will support two main cases: The default constructor (MyFoo()) or the constructor accepting a BundleContext (MyFoo(BundleContext c)). iPojo also supports constructor service and property injection if you're on version 1.7.0 or later using @Requires
/ @Property
respectively on your constructor variable (or the equivalent in your metadata).
When iPojo bytecode manipulation kicks in, it manipulates the bytecode to make it manageable by iPojo, not managed by iPojo. It does this by adding a MyClass(InstanceManager) constructor among other things that is used internally by iPojo when instantiating your object.
So, to answer your questions:
You can accomplish this by defining injected variables as either properties or service requirements and delegating to iPojo to create them. iPojo has multiple methods for interacting with instance creation, but the two you may be interested in are via the OSGi ConfigurationAdmin or the iPojo factory (assuming publicFactory is set to true on your @Component
, which is the default). As an example using the config admin and assuming Bar doesn't live in the OSGi service registry your MyFoo class would look like this:
@Component(name="test")
public class MyFoo {
private List<External> externals; //injected
private Bar bar; //passed via constructor. Bar is *not* a @Component
public MyFoo(@Property(name = "somebar") Bar otherBar) {
bar = otherBar;
externals = new ArrayList();
}
@Bind(aggregate=true)
public addExternal(External service) {
externals.add(service);
}
}
Then, you would use the configuration admin (or iPojo factory) to create an instance. So, your main method would pull in the ConfigAdmin or the iPojo factory from the OSGi service layer (e.g. by pulling it out from the BundleContext, etc.), create a configuration with the "somebar" property set to your "new Bar()" and save that configuration. The iPojo managed service factory that is created for you will then instantiate a version of MyFoo with the new Bar you supplied in the configuration, injecting it into the MyFoo constructor:
...
Configuration config = configAdmin.createFactoryConfiguration(
MyFoo.class.getCanonicalName());
Hashtable<String, String> properties = new Hashtable<>();
properties.put("somebar", new Bar()); // This is where you new
config.update(properties);
// do something useful with the config if you need to update
// the instance or destroy it later.
...
The first parameter to the config admin's createFactoryConfiguration
specifies the pid. In this case the pid is the name of the class, which is what iPojo will use by default unless you override it in your @Component
annotation. Then you add your somebar to the properties and update the configuration. The iPojo factory is similar to this, though I believe it uses the builder pattern to create instances. It may be preferable to use the iPojo factory instead if you don't want to add a dependency on the OSGi config admin.
InstanceManager
. Since the constructor you've added has no metadata bound to it (i.e., annotations -- i'm assuming there is no manual metadata in a manifest or xml file) it more or less completely ignores that constructor and instead chooses to use the one dynamically generated by the bytecode manipulation process. Once it's done, it will eventually call your @Bind
method to add the external service (since that method is marked up) and you will find yourself in the state you described.