VRaptor - Injeção de dependência em módulos externos ao projeto

10 respostas
vhmolinar

Olá, possuo a seguinte estrutura de módulos em um projeto maven:

manager(maven project)
- webresources(maven module[vraptor])
- domain(maven module[jpa])
- custom_server(maven module)

Essa estrutura é toda executada a partir do módulo webresources que é publicado no tomcat.
E meu problema é que precisava injetar dependências no webresources de classes que estão nos outros dois módulos e que eu as enxergo como componentes(@Component).

Estou usando a implementação padrão do Spring no projeto e vi que para que o mesmo reconhecesse código externo ao web-inf/classes seria necessário setar:

<context-param>
        <param-name>br.com.caelum.vraptor.packages</param-name>
        <param-value>br.com.meupacote</param-value>
    </context-param>

Isso resolve bem o problema, no entanto eu possuo dois projetos, logo precisaria setar 2 diretórios diferentes. O que invalida a solução.

Dessa forma resolvi criar meu próprio factory provider da seguinte maneira:

public class FactoryProvider extends SpringProvider {
    
    @Override
    public void registerCustomComponents( ComponentRegistry registry ) {
        registry.register( EntityManagerCreator.class, EntityManagerCreator.class );
        registry.register( EntityManagerFactoryCreator.class, EntityManagerFactoryCreator.class );
        registry.register( UserLogicCreator.class, UserLogicCreator.class ); //do domain module
        registry.register( TokenConfigCreator.class, TokenConfigCreator.class ); //do custom_server module
    }
}

E o web.xml

<context-param>
        <param-name>br.com.caelum.vraptor.provider</param-name>
        <param-value>br.com.meupacote.FactoryProvider</param-value>
    </context-param>

As 3 primeiras fábricas são sequencialmente(RequestScoped,ApplicationScoped e RequestScoped), de forma que toda instância de UserLogic é composta por um EntityManager.

Só que não está funcionando. O erro que recebo é:

core.StandardContext filterStart
Grave: Exception starting filter vraptor
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'tokenConfigCreator': Unsatisfied dependency expressed through constructor argument with index 0 of type [com.neoex.domain.logic.UserLogic]: : Error creating bean with name 'com.neoex.factory.UserLogicCreator': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userLogicCreator': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'com.neoex.factory.UserLogicCreator': FactoryBean threw exception on object creation; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'userLogicCreator': Scope 'request' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:730)
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:196)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1003)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:907)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:485)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:456)
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:291)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:288)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:190)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:580)
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:895)
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:425)
	at br.com.caelum.vraptor.ioc.spring.SpringBasedContainer.start(SpringBasedContainer.java:106)
	at br.com.caelum.vraptor.ioc.spring.SpringProvider.start(SpringProvider.java:87)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:108)
	at br.com.caelum.vraptor.VRaptor.init(VRaptor.java:102)
	at org.apache.catalina.core.ApplicationFilterConfig.initFilter(ApplicationFilterConfig.java:273)
	at org.apache.catalina.core.ApplicationFilterConfig.getFilter(ApplicationFilterConfig.java:254)
	at org.apache.catalina.core.ApplicationFilterConfig.setFilterDef(ApplicationFilterConfig.java:372)
	at org.apache.catalina.core.ApplicationFilterConfig.<init>(ApplicationFilterConfig.java:98)
	at org.apache.catalina.core.StandardContext.filterStart(StandardContext.java:4562)
	at org.apache.catalina.core.StandardContext$2.call(StandardContext.java:5240)
	at org.apache.catalina.core.StandardContext$2.call(StandardContext.java:5235)
	at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:334)
	at java.util.concurrent.FutureTask.run(FutureTask.java:166)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1110)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:603)
	at java.lang.Thread.run(Thread.java:722)

Eu vi que o meu provider é chamado no init do projeto mas mesmo assim ele não ta dando conta de resolver as dependências.

Alguma idéia?

10 Respostas

A

Acho que consegue setar mais de um pacote separando os valores por vírgula

Lucas_Cavalcanti

como o Abel disse, você pode colocar vários pacotes separados por vírgula, não precisa usar o seu provider…

<context-param>  
    <param-name>br.com.caelum.vraptor.packages</param-name>  
    <param-value>
         br.com.meupacote.domain,
         br.com.meupacote.custom_server
    </param-value>  
</context-param>
vhmolinar

Não deu certo. Dá o mesmo erro na hora de subir o tomcat.

Ao invés disso, eu tentei aqui foi deixar apenas o pacote do meu custom_server no web.xml e manter os ComponentFactory TokenConfigCreator(ApplicationScoped) e UserLogicCreator(PrototypeScopped).
Tomcat subiu beleza e aparentemente tudo funcionou, só que agora o VRaptor ta agindo de maneira estranha na hora de me entregar as dependências.

