Tip #19 Module Private Visibility in OSGi
- by ByronNevins
I hate public and protected methods and classes. It requires so much work to change them in a huge project like GlassFish. Not to mention that you may well have to support those APIs forever. They are highly overused in GlassFish. In fact I'd bet that > 95% of classes are marked as public for no good reason. It's just (bad) habit is my guess.
private and default visibility (I call it package-private) is easier to maintain. It is much much easier to change such classes and methods around.
If you have ANY public method or public class in GlassFish you'll need to grep through a tremendous amount of source code to find all callers. But even that won't be theoretically reliable. What if a caller is using reflection to access public methods? You may never find such usages.
If you have package private methods, it's easy. Simply grep through all the code in that one package. As long as that package compiles ok you're all set. There can' be any compile errors anywhere else. It's a waste of time to even look around or build the "outside" world.
So you may be thinking: "Aha! I'll just make my module have one giant package with all the java files. Then I can use the default visibility and maintenance will be much easier. But there's a problem. You are wasting a very nice feature of java -- organizing code into separate packages. It also makes the code much more encapsulated. Unfortunately to share code between the packages you have no choice but to declare public visibility.
What happens in practice is that a module ends up having tons of public classes and methods that are used exclusively inside the module. Which finally brings me to the point of this blog:
If Only There Was A Module-Private Visibility Available
Well, surprise! There is such a mechanism. If your project is running under OSGi that is. Like GlassFish does! With this mechanism you can easily add another level of visibility by telling OSGi exactly which public you want to be exposed outside of the module. You get the best of both worlds:
Better encapsulation of your code so that maintenance is easier and productivity is increased.
Usage of public visibility inside the module so that you can encapsulate intra-module better with packages.
How I do this in GlassFish:
Carefully plan out at least one package that will contain "true" publics. This is the package that will be exported by OSGi. I recommend just one package.
Here is how to tell OSGi to use it in GlassFish -- edit osgi.bundle like so:-exportcontents: org.glassfish.mymodule.truepublics; version=${project.osgi.version}
Now all publics declared in any other packages will be visible module-wide but not outside the module.
There is one caveat:
Accessing "module-private" items outside of the module is controlled at run-time, not compile-time. The compiler has no clue that a public in a dependent module isn't really public. it will happily compile it. At runtime you will definitely see fireworks. The good news is that you don't have to wait for the code path that tries to use the "module-private" items to fire. OSGi will complain loudly when that module gets loaded. OSGi will refuse to load it. You will see an error like this:
remote failure: Error while loading FOO: Exception while adding the new
configuration : Error occurred during deployment: Exception while
loading the app : org.osgi.framework.BundleException: Unresolved
constraint in bundle com.oracle.glassfish.miscreant.code [115]:
Unable to resolve 115.0: missing requirement [115.0]
osgi.wiring.package;
(osgi.wiring.package=org.glassfish.mymodule.unexported). Please
see server.log for more details.
That is if you accidentally change code in module B to use a public that is really a "module-private" in module A, then you will see the error immediately when you try to test whatever you were changing in module B.