Рубрики
Без рубрики

Guice vs Spring – Инъекция зависимостей

Узнайте о сходствах и различиях между Guice и Spring для внедрения зависимостей

Автор оригинала: baeldung.

1. введение

Google Guice и Spring-это две надежные платформы, используемые для внедрения зависимостей. Обе структуры охватывают все понятия внедрения зависимостей, но каждая из них имеет свой собственный способ их реализации.

В этом уроке мы обсудим, как фреймворки Guice и Spring отличаются по конфигурации и реализации.

2. Зависимости Maven

Давайте начнем с добавления зависимостей Guice и Spring Maven в ваш pom.xml файл:


    org.springframework
    spring-context
    5.1.4.RELEASE



    com.google.inject
    guice
    4.2.2

Мы всегда можем получить доступ к последним зависимостям spring-context | или guice от Maven Central.

3. Конфигурация Внедрения Зависимостей

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

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

3.1. Пружинная проводка

Spring объявляет конфигурации внедрения зависимостей в специальном классе конфигурации. Этот класс должен быть аннотирован аннотацией @Configuration . Контейнер Spring использует этот класс в качестве источника определений компонентов.

Классы, управляемые Spring, называются Spring beans .

Spring использует аннотацию @Autowired для автоматического подключения зависимостей . @Autowired является частью встроенных аннотаций ядра Spring . Мы можем использовать @Autowired для переменных-членов, методов настройки и конструкторов.

Spring также поддерживает @Inject. @Inject является частью Java CDI (Контексты и внедрение зависимостей) , который определяет стандарт для внедрения зависимостей.

Допустим, мы хотим автоматически связать зависимость с переменной-членом. Мы можем просто аннотировать его с помощью @Autowired :

@Component
public class UserService {
    @Autowired
    private AccountService accountService;
}
@Component
public class AccountServiceImpl implements AccountService {
}

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

@Configuration
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
}

Обратите внимание, что мы также аннотировали User Service и AccountServiceImpl с @Component , чтобы зарегистрировать их в качестве компонентов. Это @ComponentScan аннотация, которая подскажет Spring, где искать аннотированные компоненты.

Несмотря на то, что мы аннотировали AccountServiceImpl , |/Spring может сопоставить его с Account Service , поскольку он реализует AccountService .

Затем нам нужно определить контекст приложения для доступа к компонентам. Давайте просто отметим, что мы будем ссылаться на этот контекст во всех наших весенних модульных тестах:

ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);

Теперь во время выполнения мы можем получить экземпляр A account Service из нашего UserService bean:

UserService userService = context.getBean(UserService.class);
assertNotNull(userService.getAccountService());

3.2. Привязка Guice

Guice управляет своими зависимостями в специальном классе, называемом модулем. Модуль Guice должен расширить класс AbstractModule и переопределить его метод configure () .

Guice использует привязку как эквивалент проводки весной. Проще говоря, привязки позволяют нам определить, как зависимости будут вводиться в класс . Привязки Guice объявляются в методе configure() нашего модуля.

Вместо @Autowired , Guice использует @Инъекция аннотация для введения зависимостей.

Давайте создадим эквивалентный пример Guice:

public class GuiceUserService {
    @Inject
    private AccountService accountService;
}

Во-вторых, мы создадим класс модуля, который является источником наших определений привязки:

public class GuiceModule extends AbstractModule {
    @Override
    protected void configure() {
        bind(AccountService.class).to(AccountServiceImpl.class);
    }
}

Обычно мы ожидаем, что Guice создаст экземпляр каждого объекта зависимости из своих конструкторов по умолчанию, если в методе configure() нет явной привязки. Но поскольку интерфейсы не могут быть созданы напрямую, нам нужно определить привязки , чтобы сообщить Guice, какой интерфейс будет сопряжен с какой реализацией.

Затем нам нужно определить Инжектор с помощью GuiceModule , чтобы получить экземпляры наших классов. Давайте просто отметим, что все наши тесты Guice будут использовать это Инжектор :

Injector injector = Guice.createInjector(new GuiceModule());

Наконец, во время выполнения мы получаем Служба пользователей Guice экземпляр с ненулевым значением Служба учетных записей зависимость:

GuiceUserService guiceUserService = injector.getInstance(GuiceUserService.class);
assertNotNull(guiceUserService.getAccountService());

3.3. Аннотация @Bean Spring

Spring также предоставляет аннотацию уровня метода @Bean для регистрации бобов в качестве альтернативы аннотациям уровня класса, таким как @Component . Возвращаемое значение аннотированного метода @Bean регистрируется в контейнере как боб.

Допустим, у нас есть экземпляр BookServiceImpl , который мы хотим сделать доступным для инъекции. Мы могли бы использовать @Bean для регистрации нашего экземпляра:

@Bean 
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

И теперь мы можем получить BookService bean:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

3.4. @Guice Предоставляет Аннотацию

Как эквивалент аннотации Spring @Bean , Guice имеет встроенную аннотацию @Предоставляет |/для выполнения той же работы . Например, @Bean , @Provides применяется только к методам.

