Managing Spring beans with JMX - Integrating Spring - Spring in Action, 4th Edition: Covers Spring 4 (2015)

Spring in Action, 4th Edition: Covers Spring 4 (2015)

Part 4. Integrating Spring

Chapter 20. Managing Spring beans with JMX

This chapter covers

· Exposing Spring beans as managed beans

· Remotely managing Spring beans

· Handling JMX notifications

Spring’s support for DI is a great way to configure bean properties in an application. But once the application has been deployed and is running, DI alone can’t do much to help you change that configuration. Suppose you want to dig into a running application and change its configuration on the fly. That’s where Java Management Extensions (JMX) comes in.

JMX is a technology that enables you to instrument applications for management, monitoring, and configuration. Originally available as a separate extension to Java, JMX is now a standard part of the Java 5 distribution.

The key component of an application that’s instrumented for management with JMX is the managed bean (MBean). An MBean is a JavaBean that exposes certain methods that define the management interface. The JMX specification defines four types of MBeans:

· Standard MBeans —MBeans whose management interface is determined by reflection on a fixed Java interface that’s implemented by the bean class.

· Dynamic MBeans —MBeans whose management interface is determined at runtime by invoking methods of the DynamicMBean interface. Because the management interface isn’t defined by a static interface, it can vary at runtime.

· Open MBeans —A special kind of dynamic MBean whose attributes and operations are limited to primitive types, class wrappers for primitive types, and any type that can be decomposed into primitives or primitive wrappers.

· Model MBeans —A special kind of dynamic MBean that bridges a management interface to the managed resource. Model MBeans aren’t written as much as they are declared. They’re typically produced by a factory that uses some meta-information to assemble the management interface.

Spring’s JMX module enables you to export Spring beans as model MBeans so that you can see inside your application and tweak the configuration—even while the application is running. Let’s see how to JMX-enable your Spring application so that you can manage the beans in the Spring application context.

20.1. Exporting Spring beans as MBeans

There are several ways you can use JMX to manage the beans in the Spittr application. In the interest of keeping things simple, let’s start by making a modest change to SpittleController as it appeared in listing 5.10. We’ll add a new spittlesPerPage property:

public static final int DEFAULT_SPITTLES_PER_PAGE = 25;

private int spittlesPerPage = DEFAULT_SPITTLES_PER_PAGE;

public void setSpittlesPerPage(int spittlesPerPage) {

this.spittlesPerPage = spittlesPerPage;

}

public int getSpittlesPerPage() {

return spittlesPerPage;

}

Before this change, when SpittleController called findSpittles() on the SpitterService, it passed in 20 for the second argument, asking for only the most recent 20 Spittles. Now, rather than commit to that decision at build time with a hard-coded value, you’re going to use JMX to leave the decision open to change at runtime. The new spittlesPerPage property is the first step toward enabling that.

But on its own, the spittlesPerPage property can’t enable external configuration of the number of spittles displayed on the page. It’s just a property on a bean, like any other property. What you need to do next is expose the SpittleController bean as an MBean. Then thespittlesPerPage property will be exposed as the MBean’s managed attribute, and you’ll be able to change its value at runtime.

Spring’s MBeanExporter is the key to JMX-ifying beans in Spring. MBeanExporter is a bean that exports one or more Spring-managed beans as model MBeans in an MBean server. An MBean server (sometimes called an MBean agent) is a container where MBeans live and through which the MBeans are accessed.

As illustrated in figure 20.1, exporting Spring beans as JMX MBeans makes it possible for a JMX-based management tool such as JConsole or VisualVM to peer inside a running application to view the beans’ properties and invoke their methods.

Figure 20.1. Spring’s MBeanExporter exports the properties and methods of Spring beans as JMX attributes and operations in an MBean server. From there, a JMX management tool such as JConsole can look inside the running application.

The following @Bean method declares an MBeanExporter in Spring to export the spittleController bean as a model MBean:

@Bean

