2012-10-30 9 views
11

Ho scritto un servizio di riposo utilizzando la jersey 1.13 e la 3.1.1 di primavera che gira su tomcat 6. In tomcat sto usando un dominio che eseguirà l'autenticazione. Nella mia applicazione ho bisogno dell'utente corrente ma non voglio accedere a SecurityContext dalla maglia in ogni risorsa. Voglio iniettare un oggetto ApplicationConfig con scope scope nelle mie risorse resto che conterrà l'utente corrente. Più tardi posso estendere questa classe per contenere più parametri di configurazione a livello di richiesta. Questa mi sembra una bella astrazione.Compilazione di un bean con ambito di richiesta molla in un ContainerRequestFilter

@Component 
@Scope(value = "request") 
public class ApplicationConfig 
{ 
    private String userCode; 

    public String getUserCode() 
    { 
     return this.userCode; 
    } 

    public void setUserCode(String userCode) 
    { 
     this.userCode = userCode; 
    } 
} 

Ho creato un ApplicationConfigManager per fornire l'accesso alla configurazione.

@Component 
public class ApplicationConfigManager 
{ 
    @Autowired 
    public ApplicationConfig applicationConfig; 

    public ApplicationConfig getApplicationConfig() 
    { 
     return this.applicationConfig; 
    } 
} 

L'applicazione di configurazione gestore è definito come un singoletto (default), ma l'ApplicationConfig dovrebbe essere richiesta portata, quindi l'annotazione @Scope.

Sto usando un (jersey) ContainterRequestFilter per impostare l'utente sull'oggetto di configurazione dell'applicazione.

@Component 
@Provider 
public class ApplicationConfigFilter implements ResourceFilter, ContainerRequestFilter 
{ 
    @Autowired 
    private ApplicationConfigManager applicationConfigManager; 

    @Override 
    public ContainerRequest filter(ContainerRequest request) 
    { 
     this.applicationConfigManager.getApplicationConfig().setUserCode(
      request.getSecurityContext().getUserPrincipal().getName() 
     ); 
     return request; 
    } 

    @Override 
    public ContainerRequestFilter getRequestFilter() 
    { 
     return this; 
    } 

    @Override 
    public ContainerResponseFilter getResponseFilter() 
    { 
     return null; 
    } 
} 

Per registrare questo filtro ho creato un ResourceFilterFactory

@Component 
@Provider 
public class ResourceFilterFactory extends RolesAllowedResourceFilterFactory 
{ 
    @Autowired 
    private ApplicationConfigFilter applicationConfigFilter; 

    @Override 
    public List<ResourceFilter> create(AbstractMethod am) 
    { 
     // get filters from RolesAllowedResourceFilterFactory Factory! 
     List<ResourceFilter> rolesFilters = super.create(am); 
     if (null == rolesFilters) { 
      rolesFilters = new ArrayList<ResourceFilter>(); 
     } 

     // Convert into mutable List, so as to add more filters that we need 
     // (RolesAllowedResourceFilterFactory generates immutable list of filters) 
     List<ResourceFilter> filters = new ArrayList<ResourceFilter>(rolesFilters); 

     filters.add(this.applicationConfigFilter); 

     return filters; 
    } 
} 

ho attivato questa fabbrica impostando questo in web.xml

<servlet> 
    <servlet-name>Jersey REST Service</servlet-name> 
    <servlet-class>com.sun.jersey.spi.spring.container.servlet.SpringServlet</servlet-class> 
    <init-param> 
     <param-name>com.sun.jersey.config.property.packages</param-name> 
     <param-value>com.mypackage</param-value> 
    </init-param> 
    <init-param> 
     <param-name>com.sun.jersey.spi.container.ResourceFilters</param-name> 
     <param-value>com.mypackage.ResourceFilterFactory</param-value> 
    </init-param> 
    <load-on-startup>1</load-on-startup> 
</servlet> 

Ho anche aggiunto questi ascoltatori di bootstrap della molla contesto e per ottenere l'ambito della richiesta di lavoro

<listener> 
    <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class> 
</listener> 
<listener> 
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> 
</listener> 

Questo è quello che ho messo nel mio contesto di applicazione per ottenere la funzione di scansione di lavoro e di definire l'oggetto aplication config (Credo che questo non è nemmeno necessario come la primavera, la troverà automagicamente)

<context:annotation-config/> 
<context:component-scan base-package="com.mypackage" /> 

<bean id="applicationConfig" class="com.mypackage.ApplicationConfig" scope="request"/> 

E ora la mia problema. All'avvio dell'applicazione, la molla verrà riavviata e inietterà l'oggetto ApplicationConfig su ApplicationConfigManager che viene iniettato su ApplicationConfigFilter.

A questo punto si genera l'eccezione:

. 
. 
Caused by: 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 o 
perating 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.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE] 
     at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:40) ~[spring-web-3.1.1.RELEASE.jar:3.1.1.RELEASE] 
     at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:328) ~[spring-beans-3.1.1.RELEASE.jar:3.1.1.RELEASE] 
     ... 57 common frames omitted 

Questa eccezione è abbastanza chiaro e penso che significa che la richiesta ambito ApplicationConfig non può essere iniettato perché nessuna richiesta è stata inviata ancora.

Quindi è consigliabile creare istanza dell'oggetto ApplicationConfig SOLO quando viene inviata una richiesta e non durante l'avvio dell'applicazione. Ho cercato soluzioni e ho scoperto che l'iniezione di un bean con scope richiesta in un bean singleton non è molto logica. Otterrei sempre lo stesso oggetto comunque. La soluzione è quella di utilizzare un proxy così ho cambiato l'annotazione @Scope sulla classe ApplicationConfig a questo

@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS) 

Questo dovrebbe darmi un nuovo oggetto ApplicationConfig per ogni richiesta.

L'app ora viene avviata correttamente (non sembra creare un'istanza di ApplicationConfig) ma quando invio una richiesta al mio servizio di ripristino ho trovato, usando il debug, che ottengo diversi oggetti ApplicationConfig invece dello stesso per la richiesta.

Quindi cosa sto sbagliando?

risposta

7

Dopo aver giocato con questo ho scoperto che l'impostazione proxyMode sull'annotazione @Scope ha comunque funzionato. Quindi questa è la soluzione. Il proxy si prenderà cura della creazione di una nuova istanza ogni volta e si assicurerà che sia richiesta dell'ambito.

Problemi correlati