Tapestry Ioc
Todos los servicios son lazy, es decir no son completamente inicializados hasta que se utilizan por completo, a menudo lo que parece un servicio es un objeto proxy, la primer vez que se invoca un método del proxy, el servicio actual es inicializado e instanciado (realized). Estados de un servicio:
Construcción simple (autobuild services - bind):
package org.example.myapp.services;
import org.apache.tapestry5.ioc.ServiceBinder;
public class MyAppModule
{
public static void bind(ServiceBinder binder)
{
binder.bind(Indexer.class, IndexerImpl.class);
}
}
Construcción personalizada (build):
El método empieza con la frase build``serviceName
, el siguiente código crea el servicio "Indexer":
package org.example.myapp.services;
public class MyAppModule
{
public static Indexer buildIndexer()
{
return new IndexerImpl();
}
}
Un servicio puede ser configurado a través de recibir un objeto (mapa, lista, colección) que contenga elementos agregados por otros servicios (servicios que contribuyen a la configuración de un servicio).
@Local
inyecta solo los servicios definidos en el modulo.Para obtener un servicio por id:
public static Indexer build(@InjectService("JobScheduler") JobScheduler scheduler,
@InjectService("FileSystem") FileSystem fileSystem)
{
IndexerImpl indexer = new IndexerImpl(fileSystem);
scheduler.scheduleDailyJob(indexer);
return indexer;
}
Dentro de tapestry IoC, se pueden inyectar servicios dentro de la implementación de otros servicios. Las dependencias son proporcionadas a los servicios de las siguientes maneras:
Como parámetros en el método service builder:
public static Indexer build(JobScheduler scheduler, FileSystem fileSystem)
{
IndexerImpl indexer = new IndexerImpl(fileSystem);
scheduler.scheduleDailyJob(indexer);
return indexer;
}
Inyecciones a través del constructor:
package org.example.myapp.services;
import org.apache.tapestry5.ioc.annotations.InjectService;
public class IndexerImpl implements Indexer
{
private final FileSystem fileSystem;
public IndexerImpl(@InjectService("FileSystem") FileSystem fileSystem)
{
this.fileSystem = fileSystem;
}
}
Inyección a través de campos (nota: internamente utiliza reflection por lo tanto no es thread-safe por lo que se recomienda realizar la inyección a través del constructor y campos finales):
package org.example.myapp.services;
import org.apache.tapestry5.ioc.annotations.InjectService;
public class IndexerImpl implements Indexer
{
@InjectService("FileSystem")
private FileSystem fileSystem;
. . .
}
Además de inyectar servicios, tapestry permite inyectar otras cosas:
Ejemplo inyectando object providers, alertEmail es un símbolo inyectado:
public static Indexer build(String serviceId, Log serviceLog,
JobScheduler scheduler, FileSystem fileSystem,
@Inject @Value("${index-alerts-email}")
String alertEmail)
{
IndexerImpl indexer = new IndexerImpl(serviceLog, fileSystem, alertEmail);
scheduler.scheduleDailyJob(serviceId, indexer);
return indexer;
}
Nota: los recursos no pueden ser inyectados a través de campos, debe ser a través de constructores.
Ejemplo: se construye el servicio FileServicerDispatcher
, en la construcción se envía un mapa que tendrá las configuraciones:
public static FileServiceDispatcher buildFileServicerDispatcher(Map<String,FileServicer> contributions)
{
return new FileServiceDispatcherImpl(contributions);
}
Luego, para especificar un parámetro en la configuración se debe contrubuir el servicio, MappedConfiguration es el mapa definido como primer parámetro de buildFileServiceDispatcher
:
public static void contributeFileServicerDispatcher(MappedConfiguration<String,FileServicer> configuration)
{
configuration.add("txt", new TextFileServicer());
configuration.add("pdf", new PDFFileServicer());
}
Ejemplo de cómo contribuir un servicio con anotación:
@Contribute(FileServiceDispatcher.class)
public static void arbitraryMethodName(MappedConfiguration<String,FileServicer> configuration)
{
configuration.add("doc", new WordFileServicer());
configuration.add("ppt", new PowerPointFileServicer());
}
Existen 3 tipos de configuración.
Todas las configuraciones contienen los métodos:
add
toma un nombre, un objeto contribuido para ese nombre, y luego cero o más constraints. Los constraints controlan el orden. El constraint after
asegura que la contribución es ordenada después de esa contribución, el constraint before
es lo opuesto.addInstance
toma un nombre y un objeto .class a partir del cuál se creará la instancia, al crear la instancia del servicio, la construye y carga por completo.contribute param | build param |
---|---|
Configuration | Collection |
OrderedConfiguration | List |
MappedConfiguration | Map |
Cada configuración tiene un nombre único:
public void contributeMasterDispatcher(OrderedConfiguration<Dispatcher> configuration, . . .)
{
configuration.add("RootPath", new RootPathDispatcher(. . .), "before:Asset");
configuration.add(
"Asset",
new AssetDispatcher(. . .),
"before:PageRender");
configuration.add("PageRender", new PageRenderDispatcher(. . .));
configuration.add("ComponentAction", new ComponentActionDispatcher(. . .), "after:PageRender");
}
Nota: La configuración, una vez ensamblada y ordenada, es proporcionada como una lista.
Se puede agragar funcionalidad a los servicios decorándolos (a través de service decorator methods). Esos métodos crean interceptores que agregan comportamiento.
Service advice permite interceptar invocaciones de métodos en tus servicios, es una parte de la programación orientada a aspectos (AOP).
Service decorators son otra forma de hacer lo mismo (antes de tap5.1), services advisors es una característica más reciente (agregada en tap5.1). No se recomienda mezclar advice y decorator, si se hace los decoradores tienen preferencia.
Ejemplos de intercepciones: logging, validaciones de seguridad, manejo de transacciones,
Ejemplo que intercepta los métodos del servicio Executable, y remplaza el retorno, en vez de retornar null retorna la constante Executable.NULL. En todos los métodos advise, es obligatorio el parámetro MethodAdviceReceiver
:
public static void adviseExecutable(MethodAdviceReceiver receiver) {
MethodAdvice advice = new MethodAdvice() {
public void advise(MethodInvocation invocation) {
invocation.proceed(); // ejecuta el método
if (invocation.getReturnValue() == null) {
invocation.setReturnValue(Executable.NULL);
}
}
};
receiver.adviseAllMethods(advice);
}
El mismo ejemplo pero con anotaciones:
@Advise(serviceInterface = Executable.class)
public static void adviseExecutable(MethodAdviceReceiver receiver) {
...
}
}
Se puede utilizar la anotación @Order
para indicar en qué orden se ejecuta el advise. Por ejemplo, "before:*"
indica que el advise se debe ejecutar antes que cualquier otro decorador.
Artículo sobre advice/decorators con anotaciones
Permite agregar nuevas características a una aplicación con tan solo soltar un jar que contenga un módulo. Los servicios dentro del módulo son automáticamente integrados al registro global de la aplicación.
Para que un archivo jar sea identificado como un módulo autoloaded necesita contener en el manifest algo como:
Manifest-Version: 1.0
Tapestry-Module-Classes: org.example.mylib.LibModule, org.example.mylib.internal.InternalModule
La anotación @SubModule sirve para enlistar los módulos adicionales que serán autoloaded.
En general, deberá identificar un módulo en el manifest del jar, y hacer uso de @SubModule para agregar módulos adicionales (sin agregarlos al archivo manifest).
Los símbolos son llamados los parámetros de configuración de los servicios.
Los símbolos se pueden inyectar con la anotación @Simbol
o @Value
:
public class MyService implements MyServiceInterface
{
public MyService(@Inject @Value("${tapestry.production-mode}") boolean productionMode, ...)
{
if (productionMode) {
. . .
Note: When injecting a symbol as a string into a service, you must use the @Inject annotation as well as @Value or @Symbol; otherwise Tapestry will inject the service's service id.
De dónde se obtienen los símbolos?:
Para agregar símbolos a la aplicación:
public void contributeApplicationDefaults(MappedConfiguration<String, String> configuration)
{
configuration.add("some-service-id", "WackyCollaborator");
}