2012-07-11 6 views
4

У меня есть класс утилит U, который зависит от библиотеки X и должен идти в пакете, который будет использоваться из программ с доступным X (где он должен делать свои обычные вещи) и места без X (где он ничего не должен делать). Без разделения класса на две части, я нашел простой шаблон, который решает эту проблему:Java-класс с возможно отсутствующими зависимостями во время выполнения

package foo; 
import bar.MisteriousX; 

public class U { 
    static private boolean isXPresent = false; 
    static { 
     try { 
      isXPresent = (null != U.class.getClassLoader().loadClass("bar.MisteriousX")); 
     } catch (Exception e) { 
      // loading of a sample X class failed: no X for you 
     } 
    } 
    public static void doSomething() { 
     if (isXPresent) { 
      new Runnable() { 
       public void run() { 
        System.err.println("X says " + MisteriousX.say()); 
       } 
      }.run(); 
     } else { 
      System.err.println("X is not there"); 
     } 
    } 
    public static void main(String args[]) { doSomething(); } 
} 

С помощью этой модели, U требует X подарка для компиляции, но работает, как ожидалось при запуске с или без X настоящего. Если все обращения к библиотеке X находятся внутри внутренних классов, этот код запускает исключение класса loader.

Вопросы: разрешение импорта гарантировано, что оно работает везде, или это будет зависеть от реализации JVM/ClassLoader? Устанавливается ли это для этого? Является ли вышеописанный фрагмент кода слишком хакерским для его производства?

+1

Вот почему у вас есть фреймворки, подобные пружине для инъекций зависимостей.В любом случае, я думаю, вы должны использовать интерфейс здесь, который всегда будет доступен, а не проверяет существование класса. – TedTrippin

ответ

3

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

Linking section в спецификациях JVM дает большую свободу для реализации. Если вы не используете подход с двумя классами, то проверка U в реализации с использованием надежного связывания приведет к потере нагрузкиX, что приводит к LinkageError. Спецификации не требуют проверки класса ссылок, но также он не запрещает такую ​​раннюю проверку. Это, однако требует, чтобы

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

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

3

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

public final class JooqLogger { 

    private org.slf4j.Logger slf4j; 
    private org.apache.log4j.Logger log4j; 
    private java.util.logging.Logger util; 

    public static JooqLogger getLogger(Class<?> clazz) { 
     JooqLogger result = new JooqLogger(); 

     // Prioritise slf4j 
     try { 
      result.slf4j = org.slf4j.LoggerFactory.getLogger(clazz); 
     } 

     // If that's not on the classpath, try log4j instead 
     catch (Throwable e1) { 
      try { 
       result.log4j = org.apache.log4j.Logger.getLogger(clazz); 
      } 

      // If that's not on the classpath either, ignore most of logging 
      catch (Throwable e2) { 
       result.util= java.util.logging.Logger.getLogger(clazz.getName()); 
      } 
     } 

     return result; 
    } 

    [...] 

И потом, позже, переключатель регистратора, в зависимости от ранее загруженных классов:

public boolean isTraceEnabled() { 
    if (slf4j != null) { 
     return slf4j.isTraceEnabled(); 
    } 
    else if (log4j != null) { 
     return log4j.isTraceEnabled(); 
    } 
    else { 
     return util.isLoggable(Level.FINER); 
    } 
} 

Остальная часть исходного кода может можно посмотреть here. В сущности, у меня есть зависимость от времени компиляции как для slf4j, так и для log4j, которые я визуализирую дополнительно во время выполнения с использованием аналогичного шаблона, как вы.

Это может вызвать проблемы в средах OSGi, например, когда загрузка классов немного сложнее, чем при использовании стандартных механизмов загрузки JDK/JRE. Однако до сих пор мне не сообщили о каких-либо проблемах

+0

Фактическое «U» вдохновлено моим собственным классом утилиты конфигурирования журналов ... Я могу в конечном итоге использовать вашу реализацию. – tucuxi

+0

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

+1

Лукас, хотя это может быть хорошо в контексте этого вопроса, «факультативный шаблон рамки ведения журнала» является злом. См. Http://www.slf4j.org/faq.html#optional_dependency – Ceki

Смежные вопросы