Статьи

Весна против Гиса: одно важное различие, которое имеет значение

Объекты Spring распознаются по их именам
Неважно, используете ли вы XML или Java config, область видимости Spring примерно похожа на структуру Map <String, Object>. Это означает, что вы не можете иметь два объекта с одинаковым именем . Почему это плохо? Если у вас большое приложение с множеством классов @Configuration или XML-файлов, очень легко случайно использовать одно и то же имя дважды.
Хуже всего то, что при использовании одного и того же с несколькими объектами они молча переопределяют друг друга до тех пор, пока в ApplicationContext фактически не останется только один объект. Эти объекты также могут быть разных типов, и порядок объявления определяет, какой объект выигрывает. Проблема здесь в том, что если вы хотите создавать многократно используемые модули на основе Spring, вы будете вынуждены использовать префикс в имени или что-то еще, чтобы избежать конфликта имен.
Guice объекты распознаются на основе их классов
Область видимости Guice в основном похожа на структуру Map <Class <?>, Object>. Это означает, что вы не можете иметь два объекта одного типа без использования дополнительных метаданных (например, квалификаторов). У этого выбора дизайна есть свои плюсы и минусы, но в целом я думаю, что он более разумный. Если вы создаете многократно используемые модули, вам в основном нужно убедиться, что вы не экспортируете объекты общих типов (например, String). С областями на основе типов вы всегда можете создать обернутый класс для распространенных типов, тогда как с областями на основе имен вам всегда придется использовать уникальные имена, основанные на удачных догадках. Guice также имеет PrivateModules, так что вы можете использовать Guice для всех инъекций, но экспортировать только некоторые объекты в области видимости.  
Пример кода
Вот наивный пример Spring-приложения, которое прерывает время выполнения из-за переопределения bean-компонента.

Main.java

Этот класс создает экземпляр контекста приложения, регистрирует классы конфигурации и пытается получить MyBean из контекста.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package springbreak;
 
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
 
public class Main {
  public static void main(String[] args) {
    AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
 
    ctx.register(GoodConfig.class);
    ctx.register(EvilConfig.class);
 
    ctx.refresh();
    ctx.start();
 
    System.out.println(ctx.getBean(MyBean.class).getValue());
 
    ctx.stop();
  }
}

MyBean.java

Это просто пример типа компонента, который мы ожидаем получить из контекста приложения.

1
2
3
4
5
package springbreak;
 
public interface MyBean {
  String getValue();
}

GoodConfig.java

Это класс конфигурации, который экспортирует MyBean

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
package springbreak;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class GoodConfig {
 
  private static class MyBeanImpl implements MyBean {
    public String getValue() {
      return "I'm a bean";
    }
  }
 
  @Bean
  public MyBean myBean() {
    return new MyBeanImpl();
  }
 
}

EvilConfig.java

Этот класс конфигурации экспортирует строку с именем myBean. Это не очень реалистичный пример, но показывает основную идею.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
package springbreak;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
 
@Configuration
public class EvilConfig {
 
  @Bean
  public String myBean() {
    return "I'm a string!";
  }
 
}
Анализируя пример
Можете ли вы угадать, что происходит, когда вы запускаете пример? Вот основная идея:
  1. GoodConfig экспортирует MyBeanImpl с именем «myBean»
  2. EvilConfig экспортирует строку с именем «myBean», заменяющую строку из GoodConfig, даже если типы не совпадают
  3. Main получает исключение NoSuchBeanDefinitionException «Не определен уникальный бин типа [springbreak.MyBean]»
    Итак, в основном MyBeanImpl заменяется на String, и не будет bean-компонента, реализующего MyBean. Хуже всего то, что если вы измените порядок регистрации класса @Configuration, код будет работать, потому что тогда String будет заменен MyBeanImpl. Теперь представьте, что у вас есть 20 красиво инкапсулированных модулей с потенциально конфликтующими именами … Я несколько раз ударился головой о стену, пытаясь отладить проблемы в подобной ситуации.
    Spring (начиная с версии 3.0.6) не дает возможности изменить наименование экспортируемых bean-компонентов класса @Configuration. Если вы хотите создавать безопасно повторно используемые модули, вам придется использовать какие-то полностью определенные имена в методах, экспортирующих bean-компоненты (например, goodConfigMyBean, evilConfigMyBean).
    Мне нравится Spring (особенно части, не относящиеся к DI-контейнеру), но в новых проектах я отказываюсь использовать библиотеку, которая так сильно сломана. И да, использование одного и того же имени дважды является ошибкой разработчика, но любая библиотека, склонная к таким ошибкам, может считаться худшей, чем альтернатива, которая пытается минимизировать их.

    Ссылка: Spring vs Guice: Одно важное отличие, которое имеет значение от нашего партнера по JCG Йонаса Джаванайнена в техническом блоге Jawsy Solutions .