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

(View changes)

Content:

Singleton Overview

For the first time in years EJB has a new bean type, the @Singleton. In my opinion, the Singleton is going to replace a lot of what people are using @Stateless for today.

The Singleton is essentially what you get if you take a Stateless bean and adjust the pool size to be exactly 1 and optionally allow concurrent access to that bean like a servlet. It can do everything a Stateless can do such as support Web Services, Security, Transactions, etc. It will have an @Startup annotation which is similar in concept to the servlet <load-on-startup>, but unlike servlets it doesn't take a number as an argument. Instead, you can use an @DependsOn annotation to say which other Singletons you need and the container will ensure they start before you.

Concurrency

Singletons support two modes of concurrent access, Container-Managed Concurrency (the default) and Bean-Managed Concurrency.

Bean-Managed Concurrency

With Bean-Managed Concurrency, annotated as @ConcurrencyManagement(BEAN), the container sends all invocations into the bean and let's the Singleton bean instance decide how and when to synchronize access, if at all. Here the 'synchronization' keyword is allowed as well as the full javax.util.concurrent set of libraries.

Container-Managed Concurrency

With Container-Managed Concurrency, annotated as @ConcurrencyManagement(CONTAINER), the container will enforce concurrency for you via locking method access to the bean. Two modes, called locks will exist and can be assigned to the class or on a per method basis.

Write Lock

The first and the default is a "write" lock, annotated as @Lock(WRITE). Essentially with a write lock, the caller hold an exclusive lock on the bean for the duration of the method call and all other threads for that or any other method must wait.

Read Lock

The second option is a "read" lock, annotated as @Lock(READ). The read lock allows full concurrent access to the methods (assuming no write locks are held). The default mode of "write" will essentially make your bean a single-threaded bean, which is very slow. The more conservative @Lock(WRITE) as chosen as the default as this is how all the other bean types work (on a single thread may access a bean instance at any given time). Those that are aware of how to handle concurrent access can easily put @Lock(READ) on their bean class, thus changing the default, and then @Lock(WRITE) on specific methods if needed.

Relation to java.util.concurrent.ReadWriteLock

The locking modes of Container-Managed Concurrency map directly to the java.util.concurrent.ReadWriteLock API which looks like this:

Unable to find source-code formatter for language: readwritelock. Available languages are: actionscript, html, java, _javascript_, none, sql, xhtml, xml
public interface ReadWriteLock {
   /**
    * Returns the lock used for reading.
    *
    * @return the lock used for reading.
    */
   Lock readLock();

   /**
    * Returns the lock used for writing.
    *
    * @return the lock used for writing.
    */
   Lock writeLock();
}

Literally 100% of the singleton locking we're talking about is taken from this interface. It's safe to imagine that under the covers the Singleton Container is creating an instance of ReadWriteLock which it will use to enforce the locking for all the Singleton bean's methods. Essentially:

  • @Lock(READ) == theSingletonReadWriteLock.readLock().lock()
  • @Lock(WRITE) == theSingletonReadWriteLock.writeLock().lock()

Example Code

/**
 * 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 javax.ejb.Lock;
import javax.ejb.Singleton;
import java.util.HashMap;
import java.util.Map;

@Singleton
public class ComponentRegistryBean implements ComponentRegistry {

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

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

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

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

}
/**
 * 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);

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

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


        refOne.setComponent(URI.class, new URI("foo://bar/baz"));

        URI uri = refTwo.getComponent(URI.class);

        assertEquals(uri, new URI("foo://bar/baz"));


        Date now = new Date();

        refTwo.setComponent(Date.class, now);

        Date date = refOne.getComponent(Date.class);

        assertEquals(now, date);

    }
}
/**
 * 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 java.util.Properties;

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

    private final Properties properties = new Properties();

    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);
    }

}
/**
 * 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;

public class PropertiesRegistryBeanTest 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);

        PropertyRegistry refOne = (PropertyRegistry) context.lookup("PropertyRegistryBeanLocal");

        PropertyRegistry refTwo = (PropertyRegistry) context.lookup("PropertyRegistryBeanLocal");


        refOne.setProperty("url", "http://superbiz.org");

        String url = "" class="code-quote">"url");

        assertEquals("http://superbiz.org", url);


        refTwo.setProperty("version", "1.0.5");

        String version = refOne.getProperty("version");

        assertEquals("1.0.5", version);

    }
}

Reply via email to