diff --git a/spring-context/src/main/java/org/springframework/context/ApplicationContext.java b/spring-context/src/main/java/org/springframework/context/ApplicationContext.java index 44fe37f31a..85336bc43a 100644 --- a/spring-context/src/main/java/org/springframework/context/ApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/ApplicationContext.java @@ -63,6 +63,12 @@ public interface ApplicationContext extends EnvironmentCapable, ListableBeanFact */ String getId(); + /** + * Return a name for the deployed application that this context belongs to. + * @return a name for the deployed application, or the empty String by default + */ + String getApplicationName(); + /** * Return a friendly name for this context. * @return a display name for this context (never null) diff --git a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java index dead77434a..0f293027a3 100644 --- a/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java +++ b/spring-context/src/main/java/org/springframework/context/support/AbstractApplicationContext.java @@ -32,6 +32,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; + import org.springframework.beans.BeansException; import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.DisposableBean; @@ -242,14 +243,14 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader this.id = id; } - /** - * Return the unique id of this application context. - * @return the unique id of the context, or null if none - */ public String getId() { return this.id; } + public String getApplicationName() { + return ""; + } + /** * Set a friendly name for this context. * Typically done during initialization of concrete context implementations. @@ -283,7 +284,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader */ public ConfigurableEnvironment getEnvironment() { if (this.environment == null) { - this.environment = this.createEnvironment(); + this.environment = createEnvironment(); } return this.environment; } @@ -397,7 +398,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader if (parent != null) { Environment parentEnvironment = parent.getEnvironment(); if (parentEnvironment instanceof ConfigurableEnvironment) { - this.getEnvironment().merge((ConfigurableEnvironment)parentEnvironment); + getEnvironment().merge((ConfigurableEnvironment)parentEnvironment); } } } @@ -513,7 +514,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Validate that all properties marked as required are resolvable // see ConfigurablePropertyResolver#setRequiredProperties - this.getEnvironment().validateRequiredProperties(); + getEnvironment().validateRequiredProperties(); } /** @@ -549,7 +550,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Tell the internal bean factory to use the context's class loader etc. beanFactory.setBeanClassLoader(getClassLoader()); beanFactory.setBeanExpressionResolver(new StandardBeanExpressionResolver()); - beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, this.getEnvironment())); + beanFactory.addPropertyEditorRegistrar(new ResourceEditorRegistrar(this, getEnvironment())); // Configure the bean factory with context callbacks. beanFactory.addBeanPostProcessor(new ApplicationContextAwareProcessor(this)); @@ -940,6 +941,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader // Publish the final event. publishEvent(new ContextRefreshedEvent(this)); + + // Participate in LiveBeansView MBean, if active. + LiveBeansView.registerApplicationContext(this); } /** @@ -1033,6 +1037,8 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader logger.info("Closing " + this); } + LiveBeansView.unregisterApplicationContext(this); + try { // Publish shutdown event. publishEvent(new ContextClosedEvent(this)); diff --git a/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java new file mode 100644 index 0000000000..6a6291cb33 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/support/LiveBeansView.java @@ -0,0 +1,181 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.support; + +import java.lang.management.ManagementFactory; +import java.util.Collections; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.management.MBeanServer; +import javax.management.ObjectName; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ApplicationContextException; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.util.Assert; +import org.springframework.util.StringUtils; + +/** + * Adapter for live beans view exposure, building a snapshot of current beans + * and their dependencies from either a local ApplicationContext (with a + * local LiveBeansView bean definition) or all registered ApplicationContexts + * (driven by the "spring.liveBeansView.mbean" environment property). + * + *