public MBeanExporter mbeanExporter(SpittleController spittleController) {

MBeanExporter exporter = new MBeanExporter();

Map<String, Object> beans = new HashMap<String, Object>();

beans.put("spitter:name=SpittleController", spittleController);

exporter.setBeans(beans);

return exporter;

}

In its most straightforward form, MBeanExporter can be configured through its beans property by injecting a Map of one or more beans that you’d like to expose as model MBeans in JMX. The key of each <entry> is the name to be given to the MBean (composed of a management domain name and a key-value pair—spitter:name=SpittleController in the case of the SpittleController MBean). The value of entry is a reference to the Spring-managed bean that’s to be exported. Here, you’re exporting the spittleController bean so that its properties can be managed at runtime through JMX.

With the MBeanExporter in place, the spittleController bean is exported as a model MBean to the MBean server for management under the name Spittle-Controller. Figure 20.2 shows how the SpittleController MBean appears when viewed through JConsole.

Figure 20.2. SpittleController exported as an MBean and seen through the eyes of JConsole

As you can see on the left side of figure 20.2, all public members of the Spittle-Controller are exported as MBean operations and attributes. This probably isn’t what you want. All you really want to do is configure the spittlesPerPage property. You don’t need to invoke thespittles() method or muck about with any other part of SpittleController. Thus, you need a way to select which attributes and operations are available.

To gain finer control over an MBean’s attributes and operations, Spring offers a few options, including the following:

· Declaring bean methods that are to be exposed/ignored by name

· Fronting the bean with an interface to select the exposed methods

· Annotating the bean to designate managed attributes and operations

Let’s try each of these options to see which best suits the SpittleController MBean. You’ll start by selecting the bean methods to expose by name.

From whence the MBean server?

As configured, MBeanExporter assumes that it’s running in an application server (such as Tomcat) or some other context that provides an MBean server. But if your Spring application will be running standalone or in a container that doesn’t provide an MBean server, you’ll want to configure an MBean server in the Spring context.

In XML configuration, the <context:mbean-server> element can handle that for you. In Java configuration, you’ll need to take a more direct approach and configure a bean of type MBeanServerFactoryBean() (which is what <context:mbean-server> does for you in XML).

MBeanServerFactoryBean creates an MBean server as a bean in the Spring application context. By default, that bean’s ID is mbeanServer. Knowing this, you can wire it into MBeanExporter’s server property to specify which MBean server an MBean should be exposed through.

20.1.1. Exposing methods by name

An MBean info assembler is the key to constraining which operations and attributes are exported in an MBean. One such MBean info assembler is MethodNameBasedMBean-InfoAssembler. This assembler is given a list of names of methods to export as MBean operations. For theSpittleController bean, you want to export spittlesPerPage as a managed attribute. How can a method name–based assembler help you export a managed attribute?

Recall that per JavaBean rules (not necessarily Spring bean rules), what makes spittlesPerPage a property is that it has corresponding accessor methods named setSpittlesPerPage() and getSpittlesPerPage(). To limit your MBean’s exposure, you need to tellMethodNameBasedMBeanInfoAssembler to include only those methods in the MBean’s interface. The following declaration of a MethodNameBasedMBean-InfoAssembler bean singles out those methods:

@Bean

public MethodNameBasedMBeanInfoAssembler assembler() {

MethodNameBasedMBeanInfoAssembler assembler =

new MethodNameBasedMBeanInfoAssembler();

assembler.setManagedMethods(new String[] {

"getSpittlesPerPage", "setSpittlesPerPage"

});

return assembler;

}

The managedMethods property takes a list of method names. Those are the methods that will be exposed as the MBean’s managed operations. Because they’re property accessor methods, they will also result in a spittlesPerPage managed attribute on the MBean.

To put the assembler into action, you need to wire it into the MBeanExporter:

@Bean

public MBeanExporter mbeanExporter(

SpittleController spittleController,

MBeanInfoAssembler assembler) {

MBeanExporter exporter = new MBeanExporter();

Map<String, Object> beans = new HashMap<String, Object>();

beans.put("spitter:name=SpittleController", spittleController);

exporter.setBeans(beans);

exporter.setAssembler(assembler);

return exporter;

}

