Java EE provides singletons through the use of the @Singleton
annotation. In practice, this annotation will guarantee that your class will be loaded only once per JVM or per application server instance. Although this is a powerful feature, if your business scenario can't cope with this behavior (duplicate component instances, one per JVM), you need another approach; the singleton service offered by WebLogic can offer an elegant solution for such cases. This feature guarantees that a given singleton class will have only one instance across a cluster, automatically managing failover and migration to another server instance in case of failure.
In the singleton ReservationCodeBean
class there is a functionality that generates control numbers used to identify the reservations. The actual implementation is perfectly fine for a single server application, but running this application on multiple servers will end up creating several instances of ReservationCodeBean
, one per JVM and each with its own counter variable, which may generate duplicate control numbers.
This functionality will be moved to a WebLogic singleton service, which will make it a cluster-wide singleton providing the necessary business function in a safe manner.
To enable this new service, we're going to create a simple JAR file to hold it and then set it up as a domain library. Follow these steps:
TokenService
.java.rmi.Remote
interface. Create a new Java Interface named ITokenService
under the com.packt.store.services
package using the following content:public interface ITokenService extends Remote { public static final String JNDI_ENTRY = "TokenService"; public String generate() throws RemoteException; }
weblogic.cluster.singleton.SingletonService
interface so WebLogic can manage the class. Create a new Java class named TokenService
under the same package, with the following content:public class TokenService implements ITokenService,SingletonService { // ideally would be moved to an external source, like a DB private int counter = 0; private Logger logger = Logger.getLogger(TokenService.class.getCanonicalName()); private SimpleDateFormat now = new SimpleDateFormat("yyyyMMdd-hhmmss"); @Override public String generate() { return String.format("%1$s-%2$06d", now.format(new Date()), ++counter); } @Override public void activate() { logger.fine("Attempting to bind TokenService..."); Context jndiCtx = null; try { jndiCtx = new InitialContext(); jndiCtx.rebind(JNDI_ENTRY, this); logger.info("TokenService activated on this server."); } catch (NamingException e) { logger.severe("Error during TokenService activation: " + e.getMessage()); } finally { if (jndiCtx != null) try { jndiCtx.close(); } catch (NamingException e) { e.printStackTrace(); } } } @Override public void deactivate() { logger.fine("Attempting to unbind TokenService..."); Context jndiCtx = null; try { jndiCtx = new InitialContext(); jndiCtx.unbind(JNDI_ENTRY); logger.fine("TokenService was deactivated on this server."); } catch (NamingException e) { e.printStackTrace(); } finally { if (jndiCtx != null) try { jndiCtx.close(); } catch (NamingException e) { e.printStackTrace(); } } }
weblogic.jar
into the build path of the application. The file can be found under $MW_HOME/wlserver/server/lib
.TokenService.jar
.TokenService.jar
into the tickets
domain /lib
folder.http://localhost:7001/console
.Consensus
.When running services or applications on a cluster, WebLogic tries to balance instances of these subsystems into all server instances of such a cluster. There are some other services that must run pinned to a single instance and, depending on the runtime situation, they migrate these services to a different server. But, there must be a way to store and keep the information about who owns that particular component; to avoid a Single Point Of Failure (SPOF), WebLogic offers two options for leasing these services: Database leasing and Consensus leasing. Database leasing would require a High Availability (HA) database, such as Oracle RAC, but for our examples we're going to use Consensus, which is basically a less sophisticated mechanism that relies on Node Manager information about the health of our servers.
TokenService
com.packt.store.services.TokenService
At this point, you can check the ticketMS_A
log file and you should see the activation log message indicating that the configuration was successful and that the service is currently running on this server. In order to test the failover capabilities, kill or shutdown the ticketMS_A
Managed Server and monitor the ticketMS_B
log.
Jun 10, 2013 1:56:32 PM com.packt.store.services.TokenService activate INFO: ReservationService activated on this server.
Now, we need to modify the ReservationCodeBean
class to consume the singleton service. This is a very straightforward process:
TokenService.jar
file.ReservationCodeBean
class and create an attribute as follows:ITokenService tokenService;
private String getControlNumber() throws Exception { if (tokenService == null) { try { Context ctx = new InitialContext(); tokenService = (ITokenService) ctx.lookup(TokenService.JNDI_ENTRY); } catch (NamingException ex) { ex.printStackTrace(); throw new Exception("Control number was not generated!"); } } return tokenService.generate(); }
generate()
method, replace the code at the control variable with a call to getControlNumber()
:String control = this.getControlNumber();
At this point, the Store web application, deployed on multiple nodes, will consume the singleton service TokenService
that is primarily hosted on ticketMS_A
Managed Server. If ticketMS_A
goes offline due to a failure or simply during a normal shutdown process, the service will be migrated to ticketMS_B
automatically and any new request to the bean will be able to find it, even if the bean is on a different server now.
3.17.174.0