Note: This feature is still in beta and primarily designed for use with + * SpringSource Tool Suite 3.1. + * + * @author Juergen Hoeller + * @since 3.2 + * @see #getSnapshotAsJson() + * @see org.springframework.web.context.support.LiveBeansViewServlet + */ +public class LiveBeansView implements LiveBeansViewMBean, ApplicationContextAware { + + public static final String MBEAN_DOMAIN_PROPERTY_NAME = "spring.liveBeansView.mbeanDomain"; + + public static final String MBEAN_APPLICATION_KEY = "application"; + + private static final Set applicationContexts = + new LinkedHashSet(); + + static void registerApplicationContext(ConfigurableApplicationContext applicationContext) { + String mbeanDomain = applicationContext.getEnvironment().getProperty(MBEAN_DOMAIN_PROPERTY_NAME); + if (mbeanDomain != null) { + synchronized (applicationContexts) { + if (applicationContexts.isEmpty()) { + try { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + server.registerMBean(new LiveBeansView(), + new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationContext.getApplicationName())); + } + catch (Exception ex) { + throw new ApplicationContextException("Failed to register LiveBeansView MBean", ex); + } + } + applicationContexts.add(applicationContext); + } + } + } + + static void unregisterApplicationContext(ConfigurableApplicationContext applicationContext) { + synchronized (applicationContexts) { + if (applicationContexts.remove(applicationContext) && applicationContexts.isEmpty()) { + try { + MBeanServer server = ManagementFactory.getPlatformMBeanServer(); + String mbeanDomain = applicationContext.getEnvironment().getProperty(MBEAN_DOMAIN_PROPERTY_NAME); + server.unregisterMBean(new ObjectName(mbeanDomain, MBEAN_APPLICATION_KEY, applicationContext.getApplicationName())); + } + catch (Exception ex) { + throw new ApplicationContextException("Failed to unregister LiveBeansView MBean", ex); + } + } + } + } + + + private ConfigurableApplicationContext applicationContext; + + public void setApplicationContext(ApplicationContext applicationContext) { + Assert.isTrue(applicationContext instanceof ConfigurableApplicationContext, + "ApplicationContext does not implement ConfigurableApplicationContext"); + this.applicationContext = (ConfigurableApplicationContext) applicationContext; + } + + + /** + * Generate a JSON snapshot of current beans and their dependencies, + * finding all active ApplicationContexts through {@link #findApplicationContexts()}, + * then delegating to {@link #generateJson(java.util.Set)}. + */ + public String getSnapshotAsJson() { + Set contexts; + if (this.applicationContext != null) { + contexts = Collections.singleton(this.applicationContext); + } + else { + contexts = findApplicationContexts(); + } + return generateJson(contexts); + } + + /** + * Actually generate a JSON snapshot of the beans in the given ApplicationContexts + * @param contexts the set of ApplicationContexts + * @return the JSON document + */ + protected String generateJson(Set contexts) { + StringBuilder result = new StringBuilder(); + for (ConfigurableApplicationContext context : contexts) { + result.append("{\n\"context\": \"").append(context.getId()).append("\"\n"); + if (context.getParent() != null) { + result.append("\"parent\": \"").append(context.getParent().getId()).append("\"\n"); + } + else { + result.append("\"parent\": null\n"); + } + ConfigurableListableBeanFactory bf = context.getBeanFactory(); + String[] beanNames = bf.getBeanDefinitionNames(); + for (String beanName : beanNames) { + BeanDefinition bd = bf.getBeanDefinition(beanName); + if (bd.getRole() != BeanDefinition.ROLE_INFRASTRUCTURE && + (!bd.isLazyInit() || bf.containsSingleton(beanName))) { + result.append("{\n\"bean\": \"").append(beanName).append("\"\n"); + String scope = bd.getScope(); + if (!StringUtils.hasText(scope)) { + scope = BeanDefinition.SCOPE_SINGLETON; + } + result.append("\"scope\": \"").append(scope).append("\"\n"); + Class beanType = bf.getType(beanName); + if (beanType != null) { + result.append("\"type\": \"").append(beanType.getName()).append("\"\n"); + } + else { + result.append("\"type\": null\n"); + } + result.append("\"resource\": \"").append(bd.getResourceDescription()).append("\"\n"); + result.append("\"dependencies\": ["); + String[] dependencies = bf.getDependenciesForBean(beanName); + if (dependencies.length > 0) { + result.append("\""); + } + result.append(StringUtils.arrayToDelimitedString(dependencies, "\", \"")); + if (dependencies.length > 0) { + result.append("\""); + } + result.append("]\n}\n"); + } + } + result.append("}"); + } + return result.toString(); + } + + /** + * Find all applicable ApplicationContexts for the current application. + *

