The most difficult challenge in large software projects is complexity. The only approach is to break things down into smaller, more understandable modules.
Modularity enables:
- Division of Labor — separate individuals/groups work on separate modules
- Abstraction — think about the system as a whole without knowing every part
- Reuse — use modules with minimal alterations in other projects
- Ease of Maintenance — remove and replace failed modules without affecting the rest
- Self-Contained — can be moved, installed, uninstalled as a single unit
- Highly Cohesive — sticks to one logical purpose and fulfils it well
- Loosely Coupled — not concerned with internal implementation of other modules
To support all three: modules must have a well-defined interface that enforces logical boundaries and prevents access to internals.
- JAR Files — very poor modularity (no version concept, no information hiding, no dependency management, "classpath hell")
- Java Platform Module System (JPMS) — arrived in Java 9 (2017), only solves parts of what OSGi targets
- OSGi — a standard defined by an Alliance of ~40 companies. Freely available specifications, used in many big applications for years, excellent tool support.
OSGi is the services-based runtime technology providing the basis for modularized Java development:
- Highly dynamic class loading and execution environment for bundles
- Full control over visibility and lifecycle of services exposed by bundles
- A service registry provides a cooperation model taking lifecycle dynamics and version requirements into account
- Lightweight, highly dynamic — allows hot-deployment without restarting the server
- Bundle — the module in OSGi. JAR files with extra metadata.
- Package — a Java package. OSGi decides per-package if exported (accessible from other bundles) or private.
- Service — an object registered in the service registry, looked up by interface name(s).
- Component — an object whose lifecycle is managed by OSGi. May bind/consume other services.
- Lifecycle — components can be started, stopped, listen to configuration changes. Bundles have their own lifecycle too.
Bundles contain compiled Java code, scripts, content, and configuration. They can be loaded/installed during normal operations. One Maven project is usually one bundle.
Bundle lifecycle states: INSTALLED → RESOLVED → STARTING → ACTIVE → STOPPING → UNINSTALLED. Everything is OK when the bundle is active!
Each bundle and exported package has a version. OSGi has Semantic Versioning built-in:
- Major — incompatible changes
- Minor — backwards compatible, new/updated features
- Micro — backwards compatible, usually only bugfixes
- Qualifier — optional fourth part (timestamp, build number). Comparison is alphanumeric.
When a bundle cannot be activated due to missing dependencies, it's always the package version that is relevant, not the bundle version.
Only Java packages intended for use by other bundles should be exported. All others stay private.
- bnd-maven-plugin (new projects): exports only packages with
package-info.javaand@Versionor@Exportedannotation - maven-bundle-plugin (legacy): exports all packages by default (except those with "impl" or "internal" in name)
@Component(service = OracleService.class, immediate = true)
public class OracleServiceImpl implements OracleService {
@Reference
private GatekeeperService gatekeeperService;
@Activate
protected void activate(Map<String, Object> config) {
// start service...
}
@Override
public void predictFuture() {
// service logic
}
}
@ObjectClassDefinition(name = "Matrix System Configuration")
@interface OracleConfig {
@AttributeDefinition(description = "URL of prediction service")
String entryPointUrl();
@AttributeDefinition(description = "Default usage role.")
String role() default "public";
@AttributeDefinition(description = "Set timeout in sec.")
int timeout() default 5;
}
@Component(service = OracleService.class, immediate = true)
@Designate(ocd = OracleConfig.class)
public class OracleServiceImpl implements OracleService {
@Activate
protected void activate(OracleConfig config) {
String url = config.entryPointUrl();
}
}
A replacement for Listener/Observer that fits the dynamic OSGi world. Uses the service registry as "whiteboard" to decouple listeners from sources.
Examples:
- OSGi eventing (sending events across bundles)
- HTTP Whiteboard (register Servlets/Filters)
- Register scheduled tasks and job executors in Sling
The bnd tool scans all classes to identify referenced packages and creates import/export statements. Maven plugin wrappers:
- bnd-maven-plugin — recommended for all new projects. Released together with new bnd releases.
- maven-bundle-plugin — the de-facto standard for years, still usable for existing projects but no longer actively maintained.
- Non-OSGi libraries: Many open-source projects now offer OSGi bundles. Those that don't can be repackaged using wrapper Maven projects.
- Classloading problems: Libraries using Thread Context Classloader (TCCL) or classpath scanning are difficult in OSGi. Workaround: temporarily reassign the thread context classloader.
- Profilers/Coverage tools: Need boot delegation configuration to work, e.g.
org.osgi.framework.bootdelegation=com.jprofiler.*