【问题标题】:Ensure spring bean loaded from non spring context确保从非弹簧上下文加载弹簧 bean
【发布时间】:2018-05-06 04:39:57
【问题描述】:

我在(jersey 2.6 类和)servlet 旁边有 spring 应用程序。

我需要从 jersey/非 spring 上下文中获取 Spring bean,

类似的question 建议在上下文的静态包装器中获取上下文

public static ApplicationContext getContext() {
    return context;
}

如何确定上下文是否已加载或不为空?

如果我不能,我应该如何等待/检查,直到它加载弹簧上下文?

如果从 jersey 上下文调用或从 调用 bean 一个简单的 HttpServlet 代码

编辑

Jersey 使用 jersey-spring3 依赖 jar 可以正常工作,所以 我的问题只是关于 Spring 控制之外的 Servlet

编辑 2

应用程序加载的 spring 不同于 @entpnerd 建议的 article

它注册一个实现WebApplicationInitializer的Servlet

public class MyWebAppInitializer implements WebApplicationInitializer {

但在 web.xml 中也配置了DispatcherServlet

DispatcherServlet 怎么只能在 Spring 加载后加载?

因为我们在它的 init 方法中添加了自动装配功能:

WebApplicationContextUtils.getRequiredWebApplicationContext(config.getServletContext())
                    .getAutowireCapableBeanFactory().autowireBean(this);

在服务请求之前添加超时是最喜欢的解决方案,还是对类加载进行调整可以解决这个问题?

编辑 3

我找到了answersanswers 的注入,但没有发现为什么 Spring 在 Servlet 之前加载。

【问题讨论】:

  • 你确定不能让 Spring 在你的情况下注入 Jersey 吗?这是一篇很好的文章,解释了如何在 Spring Boot 中使用 Jersey。它很旧,但仍然应该是相关的。 geowarin.github.io/…
  • 感谢您的建议,我使用 jersey 2.6 而不是 3,我更喜欢包括从 HTTPServlet 获取 bean 的解决方案
  • Spring 的方式是让 Spring 上下文初始化自己,并且只有在启动其他所有内容之后。如果您想知道是否在需要时加载 Spring 应用程序上下文,您应该查看您的启动过程。
  • 在tomcat 8下,我的Servlet是在web.xml中定义的,而Spring是AnnotationConfigWebApplicationContext

标签: java spring servlets classloader autowired


【解决方案1】:

这个想法很简单,尽管实际实现可能会根据 Spring boot 和 Jersery 初始化的确切方式而有所不同。

一个想法:

Spring boot,作为一个纯粹的运行时框架,就是正确加载应用程序上下文(从问题的角度来看)。

因此,归根结底,当它被加载时,内存中的某处有一个应用程序上下文,并且可以从该应用程序上下文访问 bean。

现在,既然你说 Jersey 不是 spring/spring-boot 驱动的,那么这个应用程序上下文必须可以从 Jersey 的某种静态全局变量中访问,这很丑但应该可以工作。

所以这个想法有两个步骤:

  1. 将应用程序上下文引用放置到可从 Jersey 访问的某个静态持有者。
  2. 在 Jersey 组件的一些基础设施级代码中读取此值。

一种可能的实现方式

技术上第一步可以通过实现某种Spring Boot侦听器来完成,该侦听器将应用程序上下文存储在某种单例中:

enum ApplicationContextHolder {
   INSTANCE;
    private ApplicationContext ctx;
    void setApplicationContext(ApplicationContext ctx) {
        this.ctx = ctx;
    }

    ApplicationContext getCtx() {
        return this.ctx;
    }

}


// and a listener (spring boot provides many ways to register one, but the 
// implementation should be something like this):
// The main point is that its managed by spring boot, and hence and access to 
// the application context
class StartupListener implements ApplicationListener<ContextRefreshedEvent> {
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {           
             ApplicationContextHolder
             .INSTANCE
             .setApplicationContext(event.getApplicationContext());
    }
}

现在第二步是:

class MyJerseyOrWhateverComponentThatWantsToAccessApplicationContext {