Called if no specific ApplicationContext has been set for this LiveBeansView. + * @return the set of ApplicationContexts + */ + protected Set findApplicationContexts() { + synchronized (applicationContexts) { + return new LinkedHashSet(applicationContexts); + } + } + +} diff --git a/spring-context/src/main/java/org/springframework/context/support/LiveBeansViewMBean.java b/spring-context/src/main/java/org/springframework/context/support/LiveBeansViewMBean.java new file mode 100644 index 0000000000..4065a557d0 --- /dev/null +++ b/spring-context/src/main/java/org/springframework/context/support/LiveBeansViewMBean.java @@ -0,0 +1,32 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.context.support; + +/** + * MBean operation interface for the {@link LiveBeansView} feature. + * + * @author Juergen Hoeller + * @since 3.2 + */ +public interface LiveBeansViewMBean { + + /** + * Generate a JSON snapshot of current beans and their dependencies. + */ + String getSnapshotAsJson(); + +} diff --git a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java index de5e078ec7..7f099ac849 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/AbstractRefreshableWebApplicationContext.java @@ -109,7 +109,7 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR public void setServletConfig(ServletConfig servletConfig) { this.servletConfig = servletConfig; if (servletConfig != null && this.servletContext == null) { - this.setServletContext(servletConfig.getServletContext()); + setServletContext(servletConfig.getServletContext()); } } @@ -133,6 +133,21 @@ public abstract class AbstractRefreshableWebApplicationContext extends AbstractR return super.getConfigLocations(); } + @Override + public String getApplicationName() { + if (this.servletContext == null) { + return ""; + } + if (this.servletContext.getMajorVersion() == 2 && this.servletContext.getMinorVersion() < 5) { + String name = this.servletContext.getServletContextName(); + return (name != null ? name : ""); + } + else { + // Servlet 2.5 available + return this.servletContext.getContextPath(); + } + } + /** * Create and return a new {@link StandardServletEnvironment}. Subclasses may override * in order to configure the environment or specialize the environment type returned. diff --git a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java index 99f83dcc5d..2ddb0466a0 100644 --- a/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java +++ b/spring-web/src/main/java/org/springframework/web/context/support/GenericWebApplicationContext.java @@ -68,6 +68,7 @@ public class GenericWebApplicationContext extends GenericApplicationContext private ThemeSource themeSource; + /** * Create a new GenericWebApplicationContext. * @see #setServletContext @@ -123,6 +124,20 @@ public class GenericWebApplicationContext extends GenericApplicationContext return this.servletContext; } + @Override + public String getApplicationName() { + if (this.servletContext == null) { + return ""; + } + if (this.servletContext.getMajorVersion() == 2 && this.servletContext.getMinorVersion() < 5) { + String name = this.servletContext.getServletContextName(); + return (name != null ? name : ""); + } + else { + // Servlet 2.5 available + return this.servletContext.getContextPath(); + } + } /** * Create and return a new {@link StandardServletEnvironment}. diff --git a/spring-web/src/main/java/org/springframework/web/context/support/LiveBeansViewServlet.java b/spring-web/src/main/java/org/springframework/web/context/support/LiveBeansViewServlet.java new file mode 100644 index 0000000000..453926f541 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/support/LiveBeansViewServlet.java @@ -0,0 +1,58 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.context.support; + +import java.io.IOException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.springframework.context.support.LiveBeansView; + +/** + * Servlet variant of {@link LiveBeansView}'s MBean exposure. + * + *

Generates a JSON snapshot for current beans and their dependencies in + * all ApplicationContexts that live within the current web application. + * + * @author Juergen Hoeller + * @since 3.2 + * @see org.springframework.context.support.LiveBeansView#getSnapshotAsJson() + */ +public class LiveBeansViewServlet extends HttpServlet { + + private LiveBeansView liveBeansView; + + @Override + public void init() throws ServletException { + this.liveBeansView = buildLiveBeansView(); + } + + protected LiveBeansView buildLiveBeansView() { + return new ServletContextLiveBeansView(getServletContext()); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + String content = this.liveBeansView.getSnapshotAsJson(); + response.setContentType("application/json"); + response.setContentLength(content.length()); + response.getWriter().write(content); + } + +} diff --git a/spring-web/src/main/java/org/springframework/web/context/support/ServletContextLiveBeansView.java b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextLiveBeansView.java new file mode 100644 index 0000000000..df7a4537b0 --- /dev/null +++ b/spring-web/src/main/java/org/springframework/web/context/support/ServletContextLiveBeansView.java @@ -0,0 +1,62 @@ +/* + * Copyright 2002-2012 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.web.context.support; + +import java.util.Enumeration; +import java.util.LinkedHashSet; +import java.util.Set; +import javax.servlet.ServletContext; + +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.support.LiveBeansView; +import org.springframework.util.Assert; + +/** + * {@link LiveBeansView} subclass which looks for all ApplicationContexts + * in the web application, as exposed in ServletContext attributes. + * + * @author Juergen Hoeller + * @since 3.2 + */ +public class ServletContextLiveBeansView extends LiveBeansView { + + private final ServletContext servletContext; + + /** + * Create a new LiveBeansView for the given ServletContext. + * @param servletContext current ServletContext + */ + public ServletContextLiveBeansView(ServletContext servletContext) { + Assert.notNull(servletContext, "ServletContext must not be null"); + this.servletContext = servletContext; + } + + @Override + protected Set findApplicationContexts() { + Set contexts = new LinkedHashSet(); + Enumeration attrNames = this.servletContext.getAttributeNames(); + while (attrNames.hasMoreElements()) { + String attrName = attrNames.nextElement(); + Object attrValue = this.servletContext.getAttribute(attrName); + if (attrValue instanceof ConfigurableApplicationContext) { + contexts.add((ConfigurableApplicationContext) attrValue); + } + } + return contexts; + } + +} diff --git a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java index 56b04da88f..c61dd503ed 100644 --- a/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java +++ b/spring-webmvc-portlet/src/main/java/org/springframework/web/portlet/context/AbstractRefreshablePortletApplicationContext.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2009 the original author or authors. + * Copyright 2002-2012 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -143,6 +143,22 @@ public abstract class AbstractRefreshablePortletApplicationContext extends Abstr return super.getConfigLocations(); } + @Override + public String getApplicationName() { + if (this.portletContext == null) { + return ""; + } + String name = this.portletContext.getPortletContextName(); + return (name != null ? name : ""); + } + + /** + * Create and return a new {@link StandardPortletEnvironment}. + */ + @Override + protected ConfigurableEnvironment createEnvironment() { + return new StandardPortletEnvironment(); + } /** * Register request/session scopes, a {@link PortletContextAwareProcessor}, etc. @@ -160,14 +176,6 @@ public abstract class AbstractRefreshablePortletApplicationContext extends Abstr beanFactory, this.servletContext, this.portletContext, this.portletConfig); } - /** - * Create and return a new {@link StandardPortletEnvironment}. - */ - @Override - protected ConfigurableEnvironment createEnvironment() { - return new StandardPortletEnvironment(); - } - /** * This implementation supports file paths beneath the root of the PortletContext. * @see PortletContextResource @@ -190,4 +198,5 @@ public abstract class AbstractRefreshablePortletApplicationContext extends Abstr protected void customizeBeanFactory(DefaultListableBeanFactory beanFactory) { super.customizeBeanFactory(beanFactory); } + }