What's new in Slice 4.1

Caching per path

SInce Slice 4.1 you can cache your SliceResources created for a given path within single request processing.This is especially helpful if you have a heavy object (which reads a lot of properties) and you are using it a couple of times within single request, e.g. PageModel which keeps your page properties - this model may be often referenced by other components and there's no need to re-create it each time. Please note that caching can only be applied for resources which are not changed while a request is processed, otherwise such changes won't be reflected in cached objects.

Additionally, caching works not only within request context but also in osgi-services context. It's basically based on Slice's @ContextScope.

To make your SliceResources cacheable per path you should simply annotate them with @Cacheable annotation.

@SliceResource
@Cacheable
public class PageModel {
	@JcrProperty("jcr:title")
	private String title;
	...
} 

Simpler OSGi service support

If you want to use OSGi services within your guicified code, you can simple go for Peaberry solution which allows you to bind an interface to OSGi service (Slice is shipped with Peaberry out of the box). Normally, you bind OSGi services in some Guice module, e.g.

public class MyAppModule extends AbstractModule {
	@Override
	public void configure() {
		bind(ResourceResolverFactory.class).toProvider(Peaberry.service(ResourceResolverFactory.class)).single());
		// other bindings
	}
}

public class MyClass {
	@Inject
	ResourceResolverFactory factory;
    ...
}

From now on, you can simplify this declarations and Slice will do the work for you. You just need to annotate field or constructor argument with @OsgiService and Slice will bind such an interface/class to a Peaberry provider automatically when injector is being created. So your code can just look like this:

public class MyClass {
	@Inject
	@OsgiService
	ResourceResolverFactory factory;
    ...
}

In order to use OSGi service support you need to install OsgiToGuiceAutoBindModule module in your injector.

Enhanced injector registration

By convention, Slice reads injector name from second part of a resource type, e.g. if you request a /apps/myapp/mycomponent resource, the injector name will be myapp. This may be limiting in some cases, especially if you have multiple applications running on a single Sling/AEM instance and you want to group them meaningfully. Since Slice 4.1. you can instruct Slice where to look for an application, e.g. /apps/plugins/my-super-plugin-app. In this case your injector name will be "my-super-plugin-app" instead of "plugins". You can do this by creating InjectorRunner appropriately in your application Activator, passing applicationPath argument, e.g.:

@Override
public void start(final BundleContext bundleContext) {
    final InjectorRunner injectorRunner = new InjectorRunner(bundleContext, INJECTOR_NAME,
                "/apps/plugins/my-super-plugin-app", BUNDLE_NAME_FILTER, BASE_PACKAGE);
	//modules installation
    injectorRunner.start();
} 

Injector listener

Sometimes it's necessary to use Slice bindings during the activation of the OSGi component. It can't be done in the @Activate method, as there is a race between component activation and the application activator that creates Slice injector - the result is that Slice injector isn't created yet, so OSGi component can't use it. The workaround is to use lazy loading and initialize data on the first request, but it may make the OSGi service design more complex.

The new InjectorListener interface allows to overcome this problem. It contains method injectorAvailable(String) that will be invoked for each injector that has been already created. Therefore, the initialization logic may be moved there:

@Component
@Service
public class MyComponent implements InjectorListener {
	// we need to have this atomic boolean, as the injector may become available
	// a few times during component lifecycle
	private final AtomicBoolean initialized = new AtomicBoolean();

	@Activate
	protected void activate() {
		initialized.set(false);
	}

	@Override
	public void injectorAvailable(String injectorName) {
		if ("myapp".equals(injectorName) && !initialized.getAndSet(true)) {
			InjectorWithContext injector = InjectorUtil.getInjector("myapp", getResolver());
			// do something clever with the injector
		}
	}
}