Browse Source
LiveBeansView includes MBean exposure as well as Servlet exposure, with JSON as the initial output format. In order to identify an MBean per application, a new "getApplicationName()" method got introduced on the ApplicationContext interface, returning the Servlet container context path in case of a web application and defaulting to the empty String. MBean exposure can be driven by the "spring.liveBeansView.mbeanDomain" property, e.g. specifying "liveBeansView" as its value, leading to "liveBeansView:application=" or "liveBeansView:application=/myapp" style names for the per-application MBean. Issue: SPR-9662pull/148/merge
9 changed files with 402 additions and 18 deletions
@ -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). |
||||||
|
* |
||||||
|
* <p>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<ConfigurableApplicationContext> applicationContexts = |
||||||
|
new LinkedHashSet<ConfigurableApplicationContext>(); |
||||||
|
|
||||||
|
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<ConfigurableApplicationContext> 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<ConfigurableApplicationContext> 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. |
||||||
|
* <p>Called if no specific ApplicationContext has been set for this LiveBeansView. |
||||||
|
* @return the set of ApplicationContexts |
||||||
|
*/ |
||||||
|
protected Set<ConfigurableApplicationContext> findApplicationContexts() { |
||||||
|
synchronized (applicationContexts) { |
||||||
|
return new LinkedHashSet<ConfigurableApplicationContext>(applicationContexts); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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(); |
||||||
|
|
||||||
|
} |
||||||
@ -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. |
||||||
|
* |
||||||
|
* <p>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); |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
@ -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<ConfigurableApplicationContext> findApplicationContexts() { |
||||||
|
Set<ConfigurableApplicationContext> contexts = new LinkedHashSet<ConfigurableApplicationContext>(); |
||||||
|
Enumeration<String> 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; |
||||||
|
} |
||||||
|
|
||||||
|
} |
||||||
Loading…
Reference in new issue