Revisiting OSGi DS Annotations

A deeper dive into @Activate, @Deactivate and @Modified annotations.

I've been asked a couple of times recently about different aspects of @Modified annotation that I'm not sure have been made clear in the documentation, so I wanted to cover the lifecycle annotations in a little further detail so we can use them effectively.

The @Activate, @Deactivate and @Modified annotations are used for lifecycle event notifications for the DS components. They get called when the component itself is activated, deactivated or modified and allow you to take appropriate action as a result.

One important note - these lifecycle events will only be triggered if your @Component is actually alive. I know, sounds kind of weird, but it can happen. If you have an @Reference which is mandatory but is not available, your @Component will not be alive and your @Activate (and other) method will not be invoked.

@Activate

This annotation is used to notify your component that it is now loaded, resolved and ready to provide service. You use this method to do some final setup in your component. It is equivalent to Spring's afterPropertiesSet() InitializingBean interface.

One of the cool things about the @Activate method is that the signature for the method that you are creating is not fixed. You can have zero, one or more of the following parameters:

Map<String, Object> properties The property map from the @Component properties, also can contain your ConfigurationAdmin properties.
BundleContext bundleContext The bundle context for the bundle that holds the component that is being activated. Saves you from having to look it up, is great when you want to enable a ServiceTracker.
ComponentContext The component context contains the above objects, but most of all it has context information about the component itself.

 

So the following @Activate methods would all individually be considered valid:

@Activate
protected void activate() {...}

@Activate
protected void afterPropertiesSet(Map<String,Object> props, 
    BundleContext bCtx) { ... }

@Activate
public void dontCallMe(BundleContext bundleContext,  Map<String, Object> properties, 
    ComponentContext componentContext) { ... }

So we can use any method name (although Liferay tends to stick with activate()), any combination of parameters in any order we want.

@Deactivate

Hopefully this is obvious that it is invoked when the component is about to be deactivated. What might not be so obvious is when, exactly, it is called.

Basically you can be sure that your component context is still valid (nothing has been done to it yet), but it is about to happen. You want to use this lifecycle event to clean up before your component goes away.

So if you have a ServiceTracker open, this is your chance to close it. If you have a file open or a DB connection or any resource, use this as your entry point to clean it all up.

Like the @Activate annotation, the method signature for the @Deactivate methods is variable. It supports all of the same parameters as @Activate, but it has an additional value, an int which will hold the deactivation reason.

I've never been worried about the deactivation reason myself, but I suppose there are good use cases for receiving them. If you want to see the codes and explanations, check out this Felix ticket: https://issues.apache.org/jira/browse/FELIX-925

@Modified

So this one is a fun one, one that is not really documented that well, IMHO.

You can find blogs, etc where it boils down to "this method is called when the service context changes", but there is an assumption there that you understand what the service context is in the first place.

The tl;dr version is that this method is invoked mostly when your configuration changes, ala Configuration Admin.

For example, most of us will have some kind of code in our @Activate method to receive the properties for the component (from the @Component properties and also from the Config Admin), and we tend to copy the value we need into our component, basically caching it so we don't have to track it down later when we need it.

This is fine, of course, as long as no one goes to the control panel and changes your Config Admin properties.

When that happens, you won't have the updated value. Your only option in this case is to restart (the service, the component, or the container will all result in your getting the new value).

But that's kind of a pain, don't you think? Change a config, restart the container?

Enter @Modified. @Modified is how you get notified of the changes to the Config Admin properties, either via a change in the control panel or a change to the osgi/configs/<my pid>.config files.

When you have an @Modified annotation, you can update your local cache value and then you won't require a restart when the data changes.

Note that sometimes you'll see Liferay code like:

@Activate
@Modified
protected void activate(Map<String, Object> properties) { ... }

where the same method is used for both lifecycle events. This is fine, but you have to ensure that you're not doing anything in the method that you don't want invoked for the @Modified call.

For example, if you're starting a ServiceTracker, an @Modified notification will have you restarting the tracker unless you are careful in your implementation.

I often find it easier just to use separate methods so I don't have to worry about it.

The method signature for your @Modified methods follows the same rules as @Activate, so all of the same parameter types are allowed. You can still get to the bundle context or the component context if you need to, but often times this may not be necessary.

Conclusion

So there you kind of have it. There is not a heck of a lot more to it.

With @Activate, you get the lifecycle notification that your component is starting. With @Deactivate, you can clean up before your component is stopped. And with @Modified, you can avoid the otherwise required and pesky restart.