2011-03-08 109 views
1

我想测试我在tomcat上部署的REST服务。为了做到这一点与JUnit我嵌入了Tomcat 6,它工作得很好。使用嵌入式Tomcat访问Spring 6

现在,我需要访问Spring上下文,因为我需要访问DAO和其他bean。

有没有人知道如何实现这一点?我尝试了RootContext,但找不到任何东西。

真诚, 埃里克

编辑:我知道已经找到了解决方案,但它是一个糟糕的黑客,但:

import java.io.IOException; 
import java.util.Enumeration; 
import java.util.Map; 
import java.util.Properties; 
import java.util.concurrent.ConcurrentHashMap; 

import javax.servlet.ServletContext; 
import javax.servlet.ServletContextEvent; 
import javax.servlet.ServletContextListener; 

import org.apache.commons.logging.Log; 
import org.apache.commons.logging.LogFactory; 
import org.springframework.beans.BeanUtils; 
import org.springframework.beans.factory.DisposableBean; 
import org.springframework.beans.factory.access.BeanFactoryLocator; 
import org.springframework.beans.factory.access.BeanFactoryReference; 
import org.springframework.context.ApplicationContext; 
import org.springframework.context.ApplicationContextException; 
import org.springframework.context.access.ContextSingletonBeanFactoryLocator; 
import org.springframework.core.io.ClassPathResource; 
import org.springframework.core.io.support.PropertiesLoaderUtils; 
import org.springframework.util.ClassUtils; 
import org.springframework.util.ObjectUtils; 
import org.springframework.web.context.ConfigurableWebApplicationContext; 
import org.springframework.web.context.ContextLoader; 
import org.springframework.web.context.WebApplicationContext; 

public class ContextLoaderListener extends ContextLoader implements ServletContextListener { 

    private ContextLoader contextLoader; 

    /** 
    * Config param for the root WebApplicationContext implementation class to 
    * use: "<code>contextClass</code>" 
    */ 
    public static final String CONTEXT_CLASS_PARAM = "contextClass"; 

    /** 
    * Name of servlet context parameter (i.e., "<code>contextConfigLocation</code>") 
    * that can specify the config location for the root context, falling back 
    * to the implementation's default otherwise. 
    * @see org.springframework.web.context.support.XmlWebApplicationContext#DEFAULT_CONFIG_LOCATION 
    */ 
    public static final String CONFIG_LOCATION_PARAM = "contextConfigLocation"; 

    /** 
    * Optional servlet context parameter (i.e., "<code>locatorFactorySelector</code>") 
    * used only when obtaining a parent context using the default implementation 
    * of {@link #loadParentContext(ServletContext servletContext)}. 
    * Specifies the 'selector' used in the 
    * {@link ContextSingletonBeanFactoryLocator#getInstance(String selector)} 
    * method call, which is used to obtain the BeanFactoryLocator instance from 
    * which the parent context is obtained. 
    * <p>The default is <code>classpath*:beanRefContext.xml</code>, 
    * matching the default applied for the 
    * {@link ContextSingletonBeanFactoryLocator#getInstance()} method. 
    * Supplying the "parentContextKey" parameter is sufficient in this case. 
    */ 
    public static final String LOCATOR_FACTORY_SELECTOR_PARAM = "locatorFactorySelector"; 

    /** 
    * Optional servlet context parameter (i.e., "<code>parentContextKey</code>") 
    * used only when obtaining a parent context using the default implementation 
    * of {@link #loadParentContext(ServletContext servletContext)}. 
    * Specifies the 'factoryKey' used in the 
    * {@link BeanFactoryLocator#useBeanFactory(String factoryKey)} method call, 
    * obtaining the parent application context from the BeanFactoryLocator instance. 
    * <p>Supplying this "parentContextKey" parameter is sufficient when relying 
    * on the default <code>classpath*:beanRefContext.xml</code> selector for 
    * candidate factory references. 
    */ 
    public static final String LOCATOR_FACTORY_KEY_PARAM = "parentContextKey"; 

    /** 
    * Name of the class path resource (relative to the ContextLoader class) 
    * that defines ContextLoader's default strategy names. 
    */ 
    private static final String DEFAULT_STRATEGIES_PATH = "ContextLoader.properties"; 


    private static final Properties defaultStrategies; 

