Является ли следующий класс утилит потокобезопасным? - PullRequest
3 голосов
/ 11 ноября 2009

Сначала давайте посмотрим на служебный класс (большинство javadoc было удалено просто в качестве примера):

public class ApplicationContextUtils {

    /**
     * The application context; care should be taken to ensure that 1) this
     * variable is assigned exactly once (in the
     * {@link #setContext(ApplicationContext)} method, 2) the context is never
     * reassigned to {@code null}, 3) access to the field is thread-safe (no race
     * conditions can occur)
     */
    private static ApplicationContext context = null;

    public static ApplicationContext getContext() {

    if (!isInitialized()) {
        throw new IllegalStateException(
            "Context not initialized yet! (Has the "
                + "ApplicationContextProviderBean definition been configured "
                + "properly and has the web application finished "
                + "loading before you invoked this method?)");
    }

    return context;
    }

    public static boolean isInitialized() {
    return context == null;
    }

    @SuppressWarnings("unchecked")
    public static <T> T getBean(final String name, final Class<T> requiredType) {
    if (requiredType == null) {
        throw new IllegalArgumentException("requiredType is null");
    }
    return (T) getContext().getBean(name, requiredType);
    }

    static synchronized void setContext(final ApplicationContext theContext) {

    if (theContext == null) {
        throw new IllegalArgumentException("theContext is null");
    }

    if (context != null) {
        throw new IllegalStateException(
            "ApplicationContext already initialized: it cannot be done twice!");
    }

    context = theContext;
    }

    private ApplicationContextUtils() {
    throw new AssertionError(); // NON-INSTANTIABLE UTILITY CLASS
    }
}

Наконец, существует следующий управляемый bean-компонент Spring, который фактически вызывает метод 'setContext':

public final class ApplicationContextProviderBean implements
    ApplicationContextAware {

    public void setApplicationContext(
        final ApplicationContext applicationContext) throws BeansException {
    ApplicationContextUtils.setContext(applicationContext);
    }
}

Spring будет вызывать метод setApplicationContext один раз после запуска приложения. Предполагая, что nincompoop ранее не вызывал ApplicationContextUtils.setContext (), он должен заблокировать ссылку на контекст в служебном классе, позволяя вызывать getContext () к успеху (то есть isInitialized () возвращает true).

Я просто хочу знать, нарушает ли этот класс какие-либо принципы хорошей практики кодирования, в частности, в отношении безопасности потоков (но другие обнаруженные глупости приветствуются).

Спасибо, что помогли мне стать лучшим программистом, StackOverflow!

С уважением, LES

P.S. Я не стал вдаваться в , почему мне нужен этот служебный класс - пусть этого достаточно, чтобы у меня действительно была законная потребность обращаться к нему из статического контекста в любом месте приложения (после загрузки весеннего контекста, Конечно).

Ответы [ 2 ]

8 голосов
/ 11 ноября 2009

Нет. Это не потокобезопасно.

Запись в переменную класса context не гарантированно будет видимой для потоков, которые читают эту переменную через getContext().

Как минимум, объявить context равным volatile. В идеале, переопределить context как AtomicReference, установить через вызов следующим образом:

if(!context.compareAndSet(null, theContext))
  throw new IllegalStateException("The context is already set.");

Вот более полный пример:

public class ApplicationContextUtils {

  private static final AtomicReference<ApplicationContext> context = 
    new AtomicReference<ApplicationContext>();

  public static ApplicationContext getContext() {
    ApplicationContext ctx = context.get();
    if (ctx == null)
      throw new IllegalStateException();
    return ctx;
  }

  public static boolean isInitialized() {
    return context.get() == null;
  }

  static void setContext(final ApplicationContext ctx) {
    if (ctx == null) 
      throw new IllegalArgumentException();
    if (!context.compareAndSet(null, ctx))
      throw new IllegalStateException();
  }

  public static <T> T getBean(final String name, final Class<T> type) {
    if (type == null) 
      throw new IllegalArgumentException();
    return type.cast(getContext().getBean(name, type));
  }

  private ApplicationContextUtils() {
    throw new AssertionError();
  }

}

Обратите внимание, что в дополнение к безопасности потоков это также обеспечивает безопасность типов, используя преимущества экземпляра Class, переданного в метод getBean().

Я не уверен, как вы планируете использовать метод isInitialized(); это не кажется мне очень полезным, так как, как только вы позвоните, условие может измениться, и у вас не будет хорошего способа получить уведомление.

1 голос
/ 29 ноября 2009

Spring уже имеет класс под названием ContextSingletonBeanFactoryLocator, который подключает статический доступ к ApplicationContext для вас. По крайней мере, использование этого класса может избавить вас от необходимости беспокоиться о том, что ваш пользовательский подход является поточно-ориентированным.

Однако поначалу немного странно использовать этот класс, поскольку происходит небольшая косвенность. Вы можете взглянуть на этот пост для получения дополнительной информации о том, как работает этот вызов.

...