Cenário:
Eu tenho um @Resource recebendo um TokenCheckinInterface(criado pelo TokenConfigCreator) no construtor.
E o que ta acontecendo é que o VRaptor ao invés de me entregar a instância que está no component factory através do getInstance() está criando uma nova instância do mesmo e injetando no construtor do meu @Resource.

Eu ativei o debug e vi que o VRaptor até chama o getInstance no momento da requisição, no entanto não é aquela instância que ele está me entregando.

Seria um bug?

Lucas_Cavalcanti

Error creating bean with name ‘userLogicCreator’: Scope ‘request’ is not active for the current thread

se o UserLogicCreator for @ApplicationScoped, não pode receber caras @RequestScoped (escopo padrão).

pode usar a configuração do packages que ela tá funcionando.

Lucas_Cavalcanti

Outra coisa:

o vraptor sempre chama o getInstance() quando precisa entregar a dependência. Se vc quer entregar a mesma, a criação da dependencia tem que estar fora do getInstance().

vhmolinar

Lucas obrigado pela resposta.
Mas não estou usando RequestScope em nenhum component factory justamente pelo fato de realizar ações em meu custom server que não são provenientes de contexto da minha web app. Ex: recebo uma requisição de um outro sistema utilizando um protocolo customizado.

Dos meus componentes factories eu tenho

@Component
@PrototypeScoped
public class EntityManagerCreator implements ComponentFactory&lt;EntityManager&gt;

...

@Component
@ApplicationScoped
public class EntityManagerFactoryCreator implements ComponentFactory&lt;EntityManagerFactory&gt;

...

@Component
@ApplicationScoped
public class TokenConfigCreator implements ComponentFactory&lt;TokenCheckinInterface&gt;

...

@Component
@PrototypeScoped
public class UserLogicCreator implements ComponentFactory&lt;UserLogic&gt;

E a respeito de retornar a mesma instância setada em pelo TokenConfigCreator, eu crio ela em um postConstruct, veja:

@Component
@ApplicationScoped
public class TokenConfigCreator implements ComponentFactory&lt;TokenCheckinInterface&gt; {

    private TokenCheckinInterface checkinService;
    private UserLogic             userLogic;
    private ServletContext        context;

    public TokenConfigCreator( UserLogic userLogic, ServletContext context ) {
        this.userLogic = userLogic;
        this.context = context;
    }

    @PostConstruct
    public void create() throws FileNotFoundException {
        this.checkinService = new TokenCheckinServer( this.userLogic );
        XStream xstream = new XStream( new DomDriver( "UTF-8", new XmlFriendlyNameCoder( "__", "_" ) ) );
        xstream.processAnnotations( TokenCheckinServiceConfig.class );
        TokenCheckinServiceConfig cfg = (TokenCheckinServiceConfig) xstream.fromXML( new FileInputStream( this.context.getRealPath( "/WEB-INF/classes/token_container.xml" ) ) );
        this.checkinService.configure( cfg );
        this.checkinService.start();
    }

    /*
     * (non-Javadoc)
     * 
     * @see br.com.caelum.vraptor.ioc.ComponentFactory#getInstance()
     */
    @Override
    public TokenCheckinInterface getInstance() {
        return this.checkinService;
    }

    @PreDestroy
    public void destroy() {
        this.checkinService.stop();
    }

}

Como eu disse antes, quando eu debuguei vi que o getInstance() era chamado e a instância única e correta era retornada, no entanto não era essa que eu recebia no construtor do meu @Resource, porque o VRaptor tava instanciando outra manualmente.
Isso que disse que é o comportamento estranho dele. Me parece um bug, mas não tenho certeza porque não sei se estou configurando corretamente minhas injeções de dependência.

vhmolinar

Sobre a configuração de pacotes. Realmente funciona, o tomcat sobe. Foi só anotar @ProtoypedScoped na bean UserLogic. Pois como vc mesmo disse, o RequestScoped era padrão.
Deste modo eu removi meu component factory UserLogicCreator.

Tomcat sobe mas ainda ocorre a confusão por parte do VRaptor na hora de entregar a instância. Ele sempre ta criando uma nova, mas não através do ComponentFactory. Tá fazendo isso por conta própria.

Lucas_Cavalcanti

Se vc tem uma ComponentFactory pra uma classe/interface, essa classe/implementação da interface não podem estar anotadas com @Component.

Lucas_Cavalcanti

Só um cuidado: se vc usar EntityManagerCreator como @PrototypeScoped, o VRaptor provavelmente não vai conseguir chamar o @PreDestroy, e vc vai ter que fechar o EntityManager nas classes que usam…

o ideal é deixar como @RequestScoped mesmo, pq daí vc consegue fazer várias operações na mesma conexão e transação. E o vraptor vai fechar direitinho.

vhmolinar

Hm, entendi.
Era bem isso mesmo. Eu tenho um ComponentFactory pra uma classe e essa estava anotada como @Component.
Obrigado!

Criado 28 de maio de 2012
Ultima resposta 29 de mai. de 2012
Respostas 10
Participantes 3