Getting Access to Spring with Embedded Tomcat 6

风格不统一 提交于 2019-12-08 05:25:17

问题


I want to test the REST services that I deploy on a tomcat. To do this with JUnit I embedded Tomcat 6 which works quite fine.

Now, I need access to the Spring context as I need to access DAO's and other beans.

Has anyone a clue how this can be achieved? I tried with the RootContext, but could not find anything.

Sincerely, Erik

Edit: I know have found a solution, but its a bad hack though:

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;
    }

}

Now, add the ContextLoaderListener to the web.xml instead of the spring ContextLoaderListener. The custom ContextLoaderListener has exactly the same behaviour like the spring ContextLoaderListener, but has another method getContext().

So, if you have running a Webserver, you can access the Spring Context doing the following:

Context rootContext = this.tomcat.getRootContext(); System.out.println("context");

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

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

回答1:


Sure, Spring has a framework for integration testing.

If you want to customize, you can define your own base class for integration tests with a @RunWith(SpringJUnit4ClassRunner.class) class-level annotation (or write your own subclass of SpringJUnit4ClassRunner if you want to put in custom annotation handling) and a @ContextConfiguration class-level annotation listing out the context configs you need for your tests. Then write tests that inject your beans exactly the way your application code does.



来源:https://stackoverflow.com/questions/5232049/getting-access-to-spring-with-embedded-tomcat-6

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!