Many developers seek to separate API interfaces and abstract classes from their implementation in separate packages. Yet, this almost always leads to cycles between java packages (api refers to impl, and vice-versa). There has been many frameworks, such as OSGi, supporting modularization, but the documentation has not always been good and complete. Many developers have struggled with class loading issues too.
Fortunately, Java has delivered the ServiceLoader utility since release 6. The example described in this post is available from Github in the Java-ServiceLoader directory. It is inspired from here.
Service & Implementation
We define a simple interface and its implementation:package com.service.API; public interface MyService { long getTime(); } package com.service.API.Impl; import com.service.API.MyService; public final class MyServiceImpl implements MyService { public MyServiceImpl() { }; @Override public long getTime() { return System.currentTimeMillis(); } }
Service Loading
Next, we declare the implementation in a file having the fully qualified name of the API under the META-INF/services directory in the .jar:The file contains fully qualified name of the implementation:
com.service.API.Impl.MyServiceImpl
Next, we load the service using the ServiceLoader:
public class MyApp { public static <T> T loadService(Class<T> api) { T result = null; ServiceLoader<T> impl = ServiceLoader.load(api); for (T loadedImpl : impl) { result = loadedImpl; if ( result != null ) break; } if ( result == null ) throw new RuntimeException( "Cannot find implementation for: " + api); return result; } public static final MyService myService = loadService(MyService.class); public static void main(String[] args) { System.out.println("Time is: " + myService.getTime()); } }
i'm thinking whether the author is in fact java developer.
ReplyDeleteLooking at the fully qualified names of the classes, such as com.service.API.Impl.MyServiceImpl makes me feel really uncomfortable (irritated, i would say) - as if there was a API class containing inner class called Impl, containing another inner class MyServiceImpl.
Is there a reason why code conventions http://docs.oracle.com/javase/tutorial/java/package/namingpkgs.html are disobeyed?
This is only a code example to illustrate a concept... Feel free to apply any code name conventions you like!
ReplyDeleteHi Jérôme,
ReplyDeleteYou mention "The implementation must have a public parameterless constructor."
Could you please explain why this is required?
Thanks
Sure. After Java loads the code for the implementation of the service API, it needs to create a new() instance of the implementing object. But, it does not know anything about potential parameters it could/should pass to the constructor. If the implementation did not have a parameterless constructor, calling new() would trigger a runtime exception. Hence, the need for this parameterless constructor so Java can call it when new() is called to create the implementing object instance.
DeleteThanks for the explanation. A follow up question:
DeleteConsider the javadoc:
http://docs.oracle.com/javase/specs/jls/se7/html/jls-13.html#jls-13.1-110-F.2.
It says:
"In addition, the constructor of a non-private inner member class must be compiled such that it has as its first parameter, an additional implicit parameter representing the immediately enclosing instance (§8.1.3)."
So does that mean I cannot externalize a inner class. Please consider the following code:
import java.io.*;
class Y {
class ABC {
ABC() {
System.out.println("ABC Constructor");
}
public void writeExternal(ObjectOutput out)
throws IOException {
System.out.println("ABC.writeExternal");
}
public void readExternal(ObjectInput in)
throws IOException, ClassNotFoundException {
System.out.println("ABC.readExternal");
}
}
public void foo() throws IOException, ClassNotFoundException {
System.out.println("Constructing objects:");
ABC abc = new ABC();
ObjectOutputStream o = new ObjectOutputStream(
new FileOutputStream("InnerClass.out"));
System.out.println("Saving objects:");
o.writeObject(abc);
o.close();
// Now get them back:
ObjectInputStream in = new ObjectInputStream(
new FileInputStream("InnerClass.out"));
System.out.println("Recovering abc:");
// OOPS! Throws an exception:
abc = (ABC)in.readObject();
}
}
public class InnerClass {
public static void main(String[] args) throws IOException, ClassNotFoundException {
System.out.println("Hello World\n");
Y y = new Y();
System.out.println(y);
y.foo();
}
}
While running the program I get the following error:
Hello World
Y@6bc7c054
Constructing objects:
ABC Constructor
Saving objects:
Exception in thread "main" java.io.NotSerializableException: Y$ABC
at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1180)
at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:346)
at Y.foo(InnerClass.java:24)
at InnerClass.main(InnerClass.java:40)
How do I externalize ABC.
Thanks
I am sorry if the code and error message is not in format as it shows up in your blog. If there is a way, please do let me know.
DeleteAs per described in the documentation you are pointing at, the ABC constructor does not have an instance of Y as a parameter... If you made ABC a static class of Y, you would not have an issue. You can also ask such questions on StackOverflow.com too.
Delete