Теперь давайте реализуем предыдущий пример Spring bean с помощью Guice. Все, что нам нужно сделать, это добавить следующий код в ваш класс модуля:

@Provides
public BookService bookServiceGenerator() {
    return new BookServiceImpl();
}

И теперь мы можем получить экземпляр Book Service :

BookService bookService = injector.getInstance(BookService.class);
assertNotNull(bookService);

3.5. Сканирование компонентов пути к классам весной

Spring предоставляет @ComponentScan аннотацию, которая автоматически обнаруживает и создает экземпляры аннотированных компонентов путем сканирования предварительно определенных пакетов.

Аннотация @ComponentScan сообщает Spring, какие пакеты будут сканироваться на наличие аннотированных компонентов. Он используется с аннотацией @Configuration .

3.6. Сканирование компонентов пути к классам в Guice

В отличие от Spring, Guice не имеет такой функции сканирования компонентов . Но смоделировать это нетрудно. Есть некоторые плагины, такие как Governator , которые могут включить эту функцию в Guice.

3.7. Распознавание объектов весной

Весна распознает объекты по их именам. Spring содержит объекты в структуре, которая примерно похожа на Map Object> . Это означает, что у нас не может быть двух объектов с одинаковым именем. Object>

Столкновение бобов из-за наличия нескольких бобов с одинаковым именем является одной из распространенных проблем Spring developers hit. Например, рассмотрим следующие объявления компонентов:

@Configuration
@Import({SpringBeansConfig.class})
@ComponentScan("com.baeldung.di.spring")
public class SpringMainConfig {
    @Bean
    public BookService bookServiceGenerator() {
        return new BookServiceImpl();
    }
}
@Configuration
public class SpringBeansConfig {
    @Bean
    public AudioBookService bookServiceGenerator() {
        return new AudioBookServiceImpl();
    }
}

Как мы помним, у нас уже было определение компонента для Book Service в Spring Main Config class.

Чтобы создать здесь коллизию бобов, нам нужно объявить методы бобов с одинаковым именем. Но нам не разрешается иметь два разных метода с одинаковым именем в одном классе. По этой причине мы объявили компонент AudioBookService в другом классе конфигурации.

Теперь давайте рассмотрим эти бобы в модульном тесте:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService); 
AudioBookService audioBookService = context.getBean(AudioBookService.class);
assertNotNull(audioBookService);

Модульный тест завершится неудачей с:

org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AudioBookService' available

Во-первых, Spring зарегистрировал Сервис аудиокниг bean с именем “Генератор книжных услуг” в своей beanmap. Затем он должен был переопределить его определением компонента для Book Service из-за “не допускается дублирование имен” характера структуры данных HashMap .

Наконец, мы можем преодолеть эту проблему, сделав имена методов bean уникальными или установив атрибут name уникальным именем для каждого @Bean .

3.8. Распознавание объектов в Guice

В отличие от Spring, Guice в основном имеет структуру Map , Object> . Это означает, что мы не можем иметь несколько привязок к одному и тому же типу без использования дополнительных метаданных.

Guice предоставляет аннотации привязки для определения нескольких привязок для одного и того же типа. Давайте посмотрим, что произойдет, если у нас есть две разные привязки для одного и того же типа в Guice.

public class Person {
}

Теперь давайте объявим две разные привязки для класса Person :

bind(Person.class).toConstructor(Person.class.getConstructor());
bind(Person.class).toProvider(new Provider() {
    public Person get() {
        Person p = new Person();
        return p;
    }
});

И вот как мы можем получить экземпляр Person class:

Person person = injector.getInstance(Person.class);
assertNotNull(person);

Это не сработает с:

com.google.inject.CreationException: A binding to Person was already configured at GuiceModule.configure()

Мы можем преодолеть эту проблему, просто отбросив одну из привязок для класса Person .

3.9. Дополнительные зависимости весной

Необязательные зависимости-это зависимости, которые не требуются при автоматическом подключении или вводе компонентов.

Для поля , которое было аннотировано с помощью @Autowired , если компонент с соответствующим типом данных не найден в контексте, Spring выдаст NoSuchBeanDefinitionException .

Тем не менее, иногда мы можем пропустить автозапуск для некоторых зависимостей и оставить их как null |/без создания исключения:

Теперь давайте рассмотрим следующий пример:

@Component
public class BookServiceImpl implements BookService {
    @Autowired
    private AuthorService authorService;
}
public class AuthorServiceImpl implements AuthorService {
}

Как мы видим из приведенного выше кода, Author Service Impl class не был аннотирован как компонент. И мы предположим, что в наших файлах конфигурации для него нет метода объявления компонента.

Теперь давайте проведем следующий тест, чтобы увидеть, что произойдет:

BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);

Неудивительно, что он потерпит неудачу с:

org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No qualifying bean of type 'AuthorService' available

Мы можем сделать авторская служба зависимость необязательна с помощью Необязательный тип Java 8 чтобы избежать этого исключения.

public class BookServiceImpl implements BookService {
    @Autowired
    private Optional authorService;
}

