Spring Boot启动抛出异常:找不到请求范围的bean

时间:2016-06-17 20:10:40

标签: spring proxy scope jersey

我试图使用Jersey的Spring Boot(1.3.5)构建一个概念证明。我的pom将spring-boot-starter-jersey工件列为依赖项。

只定义了单例bean,它运行正常。但是,我现在尝试将请求范围的bean注入单例bean并且遇到以下异常:

Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.jndi': 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.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:355) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.getTarget(CglibAopProxy.java:687) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:637) ~[spring-aop-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at $java.util.Properties$$EnhancerBySpringCGLIB$$1a020092.size(<generated>) ~[na:na]
    at org.springframework.jndi.JndiTemplate.createInitialContext(JndiTemplate.java:133) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiTemplate.getContext(JndiTemplate.java:103) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiTemplate.execute(JndiTemplate.java:85) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:152) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiTemplate.lookup(JndiTemplate.java:179) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiLocatorSupport.lookup(JndiLocatorSupport.java:95) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.jndi.JndiObjectLocator.lookup(JndiObjectLocator.java:106) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.ejb.access.AbstractRemoteSlsbInvokerInterceptor.lookup(AbstractRemoteSlsbInvokerInterceptor.java:99) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.ejb.access.AbstractSlsbInvokerInterceptor.refreshHome(AbstractSlsbInvokerInterceptor.java:121) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.ejb.access.SimpleRemoteSlsbInvokerInterceptor.refreshHome(SimpleRemoteSlsbInvokerInterceptor.java:162) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.ejb.access.AbstractSlsbInvokerInterceptor.afterPropertiesSet(AbstractSlsbInvokerInterceptor.java:108) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.ejb.access.SimpleRemoteStatelessSessionProxyFactoryBean.afterPropertiesSet(SimpleRemoteStatelessSessionProxyFactoryBean.java:101) ~[spring-context-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1637) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1574) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    ... 21 common frames omitted
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 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.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41) ~[spring-web-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340) ~[spring-beans-4.2.6.RELEASE.jar:4.2.6.RELEASE]
    ... 40 common frames omitted

根据我所读到的内容,我需要启用我已经完成的RequestContextListener,但它没有任何区别:

@Configuration
public class AppConfig {

    @Bean(name="jndi")
    @Scope(scopeName=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
    public Properties getJNDIContext(){
        Properties p = new Properties();
        p.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces" );
        p.setProperty("java.naming.provider.url", "jnp://localhost:1099");
        p.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.security.jndi.JndiLoginInitialContextFactory");
        p.setProperty(Context.SECURITY_PRINCIPAL,"test" );
        p.setProperty(Context.SECURITY_CREDENTIALS,"12345678" );
        return p;
    }

    @Bean
    public FactoryBean<?> getAthleteManagerFactory(@Qualifier("jndi") Properties p){
        SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
        String beanName = "zone.jndi.ejb3.RemoteAthleteManager";
        factory.setJndiName(beanName);
        factory.setBusinessInterface(AthleteManager.class);
        factory.setJndiEnvironment(p);
        return factory;
    }

    @Bean 
    public RequestContextListener requestContextListener(){
        return new RequestContextListener();
    } 

}

我的控制器非常简单:

@Component
@Path("/athlete")
public class AthleteController {

    @Autowired
    private AthleteManager athleteManager;

    ....
    ....
}

我怀疑错误是工厂正在尝试使用代理请求范围的bean来实例化对象以注入到我的Controller中,并且请求范围还不存在。

有没有办法解决这个问题?我本来期望proxyMode能够处理这个问题,但它似乎没有任何区别。无论是否启用proxyMode,我都会得到类似的(尽管略有不同)异常堆栈跟踪。

如果我将我的工厂标记为Lazy,它仍然没有什么区别,因为Spring需要实例化它(及其对象)以自动装配我的控制器类。

有没有办法做到这一点?我需要一个过滤器在每次调用时覆盖getJNDIContext()属性对象中的凭据。

1 个答案:

答案 0 :(得分:0)

我终于找到了解决问题的方法,虽然我不是特别喜欢它。对于初学者,我需要从启动时延迟JNDI bean查找/初始化,以便容器不会尝试填充JNDI属性,直到它们实际可用(仅在请求时)。我还注意到我无法将JNDI范围扩展到请求范围,因此每次都必须查找它(或者身份验证凭据可能不正确)。

@Bean(name="jndi")
@Scope(scopeName=WebApplicationContext.SCOPE_REQUEST, proxyMode=ScopedProxyMode.TARGET_CLASS)
public Properties getJNDIContext(){
    Properties p = new Properties();
    p.setProperty("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces" );
    p.setProperty("java.naming.provider.url", "jnp://localhost:1099");
    p.setProperty(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.security.jndi.JndiLoginInitialContextFactory");
    return p;
}

@Bean
public FactoryBean<?> getLoginManagerFactory(@Qualifier("jndi") Properties p){
    return getEjbFactoryBean(LoginManager.class, p );
}

/**
 * Helper method to setup the factory
 * @param clazz
 * @param jndi
 * @return
 */
private SimpleRemoteStatelessSessionProxyFactoryBean getEjbFactoryBean(Class clazz, Properties jndi){
    SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
    // need to delay lookup since security params are only available in a request thread
    factory.setLookupHomeOnStartup(false);
    // refresh home in case EJB server is restarted
    factory.setRefreshHomeOnConnectFailure(true);
    // do not cache - need to lookup every time due to changing security params.  They are only set upon initial bean lookup
    factory.setCacheHome(false);
    // set the JNDI properties  
    factory.setJndiEnvironment(jndi);

    String beanName = REMOTE_EJB_PREFIX + clazz.getSimpleName();
    factory.setJndiName(beanName);
    factory.setBusinessInterface(clazz);
    return factory;
}

最后,我设置了一个过滤器,以便在每个请求中指定我的凭据:

@Provider
public class SecurityFilter implements ContainerRequestFilter {
    @Autowired  @Qualifier("jndi")
    private Properties userCreds;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        // extract username/password from basic auth
        String[] auth = decode( requestContext.getHeaderString(HttpHeaders.AUTHORIZATION) );
        userCreds.setProperty(Context.SECURITY_PRINCIPAL, auth[0] );
        userCreds.setProperty(Context.SECURITY_CREDENTIALS, auth[1] );
    }
}

从概念上讲,这是有效的,但效率非常低。我想找到一个允许我做同样的解决方案,但至少在请求级别对我的bean进行范围调整,这样每次都不会检索它。