Guice vs Spring - Injeção de Dependência
1. Introdução
Google GuiceeSpring são dois frameworks robustos usados para injeção de dependência. Ambas as estruturas abrangem todas as noções de injeção de dependência, mas cada uma tem sua própria maneira de implementá-las.
Neste tutorial, discutiremos como os frameworks Guice e Spring diferem na configuração e implementação.
2. Dependências do Maven
Vamos começar adicionando as dependências Guice e Spring Maven em nosso arquivopom.xml:
org.springframework
spring-context
5.1.4.RELEASE
com.google.inject
guice
4.2.2
Sempre podemos acessar as dependências despring-context orguice mais recentes do Maven Central.
3. Configuração de injeção de dependência
Dependency injection é uma técnica de programação que usamos para tornar nossas classes independentes de suas dependências.
Nesta seção, vamos nos referir a vários recursos principais que diferem entre Spring e Guice em suas maneiras de configurar a injeção de dependência.
3.1. Fiação da mola
Spring declares the dependency injection configurations in a special configuration class. Esta classe deve ser anotada pela anotação@Configuration. O contêiner Spring usa essa classe como uma fonte de definições de bean.
As aulas gerenciadas pelo Spring são chamadas Spring beans.
Spring uses the @Autowired annotation to wire the dependencies automatically. @Autowired faz parte deSpring’s built-in core annotations. Podemos usar@Autowired em variáveis de membro, métodos setter e construtores.
Spring também oferece suporte a@Inject. @Inject é parte deJava CDI (Contexts and Dependency Injection) que define um padrão para injeção de dependência.
Digamos que queremos conectar automaticamente uma dependência a uma variável membro. Podemos simplesmente anotá-lo com@Autowired:
@Component
public class UserService {
@Autowired
private AccountService accountService;
}
@Component
public class AccountServiceImpl implements AccountService {
}
Em segundo lugar, vamos criar uma classe de configuração para usar como fonte de beans ao carregar nosso contexto de aplicativo:
@Configuration
@ComponentScan("com.example.di.spring")
public class SpringMainConfig {
}
Observe que também anotamosUserService andAccountServiceImpl com@Component para registrá-los como feijões. It’s the @ComponentScan annotation that will tell Spring where to search para componentes anotados.
Mesmo que tenhamos anotadoAccountServiceImpl, Spring pode mapeá-lo paraAccountService s desde que implementaAccountService.
Em seguida, precisamos definir um contexto de aplicativo para acessar os beans. Vamos apenas observar que nos referiremos a este contexto em todos os nossos testes de unidade Spring:
ApplicationContext context = new AnnotationConfigApplicationContext(SpringMainConfig.class);
Agora, em tempo de execução, podemos recuperar a instânciaAccountService de nosso beanUserService:
UserService userService = context.getBean(UserService.class);
assertNotNull(userService.getAccountService());
3.2. Guice Binding
Guice manages its dependencies in a special class called a module. Um módulo Guice tem que estender o sclassAbstractModule e substituir seu métodoconfigure().
O Guice usa a ligação como equivalente à fiação no Spring. Simplificando,bindings allow us to define how dependencies are going to be injected into a class. As ligações do Guice são declaradas no métodoconfigure() do nosso módulo.
Em vez de@Autowired, o Guice usa a anotação@Inject para injetar as dependências.
Vamos criar um exemplo Guice equivalente:
public class GuiceUserService {
@Inject
private AccountService accountService;
}
Em segundo lugar, vamos criar a classe do módulo que é uma fonte de nossas definições de ligação:
public class GuiceModule extends AbstractModule {
@Override
protected void configure() {
bind(AccountService.class).to(AccountServiceImpl.class);
}
}
Normalmente, esperamos que o Guice instancie cada objeto de dependência de seus construtores padrão se não houver qualquer ligação definida explicitamente no métodoconfigure(). But since interfaces can’t be instantiated directly, we need to define bindings para informar ao Guice qual interface será emparelhada com qual implementação.
Então, precisamos definir umInjector usandoGuiceModule para obter instâncias de nossas classes. Vamos apenas observar que todos os nossos testes de Guice usarão esteInjector:
Injector injector = Guice.createInjector(new GuiceModule());
Finalmente, em tempo de execução, recuperamos uma instânciaGuiceUserService com uma dependênciaaccountService não nula:
GuiceUserService guiceUserService = injector.getInstance(GuiceUserService.class);
assertNotNull(guiceUserService.getAccountService());
3.3. Anotação @Bean de Spring
Spring also provides a method level annotation @Bean to register beans como uma alternativa para suas anotações de nível de classe como@Component. O valor de retorno de um método anotado@Bean é registrado como um bean no contêiner.
Digamos que temos uma instância deBookServiceImpl que queremos disponibilizar para injeção. Poderíamos usar@Bean para registrar nossa instância:
@Bean
public BookService bookServiceGenerator() {
return new BookServiceImpl();
}
E agora podemos obter um beanBookService:
BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);
3.4. Anotação @Provides de Guice
Como um equivalente da anotação@Bean de Spring,Guice has a built-in annotation @Provides to do the same job. Como@Bean,@Provides só se aplica aos métodos.
Agora vamos implementar o exemplo anterior de bean Spring com Guice. Tudo o que precisamos fazer é adicionar o seguinte código em nossa classe de módulo:
@Provides
public BookService bookServiceGenerator() {
return new BookServiceImpl();
}
E agora, podemos recuperar uma instância deBookService:
BookService bookService = injector.getInstance(BookService.class);
assertNotNull(bookService);
3.5. Varredura do componente Classpath na primavera
Spring fornece um@ComponentScan annotation detects and instantiates annotated components automatically escaneando pacotes pré-definidos.
A anotação@ComponentScan diz ao Spring quais pacotes serão verificados quanto a componentes anotados. É usado com a anotação@Configuration.
3.6. Verificação do componente Classpath no Guice
Ao contrário da primavera,Guice doesn’t have such a component scanning feature. Mas não é difícil simular isso. Existem alguns plug-ins comoGovernator que podem trazer esse recurso para o Guice.
3.7. Reconhecimento de Objetos na Primavera
Spring reconhece objetos por seus nomes. Spring holds the objects in a structure which is roughly like a Map<String, Object>. Isso significa que não podemos ter dois objetos com o mesmo nome.
Bean collision due to having multiple beans of the same name is one common problem sucesso dos desenvolvedores do Spring. Por exemplo, vamos considerar as seguintes declarações de bean:
@Configuration
@Import({SpringBeansConfig.class})
@ComponentScan("com.example.di.spring")
public class SpringMainConfig {
@Bean
public BookService bookServiceGenerator() {
return new BookServiceImpl();
}
}
@Configuration
public class SpringBeansConfig {
@Bean
public AudioBookService bookServiceGenerator() {
return new AudioBookServiceImpl();
}
}
Como lembramos, já tínhamos uma definição de bean paraBookService na classeSpringMainConfig.
Para criar uma colisão de bean aqui, precisamos declarar os métodos de bean com o mesmo nome. Mas não podemos ter dois métodos diferentes com o mesmo nome em uma classe. Por esse motivo, declaramos o beanAudioBookService em outra classe de configuração.
Agora, vamos consultar esses grãos em um teste de unidade:
BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);
AudioBookService audioBookService = context.getBean(AudioBookService.class);
assertNotNull(audioBookService);
O teste de unidade falhará com:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AudioBookService' available
Primeiro, o Spring registrou o beanAudioBookService com o nome“bookServiceGenerator” em seu mapa de bean. Em seguida, ele teve que substituí-lo pela definição de bean paraBookService devido à natureza“no duplicate names allowed” da estrutura de dadosHashMap.
Por fim,we can overcome this issue by making bean method names unique or setting the name attribute to a unique name for each @Bean.
3.8. Reconhecimento de Objetos no Guice
Ao contrário da primavera,Guice basically has a Map<Class<?>, Object> structure. Isso significa que não podemos ter várias ligações para o mesmo tipo sem usar metadados adicionais.
O Guice fornecebinding annotations para permitir a definição de várias ligações para o mesmo tipo. Vamos ver o que acontece se tivermos duas ligações diferentes para o mesmo tipo no Guice.
public class Person {
}
Agora, vamos declarar duas ligações diferentes para a classePerson:
bind(Person.class).toConstructor(Person.class.getConstructor());
bind(Person.class).toProvider(new Provider() {
public Person get() {
Person p = new Person();
return p;
}
});
E aqui está como podemos obter uma instância da classePerson:
Person person = injector.getInstance(Person.class);
assertNotNull(person);
Isso falhará com:
com.google.inject.CreationException: A binding to Person was already configured at GuiceModule.configure()
Podemos superar esse problema simplesmente descartando uma das ligações para a classePerson.
3.9. Dependências opcionais no Spring
Optional dependencies são dependências que não são necessárias ao autowiring ou injeção de beans.
Para um campo que foi anotado com@Autowired, se um bean com o tipo de dados correspondente não for encontrado no contexto, o Spring lançaráNoSuchBeanDefinitionException.
No entanto,sometimes we may want to skip autowiring for some dependencies and leave them as null sem lançar uma exceção:
Agora vamos dar uma olhada no seguinte exemplo:
@Component
public class BookServiceImpl implements BookService {
@Autowired
private AuthorService authorService;
}
public class AuthorServiceImpl implements AuthorService {
}
Como podemos ver no código acima, a classeAuthorServiceImpl não foi anotada como um componente. E vamos assumir que não há um método de declaração de bean para ele em nossos arquivos de configuração.
Agora, vamos executar o seguinte teste para ver o que acontece:
BookService bookService = context.getBean(BookService.class);
assertNotNull(bookService);
Não surpreendentemente, ele falhará com:
org.springframework.beans.factory.NoSuchBeanDefinitionException:
No qualifying bean of type 'AuthorService' available
Podemos tornar a dependência deauthorService opcional usandoJava 8’s Optional type para evitar essa exceção.
public class BookServiceImpl implements BookService {
@Autowired
private Optional authorService;
}
Agora, nossa dependênciaauthorService é mais como um contêiner que pode ou não conter um bean do tipoAuthorService. Mesmo que não haja um bean paraAuthorService em nosso contexto de aplicativo, nosso campoauthorService ainda será um recipiente não -null vazio. Portanto, o Spring não terá nenhum motivo para lançarNoSuchBeanDefinitionException.
Como alternativa aOptional, podemos usar o atributo@Autowired'srequired, que é definido comotrue por padrão, para tornar uma dependência opcional. We can set the required attribute to false to make a dependency optional for autowiring.
Portanto, o Spring ignorará a injeção de dependência se um bean para seu tipo de dados não estiver disponível no contexto. A dependência permanecerá definida comonull:
@Component
public class BookServiceImpl implements BookService {
@Autowired(required = false)
private AuthorService authorService;
}
Às vezes, marcar dependências opcional pode ser útil, pois nem todas as dependências são sempre necessárias.
Com isso em mente, devemos lembrar que precisaremos ter cuidado extra e verificações denull durante o desenvolvimento para evitarNullPointerException devido às dependências denull.
3.10. Dependências opcionais no Guice
Assim comoSpring,Guice can also use Java 8’s Optional type to make a dependency optional.
Digamos que queremos criar uma classe e com uma dependênciaFoo:
public class FooProcessor {
@Inject
private Foo foo;
}
Agora, vamos definir uma ligação para a classeFoo:
bind(Foo.class).toProvider(new Provider() {
public Foo get() {
return null;
}
});
Agora vamos tentar obter uma instância deFooProcessor em um teste de unidade:
FooProcessor fooProcessor = injector.getInstance(FooProcessor.class);
assertNotNull(fooProcessor);
Nosso teste de unidade falhará com:
com.google.inject.ProvisionException:
null returned by binding at GuiceModule.configure(..)
but the 1st parameter of FooProcessor.[...] is not @Nullable
Para pular essa exceção, podemos tornar a dependênciafoo opcional com uma atualização simples:
public class FooProcessor {
@Inject
private Optional foo;
}
@Inject não tem um atributorequired para marcar a dependência opcional. An alternative approach to faza dependency optional in Guice is to use the @Nullable annotation.
O Guice tolera a injeção de valores denull no caso de usar@Nullable conforme expresso na mensagem de exceção acima. Vamos aplicar a anotação@Nullable:
public class FooProcessor {
@Inject
@Nullable
private Foo foo;
}
4. Implementações de tipos de injeção de dependência
Nesta seção, vamos dar uma olhada emdependency injection typese comparar as implementações fornecidas pelo Spring e Guice, passando por vários exemplos.
4.1. Injeção de Construtor na Primavera
Emconstructor-based dependency injection, passamos as dependências necessárias para uma classe no momento da instanciação.
Digamos que queremos um componente Spring e queremos adicionar dependências por meio de seu construtor. Podemos anotar esse construtor com@Autowired:
@Component
public class SpringPersonService {
private PersonDao personDao;
@Autowired
public SpringPersonService(PersonDao personDao) {
this.personDao = personDao;
}
}
A partir do Spring 4, a dependência@Autowired não é necessária para esse tipo de injeção se a classe tiver apenas um construtor.
Vamos recuperar um beanSpringPersonService em um teste:
SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);
4.2. Injeção de construtor no Guice
Podemos reorganizar o exemplo anterior paraimplement constructor injection in Guice. Observe que o Guice usa@Inject em vez de@Autowired.
public class GuicePersonService {
private PersonDao personDao;
@Inject
public GuicePersonService(PersonDao personDao) {
this.personDao = personDao;
}
}
Aqui está como podemos obter uma instância da classeGuicePersonService deinjector em um teste:
GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);
4.3. Setter ou método de injeção na primavera
Na injeção de dependência baseada em setter, o contêiner chamará métodos setter da classe, após invocar o construtor para instanciar o componente.
Digamos que queremos que o Spring autowire uma dependência usando um método setter. Podemos anotar esse método setter com@Autowired:
@Component
public class SpringPersonService {
private PersonDao personDao;
@Autowired
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
Sempre que precisarmos de uma instância da classeSpringPersonService, o Spring irá autowire o campopersonDao invocando o métodosetPersonDao().
Podemos obter um beanSpringPersonService e acessar seu campopersonDao em um teste como abaixo:
SpringPersonService personService = context.getBean(SpringPersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());
4.4. Setter or Method Injection in Guice
Vamos simplesmente mudar nosso exemplo um pouco para atingirsetter injection in Guice.
public class GuicePersonService {
private PersonDao personDao;
@Inject
public void setPersonDao(PersonDao personDao) {
this.personDao = personDao;
}
}
Cada vez que obtemos uma instância da classeGuicePersonService do injetor, teremos o campopersonDao passado para o método setter acima.
Aqui está como podemos criar uma instância da classeGuicePersonServicee acessar seu campopersonDao __ em um teste:
GuicePersonService personService = injector.getInstance(GuicePersonService.class);
assertNotNull(personService);
assertNotNull(personService.getPersonDao());
4.5. Injeção de campo na primavera
Já vimos como aplicar injeção de campo para Spring e Guice em todos os nossos exemplos. Portanto, não é um conceito novo para nós. Mas vamos apenas listar novamente para completar.
No caso de injeção de dependência baseada em campo, injetamos as dependências marcando-as com@Autowired ou@Inject.
4.6. Injeção de campo no Guice
Como mencionamos na seção acima, já cobrimosfield injection for Guice using @Inject.