    static { 
     // Load default strategy implementations from properties file. 
     // This is currently strictly internal and not meant to be customized 
     // by application developers. 
     try { 
      ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, ContextLoader.class); 
      defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); 
     } 
     catch (IOException ex) { 
      throw new IllegalStateException("Could not load 'ContextLoader.properties': " + ex.getMessage()); 
     } 
    } 


    /** 
    * Map from (thread context) ClassLoader to corresponding 'current' WebApplicationContext. 
    */ 
    private static final Map<ClassLoader, WebApplicationContext> currentContextPerThread = 
      new ConcurrentHashMap<ClassLoader, WebApplicationContext>(1); 

    /** 
    * The 'current' WebApplicationContext, if the ContextLoader class is 
    * deployed in the web app ClassLoader itself. 
    */ 
    private static volatile WebApplicationContext currentContext; 

    /** 
    * The root WebApplicationContext instance that this loader manages. 
    */ 
    private WebApplicationContext context; 

    /** 
    * Holds BeanFactoryReference when loading parent factory via 
    * ContextSingletonBeanFactoryLocator. 
    */ 
    private BeanFactoryReference parentContextRef; 


    /** 
    * Initialize Spring's web application context for the given servlet context, 
    * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and 
    * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params. 
    * @param servletContext current servlet context 
    * @return the new WebApplicationContext 
    * @see #CONTEXT_CLASS_PARAM 
    * @see #CONFIG_LOCATION_PARAM 
    */ 
    public WebApplicationContext initWebApplicationContext(ServletContext servletContext) { 
     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) { 
      throw new IllegalStateException(
        "Cannot initialize context because there is already a root application context present - " + 
        "check whether you have multiple ContextLoader* definitions in your web.xml!"); 
     } 

     Log logger = LogFactory.getLog(ContextLoader.class); 
     servletContext.log("Initializing Spring root WebApplicationContext"); 
     if (logger.isInfoEnabled()) { 
      logger.info("Root WebApplicationContext: initialization started"); 
     } 
     long startTime = System.currentTimeMillis(); 

     try { 
      // Determine parent for root web application context, if any. 
      ApplicationContext parent = loadParentContext(servletContext); 

      // Store context in local instance variable, to guarantee that 
      // it is available on ServletContext shutdown. 
      this.context = createWebApplicationContext(servletContext, parent); 
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context); 

      ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 
      if (ccl == ContextLoader.class.getClassLoader()) { 
       currentContext = this.context; 
      } 
      else if (ccl != null) { 
       currentContextPerThread.put(ccl, this.context); 
      } 

      if (logger.isDebugEnabled()) { 
       logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + 
         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]"); 
      } 
      if (logger.isInfoEnabled()) { 
       long elapsedTime = System.currentTimeMillis() - startTime; 
       logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms"); 
      } 

      return this.context; 
     } 
     catch (RuntimeException ex) { 
      logger.error("Context initialization failed", ex); 
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex); 
      throw ex; 
     } 
     catch (Error err) { 
      logger.error("Context initialization failed", err); 
      servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err); 
      throw err; 
     } 
    } 

    /** 
    * Instantiate the root WebApplicationContext for this loader, either the 
    * default context class or a custom context class if specified. 
    * <p>This implementation expects custom contexts to implement the 
    * {@link ConfigurableWebApplicationContext} interface. 
    * Can be overridden in subclasses. 
    * <p>In addition, {@link #customizeContext} gets called prior to refreshing the 
    * context, allowing subclasses to perform custom modifications to the context. 
    * @param sc current servlet context 
    * @param parent the parent ApplicationContext to use, or <code>null</code> if none 
    * @return the root WebApplicationContext 
    * @see ConfigurableWebApplicationContext 
    */ 
    protected WebApplicationContext createWebApplicationContext(ServletContext sc, ApplicationContext parent) { 
     Class<?> contextClass = determineContextClass(sc); 
     if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) { 
      throw new ApplicationContextException("Custom context class [" + contextClass.getName() + 
        "] is not of type [" + ConfigurableWebApplicationContext.class.getName() + "]"); 
     } 
     ConfigurableWebApplicationContext wac = 
       (ConfigurableWebApplicationContext) BeanUtils.instantiateClass(contextClass); 

     // Assign the best possible id value. 
     if (sc.getMajorVersion() == 2 && sc.getMinorVersion() < 5) { 
      // Servlet <= 2.4: resort to name specified in web.xml, if any. 
      String servletContextName = sc.getServletContextName(); 
      wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 
        ObjectUtils.getDisplayString(servletContextName)); 
     } 
     else { 
      // Servlet 2.5's getContextPath available! 
      try { 
       String contextPath = (String) ServletContext.class.getMethod("getContextPath").invoke(sc); 
       wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + 
         ObjectUtils.getDisplayString(contextPath)); 
      } 
      catch (Exception ex) { 
       throw new IllegalStateException("Failed to invoke Servlet 2.5 getContextPath method", ex); 
      } 
     } 

     wac.setParent(parent); 
     wac.setServletContext(sc); 
     wac.setConfigLocation(sc.getInitParameter(CONFIG_LOCATION_PARAM)); 
     customizeContext(sc, wac); 
     wac.refresh(); 
     return wac; 
    } 

    /** 
    * Return the WebApplicationContext implementation class to use, either the 
    * default XmlWebApplicationContext or a custom context class if specified. 
    * @param servletContext current servlet context 
    * @return the WebApplicationContext implementation class to use 
    * @see #CONTEXT_CLASS_PARAM 
    * @see org.springframework.web.context.support.XmlWebApplicationContext 
    */ 
    protected Class<?> determineContextClass(ServletContext servletContext) { 
     String contextClassName = servletContext.getInitParameter(CONTEXT_CLASS_PARAM); 
     if (contextClassName != null) { 
      try { 
       return ClassUtils.forName(contextClassName, ClassUtils.getDefaultClassLoader()); 
      } 
      catch (ClassNotFoundException ex) { 
       throw new ApplicationContextException(
         "Failed to load custom context class [" + contextClassName + "]", ex); 
      } 
     } 
     else { 
      contextClassName = defaultStrategies.getProperty(WebApplicationContext.class.getName()); 
      try { 
       return ClassUtils.forName(contextClassName, ContextLoader.class.getClassLoader()); 
      } 
      catch (ClassNotFoundException ex) { 
       throw new ApplicationContextException(
         "Failed to load default context class [" + contextClassName + "]", ex); 
      } 
     } 
    } 

    /** 
    * Customize the {@link ConfigurableWebApplicationContext} created by this 
    * ContextLoader after config locations have been supplied to the context 
    * but before the context is <em>refreshed</em>. 
    * <p>The default implementation is empty but can be overridden in subclasses 
    * to customize the application context. 
    * @param servletContext the current servlet context 
    * @param applicationContext the newly created application context 
    * @see #createWebApplicationContext(ServletContext, ApplicationContext) 
    */ 
    protected void customizeContext(
      ServletContext servletContext, ConfigurableWebApplicationContext applicationContext) { 
    } 

    /** 
    * Template method with default implementation (which may be overridden by a 
    * subclass), to load or obtain an ApplicationContext instance which will be 
    * used as the parent context of the root WebApplicationContext. If the 
    * return value from the method is null, no parent context is set. 
    * <p>The main reason to load a parent context here is to allow multiple root 
    * web application contexts to all be children of a shared EAR context, or 
    * alternately to also share the same parent context that is visible to 
    * EJBs. For pure web applications, there is usually no need to worry about 
    * having a parent context to the root web application context. 
    * <p>The default implementation uses 
    * {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}, 
    * configured via {@link #LOCATOR_FACTORY_SELECTOR_PARAM} and 
    * {@link #LOCATOR_FACTORY_KEY_PARAM}, to load a parent context 
    * which will be shared by all other users of ContextsingletonBeanFactoryLocator 
    * which also use the same configuration parameters. 
    * @param servletContext current servlet context 
    * @return the parent application context, or <code>null</code> if none 
    * @see org.springframework.context.access.ContextSingletonBeanFactoryLocator 
    */ 
    protected ApplicationContext loadParentContext(ServletContext servletContext) { 
     ApplicationContext parentContext = null; 
     String locatorFactorySelector = servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM); 
     String parentContextKey = servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM); 

     if (parentContextKey != null) { 
      // locatorFactorySelector may be null, indicating the default "classpath*:beanRefContext.xml" 
      BeanFactoryLocator locator = ContextSingletonBeanFactoryLocator.getInstance(locatorFactorySelector); 
      Log logger = LogFactory.getLog(ContextLoader.class); 
      if (logger.isDebugEnabled()) { 
       logger.debug("Getting parent context definition: using parent context key of '" + 
         parentContextKey + "' with BeanFactoryLocator"); 
      } 
      this.parentContextRef = locator.useBeanFactory(parentContextKey); 
      parentContext = (ApplicationContext) this.parentContextRef.getFactory(); 
     } 

     return parentContext; 
    } 

    /** 
    * Close Spring's web application context for the given servlet context. If 
    * the default {@link #loadParentContext(ServletContext)} implementation, 
    * which uses ContextSingletonBeanFactoryLocator, has loaded any shared 
    * parent context, release one reference to that shared parent context. 
    * <p>If overriding {@link #loadParentContext(ServletContext)}, you may have 
    * to override this method as well. 
    * @param servletContext the ServletContext that the WebApplicationContext runs in 
    */ 
    public void closeWebApplicationContext(ServletContext servletContext) { 
     servletContext.log("Closing Spring root WebApplicationContext"); 
     try { 
      if (this.context instanceof ConfigurableWebApplicationContext) { 
       ((ConfigurableWebApplicationContext) this.context).close(); 
      } 
     } 
     finally { 
      ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 
      if (ccl == ContextLoader.class.getClassLoader()) { 
       currentContext = null; 
      } 
      else if (ccl != null) { 
       currentContextPerThread.remove(ccl); 
      } 
      servletContext.removeAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE); 
      if (this.parentContextRef != null) { 
       this.parentContextRef.release(); 
      } 
     } 
    } 


    /** 
    * Obtain the Spring root web application context for the current thread 
    * (i.e. for the current thread's context ClassLoader, which needs to be 
    * the web application's ClassLoader). 
    * @return the current root web application context, or <code>null</code> 
    * if none found 
    * @see org.springframework.web.context.support.SpringBeanAutowiringSupport 
    */ 
    public static WebApplicationContext getCurrentWebApplicationContext() { 
     ClassLoader ccl = Thread.currentThread().getContextClassLoader(); 
     if (ccl != null) { 
      WebApplicationContext ccpt = currentContextPerThread.get(ccl); 
      if (ccpt != null) { 
       return ccpt; 
      } 
     } 
     return currentContext; 
    } 


    /** 
    * Initialize the root web application context. 
    */ 
    public void contextInitialized(ServletContextEvent event) { 
     this.contextLoader = createContextLoader(); 
     if (this.contextLoader == null) { 
      this.contextLoader = this; 
     } 
     this.contextLoader.initWebApplicationContext(event.getServletContext()); 
    } 

    /** 
    * Create the ContextLoader to use. Can be overridden in subclasses. 
    * @return the new ContextLoader 
    * @deprecated in favor of simply subclassing ContextLoaderListener itself 
    * (which extends ContextLoader, as of Spring 3.0) 
    */ 
    @Deprecated 
    protected ContextLoader createContextLoader() { 
     return null; 
    } 

    /** 
    * Return the ContextLoader used by this listener. 
    * @return the current ContextLoader 
    * @deprecated in favor of simply subclassing ContextLoaderListener itself 
    * (which extends ContextLoader, as of Spring 3.0) 
    */ 
    @Deprecated 
    public ContextLoader getContextLoader() { 
     return this.contextLoader; 
    } 


    /** 
    * Close the root web application context. 
    */ 
    public void contextDestroyed(ServletContextEvent event) { 
     if (this.contextLoader != null) { 
      this.contextLoader.closeWebApplicationContext(event.getServletContext()); 
     } 
     cleanupAttributes(event.getServletContext()); 
    } 

    /** 
    * Find all ServletContext attributes which implement {@link DisposableBean} 
    * and destroy them, removing all affected ServletContext attributes eventually. 
    * @param sc the ServletContext to check 
    */ 
    static void cleanupAttributes(ServletContext sc) { 
     Enumeration attrNames = sc.getAttributeNames(); 
     while (attrNames.hasMoreElements()) { 
      String attrName = (String) attrNames.nextElement(); 
      if (attrName.startsWith("org.springframework.")) { 
       Object attrValue = sc.getAttribute(attrName); 
       if (attrValue instanceof DisposableBean) { 
        try { 
         ((DisposableBean) attrValue).destroy(); 
        } 
        catch (Throwable ex) { 
         System.out.println("Couldn't invoke destroy method of attribute with name '" + attrName + "'"); 
        } 
       } 
      } 
     } 
    } 

    public WebApplicationContext getContext() { 
     return context; 
    } 

} 

