每次从中调用方法时都会创建代理原型Bean

时间:2018-12-07 10:02:43

标签: java spring spring-boot

我在从websockets回调线程(不同于管理原始HTTP请求的线程)中访问请求范围的bean时遇到问题。你可以在这里读更多关于它的内容: Accessing a request scoped bean in a different thread (that handles websocket traffic)

此后,我设法解决了这个问题(即使我对解决方案不是100%满意的),但是我看到的是我不理解的行为。

我当前的解决方案如下: 我已将Bean从请求范围更改为原型范围:

@Bean
@Scope(value = "prototype", proxyMode = ScopedProxyMode.TARGET_CLASS)
public DbClientI getDbClient() throws StrykeDomainException {       
   DbClientI dbClient = requestContextHolder.getAttribute("dbClientReq", DbClientI.class);      
   if (dbClient != null) {
       logger.info("Retrieved DbClient proxy instance: {}", dbClient.hashCode());
   }    
   return dbClient;
}

我正在使用专用的拦截器(HandlerInterceptorAdapter)创建和销毁Bean后面的实例,并将其存储在RequestContextHolder中,以便可以通过我的Bean配置(上方)进行检索。

@Override
public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) {

    boolean shouldProceedInterceptorChain = true;

    if (authenticatedUserInfo.isUserAuthenticated()) {

        try {
            DbClientI dbClient = dbCliFactory.createDbClientForCurrentRequest();
            requestContextHolder.setAttribute("dbClientReq", dbClient, true);                           
            dbClient.connect();     

        } catch (Exception e) {
            shouldProceedInterceptorChain = false;              
        }                           
    }

    return shouldProceedInterceptorChain;
}

@Override
public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) {

    //Note: we must use this method to disconnect instead of "postHandle", because postHandle will not run in case of an exception 
    if (authenticatedUserInfo.isUserAuthenticated()) {          
        DbClientI dbClient = appContext.getBean(DbClientI.class);            
        if (dbClient != null && dbClient.isConnected()) {
            dbClient.disconnect();                
            dbClient = null;
        }
    }
}

该解决方案有效,但是每次在代码中访问dbClient Bean时都会调用方法getDbClient()!从另一个Bean内的dbClient Bean调用方法会导致调用getDbClient()方法。 我的理解是,仅当每次将getDbClient()方法注入另一个bean的构造函数中时,才应调用该方法。这是Spring文档所说的:

  

bean部署的非单一原型范围导致   每次针对特定的请求创建一个新的bean实例   制作了bean(即,将其注入另一个bean或   通过容器上的程序化getBean()方法调用请求)   https://docs.spring.io/spring/docs/3.0.0.M3/reference/html/ch04s04.html#beans-factory-scopes-prototype

从功能上讲,这很好,因为在其下总是有相同的DbClient实例(由拦截器创建的实例),但是每次使用bean时都会调用方法getDbClient()的事实肯定会影响性能。

如何更改代码,以便仅在将bean提供给另一个bean时而不是每次使用它时才调用getDbClient()? 谢谢。

1 个答案:

答案 0 :(得分:0)

利用评论中的洞察力,并对终端进行更多测试,我意识到我的误解的关键在于代理的使用。

实际上,正如Spring文档所说,使用Prototype范围时,仅在每次注入bean或调用ApplicationContext.getBean()时才会创建一个新实例。仅通过访问bean(例如通过在其上调用方法)就不会创建新实例。

但是,如果同一个bean也使用proxy属性进行修饰,则注入时创建的是代理而不是类的实际实例。这导致Spring每次访问bean时(例​​如:在其上调用方法)都调用“配置方法”来检索实际的基础实例(在我的情况下为getDbClient方法)。

请注意,以上内容适用于“代理”原型bean。对于“代理”请求范围的Bean,将在请求开始时执行一次获取实际实例的调用。对该bean的后续使用不会触发调用以检索该bean的新实例。