Now, if you fire up the application, SpittleController’s spittlesPerPage is available as a managed attribute, but the spittles() method isn’t exposed as a managed operation. Figure 20.3 shows what this looks like in JConsole.

Figure 20.3. After specifying which methods are exported in the SpittleController MBean, the spittles() method is no longer a managed operation.

Another method name–based assembler to consider is MethodExclusionMBeanInfo-Assembler. This MBean info assembler is the inverse of MethodNameBasedMBean-InfoAssembler. Rather than specifying which methods to expose as managed operations,MethodExclusionMBeanInfoAssembler is given a list of methods to not reveal as managed operations. For example, here’s how to use MethodExclusion-MBeanInfoAssembler to keep spittles() out of consideration as a managed operation:

@Bean

public MethodExclusionMBeanInfoAssembler assembler() {

MethodExclusionMBeanInfoAssembler assembler =

new MethodExclusionMBeanInfoAssembler();

assembler.setIgnoredMethods(new String[] {

"spittles"

});

return assembler;

}

Method name–based assemblers are straightforward and easy to use. But can you imagine what would happen if you were to export several Spring beans as MBeans? After a while, the list of method names given to the assembler would be huge. And there’s also a possibility that you may want to export a method from one bean while another bean has a same-named method that you don’t want to export.

Clearly, in terms of Spring configuration, the method-name approach doesn’t scale well when exporting multiple MBeans. Let’s see if using interfaces to expose MBean operations and attributes is any better.

20.1.2. Using interfaces to define MBean operations and attributes

Spring’s InterfaceBasedMBeanInfoAssembler is another MBean info assembler that lets you use interfaces to pick and choose which methods on a bean are exported as MBean-managed operations. It’s similar to the method name–based assemblers, except that instead of listing method names to be exported, you list interfaces that define the methods to be exported.

For example, suppose I define an interface named SpittleControllerManagedOperations like this:

package com.habuma.spittr.jmx;

public interface SpittleControllerManagedOperations {

int getSpittlesPerPage();

void setSpittlesPerPage(int spittlesPerPage);

}

Here I’ve selected the setSpittlesPerPage() and getSpittlesPerPage() methods as the operations I want to export. Again, these accessor methods will indirectly export the spittlesPerPage property as a managed attribute. To use this assembler, I just need to use the followingassembler bean instead of the method name–based assemblers from before:

@Bean

public InterfaceBasedMBeanInfoAssembler assembler() {

InterfaceBasedMBeanInfoAssembler assembler =

new InterfaceBasedMBeanInfoAssembler();

assembler.setManagedInterfaces(

new Class<?>[] { SpittleControllerManagedOperations.class }

);

return assembler;

}

The managedInterfaces property takes a list of one or more interfaces that serve as the MBean-managed operation interfaces—in this case, the SpittleController-ManagedOperations interface.

What may not be apparent, but is certainly interesting, is that SpittleController doesn’t have to explicitly implement SpittleControllerManagedOperations. The interface is there for the sake of the exporter, but you don’t need to implement it directly in any of your code.SpittleController probably should implement the interface, though, if for no other reason than to enforce a consistent contract between the MBean and the implementation class.

The nice thing about using interfaces to select managed operations is that you can collect dozens of methods into a few interfaces and keep the configuration of Interface-BasedMBeanInfoAssembler clean. This goes a long way toward keeping the Spring configuration tidy even when exporting multiple MBeans.

Ultimately, those managed operations must be declared somewhere, whether in Spring configuration or in an interface. Moreover, the declaration of the managed operations represents a duplication in code: method names declared in an interface or Spring context and method names in the implementation. This duplication exists for no other reason than to satisfy the MBeanExporter.

One of the things that Java annotations are good at is helping to eliminate such duplication. Let’s see how to annotate a Spring-managed bean so that it can be exported as an MBean.

20.1.3. Working with annotation-driven MBeans