    public void foo() {
       ApplicationContext ctx = ApplicationContextHolder.INSTANCE.getCtx();
       ... 
       ctx.getBean(...);
    }
}

【讨论】:

  • 在收到对 servlet 的请求之前,如何确定步骤 1 已完成?
  • 一般情况下不能,所以holder的实现可以根据你的实际需要而有所不同。您是否希望 Jersey Servlet“等待”到应用程序上下文启动?如果应用程序上下文不可用,您想回退到某些初始行为吗?您可以通过稍微修改该单例的代码来实现这两种方式。很难说更多,因为“进程”(球衣初始化)和 Spring Boot 初始化完全独立工作
  • 请注意我的问题(编辑)是针对独立 servlet 的。我需要等待吗?是否有一个选项可以确保在 servlet 接收请求之前加载 spring 上下文,这是一个有效的解决方案吗?
  • 这实际上取决于您如何启动 Spring Boot 应用程序。 WebApp 有自己的生命周期(正如我从你的问题中看到的那样)泽西岛绑定到这个生命周期,所以应该有某种监听器。现在的问题是 Spring boot 是如何实际加载的。如果它也在侦听器中,那么您可以将它们组合起来,以便 Spring boot 将首先加载(它同步加载),然后才执行球衣过滤器。这样可以保证在 jersey servlet 启动时,应用程序上下文已经可用
【解决方案2】:

因此,一个可行的解决方案可能分两个阶段进行:

  1. Spring bean 获取 ApplicationContext 实例并将其发送到 Spring 上下文之外的静态单例。
  2. 您的独立 servlet 从静态单例中获取 ApplicationContext 实例并验证是否已加载正确的 bean。

以下面的代码为例:

SpringMetaBean.java

// @Component so that it's part of the Spring context
// Implement ApplicationContextAware so that the ApplicationContext will be loaded
// correctly
@Component
public class SpringMetaBean implements ApplicationContextAware {
  private ApplicationContext appCtx;
  public setApplicationContext(ApplicationContext appCtx) {
    this.appCtx = appCtx;
  }

  // @PostConstruct so that when loaded into the Spring context, this method will
  // automatically execute and notify ApplicationContextHolder with a reference to
  // the ApplicationContext
  @PostConstruct
  public void setup() {
    ApplicationContextHolder.set(this.appCtx);
  }
}

ApplicationContextHolder.java

public class ApplicationContextHolder {
  // ensure the reference is thread-safe because Spring and standalone Servlet will
  // probably be running on different threads.
  private final AtomicReference<ApplicationContext> appCtxContainer = new AtomicReference<>();

  public void set(ApplicationContext appCtx) {
    this.appCtxContainer.set(appCtx);
  }

  public ApplicationContext get() {
    return this.appCtxContainer.get();
  }
}

MyStandaloneServlet.java

public class MyStandaloneServlet {
  // my request handler method
  public void getResponse(HttpServletRequest rq) {
    ApplicationContext springAppCtx = ApplicationContextHolder.get();
    // if not null, we know that Spring has been loaded and we can dig into the
    // application context.
  }
}

【讨论】:

  • 如何确保ApplicationContextHolderMyStandaloneServlet 第一次请求之前加载?
  • 由于ApplicationContextHolder 在 Spring 上下文之外,因此应该在第一个 set()get() 调用发生时加载它,因为 Java 延迟加载这些类。但是,第一次调用 set() 方法应该在第一次请求之前发生 - 当 Spring 根据您的 web.xml 配置加载时,SpringMetaBean 调用 set() 方法作为加载 Spring 和 Web 容器的一部分.
  • 如何/为什么在 web.xml 中的 Servlet 之前加载 Spring?
  • web.xml 文件通常使用调度程序 servlet。有关示例,请参见 baeldung.com/spring-xml-vs-java-config
  • 我添加了有关文章的更多信息
猜你喜欢
  • 2020-01-10
  • 2015-10-22
  • 1970-01-01
  • 2015-04-05
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 1970-01-01
  • 2011-08-06
相关资源
最近更新 更多