Теперь наша зависимость author Service больше похожа на контейнер, который может содержать или не содержать компонент типа Author Service . Даже если в контексте нашего приложения нет компонента для Author Service , наше поле author Service все равно будет не null пустым контейнером. Следовательно, у Spring не будет никаких причин выбрасывать NoSuchBeanDefinitionException .

В качестве альтернативы Optional мы можем использовать атрибут @Autowired ‘s required , который по умолчанию имеет значение true , чтобы сделать зависимость необязательной. Мы можем установить атрибут required в false , чтобы сделать зависимость необязательной для автозапуска.

Следовательно, Spring пропустит инъекцию зависимости, если компонент для его типа данных недоступен в контексте. Зависимость останется установленной в null:

@Component
public class BookServiceImpl implements BookService {
    @Autowired(required = false)
    private AuthorService authorService;
}

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

Имея это в виду, мы должны помнить, что нам нужно будет использовать дополнительную осторожность и null -проверки во время разработки, чтобы избежать любого NullPointerException из-за зависимостей null|/.

3.10. Необязательные зависимости в Guice

Так же, как Spring , Guice также может использовать тип Java 8 Optional , чтобы сделать зависимость необязательной.

Допустим, мы хотим создать класс и с зависимостью Foo :

public class FooProcessor {
    @Inject
    private Foo foo;
}

Теперь давайте определим привязку для класса Foo :

bind(Foo.class).toProvider(new Provider() {
    public Foo get() {
        return null;
    }
});

Теперь давайте попробуем получить экземпляр Кухонного комбайна в модульном тесте:

FooProcessor fooProcessor = injector.getInstance(FooProcessor.class);
assertNotNull(fooProcessor);

Наш модульный тест провалится с:

com.google.inject.ProvisionException:
null returned by binding at GuiceModule.configure(..)
but the 1st parameter of FooProcessor.[...] is not @Nullable

Чтобы пропустить это исключение, мы можем сделать зависимость foo необязательной с помощью простого обновления:

public class FooProcessor {
    @Inject
    private Optional foo;
}

@Inject не имеет атрибута required , чтобы отметить зависимость необязательной. Альтернативный подход к сделать зависимость необязательной в Guice заключается в использовании аннотации @Nullable .

Guice допускает ввод значений null в случае использования @Nullable , как указано в сообщении об исключении выше. Давайте применим аннотацию @Nullable :

public class FooProcessor {
    @Inject
    @Nullable
    private Foo foo;
}

4. Реализации типов внедрения зависимостей

В этом разделе мы рассмотрим типы внедрения зависимостей и сравним реализации, предоставляемые Spring и Guice, просмотрев несколько примеров.

4.1. Инъекция конструктора весной

В внедрение зависимостей на основе конструктора , мы передаем необходимые зависимости в класс во время создания экземпляра.

Допустим, мы хотим иметь компонент Spring и хотим добавить зависимости через его конструктор. Мы можем аннотировать этот конструктор с помощью @Autowired :

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public SpringPersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Начиная с Spring 4, зависимость @Autowired не требуется для этого типа внедрения, если класс имеет только один конструктор.

Давайте извлекем Spring PersonService bean в тесте:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);

4.2. Инъекция конструктора в Guice

Мы можем изменить предыдущий пример, чтобы реализовать инъекцию конструктора в Guice . Обратите внимание, что Guice использует @Inject вместо @Autowired .

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public GuicePersonService(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Вот как мы можем получить экземпляр класса GuicePersonService из инжектора в тесте:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);

4.3. Установка или Метод впрыска весной

При внедрении зависимостей на основе сеттера контейнер вызовет методы сеттера класса после вызова конструктора для создания экземпляра компонента.

Допустим, мы хотим, чтобы Spring автоматически подключал зависимость с помощью метода setter. Мы можем аннотировать этот метод установки с помощью @Autowired :

@Component
public class SpringPersonService {

    private PersonDao personDao;

    @Autowired
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Всякий раз, когда нам нужен экземпляр класса Spring PersonService , Spring автоматически подключит поле PersonDao , вызвав метод setPersonDao () .

Мы можем получить Spring PersonService bean и получить доступ к его PersonDao полю в тесте, как показано ниже:

SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.4. Инъекция сеттера или метода в Guice

Мы просто немного изменим наш пример, чтобы добиться инъекции сеттера в Guice .

public class GuicePersonService {

    private PersonDao personDao;

    @Inject
    public void setPersonDao(PersonDao personDao) {
        this.personDao = personDao;
    }
}

Каждый раз, когда мы получаем экземпляр класса GuicePersonService из инжектора, поле PersonDao передается методу setter выше.

Вот как мы можем создать экземпляр класса GuicePersonService и получить доступ к его PersonDao полю в тесте:

GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());

4.5. Полевая закачка весной

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

В случае внедрения зависимостей на основе полей мы вводим зависимости, помечая их @Autowired или @Инъекция .

4.6. Полевая инъекция в Guice

Как мы уже упоминали в разделе выше, мы уже рассмотрели инъекцию поля для Guice с использованием @Inject .

5. Заключение

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