In addition to the MBean info assemblers you’ve seen thus far, Spring provides another assembler known as MetadataMBeanInfoAssembler that can be configured to use annotations to appoint bean methods as managed operations and attributes. I could show you how to use that assembler, but I won’t. That’s because wiring it up manually is burdensome and not worth the trouble just to be able to use annotations. Instead, I’m going to show you how to use the <context:mbean-export> element from Spring’s context configuration namespace. This handy element wires up an MBean exporter and all the appropriate assemblers to turn on annotation-driven MBeans in Spring. All you have to do is use it instead of the MBeanExporter bean that you’ve been using:

<context:mbean-export server="mbeanServer" />

Now, to turn any Spring bean into an MBean, all you must do is annotate it with @ManagedResource and annotate its methods with @ManagedOperation or @Managed-Attribute. For example, the following listing shows how to alter SpittleController to be exported as an MBean using annotations.

Listing 20.1. Annotating SpittleController to be an MBean

The @ManagedResource annotation is applied at the class level to indicate that this bean should be exported as an MBean. The objectName attribute indicates the domain (spitter) and name (SpittleController) of the MBean.

The accessor methods for the spittlesPerPage property are both annotated with @ManagedAttribute to indicate that it should be exposed as a managed attribute. Note that it’s not strictly necessary to annotate both accessor methods. If you choose to only annotate thesetSpittlesPerPage() method, then you can still set the property through JMX, but you won’t be able to see what its value is. Conversely, annotating getSpittlesPerPage() enables the property’s value to be viewed as read-only via JMX.

Also note that it’s possible to annotate the accessor methods with @Managed-Operation instead of @ManagedAttribute. For example,

@ManagedOperation

public void setSpittlesPerPage(int spittlesPerPage) {

this.spittlesPerPage = spittlesPerPage;

}

@ManagedOperation

public int getSpittlesPerPage() {

return spittlesPerPage;

}

This exposes those methods through JMX, but it doesn’t expose the spittlesPerPage property as a managed attribute. That’s because methods annotated with @ManagedOperation are treated strictly as methods and not as JavaBean accessors when it comes to exposing MBean functionality. Thus, @ManagedOperation should be reserved for exposing methods as MBean operations, and @ManagedAttribute should be used when exposing managed attributes.

20.1.4. Handling MBean collisions

So far you’ve seen how to publish an MBean into an MBean server using several approaches. In all cases, you’ve given the MBean an object name that’s made up of a management domain name and a key-value pair. Assuming that there’s not already an MBean published with the name you’ve given your MBean, you should have no trouble publishing your MBean. But what happens if there’s a name collision?

By default, MBeanExporter throws an InstanceAlreadyExistsException if you try to export an MBean that’s named the same as an MBean that’s already in the MBean server. But you can change that behavior by specifying how the collision should be handled via theMBeanExporter’s registrationBehaviorName property or through <context:mbean-export>’s registration attribute.

There are three ways to handle an MBean name collision via the registration-Policy property:

· FAIL_ON_EXISTING —Fail if an existing MBean has the same name (this is the default behavior).

· IGNORE_EXISTING —Ignore the collision and don’t register the new MBean.

· REPLACING_EXISTING —Replace the existing MBean with the new MBean.

For example, if you’re using MBeanExporter, you can configure it to ignore collisions by setting the registrationPolicy property to RegistrationPolicy.IGNORE_EXISTING, like this:

@Bean

public MBeanExporter mbeanExporter(

SpittleController spittleController,

MBeanInfoAssembler assembler) {

MBeanExporter exporter = new MBeanExporter();

Map<String, Object> beans = new HashMap<String, Object>();

beans.put("spitter:name=SpittleController", spittleController);

exporter.setBeans(beans);

exporter.setAssembler(assembler);

exporter.setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING);

return exporter;

}

The registrationPolicy property accepts a value from the RegistrationPolicy enum representing one of the three collision-handling behaviors available.

