Closed Flamenco closed 7 years ago
Per the Java Language Specification, static initializers are only executed when the class is… initialized, which basically only happens when it's "reached" by some code (before it can execute itself). See https://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.4.1
If nothing in your code ever references the CodeFormatter
class explicitly triggering its initialization, then its static initializer won't even be called. So, the behavior you're experiencing is expected.
So how about an annotation?
BTW I do see now that my calls to new are triggering the static block.
An annotation by itself wouldn't be enough, and an annotation specific to GWT is out of the question : GWT follows Java here. An annotation could however trigger an annotation processor that could generate some code to explicitly initialize the annotated classes, but the generated code would still have to be called somehow.
Your best bet is to refactor your code: you could use a static method instead of the static initializer (and call those methods from somewhere, or move all your registrations to the key classes (Goody
, rather than CodeFormatter
).
Either that or the annotation processor approach (which could then somehow mimick the java.util.ServiceLoader
for your classes).
I'd personnally recommend explicit initialization, as otherwise you might waste time debugging why your annotation processor skips a class or another and doesn't generate the expected code.
Ultimately, I am going for something like this (OSGi DS)
@Component
class Service1 implements Service
{
}
I think your idea to
trigger an annotation processor that could generate some code to explicitly initialize the annotated classes
would work here, as I could generate a single file that registers all the components and then activate them by calling the generated class after the module is loaded.
Doing this would mean I could simply drop in components that have no dependencies to themselves and only a single dependency to the annotation. I think that is better than having to manually add each of them and explicitly call a method.
So I created an annotation processor that works like this:
@Init
public class MyInit {
public static void init() {
Logger.getGlobal().info("** My Init was called");
}
}
@Init("hello")
public class MyInit2 {
public static void hello() {
Logger.getGlobal().info("** My Init2 was called");
}
}
I added a module and entry point to the jar that declares the annotation, and it calls the generated init file (in the main project) on startup. So this solves:
It would be nice if this was built into the core, and called before ANY other modules. @tbroyer Should I assume that your comment that "an annotation specific to GWT is out of the question" means I should not submit my work to the GWT project?
So you have added a module, an entry point, a generator, an annotation... and lot of obscure magic 😉 just to avoid this line in your code... IMO use just this line in your APP code.
ServiceManager.getInstance().register(Goody.class, new CodeFormatter());
In 2 years you will appreciate those explicit references 😜. Even if you are really motivated to leave this code in the lib, you can crearte a kind of ServiceManager module using ~lambdas~ one static method.
// lib code
public class CodeFormatter implements Goody {
public static void module(ServiceManager sm) { sm.register(Goody.class, new CodeFormatter()); }
@Override public void process(TextBoxBase textBox, CanRender renderer) { }
}
// app code (some bootstrapping class)
CodeFormatter.module(ServiceManager.getInstance());
// or with minimal change in ServiceManager (register accept Consumer<ServiceManager>)
ServiceManager.getInstance().register(CodeFormatter::module);
@ibaca I have also implemented the following annotation. I did not post it because the topic was not about 'service registry'. It was about adding GWT code that is initialized without any direct references. Anyway, the beauty of this approach is that I can add and remove additional services without any references from any other code, and with only a single dependency on 1 annotation. I can even move a service to another GWT module without having to manually update a static file.
BTW I was using one static method explicitly called from my entry point, and given I have over 300 of these classes, it was a PITA to maintain. So yes, there was much effort, but thats all in the past and now I can enjoy the fruits of my labor. Definitely worth the few hours learning about Annotation Processors!
And going forward I can do all sorts of magic, such as using factories to lazy load the services and add key/value map to the annotation to attached information for filtering and prioritizing the services. I have been using OSGi DS for years, and have never regretted it. This use case is exactly what annotations are meant for.
@Component
Here is the usage
@Component
public class CodeFormatter implements Goody {
@Override
public String getIcon() {
return "images/menu/format.png";
}
@Override
public void process(RenderedDocumentInfo currentModel) {
}
@Override
public String getCaption() {
return "Format";
}
}
Here is the generated code that is called automatically (could be refactored a bit...)
package tv.twelvetone.gwt.component.client;
import tv.twelvetone.gwt.quickchords.client.service.ServiceManager;
public class GwtComponentService {
static public void register() {
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.RemoveLyrics());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.TextBlockPageBreak());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.dialogs.FindReplaceDlg());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.ToggleLyrics());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.QuickCapoGoody());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.EmailUsGoodie());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.ReportBugGoodie());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.BadRenderGoodie());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.CompareTo());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.FormAnalyzer());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.Compare());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.CodeFormatter());
ServiceManager.getInstance().register("tv.twelvetone.gwt.quickchords.client.goodies.Goody", new tv.twelvetone.gwt.quickchords.client.goodies.FullScreen());
}
}
It would be nice if this was built into the core, and called before ANY other modules. @tbroyer Should I assume that your comment that "an annotation specific to GWT is out of the question" means I should not submit my work to the GWT project?
That's right; specifically because we don't want things like:
the beauty of this approach is that I can add and remove additional services without any references from any other code
We have this issue with GWT-RPC already, where your generated code will be different (understand: could be unnecessarily bloated) depending on which classes are present in your classpath, whether you use them or not.
The same is true in your case : add a JAR to your classpath and you suddenly get possibly tens of things registered for your, with all their dependencies linked in, whether you'll actually use them or not (because the GWT compiler won't optimize things out just because they are never looked-up in a map).
GWT is about generating code that's as optimized as possible; your proposal is a recipe for failure, as anything that puts developer experience before user experience.
See http://www.gwtproject.org/makinggwtbetter.html, specifically:
Sometimes there are conflicts between what is easy for us developers and what benefits end users the most. When the two are in conflict, end user experience almost always wins.
Thanks for the explaination @tbroyer. I'm glad I asked so as to not 'bark up the wrong tree'. I do think however that the developer can always look at the generated code and determine what is best for the user experience, with the option to edit and manually call the initialization if needed. As my initial example had a static initializer, that is also out of the developers control and will be called (although not before the module entry). It will also pull in the same dependencies.
GWT version:2.8 Browser (with version):All *Operating System:All*
Description
I have created several classes that have a static initializer and register themselves by interface in a service manager. They are consumed by calling the service manager and using the registered instantiation.
The classes are not getting included in the compile because I have essentially removed dependencies to them.
It seems an annotation to force these classes to be included would be appropriate, Or perhaps the static initializer should be enough to trigger the inclusion, as it references the class CTOR.
Steps to reproduce
Known workarounds
In a class that is included, call new on each class.
Links to further discussions