如何使用构造函数注入将scoped-proxy bean注入到单例bean中

时间:2016-10-06 06:07:45

标签: java spring spring-mvc

我正在使用Spring MVC开发一个Web应用程序,并希望有一个请求作用域Date bean,它指示每个请求何时发生。为了定义这样的Date bean,我在应用程序上下文xml中编写了以下bean定义。

<bean id="now"
      class="java.util.Date"
      scope="request">
    <aop:scoped-proxy/>
</bean>

使用字段注入将此bean注入单个bean中可以正常工作。

public class ASingletonBean {
    @Autowired
    private Date now;
    ...
}

但我不想使用野外注射,因为它不推荐。我的IDE建议使用构造函数注入。

public class ASingletonBean{
    private final Date now;

    @Autowired
    public ASingletonBean(Date now) {
        this.now = now;
    }
}

现在,上面的代码在应用程序启动时抛出以下异常。

org.springframework.beans.factory.BeanCreationException:
Error creating bean with name 'scopedTarget.java.util.Date#0':
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.

如何避免此错误?

2 个答案:

答案 0 :(得分:0)

请不要那样做。在每个请求上创建一个新的bean(你不需要)是一个很容易避免的开销。而是创建一个实现java.util.function.Supplier的类+ bean,例如:

@Component
class DateTimeProvider implements java.util.function.Supplier<Date> {
...
}

然后将此java.util.function.Supplier<Date>注入您的`ASingletonBean&#39;。这样,您就可以在处理请求时获得当前日期/时间。

还有一些补充说明:

  • 使用JodaTime或JDK8 JavaTime API代替java.util.Date,
  • 如果您不能使用JDK8(JDK8中添加了java.util.function.Supplier),那么您可以创建自己的界面或使用Guava提供的界面,
  • 如果您需要一个非常精确的接收请求时间,那么您应该考虑创建某种时间戳&#34;过滤,最有可能通过扩展org.springframework.web.filter.OncePerRequestFilter类。

已编辑 - 回答评论中的问题:

  • &#34;为什么最好使用请求范围的bean&#34; - 始终必须将bean注入您将创建的任何业务组件。有一点请求时间戳&#39;似乎有点多。

  • 您从外部世界收到的请求对象应该被转换为某种域请求&#39; (包含时间戳),然后仅在其域形式内部处理。 (阅读更多内容:六角架构a.k.a端口和适配器,域驱动程序设计)。为什么这样?因为人们很容易想象现在只能通过HTTP请求进入系统的请求可以以JMS消息或批量导入操作的形式进入系统 - 那么你只需要为系统提供一个新的适配器和整个核心领域内的逻辑不会改变。

  • 如果您正在使用Spring Boot,那么创建一个扩展org.springframework.web.filter.OncePerRequestFilter#OncePerRequestFilter的bean并在您需要实现的唯一方法中实现逻辑就足够了。 Spring会自动使用你的过滤器。

答案 1 :(得分:0)

虽然Rafal G的回答很有意义并且解释了良好做法,但我也找到了一种解决问题的方法。

解决方法是不是注入Date bean而是注入“provider”bean。首先,将提供者bean定义为aop:scoped-proxy bean。

<bean id="currentLocalDateTimeProvider"
      class="com.example.CurrentLocalDateTimeProvider"
      init-method="init"
      scope="request">
    <aop:scoped-proxy/>
</bean>

CurrentLocalDateTimeProvider的定义如下:

import java.time.LocalDateTime;

public class CurrentLocalDateTimeProvider {
    private LocalDateTime now;

    public void init() {
        now = LocalDateTime.now();
    }

    public LocalDateTime now() {
        return now;
    }
}

并将其注入单个豆类。

public class ASingletonBean {
    @Autowired
    private final CurrentLocalDateTimeProvider provider;
}

然后调用它的now方法来获取请求时间戳。

希望这有助于某人:)