Now that you’ve registered your MBeans using MBeanExporter, you need a way to access them for management. As you’ve seen already, you can use tools like JConsole to access a local MBean server to view and manipulate MBeans. But a tool such as JConsole doesn’t lend itself to programmatic management of MBeans. How can you manipulate MBeans in one application from within another application? Fortunately, there’s another way to access MBeans as remote objects. Let’s explore how Spring’s support for remote MBeans enables you to access your MBeans in a standard way through a remote interface.

20.2. Remoting MBeans

Although the original JMX specification referred to remote management of applications through MBeans, it didn’t define the actual remoting protocol or API. Consequently, it fell to JMX vendors to define their own, often proprietary, remoting solutions for JMX.

In response to the need for a standard for remote JMX, the Java Community Process produced JSR-160, the Java Management Extensions Remote API Specification. This specification defines a standard for JMX remoting, which at a minimum requires an RMI binding and optionally theJMX Messaging Protocol (JMXMP).

In this section, you’ll see how Spring enables remote MBeans. You’ll start by configuring Spring to export the SpittleController MBean as a remote MBean. Then you’ll see how to use Spring to manipulate that MBean remotely.

20.2.1. Exposing remote MBeans

The simplest thing you can do to make your MBeans available as remote objects is to configure Spring’s ConnectorServerFactoryBean:

@Bean

public ConnectorServerFactoryBean connectorServerFactoryBean() {

return new ConnectorServerFactoryBean();

}

ConnectorServerFactoryBean creates and starts a JSR-160 JMXConnectorServer. By default, the server listens for the JMXMP protocol on port 9875—thus, it’s bound to service:jmx:jmxmp://localhost:9875. But you’re not limited to exporting MBeans using only JMXMP.

Depending on the JMX implementation, you may have several remoting protocol options to choose from, including Remote Method Invocation (RMI), SOAP, Hessian/Burlap, and even Internet InterORB Protocol (IIOP). To specify a different remote binding for your MBeans, you need to set the serviceUrl property of Connector-ServerFactoryBean. For example, if you want to use RMI for MBean remoting, you’d set serviceUrl like this:

@Bean

public ConnectorServerFactoryBean connectorServerFactoryBean() {

ConnectorServerFactoryBean csfb = new ConnectorServerFactoryBean();

csfb.setServiceUrl(

"service:jmx:rmi://localhost/jndi/rmi://localhost:1099/spitter");

return csfb;

}

Here, you’re binding the ConnectorServerFactoryBean to an RMI registry listening on port 1099 of the localhost. That means you also need an RMI registry running and listening at that port. As you’ll recall from chapter 15, RmiServiceExporter can start an RMI registry automatically for you. But in this case, you’re not using RmiServiceExporter, so you need to start an RMI registry by declaring an RmiRegistryFactoryBean in Spring with the following @Bean method:

@Bean

public RmiRegistryFactoryBean rmiRegistryFB() {

RmiRegistryFactoryBean rmiRegistryFB = new RmiRegistryFactoryBean();

rmiRegistryFB.setPort(1099);

return rmiRegistryFB;

}

And that’s it! Now your MBeans are available through RMI. But there’s little point in doing this if nobody will ever access the MBeans over RMI. Let’s turn our attention to the client side of JMX remoting and see how to wire up a remote MBean in the Spring context of a JMX client.

20.2.2. Accessing remote MBeans

Accessing a remote MBean server involves configuring an MBeanServerConnectionFactoryBean in the Spring context. The following bean declaration sets up an MBeanServerConnectionFactoryBean that can be used to access the RMI-based remote server you created in the previous section:

@Bean

public MBeanServerConnectionFactoryBean connectionFactoryBean() {

MBeanServerConnectionFactoryBean mbscfb =

new MBeanServerConnectionFactoryBean();

mbscfb.setServiceUrl(

"service:jmx:rmi://localhost/jndi/rmi://localhost:1099/spitter");

return mbscfb;

}

As its name implies, MBeanServerConnectionFactoryBean is a factory bean that creates an MBeanServerConnection. The MBeanServerConnection produced by MBean-ServerConnectionFactoryBean acts as a local proxy to the remote MBean server. It can be wired into a bean property as an MBeanServerConnection:

@Bean