现在,添加的ContextLoaderListener在web.xml中,而不是spring的ContextLoaderListener。自定义ContextLoaderListener具有与Spring ContextLoaderListener完全相同的行为,但具有另一个方法getContext()。

所以,如果你运行的Web服务器,您可以访问Spring上下文执行以下操作:

语境rootContext = this.tomcat.getRootContext(); System.out.println(“context”);

ContextLoaderListener ctxListener = (ContextLoaderListener) rootContext.getApplicationLifecycleListeners()[0]; 

    XmlWebApplicationContext ctx = (XmlWebApplicationContext)ctxListener .getContext(); 

回答

0

当然,Spring有一个framework集成测试。

如果你想定制,你可以定义自己的基类,用@RunWith(SpringJUnit4ClassRunner.class)类级别的注解集成测试(或者,如果你希望把定制注释处理编写自己的SpringJUnit4ClassRunner子类)和@ContextConfiguration类 - 级别注释列出了测试所需的上下文配置。然后编写测试,以完全按应用程序代码的方式注入bean。

+0

感谢您的回答。我知道这些注释并经常使用它们。但是,我想要的是测试我的Http REST服务是否正常工作。为此,我需要启动Tomcat6 ... – Erik 2011-03-08 13:19:49

+0

@Erik你可以公开一些额外的web服务,它们可以访问你需要的任何数据来摆脱Spring上下文吗? – dfichter 2011-03-08 14:02:32

+0

是的,我可以做到,但我认为它的开销更大。我知道找到了一个可行的解决方案,尽管它很肮脏。我在上面张贴它。 – Erik 2011-03-08 14:49:42

相关问题