Singleton Example has been edited by David Blevins (Aug 22, 2008).

(View changes)

Content:

Overview

As the name implies a javax.ejb.Singleton is a session bean with a guarantee that there is at most one instance in the application.

What it gives you that is completely missing in EJB 3.0 and prior versions is the ability to have an EJB that is notified when the application starts and notified when the application stops. So you can do all sorts of things that you previously could only do with a load-on-startup servlet. It also gives you a place to hold data that pertains to the entire application and all users using it, without the need for a static. Additionally, Singleton beans can be invoked by several threads at one time similar to a Servlet.

See the Singleton Beans page for a full description of the javax.ejb.Singleton api.

The Code

Here we see a bean that uses the Bean-Managed Concurrency option as well as the @Startup annotation which causes the bean to be instantiated by the container when the application starts. Singleton beans with @ConcurrencyManagement(BEAN) are responsible for their own thread-safety. The bean shown is a simple properties "registry" and provides a place where options could be set and retrieved by all beans in the application.

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.superbiz.registry;

import static javax.ejb.ConcurrencyManagementType.BEAN;
import javax.ejb.Singleton;
import javax.ejb.ConcurrencyManagement;
import javax.ejb.Startup;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.Properties;

@Singleton
@ConcurrencyManagement(BEAN)
@Startup
public class PropertyRegistryBean implements PropertyRegistry {

    // Note the java.util.Properties object is a thread-safe
    // collections that uses synchronization.  If it didn't
    // you would have to use some form of synchronization
    // to ensure the PropertyRegistryBean is thread-safe.
    private final Properties properties = new Properties();

    // The @Startup method ensures that this method is
    // called when the application starts up.
    @PostConstruct
    public void applicationStartup() {
        properties.putAll(System.getProperties());
    }

    @PreDestroy
    public void applicationShutdown() {
        properties.clear();
    }

    public String getProperty(String key) {
        return properties.getProperty(key);
    }

    public String setProperty(String key, String value) {
        return (String) properties.setProperty(key, value);
    }

    public String removeProperty(String key) {
        return (String) properties.remove(key);
    }

}

Here we see a bean that uses the Container-Managed Concurrency option, the default. With @ConcurrencyManagement(CONTAINER) the container controls whether multi-threaded access should be allowed to the bean (@Lock(READ)) or if single-threaded access should be enforced (@Lock(WRITE)).

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.superbiz.registry;

import static javax.ejb.LockType.READ;
import static javax.ejb.LockType.WRITE;
import javax.ejb.Lock;
import javax.ejb.Singleton;
import java.util.HashMap;
import java.util.Map;
import java.util.Collection;

@Singleton
@Lock(READ)
public class ComponentRegistryBean implements ComponentRegistry {

    private final Map<Class, Object> components = new HashMap<Class, Object>();

    public <T> T getComponent(Class<T> type) {
        return (T) components.get(type);
    }

    public Collection<?> getComponents() {
        return components.values();
    }

    @Lock(WRITE)
    public <T> T setComponent(Class<T> type, T value) {
        return (T) components.put(type, value);
    }

    @Lock(WRITE)
    public <T> T removeComponent(Class<T> type) {
        return (T) components.remove(type);
    }

}

The default @Lock value for a method is @Lock(WRITE). The code above uses the @Lock(READ) annotation on bean class to change the default so that multi-threaded access is granted by default. We then only need to apply the @Lock(WRITE) annotation to the methods that modify the state of the bean. The important thing to keep in mind is that when an @Lock(WRITE) method is invoked the container will block all access to the bean, even to @Lock(READ) methods, until the method completes. This is very good and is an advanced form of synchronization that allows for safe multi-threaded reading and single-threaded writing.

Test the remote business interface

/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.superbiz.registry;

import junit.framework.TestCase;

import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Properties;
import java.util.Date;
import java.net.URI;

public class ComponentRegistryBeanTest extends TestCase {

    public void test() throws Exception {
        Properties props = new Properties();
        props.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.apache.openejb.client.LocalInitialContextFactory");

        InitialContext context = new InitialContext(props);

        // Both references below will point to the exact same instance
        ComponentRegistry  context.lookup("ComponentRegistryBeanLocal");

        ComponentRegistry two = (ComponentRegistry) context.lookup("ComponentRegistryBeanLocal");


        URI expectedUri = new URI("foo://bar/baz");

        one.setComponent(URI.class, expectedUri);

        URI actualUri = two.getComponent(URI.class);

        assertSame(expectedUri, actualUri);


        Date expectedDate = new Date();

        two.setComponent(Date.class, expectedDate);

        Date actualDate = one.getComponent(Date.class);

        assertSame(expectedDate, actualDate);

    }
}

Running

Running the example is fairly simple. In the "simple-singleton" directory run:

$ mvn clean install

Which should create output like the following.

-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.superbiz.registry.ComponentRegistryBeanTest
Apache OpenEJB 3.1-SNAPSHOT    build: 20080820-09:53
http://openejb.apache.org/
INFO - openejb.home = /Users/dblevins/work/openejb3/examples/simple-singleton
INFO - openejb.base = /Users/dblevins/work/openejb3/examples/simple-singleton
INFO - Configuring Service(id=Default Security Service, type=SecurityService, provider-id=Default Security Service)
INFO - Configuring Service(id=Default Transaction Manager, type=TransactionManager, provider-id=Default Transaction Manager)
INFO - Found EjbModule in classpath: /Users/dblevins/work/openejb3/examples/simple-singleton/target/classes
INFO - Beginning load: /Users/dblevins/work/openejb3/examples/simple-singleton/target/classes
INFO - Configuring enterprise application: classpath.ear
INFO - Configuring Service(id=Default Singleton Container, type=Container, provider-id=Default Singleton Container)
INFO - Auto-creating a container for bean ComponentRegistryBean: Container(type=SINGLETON, id=Default Singleton Container)
INFO - Enterprise application "classpath.ear" loaded.
INFO - Assembling app: classpath.ear
INFO - Jndi(name=ComponentRegistryBeanLocal) --> Ejb(deployment-id=ComponentRegistryBean)
INFO - Jndi(name=PropertyRegistryBeanLocal) --> Ejb(deployment-id=PropertyRegistryBean)
INFO - Created Ejb(deployment-id=ComponentRegistryBean, ejb-name=ComponentRegistryBean, container=Default Singleton Container)
INFO - Created Ejb(deployment-id=PropertyRegistryBean, ejb-name=PropertyRegistryBean, container=Default Singleton Container)
INFO - Deployed Application(path=classpath.ear)
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.879 sec
Running org.superbiz.registry.PropertiesRegistryBeanTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.009 sec

Results :

Tests run: 2, Failures: 0, Errors: 0, Skipped: 0

Reply via email to