public JmxClient jmxClient(MBeanServerConnection connection) {

JmxClient jmxClient = new JmxClient();

jmxClient.setMbeanServerConnection(connection);

return jmxClient;

}

MBeanServerConnection provides several methods that let you query the remote MBean server and invoke methods on the MBeans contained therein. For example, say that you’d like to know how many MBeans are registered in the remote MBean server. The following code snippet prints that information:

int mbeanCount = mbeanServerConnection.getMBeanCount();

System.out.println("There are " + mbeanCount + " MBeans");

You can also query the remote server for the names of all the MBeans using the query-Names() method:

java.util.Set mbeanNames = mbeanServerConnection.queryNames(null, null);

The two parameters passed to queryNames() are used to refine the results. Passing in null for both parameters indicates that you’re asking for the names of all the registered MBeans.

Querying the remote MBean server for bean counts and names is fun, but doesn’t get much work done. The real value of accessing an MBean server remotely is found in accessing attributes and invoking operations on the MBeans that are registered in the remote server.

For accessing MBean attributes, you’ll want to use the getAttribute() and set-Attribute() methods. For example, to retrieve the value of an MBean attribute, you’d call the getAttribute() method like so:

String cronExpression = mbeanServerConnection.getAttribute(

new ObjectName("spitter:name=SpittleController"), "spittlesPerPage");

Similarly, you can change the value of an MBean attribute using the setAttribute() method:

mbeanServerConnection.setAttribute(

new ObjectName("spitter:name=SpittleController"),

new Attribute("spittlesPerPage", 10));

If you’d like to invoke an MBean’s operation, the invoke() method is what you’re looking for. Here’s how you might invoke the setSpittlesPerPage() method on the SpittleController MBean:

mbeanServerConnection.invoke(

new ObjectName("spitter:name=SpittleController"),

"setSpittlesPerPage",

new Object[] { 100 },

new String[] {"int"});

You can do dozens of other things with remote MBeans by using the methods available through MBeanServerConnection. I’ll leave it to you to explore the possibilities. But invoking methods and setting attributes on remote MBeans is awkward when done throughMBeanServerConnection. Doing something as simple as calling the setSpittlesPerPage() method involves creating an ObjectName instance and passing several other parameters to the invoke() method. This isn’t nearly as intuitive as a normal method invocation would be. For a more direct approach, you need to proxy the remote MBean.

20.2.3. Proxying MBeans

Spring’s MBeanProxyFactoryBean is a proxy factory bean in the same vein as the remoting proxy factory beans we examined in chapter 15. But instead of providing proxy-based access to remote Spring-managed beans, MBeanProxyFactoryBean lets you access remote MBeans directly (as if they were any other locally configured bean). Figure 20.4 illustrates how this works.

Figure 20.4. MBeanProxyFactoryBean produces a proxy to a remote MBean. The proxy’s client can then interact with the remote MBean as if it were a locally configured POJO.

For example, consider the following declaration of MBeanProxyFactoryBean:

@Bean

public MBeanProxyFactoryBean remoteSpittleControllerMBean(

MBeanServerConnection mbeanServerClient) {

MBeanProxyFactoryBean proxy = new MBeanProxyFactoryBean();

proxy.setObjectName("");

proxy.setServer(mbeanServerClient);

proxy.setProxyInterface(SpittleControllerManagedOperations.class);

return proxy;

}

The objectName property specifies the object name of the remote MBean that’s to be proxied locally. Here it’s referring to the SpittleController MBean that you exported earlier.

The server property refers to an MBeanServerConnection through which all communication with the MBean is routed. Here you’ve wired in the MBeanServer-ConnectionFactoryBean that you configured earlier.

Finally, the proxyInterface property specifies the interface that will be implemented by the proxy. In this case, you’re using the same SpittleControllerManagedOperations interface that you defined in section 20.1.2.

With the remoteSpittleControllerMBean bean declared, you can now wire it into any bean property whose type is SpittleControllerManagedOperations and use it to access the remote MBean. From there, you can invoke the setSpittlesPerPage() andgetSpittlesPerPage() methods.

