
net.bull.javamelody.JavaMelodyAutoConfiguration Maven / Gradle / Ivy
/*
* Copyright 2008-2019 by Emeric Vernat
*
* This file is part of Java Melody.
*
* 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 net.bull.javamelody;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.EventListener;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.DispatcherType;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.Pointcut;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.aop.support.Pointcuts;
import org.springframework.aop.support.annotation.AnnotationMatchingPointcut;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.scheduling.annotation.Schedules;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
/**
* Spring Boot auto-configuration for JavaMelody.
*
*
* This class is picked up by the Spring Boot auto-configuration mechanism and creates the beans required to set up JavaMelody.
* Configuration values are injected using {@link JavaMelodyConfigurationProperties}.
*
*
*
* The auto-configured filter can be overridden by defining a custom {@link FilterRegistrationBean} with the name
* "javamelody-registration" in the application context.
*
*
*
* The configuration is enabled for web applications by default. It is possible to opt-out of the auto-configuration by
* setting the application configuration "javamelody.enabled" to the value "false".
*
*
* @author Georg Wittberger, Emeric Vernat
* @since 1.64.0
*/
@Configuration
@EnableConfigurationProperties(JavaMelodyConfigurationProperties.class)
@ConditionalOnWebApplication
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "enabled", matchIfMissing = true)
public class JavaMelodyAutoConfiguration {
/**
* Name of the FilterRegistrationBean.
*/
public static final String REGISTRATION_BEAN_NAME = "javamelody-registration";
private final MonitoredWithAnnotationPointcut monitoredWithSpringAnnotationPointcut = new MonitoredWithAnnotationPointcut();
private final Pointcut asyncAnnotationPointcut = Pointcuts.union(
new AnnotationMatchingPointcut(Async.class),
new AnnotationMatchingPointcut(null, Async.class));
private final Pointcut scheduledAnnotationPointcut = Pointcuts.union(
new AnnotationMatchingPointcut(null, Scheduled.class),
new AnnotationMatchingPointcut(null, Schedules.class));
/**
* Registers the JavaMelody {@link SessionListener}.
* @param servletContext ServletContext
* @return ServletListenerRegistrationBean
*/
@Bean
public ServletListenerRegistrationBean monitoringSessionListener(
ServletContext servletContext) {
final ServletListenerRegistrationBean servletListenerRegistrationBean = new ServletListenerRegistrationBean<>(
new SessionListener());
if (servletContext.getFilterRegistration("javamelody") != null) {
// if webapp deployed as war in a container with MonitoringFilter and SessionListener already added by web-fragment.xml,
// do not add again
servletListenerRegistrationBean.setEnabled(false);
}
return servletListenerRegistrationBean;
}
/**
* Registers the JavaMelody {@link MonitoringFilter}. The filter can be overridden completely by creating a custom
* {@link FilterRegistrationBean} with the name "javamelody-registration" in the application context.
* @param properties JavaMelodyConfigurationProperties
* @param servletContext ServletContext
* @return FilterRegistrationBean
*/
@Bean(name = REGISTRATION_BEAN_NAME)
@ConditionalOnMissingBean(name = REGISTRATION_BEAN_NAME)
public FilterRegistrationBean monitoringFilter(
JavaMelodyConfigurationProperties properties, ServletContext servletContext) {
final FilterRegistrationBean registrationBean = new FilterRegistrationBean<>();
// Create the monitoring filter and set its configuration parameters.
final MonitoringFilter filter;
if (properties.isManagementEndpointMonitoringEnabled()) {
// if the management endpoint is enabled, disable the /monitoring reports on the application port
filter = new MonitoringFilter() {
@Override
protected boolean isAllowed(HttpServletRequest request,
HttpServletResponse response) throws IOException {
response.sendError(HttpServletResponse.SC_FORBIDDEN, "Forbidden access");
return false;
}
};
} else {
filter = new MonitoringFilter();
}
filter.setApplicationType("Spring Boot");
// Wrap the monitoring filter in the registration bean.
registrationBean.setFilter(filter);
registrationBean.setAsyncSupported(true);
registrationBean.setName("javamelody");
registrationBean.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ASYNC);
// Set the initialization parameter for the monitoring filter.
for (final Entry parameter : properties.getInitParameters().entrySet()) {
registrationBean.addInitParameter(parameter.getKey(), parameter.getValue());
}
// Set the URL patterns to activate the monitoring filter for.
registrationBean.addUrlPatterns("/*");
final FilterRegistration filterRegistration = servletContext
.getFilterRegistration("javamelody");
if (filterRegistration != null) {
// if webapp deployed as war in a container with MonitoringFilter already added by web-fragment.xml,
// do not try to add it again
registrationBean.setEnabled(false);
for (final Map.Entry entry : registrationBean.getInitParameters()
.entrySet()) {
filterRegistration.setInitParameter(entry.getKey(), entry.getValue());
}
}
return registrationBean;
}
/**
* When enabled, management endpoint for /monitoring reports on the management http port instead of the application http port.
* @param servletContext ServletContext
* @return MonitoringEndpoint
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "management-endpoint-monitoring-enabled", matchIfMissing = false)
public MonitoringEndpoint monitoringEndpoint(final ServletContext servletContext) {
return new MonitoringEndpoint(servletContext);
}
/**
* Now disabled by default, since dependency spring-boot-starter-aop was added in 1.76.
* @return DefaultAdvisorAutoProxyCreator
*/
@Bean
@ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "advisor-auto-proxy-creator-enabled", matchIfMissing = false)
public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
return new DefaultAdvisorAutoProxyCreator();
}
/**
* Monitoring of JDBC {@link DataSource}s
* @param excludedDatasources Comma separated list of excluded datasources
* @return SpringDataSourceBeanPostProcessor
*/
@Bean
public SpringDataSourceBeanPostProcessor monitoringDataSourceBeanPostProcessor(
@Value("${javamelody.excluded-datasources:}") String excludedDatasources) {
// IMPORTANT: We cannot inject JavaMelodyConfigurationProperties here because of bean load order! Therefore we have
// to use that rather dirty way to inject the configuration value.
final SpringDataSourceBeanPostProcessor processor = new SpringDataSourceBeanPostProcessor();
if (excludedDatasources != null && excludedDatasources.trim().length() > 0) {
processor.setExcludedDatasources(
new HashSet<>(Arrays.asList(excludedDatasources.split(","))));
}
return processor;
}
/**
* Monitoring of beans and methods having the {@link MonitoredWithSpring} annotation.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public MonitoringSpringAdvisor monitoringSpringAdvisor() {
return new MonitoringSpringAdvisor(monitoredWithSpringAnnotationPointcut);
}
/**
* Monitoring of beans having the {@link Service} annotation.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public MonitoringSpringAdvisor monitoringSpringServiceAdvisor() {
return createMonitoringSpringAdvisorWithExclusions(
new AnnotationMatchingPointcut(Service.class),
monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
scheduledAnnotationPointcut);
}
/**
* Monitoring of beans having the {@link Controller} annotation.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public MonitoringSpringAdvisor monitoringSpringControllerAdvisor() {
return createMonitoringSpringAdvisorWithExclusions(
new AnnotationMatchingPointcut(Controller.class),
monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
scheduledAnnotationPointcut);
}
/**
* Monitoring of beans having the {@link RestController} annotation.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public MonitoringSpringAdvisor monitoringSpringRestControllerAdvisor() {
return createMonitoringSpringAdvisorWithExclusions(
new AnnotationMatchingPointcut(RestController.class),
monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut,
scheduledAnnotationPointcut);
}
/**
* Monitoring of beans or methods having the {@link Async} annotation.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public MonitoringSpringAdvisor monitoringSpringAsyncAdvisor() {
return createMonitoringSpringAdvisorWithExclusions(asyncAnnotationPointcut,
monitoredWithSpringAnnotationPointcut, scheduledAnnotationPointcut);
}
/**
* Monitoring of beans methods having the {@link Scheduled} or {@link Schedules} annotations.
* @return MonitoringSpringAdvisor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "scheduled-monitoring-enabled", matchIfMissing = true)
@ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
public MonitoringSpringAdvisor monitoringSpringScheduledAdvisor() {
return createMonitoringSpringAdvisorWithExclusions(scheduledAnnotationPointcut,
monitoredWithSpringAnnotationPointcut, asyncAnnotationPointcut);
}
private MonitoringSpringAdvisor createMonitoringSpringAdvisorWithExclusions(Pointcut pointcut,
Pointcut... excludedPointcuts) {
final Pointcut myPointcut;
if (excludedPointcuts.length == 0) {
myPointcut = pointcut;
} else {
Pointcut excludedPointcut = excludedPointcuts[0];
if (excludedPointcuts.length > 1) {
for (int i = 1; i < excludedPointcuts.length; i++) {
excludedPointcut = Pointcuts.union(excludedPointcut, excludedPointcuts[i]);
}
}
myPointcut = new ExcludingPointcut(pointcut).exclude(excludedPointcut);
}
return new MonitoringSpringAdvisor(myPointcut);
}
/**
* Monitoring of Feign clients.
* @return MonitoringSpringAdvisor
* @throws ClassNotFoundException should not happen
*/
@SuppressWarnings("unchecked")
@Bean
@ConditionalOnClass(name = "org.springframework.cloud.openfeign.FeignClient")
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
// we check that the DefaultAdvisorAutoProxyCreator above is not enabled, because if it's enabled then feign calls are counted twice
@ConditionalOnMissingBean(DefaultAdvisorAutoProxyCreator.class)
public MonitoringSpringAdvisor monitoringFeignClientAdvisor() throws ClassNotFoundException {
final Class extends Annotation> feignClientClass = (Class extends Annotation>) Class
.forName("org.springframework.cloud.openfeign.FeignClient");
final MonitoringSpringAdvisor advisor = new MonitoringSpringAdvisor(
new AnnotationMatchingPointcut(feignClientClass, true));
advisor.setAdvice(new MonitoringSpringInterceptor() {
private static final long serialVersionUID = 1L;
@Override
protected String getRequestName(MethodInvocation invocation) {
final StringBuilder sb = new StringBuilder();
final Method method = invocation.getMethod();
final RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
if (requestMapping != null) {
String[] path = requestMapping.value();
if (path.length == 0) {
path = requestMapping.path();
}
if (path.length > 0) {
sb.append(path[0]);
sb.append(' ');
if (requestMapping.method().length > 0) {
sb.append(requestMapping.method()[0].name());
} else {
sb.append("GET");
}
sb.append('\n');
}
}
final Class> declaringClass = method.getDeclaringClass();
final String classPart = declaringClass.getSimpleName();
final String methodPart = method.getName();
sb.append(classPart).append('.').append(methodPart);
return sb.toString();
}
});
return advisor;
}
/**
* Monitoring of {@link RestTemplate} beans.
* @return SpringRestTemplateBeanPostProcessor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public SpringRestTemplateBeanPostProcessor monitoringRestTemplateBeanPostProcessor() {
return new SpringRestTemplateBeanPostProcessor();
}
/**
* Monitoring of MongoDbFactory beans.
* @return SpringMongoDbFactoryBeanPostProcessor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public SpringMongoDbFactoryBeanPostProcessor monitoringMongoDbFactoryBeanPostProcessor() {
return new SpringMongoDbFactoryBeanPostProcessor();
}
/**
* Monitoring of ElasticsearchOperations beans.
* @return SpringElasticsearchOperationsBeanPostProcessor
*/
@Bean
@ConditionalOnProperty(prefix = JavaMelodyConfigurationProperties.PREFIX, name = "spring-monitoring-enabled", matchIfMissing = true)
public SpringElasticsearchOperationsBeanPostProcessor monitoringElasticsearchOperationsBeanPostProcessor() {
return new SpringElasticsearchOperationsBeanPostProcessor();
}
/**
* Configure Spring's Schedulers for Quartz Scheduler
* @return SchedulerFactoryBeanCustomizer
*/
@ConditionalOnClass(name = {
"org.springframework.boot.autoconfigure.quartz.SchedulerFactoryBeanCustomizer",
"org.springframework.scheduling.quartz.SchedulerFactoryBean",
"org.quartz.JobListener" })
@Bean
@ConditionalOnMissingBean
public SchedulerFactoryBeanCustomizer schedulerFactoryBeanCustomizer() {
return new SchedulerFactoryBeanCustomizer() {
@Override
public void customize(SchedulerFactoryBean schedulerFactoryBean) {
final JobGlobalListener jobGlobalListener = new JobGlobalListener();
schedulerFactoryBean.setGlobalJobListeners(jobGlobalListener);
schedulerFactoryBean.setExposeSchedulerInRepository(true);
}
};
}
/**
* @return Enregistrement du context Spring.
*/
@Bean
public SpringContext javamelodySpringContext() {
return new SpringContext();
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy