Java Modules Wish List – provide services using lambda functions

In the Java Module System different modules can integrate which each other in a loosely coupled way using services obtained through the ServiceLoader API.

module ‘bar’ provides a service to module ‘foo’ which uses the service.

In this blogpost, I describe how the service loader currently works and how it could work if it would allow a little more flexibility. Some ideas for the Java Architects to consider …

How it currently works

To use a service in 1 module you declare this in the module-info

module foo {
  uses ServiceA;
}

and then an instance of this service can be obtained programmatically using the service loader

ServiceLoader.load(ServiceA.class);

To provide a service from another module you declare this in the module-info

module bar {
  provides ServiceA with MyServiceA;
}

where the class MyServiceA will be used to obtain an instance. This is currently supported in 2 ways:

  • default constructor of MyServiceA is called on each load
  • a static ‘provider’ method is present on MyServiceA returning an instance on each load

Using the default constructor will create a new instance for every call to the ServiceLoader. Now suppose you are using a dependency injection framework like Spring. Services exist as singleton beans in a spring application context.

Using the static ‘provider’ method allows us to get the service instance from a spring application context. Every call to the ServiceLoader will get the same singleton instance from the application context. In most cases that is what you need.

To get this working you would then define the service as bean in spring

@Named
public class MyServiceA implements ServiceA { }

and make sure you can statically access the application context through a class that gets the application context injected in a static field

@Named
public class ApplicationContextHolder implements ApplicationContextAware { 
	private static ApplicationContext context;
	
	@Override 
	public void setApplicationContext(ApplicationContext context) {

		ApplicationContextHolder.context = context;
	}
	
	public static <T> T provider(Class<T> serviceType) {
		return ApplicationContextHolder.context.getBean(serviceType);

	}
}

and in the MyServiceA class you add the static ‘provider’ method

@Named
class MyServiceA implements ServiceA {
	public static ServiceA provider() {
		return ApplicationContextHolder.provider(ServiceA.class);
	}
}

The biggest disadvantage is that you need to create this static provider method on every class even when it only delegates to the static ApplicationContextHolder.provider() .

How it could work

What if in the module-info you could provide a service using a lambda expression?

Something like

module bar {
  provides ServiceA with (serviceType) -> ApplicationContextHolder.provider(servicetype);
}

or even shorter using a method reference

module bar {
  provides ServiceA with ApplicationContextHolder::provider;
}

or a small adaptation to the language could improve the convention to call a static ‘provider’ method on a class: passing through the class type of the service to provide as parameter. Then the module declaration does not look very different from what is currently supported

module bar {
  provides ServiceA with ApplicationContextHolder;
  provides ServiceB with ApplicationContextHolder;
}

and we still get a lot more flexibility with less boilerplate to write. Java then needs to call the ApplicationContextHolder.provider(serviceType) method with the serviceType as parameter. Notice that the same class can be used to provide all services by leveraging the parameter to know which service type to provide.

Ideas for more advanced extensions of the services model

Thinking further on this, if the module system could support an annotation to define extensions of module descriptors even more is possible for framework developers. For example

@ModuleServiceExtension(SpringModuleExtension.class)
module bar {
  provides ServiceA;
  provides ServiceB;
}

This is very similar to Junit5 extensions. Notice the missing ‘with’ declarations which could be allowed if java detects a ModuleServiceExtension annotation being present.

This ModuleServiceExtension annotation should then allow the given class to post-process the module descriptor by adding the service providing code. For example, the SpringModuleExtension could look like

public class SpringModuleExtension implements ModuleServiceExtender {
  @Override
  public void extend(ModuleDescriptor moduleDescriptior) {
    moduleDescriptor.provides().forEach((provide)->{
      provide.with(ApplicationContextHolder.provider(provide.serviceType()));
    });
  }
}

The java-modules-context-boot framework makes modules spring context aware through annotations (see blogpost on Boot Spring Context within each Java Module). This framework could include the @ModuleServiceExtension(SpringModuleExtension.class) inside its @ModuleContext annotation

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.MODULE)
@Documented
@ModuleServiceExtension(SpringModuleExtension.class)
public @interface ModuleContext {
...
}

Then the following code will get the spring services transparently provided to the ServiceLoader API:

module foo {
  uses ServiceA;
}

@ModuleContext(mainConfigurationClass=BarContextConfiguration.class)
module bar {
  provides ServiceA;
}

Conclusion

Although the code in this blogpost currently is not supported, it could be in future? Making the options to provide services in Java Modules a little more flexible allows better use of the ServiceLoader mechanism between loosely coupled modules.

Up to the JDK architects to consider these ideas and take Java Modules to a next level. Please leave your comments and feedback below.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s