You’ve seen several ways that you can communicate with MBeans, and you can now view and tweak your Spring bean configuration while the application is running. But thus far it’s been a one-sided conversation. You’ve talked to the MBeans, but the MBeans haven’t been able to get a word in edgewise. It’s time for you to hear what they have to say by listening for notifications.

20.3. Handling notifications

Querying an MBean for information is only one way of keeping an eye on the state of an application. But it’s not the most efficient way to be informed of significant events within the application.

For example, suppose the Spittr application were to keep a count of how many spittles have been posted. And suppose you want to know every time the count has increased by one million spittles (the one millionth spittle, the two millionth, the three millionth, and so on). One way to handle this would be to write some code that periodically queried the database, counting the number of spittles. But the process that performed that query would keep itself and the database busy as it constantly checked for the spittle count.

Instead of repeatedly querying the database to get that information, a better approach may be to have an MBean notify you when the momentous occasion takes place. JMX notifications, as illustrated in figure 20.5, are a way that MBeans can communicate with the outside world proactively, instead of waiting for some external application to query them for information.

Figure 20.5. JMX notifications enable MBeans to communicate proactively with the outside world.

Spring’s support for sending notifications comes in the form of the NotificationPublisherAware interface. Any bean-turned-MBean that wishes to send notifications should implement this interface. For example, consider SpittleNotifierImpl in the next listing.

Listing 20.2. Using a NotificationPublisher to send JMX notifications

As you can see, SpittleNotifierImpl implements NotificationPublisherAware. This isn’t a demanding interface. It requires only that a single method be implemented: setNotificationPublisher.

SpittleNotifierImpl also implements a single method from the Spittle-Notifier interface, millionthSpittlePosted(). This method uses the Notification-Publisher that’s automatically injected via the setNotificationPublisher() method to send a notification that another million spittles have been posted.

Once the sendNotification() method has been called, the notification is on its way to ... hmm ... it seems that you haven’t yet decided who will receive the notification. Let’s set up a notification listener to listen to and react to the notification.

20.3.1. Listening for notifications

The standard way to receive MBean notifications is to implement the javax .management.NotificationListener interface. For example, consider Paging-NotificationListener:

package com.habuma.spittr.jmx;

import javax.management.Notification;

import javax.management.NotificationListener;

public class PagingNotificationListener

implements NotificationListener {

public void handleNotification(

Notification notification, Object handback) {

// ...

}

}

PagingNotificationListener is a typical JMX notification listener. When a notification is received, its handleNotification() method is invoked to react to the notification. Presumably, PagingNotificationListener’s handleNotification() method will send a message to a pager or cell phone about the fact that another million spittles have been posted. (I’ve left the actual implementation to your imagination.)

The only thing left to do is register PagingNotificationListener with the MBeanExporter:

@Bean

public MBeanExporter mbeanExporter() {

MBeanExporter exporter = new MBeanExporter();

Map<?, NotificationListener> mappings =

new HashMap<?, NotificationListener>();

mappings.put("Spitter:name=PagingNotificationListener",

new PagingNotificationListener());

exporter.setNotificationListenerMappings(mappings);

return exporter;

}

MBeanExporter’s notificationListenerMappings property is used to map notification listeners to the MBeans they will be listening to. In this case, you set up Paging-NotificationListener to listen to any notifications published by the SpittleNotifier MBean.

20.4. Summary

With JMX, you can open a window into the inner workings of your application. In this chapter, you saw how to configure Spring to automatically export Spring beans as JMX MBeans so that their details can be viewed and manipulated through JMX-ready management tools. You also learned how to create and use remote MBeans for times when those MBeans and tools are distant from each other. Finally, you saw how to use Spring to publish and listen for JMX notifications.

By now you’ve probably noticed that the number of remaining pages in this book is dwindling fast. Your journey through Spring is almost complete. But before we conclude, we have one more quick stop to make. In the next chapter, we’ll look at Spring Boot, an exciting new way to build Spring applications with little or no explicit configuration.