Proposal: Enhance ServiceLoader to understand factory methods
Justification: A lot of our services in NetBeans are final classes. Still,
we
would like to use the META-INF/services registration to register their
providers. Currently this does not work, we either need to unfinalize the
classes (which restricts future evolution options) or use different ways of
registering the providers. We propose simple and backward compatible
extension to current ServiceLoader registration scheme. If accepted,
NetBeans'll eliminate our non-standard registration methods, while keeping
benefits of final classes. Moreover the same techniques will be available to
any JavaSE project.
Risks: API change includes simple, compatible extension. New code is able to
accept old registrations. Old code (JDK6) will generate
ServiceConfigurationError in case of seeing new registration, however.
Test: I have found tests in test/java/util/ServiceLoader/ directory and I've
modified them to verify also our new usecase. All of them are passing, if I
run them by running ./basic.sh script - I am not sure if that is the right
way of running your tests.
I am attaching my current patch. Let me know what shall I change to allow
this
to be integrated to OpenJDK7. Thanks a lot.
Jaroslav Tulach
NetBeans Platform Architect
diff -r 14f50aee4989 src/share/classes/java/util/ServiceLoader.java
--- a/src/share/classes/java/util/ServiceLoader.java Thu Oct 02 19:58:32 2008 -0700
+++ b/src/share/classes/java/util/ServiceLoader.java Sat Oct 04 13:53:11 2008 +0200
@@ -29,6 +29,7 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
+import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
@@ -260,8 +261,13 @@
fail(service, u, lc, "Illegal provider-class name: " + ln);
for (int i = Character.charCount(cp); i < n; i += Character.charCount(cp)) {
cp = ln.codePointAt(i);
- if (!Character.isJavaIdentifierPart(cp) && (cp != '.'))
+ if (!Character.isJavaIdentifierPart(cp) && (cp != '.')) {
+ if (cp == '(' && i == ln.length() - 2 && ln.codePointAt(i + 1) == ')') {
+ // OK ends with "()"
+ break;
+ }
fail(service, u, lc, "Illegal provider-class name: " + ln);
+ }
}
if (!providers.containsKey(ln) && !names.contains(ln))
names.add(ln);
@@ -359,8 +365,20 @@
String cn = nextName;
nextName = null;
try {
- S p = service.cast(Class.forName(cn, true, loader)
+ S p;
+ if (cn.endsWith("()")) { // NOI18N
+ int lastDot = cn.lastIndexOf('.');
+ if (lastDot == -1) {
+ fail(service, "Use pkg.ClassName.methodName() in " + cn);
+ }
+ String mn = cn.substring(lastDot + 1, cn.length() - 2);
+ Class<?> clazz = Class.forName(cn.substring(0, lastDot), true, loader);
+ Method method = clazz.getDeclaredMethod(mn);
+ p = service.cast(method.invoke(null));
+ } else {
+ p = service.cast(Class.forName(cn, true, loader)
.newInstance());
+ }
providers.put(cn, p);
return p;
} catch (ClassNotFoundException x) {
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooProvider1.java
--- a/test/java/util/ServiceLoader/FooProvider1.java Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooProvider1.java Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,6 @@
* have any questions.
*/
-public class FooProvider1 extends FooService { }
+public class FooProvider1 extends FooService {
+ public static FooProvider1 create1() { return new FooProvider1(); }
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooProvider2.java
--- a/test/java/util/ServiceLoader/FooProvider2.java Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooProvider2.java Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,6 @@
* have any questions.
*/
-public class FooProvider2 extends FooService { }
+public class FooProvider2 extends FooService {
+ public static FooService create2() { return new FooProvider2(); }
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/FooService.java
--- a/test/java/util/ServiceLoader/FooService.java Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/FooService.java Sat Oct 04 13:53:11 2008 +0200
@@ -21,4 +21,5 @@
* have any questions.
*/
-public abstract class FooService { }
+public abstract class FooService {
+}
diff -r 14f50aee4989 test/java/util/ServiceLoader/basic.sh
--- a/test/java/util/ServiceLoader/basic.sh Thu Oct 02 19:58:32 2008 -0700
+++ b/test/java/util/ServiceLoader/basic.sh Sat Oct 04 13:53:11 2008 +0200
@@ -71,6 +71,18 @@
(cd $JARD; "$JAR" -cf ../p$n.jar *)
done
+ rm -rf $JARD/*; mkdir -p $JARD/META-INF/services
+ echo "FooProvider1.create1()" \
+ >$JARD/META-INF/services/FooService
+ cp $TESTCLASSES/FooProvider1.class $JARD
+ (cd $JARD; "$JAR" -cf ../p-c1.jar *)
+
+ rm -rf $JARD/*; mkdir -p $JARD/META-INF/services
+ echo "FooProvider2.create2()" \
+ >$JARD/META-INF/services/FooService
+ cp $TESTCLASSES/FooProvider1.class $JARD
+ (cd $JARD; "$JAR" -cf ../p-c2.jar *)
+
mv p3.jar $EXTD
cp $TESTCLASSES/Load.class $TESTD
@@ -113,6 +125,9 @@
go ".${SEP}p2.jar" "" FooProvider2
+go ".${SEP}p-c2.jar" "" FooProvider2
+go ".${SEP}p-c1.jar" "" FooProvider1
+
go "" "-Djava.ext.dirs=$EXTD" FooProvider3
go "$TESTD${SEP}p2.jar" "" FooProvider1 FooProvider2