With the GF v3 module system, the module system is already responsible for instantiating many classes that constitute the application server functionality.
The proposal is to further enhance the module system so that not only have it create objects, but also have it configure such objects by:
This additional service can be implemented on top of existing Lookup
abstraction, by using Lookup as a place to keep
the information.
With this enhancement, the above code would be written as follows:
@Requires
public GrizzlyAdapter adapter;
@Requires
public AdminAdapter adminAdapter;
public void start() {
adapter.registerEndpoint(adminAdapter.PREFIX_URI, adminAdapter);
// see later dicussion about where the command registration went
}
The Resource annotation indicates the field to be injected by the module system. The type of the field and the context in which the instanciation happens is used to determine the instance to be injected.
Once services have executed, they usually provide instances of objects to other parts of the system. Such instances are usually made available through a lookup system or jndi type environments. By using the @Provides, a service indicates that the resource should be extracted once the service execution has returned successfully.
@Service
public class ConfigService {
@Provides
Domain domainInfo;
public void start() {
domainInfo = ....
}
}
Domain instance will be extracted and stored in the lookup service.
or it can alternatively publish the service itself
@Service
@Provides
public class FooBar {
@Requires
Domain domainInfo;
public void start() {...}
}
The FooBar service instance will be stored in the lookup for access by other services.
In the above example both resources are "singleton" resources. That is to say, there's only one instance of it per the whole GF instance. But some objects have a different scope. For example, some objects in GF is scoped to the current thread (such as EJB InvocationContext, or security tokens), while others are scoped to the deployed application (servlet contexts, web services), yet still others are meant to be created and disposed for each use (ListApplicationCommand for example.)
Marking classes with the scoping annotation enables the module system to inject the proper instance, for example:
@Component(scope=THREAD)
class InvocationContext { ... }@Service(scope=SINGLETON)
class CommandRegistry { ... }@Service(scope=APPLICATION)
class NewWebServiceContextImpl { ... }@Service(scope=ONETIME)
class ListApplicationCommand { ... }
It is a common practice to split the contract interface and its implementation. The GF v3 module system encourages this design by separating the public class loader from the private class loader. We can take it one step further so that the actual implementations can be injected where contract is asked. Doing this requires the module system to understand what interfaces are contracts, so this calls for another annotation.
@Contract
interface Startup { ... }
@Service class AdminService implements Startup { ... }
@Service class ConfigService implements Startup { ... }
class AppServerStartup {
@Resource
public List<Startup> startupServices;
...
}
This allows the module system to recognize that AdminService and ConfigService are Startups, and allow them to be injected into AppServerStartup.startupServices.
An APT plugin was developed to automatically create a META-INF/service file for each @Service class implementing interfaces annotated with the @Contract. Consider the following service implementation :
package com.sun.v3.annotations;
@Contract
public interface Startup {...}
package com.wombat;
@Contract
public interface RandomContract {...}
package com.sun.v3;
@Service
public class MyService implements Startup, RandomContract, PropertyChangeListener {
...
}
will generate the following META-INF/services files :
com.sun.v3.annotations.Startup com.wombat.RandomContractwith the com.sun.v3.MyService content
Also see the discussions that follow for the additional benefits of contract/implementation separation.
As with the case of Startup, sometimes different implementations of a contract are meant to be all used together. In other cases, components have identifiers that distinguish them among other implementations of the same contract (for example like Command, identified by the command name.)
While in theory such "identifier" is quite open-ended, in practice, a string identifier seems to be working just fine with most existing IoC containers. So we allow components to have names, like this:
@Contract interface Command { ... }
@Service(name="stop-domain")
class StopDomainCommand { ... }
@Service(name="list-applications")
class ListApplicationsCommand { ... }
The exact meaning of the name depends on the contract for which the class is exposed. In the above example the intention is that the name is used as the command name.
Named components are often used for manually looking up resources, as opposed to automatic injection via @Resource, since often the name is only available after some computation. The example is shown below:
class AdminAdapter {
@Requires
Lookup lookup;
...
public void service(Request req, Response res) {
...
String commandName = ...;
Command cmd = lookup.lookup(Command.class,commandName);
cmd.execute(...);
}
}
Another possible useful service on top of this abstraction is to inject a smart List that defers the actual injection. For example, we can inject all Startups lazily when it's accessed for the first time. This makes the code simpler by avoiding the separation between initialization and storage:
class MyCode {
@Lazy @Resource
public List<Startup> startups;
...
void f() {
for( Startup s : startups ) // 's' is instanciated as it's accessed.
s.doSomething();
}
}
Or a smart Map could be used and have named :
class MyCode {
@Lazy @Resource
public Map<String,Command> commands;
...
void f() {
Command c = commands.get("list-domains"); // this will instanciate a properly configured ListDomainsCommand
}
}
The other thing we should inject is the configuration information. I'm not too familiar about the kind of configurations GF has, but for example, perhaps something like this for system property:
class PhobosContainer {
@Resource("phobos.home" )
public File home;
}
... and perhaps something like this for the configuration in domain.xml
class GrizzlyService {
// some kind of path expression over JavaBeans
@Resource("domain.config.httpService.httpListner.port" )
public int port;
}
It's not clear if we should reuse @Resource or create a new annotation, or it's not clear if we need a single symbol space for all configurations or different annotations for each distinct configuration space. This needs more discussion.