Usando o Java Config da Spring, preciso adquirir / instanciar um bean com escopo de protótipo com argumentos de construtor que só podem ser obtidos em tempo de execução. Considere o seguinte exemplo de código (simplificado por questões de brevidade):
@Autowired
private ApplicationContext appCtx;
public void onRequest(Request request) {
//request is already validated
String name = request.getParameter("name");
Thing thing = appCtx.getBean(Thing.class, name);
//System.out.println(thing.getName()); //prints name
}
onde a classe Thing é definida da seguinte maneira:
public class Thing {
private final String name;
@Autowired
private SomeComponent someComponent;
@Autowired
private AnotherComponent anotherComponent;
public Thing(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
O aviso name
é final
: ele só pode ser fornecido por meio de um construtor e garante imutabilidade. As outras dependências são dependências específicas da implementação da Thing
classe e não devem ser conhecidas (fortemente acopladas) à implementação do manipulador de solicitações.
Este código funciona perfeitamente bem com a configuração XML da Spring, por exemplo:
<bean id="thing", class="com.whatever.Thing" scope="prototype">
<!-- other post-instantiation properties omitted -->
</bean>
Como faço para conseguir a mesma coisa com a configuração Java? O seguinte não funciona com o Spring 3.x:
@Bean
@Scope("prototype")
public Thing thing(String name) {
return new Thing(name);
}
Agora, eu poderia criar uma fábrica, por exemplo:
public interface ThingFactory {
public Thing createThing(String name);
}
Mas isso anula todo o sentido de usar o Spring para substituir o padrão de design ServiceLocator e Factory , o que seria ideal para esse caso de uso.
Se o Spring Java Config pudesse fazer isso, eu seria capaz de evitar:
- definindo uma interface de fábrica
- definindo uma implementação de fábrica
- escrevendo testes para a implementação da fábrica
Isso é muito trabalho (relativamente falando) para algo tão trivial que o Spring já suporta via configuração XML.
Thing
implementação é realmente mais complexa e tem dependências de outros beans (eu os omiti por brevidade). Como tal, não quero que a implementação do manipulador de solicitações saiba sobre eles, pois isso acopla fortemente o manipulador às APIs / beans de que não precisa. Vou atualizar a pergunta para refletir sua (excelente) pergunta.@Qualifier
parâmetros em um setter@Autowired
no próprio setter.@Bean
obras. O@Bean
método é chamado com os argumentos apropriados aos quais você passougetBean(..)
.Respostas:
Em uma
@Configuration
classe, um@Bean
método como esseé usado para registrar uma definição de bean e fornecer a fábrica para a criação do bean . O bean que ele define é instanciado somente mediante solicitação, usando argumentos que são determinados diretamente ou através da varredura
ApplicationContext
.No caso de um
prototype
bean, um novo objeto é criado sempre e, portanto, o@Bean
método correspondente também é executado.Você pode recuperar um bean
ApplicationContext
através doBeanFactory#getBean(String name, Object... args)
método que afirmaEm outras palavras, para este
prototype
bean com escopo definido, você está fornecendo os argumentos que serão utilizados, não no construtor da classe de bean, mas na@Bean
invocação do método.Isso é verdade pelo menos para as versões Spring 4+.
fonte
@Bean
método à chamada manual. Se você alguma vez@Autowire Thing
o@Bean
método for chamado, provavelmente morrendo por não conseguir injetar o parâmetro. Mesmo se você@Autowire List<Thing>
. Achei isso um pouco frágil.@Autowire
?Com Spring> 4.0 e Java 8, você pode fazer isso com mais segurança:
Uso:
Então agora você pode obter seu bean em tempo de execução. É claro que esse é um padrão de fábrica, mas você pode economizar algum tempo escrevendo classes específicas como
ThingFactory
(no entanto, você precisará escrever personalizado@FunctionalInterface
para passar mais de dois parâmetros).fonte
fabric
em vez defactory
, meu mau :)Provider
ou para umObjectFactory
, ou estou errado? E no meu exemplo, você pode passar um parâmetro de cadeia para ele (ou de qualquer parâmetro)@Bean
eScope
anotações sobre oThing thing
método. Além disso, este método pode ser tornado privado para se esconder e sair apenas da fábrica.Desde a Primavera 4.3, há uma nova maneira de fazer isso, que foi costurada para esse problema.
ObjectProvider - Permite apenas adicioná-lo como uma dependência ao seu bean com escopo Prototype "argumentado" e instancia-lo usando o argumento
Aqui está um exemplo simples de como usá-lo:
Obviamente, isso imprimirá um hello string ao chamar usePrototype.
fonte
ATUALIZADO por comentário
Primeiro, não sei por que você diz "isso não funciona" para algo que funciona bem no Spring 3.x. Eu suspeito que algo deve estar errado em sua configuração em algum lugar.
Isso funciona:
- Arquivo de configuração:
- Arquivo de teste para executar:
O uso do Spring 3.2.8 e Java 7 fornece esta saída:
Portanto, o Bean 'Singleton' é solicitado duas vezes. No entanto, como seria de esperar, o Spring apenas o cria uma vez. Na segunda vez, ele vê que possui esse bean e apenas retorna o objeto existente. O construtor (método @Bean) não é chamado pela segunda vez. Em deferência a isso, quando o Bean 'Prototype' é solicitado do mesmo objeto de contexto duas vezes, vemos que a referência muda na saída E que o construtor (método @Bean) É chamado duas vezes.
Então a questão é como injetar um singleton em um protótipo. A classe de configuração acima mostra como fazer isso também! Você deve passar todas essas referências para o construtor. Isso permitirá que a classe criada seja um POJO puro, além de tornar imutáveis os objetos de referência contidos. Portanto, o serviço de transferência pode ser algo como:
Se você escrever testes de unidade, ficará muito feliz por ter criado as classes sem todo o @Autowired. Se você precisar de componentes com conexão automática, mantenha esses locais nos arquivos de configuração java.
Isso chamará o método abaixo no BeanFactory. Observe na descrição como isso se destina ao seu caso de uso exato.
fonte
Você pode obter um efeito semelhante usando apenas um classe interna :
fonte
no seu arquivo xml de beans, use o atributo scope = "prototype"
fonte
Resposta tardia com uma abordagem ligeiramente diferente. Esse é um acompanhamento dessa pergunta recente que se refere a essa questão.
Sim, como foi dito, você pode declarar o protótipo de bean que aceita um parâmetro em uma
@Configuration
classe que permite criar um novo bean a cada injeção.Isso fará desta
@Configuration
classe uma fábrica e, para não atribuir muitas responsabilidades a essa fábrica, isso não deve incluir outros grãos.Mas você também pode injetar esse bean de configuração para criar
Thing
s:É ao mesmo tempo seguro e conciso.
fonte
@Configuration
se esse mecanismo for alterado.BeanFactory#getBean()
. Mas isso é bem pior em termos de acoplamento, pois é uma fábrica que permite obter / instanciar quaisquer beans do aplicativo e não apenas qual deles o bean atual precisa. Com esse uso, você pode misturar responsabilidades de sua classe com muita facilidade, já que as dependências que ela pode gerar são ilimitadas, o que não é realmente recomendado, mas é um caso excepcional.