【问题标题】:Performance problems when using lots of AOP request scoped beans使用大量 AOP 请求范围 bean 时的性能问题
【发布时间】:2011-01-23 01:38:26
【问题描述】:

我正在使用 Spring 3 开发一个半大型应用程序,并且在一次将数百名用户投入其中时遇到了性能问题。我正在使用 Spring 的 AOP 代理使用几个请求范围的 bean,我可以看到,每次我在其中一个 bean 上调用任何方法时,都会调用 CGLIB 拦截器,然后调用 AbstractBeanFactory.getBean(),后者调用 add()现有 Spring bean 的同步集。由于这个 add() 是同步的,当有数千个调用都在等待添加到同一个列表时,它会有效地锁定服务器。

有没有办法使用请求范围的 bean 来解决这个问题?我在 Spring 文档中读到,如果 bean 实现任何接口(http://static.springsource.org/spring/docs/2.0.0/reference/aop.html#d0e9015),则不使用 CGLIB,但我的请求范围为 bean都实现了一个(实际上是同一个),它仍然在发生。而且我肯定需要将 bean 设置为请求范围,因为它们的某些字段是在应用程序的一个部分中针对特定请求计算的,然后我使用 SpEL 在同一请求期间在应用程序的不同部分获取它们的值。我想如果我将 bean 原型设为作用域,那么当我第二次使用 SpEL 获取它们时,我会有一个新对象。

这是一个说明我的问题的代码示例。请参阅最后两行以了解 cmets 描述我遇到问题的确切位置。

<!-- Spring config -->
<bean name="someBean" class="some.custom.class.SomeClass" scope="request">
    <property name="property1" value="value1"/>
    <property name="property2" value="value2"/>
    <aop:scoped-proxy/>
</bean>

<bean name="executingClass" class="some.other.custom.class.ExecutingClass" scope="singleton">
    <property name="myBean" ref="someBean" />
</bean>


public Interface SomeInterface {
    public String getProperty1();
    public void setProperty1(String property);
    public String getProperty2();
    public void setProperty2(String property);
}

public class SomeClass implements SomeInterface {
    private String property1;
    private String property2;

    public String getProperty1() { return propery1; }
    public void setProperty1(String property) { property1=property;}

    public String getProperty2() { return propery2; }
    public void setProperty2(String property) { property2=property;}
}


public class ExecutingClass {
    private SomeInterface myBean;

    public void execute() {
        String property = myBean.getProperty1(); // CGLIB interceptor is invoked here, registering myBean as a bean
        String otherProperty = myBean.getProperty2(); // CGLIB interceptor is invoked here too!  Seems like this is unnecessary. And it's killing my app.
    }
}

我的想法是以下之一:

  • 我可以在不代理对 bean 进行的每个方法调用的情况下发出 Spring Bean 请求吗?并且没有将每个方法都标记为“最终”?

或者...

  • 我能否覆盖 Spring 的 bean 工厂以实现 Bean 缓存,该缓存将在调用 AbstractBeanFactory.getBean() 之前检查 bean 是否被缓存?如果是这样,我在哪里配置 Spring 以使用我的自定义 bean 工厂?

【问题讨论】:

  • 1 次调用没问题,但 2 次调用会杀死您的应用?
  • 如果它在第一次引用 bean 时调用它,就可以了。如果它每次调用 bean 上的任何方法时都调用代理类,那会杀死我的应用程序。

标签: java spring aop cglib


【解决方案1】:

事实证明,Spring 实际上确实在请求属性中缓存了请求范围的 bean。如果您好奇,请查看 RequestScope 扩展的 AbstractRequestAttributesScope:

public Object get(String name, ObjectFactory objectFactory) {
    RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
    Object scopedObject = attributes.getAttribute(name, getScope());
    if (scopedObject == null) {
        scopedObject = objectFactory.getObject();
        attributes.setAttribute(name, scopedObject, getScope());
    }
    return scopedObject;
}

因此,虽然由于 aop 代理,每个 bean 方法调用都会调用 AbstractBeanFactory.getBean(),但如果在请求属性中尚未找到 bean,它只会导致 Spring 添加到该同步集。

避免对我的请求范围 bean 上的每个方法调用进行代理仍会降低复杂性,但有了这种缓存,对性能的影响将是最小的。我认为如果我想要大量请求范围的 bean 并且仍然一次服务大量请求,我将不得不忍受缓慢的性能。

【讨论】:

    【解决方案2】:

    有趣的问题。

    事实证明,Spring 的作用域代理不会缓存已解析的对象,因此每次访问作用域代理都会导致调用 getBean()

    作为一种解决方法,您可以创建一个穷人的缓存范围代理,如下所示(未经测试,目标 bean 应该是请求范围的,但没有&lt;aop:scoped-proxy /&gt;):

    public class MyScopedProxy implements SomeInterface, BeanFactoryAware {
    
        private BeanFactory factory;
        private Scope scope;
        private String targetBeanName;
        private ThreadLocal<SomeInterface> cache = new ThreadLocal<SomeInterface>();
    
        private SomeInterface resolve() {
            SomeInterface v = cache.get();
            if (v == null) {
                v = (SomeInterface) factory.getBean(targetBeanName);
                cache.set(v);
                scope.registerDestructionCallback(targetBeanName, new Runnable() {
                    public void run() {
                        cache.remove();
                    }
                });
            }
            return v;
        }
    
        public void setBeanFactory(BeanFactory factory) {
            this.factory = factory;
            this.scope = ((ConfigurableBeanFactory) factory).getRegisteredScope("request");
        }
    
        public String getProperty() {
            return resolve().getProperty();
        }
    
        ...
    }
    

    关于代理机制:与其他 AOP 代理不同,作用域代理默认为 CGLIB,您可以通过设置 &lt;aop:scoped-proxy proxy-target-class = "false" /&gt; 覆盖它,但在这种情况下无济于事。

    【讨论】:

    • 如果我在不止一个接口的多个地方遇到这个问题,我需要为每个接口创建一个新的代理吗?是否可以覆盖 AbstractBeanFactory.getBean() 并在那里实现 bean 缓存?如果是这样,我不知道在 Spring 配置中的哪个位置指定我的自定义 bean 工厂。
    • 看我的回答;事实证明,请求范围的 bean 由请求属性中的 RequestScope 缓存。谁知道!
    【解决方案3】:

    一种选择是用lookup-method 替换注入作用域代理:

    public abstract class ExecutingClass {
        protected abstract SomeInterface makeMyBean();
    
        public void execute() {
            SomeInterface myBean = makeMyBean();
            String property = myBean.getProperty1(); 
            String otherProperty = myBean.getProperty2(); 
        }
    }
    

    这将确保每个请求只向 Spring 询问一次 bean,消除范围代理引起的任何开销,并缩短堆栈跟踪。它不太灵活(因为您不能随意共享对请求范围 bean 的引用,并且让范围代理使用正确的 bean)但您可能不需要这种灵活性。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-05-26
      • 2012-01-20
      • 2010-12-29
      • 2013-01-21
      • 2011-11-11
      • 2011-09-02
      • 2014-12-02
      • 2014-11-26
      相关资源
      最近更新 更多