Monthly Archives: June 2020

Details of Spring Boot AutoConfiguration

What is the Spring Boot? How does Spring Boot Autoconfiguration work? This post will dive into the details of Spring Boot Autoconfiguration.

What is the Spring Boot?

Spring Boot website says “We take an opinionated view of the Spring platform and third-party libraries so you can get started with minimum fuss. Most Spring Boot applications need minimal Spring configuration.

Spring Boot is a framework to build applications.

Spring Boot provides different features and if you are using it to build your application, you will need different beans. So, autoconfiguration automatically configures the Spring Boot application by creating these beans.

Why you use autoconfiguration?

Efficiency and time. With autoconfiguration, Spring will do a lot of lifting for developers and will save time from creating the beans.

Behind the scenes, it is basically a bunch of @Configuration classes. These classes don’t use the annotation @Configuration .

Some of the annotations these classes use are:

  • @ConditionalOnClass – The application uses this only if the given class is on the classpath.
  • @Conditional – Only if a condition is met
  • @ConditionalOnMissingBean – The application uses this if a bean is missing or not created.

In short, @Conditional annotation is the base of all annotations.

How do you really understand this?

You or your team are working on multiple projects and these projects share some common code.  If you want to extract this common code in its own library or shared beans, so all projects can use them.


@Configuration
public class SharedObjects
{
   @Bean
   public CommonObject commonObject()
   {
      return new CommonObject();
   }
}

Once this CommonObject is shared through a jar file, other projects can import it.

The drawback with this approach is if the other project wants to use CommonObject, but don’t want to use any other beans from that common code. Importing those beans would be unnecessary overhead in the project during startup. Therefore, you need a way to tell Spring that we only need CommonObject Bean and not other beans, don’t even create other beans. That’s when we can use @Conditional annotation.

To use this @Conditional annotation, there are a few ways. Spring Boot provides Condition interface that a class can implement.


public class IsBrowserOnCondition implements Condition
{
   @Override
   public boolean matches(ConditionContext context, AnotatedTypeMetadata metadata)
   {
      return isMozillaFirefoxEnabled(context);
   }
   
   public boolean isMozillaFirefoxEnabled(ConditionContext context)
   {
      return context.getEnvironment().containsProperty("spring.preferredbrowser");
   }
}

In this class IsBrowserOnCondition , we see the implementation of interface Condition.

  • This implementation includes the method matches .
  • This method calls another method to check if the Mozilla Firefox browser has been enabled.
  • In the process, it checks for a property spring.preferredbrowser condition.
  • Now if we want to create new beans on the condition, we will use the annotation @Conditional as @Conditional(IsBrowserOnCondition.class).

In short, Spring Boot is a shared context configuration with a number of beans created using annotation @Conditional.

AutoConfiguration with Spring Boot

To understand more about autoconfiguration, we will use a simple Spring Boot application. We want to know what happens when we start this application.

So the main class of this application will look like below:


package com.betterjavacode.abccompany

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean;
import org.springframework.session.jdbc.config.annotation.web.http.EnableJdbcHttpSession;
import org.springframework.web.filter.CommonsRequestLoggingFilter;

@SpringBootApplication
@EnableJdbcHttpSession
public class HomeApplication extends SpringBootServletInitializer
{
	public static void main(String[] args)
	{
		SpringApplication.run(HomeApplication.class, args);
	}
}

When I run this main class, Spring Boot starts up the tomcat webserver.



.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v2.1.6.RELEASE)

2020-06-20 16:38:20.317  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : Starting HomeApplication on YMALI2019 with PID 19632 (C:\projects\rentersfeedback\out\production\classes started by Yogesh Mali in C:\projects\rentersfeedback)
2020-06-20 16:38:20.320  INFO 19632 --- [           main] c.rentersfeedback.home.HomeApplication   : No active profile set, falling back to default profiles: default
2020-06-20 16:38:21.483  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data repositories in DEFAULT mode.
2020-06-20 16:38:21.589  INFO 19632 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 96ms. Found 5 repository interfaces.
2020-06-20 16:38:22.052  INFO 19632 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$fdb646fa] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2020-06-20 16:38:22.674  INFO 19632 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8443 (https)
2020-06-20 16:38:22.700  INFO 19632 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
2020-06-20 16:38:22.700  INFO 19632 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.21]
2020-06-20 16:38:22.906  INFO 19632 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext


Behind the scene, Spring Boot is doing some work when starting the application on Tomcat. There are different 17 sources of properties that Spring Boot is using here. The official documentation Spring Boot provides the detail of these 17 sources. A developer can externalize these properties and many times we do that for application.properties. So if you have any of these properties configured, Spring Boot will read those properties instead of default sources.

Now if we expand the jar file spring-boot-autoconfigure-2.1.6.RELEASE.jar , you will see the number of directories under package org.springframework.boot.autoconfigure. All these sub packages are the beans that Spring Boot is pulling up, but only using them based on @Conditional annotation. Therefore, during startup Spring Boot will load some of these packages based on the dependencies you have configured in your Maven or Gradle build file.

From this jar, if we open the source file for ThymeleafAutoConfiguration, we will see the following:


/*
 * Copyright 2012-2019 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
 *
 *      https://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.boot.autoconfigure.thymeleaf;

import java.util.Collection;
import java.util.LinkedHashMap;

import javax.annotation.PostConstruct;
import javax.servlet.DispatcherType;

import com.github.mxab.thymeleaf.extras.dataattribute.dialect.DataAttributeDialect;
import nz.net.ultraq.thymeleaf.LayoutDialect;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.thymeleaf.dialect.IDialect;
import org.thymeleaf.extras.java8time.dialect.Java8TimeDialect;
import org.thymeleaf.extras.springsecurity5.dialect.SpringSecurityDialect;
import org.thymeleaf.spring5.ISpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.SpringTemplateEngine;
import org.thymeleaf.spring5.SpringWebFluxTemplateEngine;
import org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver;
import org.thymeleaf.spring5.view.ThymeleafViewResolver;
import org.thymeleaf.spring5.view.reactive.ThymeleafReactiveViewResolver;
import org.thymeleaf.templatemode.TemplateMode;
import org.thymeleaf.templateresolver.ITemplateResolver;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
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.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.autoconfigure.template.TemplateLocation;
import org.springframework.boot.autoconfigure.thymeleaf.ThymeleafProperties.Reactive;
import org.springframework.boot.autoconfigure.web.ConditionalOnEnabledResourceChain;
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
import org.springframework.boot.autoconfigure.web.servlet.ConditionalOnMissingFilterBean;
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import org.springframework.util.MimeType;
import org.springframework.util.unit.DataSize;
import org.springframework.web.servlet.resource.ResourceUrlEncodingFilter;

/**
 * {@link EnableAutoConfiguration Auto-configuration} for Thymeleaf.
 *
 * @author Dave Syer
 * @author Andy Wilkinson
 * @author Stephane Nicoll
 * @author Brian Clozel
 * @author Eddú Meléndez
 * @author Daniel Fernández
 * @author Kazuki Shimizu
 * @author Artsiom Yudovin
 */
@Configuration
@EnableConfigurationProperties(ThymeleafProperties.class)
@ConditionalOnClass({ TemplateMode.class, SpringTemplateEngine.class })
@AutoConfigureAfter({ WebMvcAutoConfiguration.class, WebFluxAutoConfiguration.class })
public class ThymeleafAutoConfiguration {

	@Configuration
	@ConditionalOnMissingBean(name = "defaultTemplateResolver")
	static class DefaultTemplateResolverConfiguration {

		private static final Log logger = LogFactory.getLog(DefaultTemplateResolverConfiguration.class);

		private final ThymeleafProperties properties;

		private final ApplicationContext applicationContext;

		DefaultTemplateResolverConfiguration(ThymeleafProperties properties, ApplicationContext applicationContext) {
			this.properties = properties;
			this.applicationContext = applicationContext;
		}

		@PostConstruct
		public void checkTemplateLocationExists() {
			boolean checkTemplateLocation = this.properties.isCheckTemplateLocation();
			if (checkTemplateLocation) {
				TemplateLocation location = new TemplateLocation(this.properties.getPrefix());
				if (!location.exists(this.applicationContext)) {
					logger.warn("Cannot find template location: " + location + " (please add some templates or check "
							+ "your Thymeleaf configuration)");
				}
			}
		}

		@Bean
		public SpringResourceTemplateResolver defaultTemplateResolver() {
			SpringResourceTemplateResolver resolver = new SpringResourceTemplateResolver();
			resolver.setApplicationContext(this.applicationContext);
			resolver.setPrefix(this.properties.getPrefix());
			resolver.setSuffix(this.properties.getSuffix());
			resolver.setTemplateMode(this.properties.getMode());
			if (this.properties.getEncoding() != null) {
				resolver.setCharacterEncoding(this.properties.getEncoding().name());
			}
			resolver.setCacheable(this.properties.isCache());
			Integer order = this.properties.getTemplateResolverOrder();
			if (order != null) {
				resolver.setOrder(order);
			}
			resolver.setCheckExistence(this.properties.isCheckTemplate());
			return resolver;
		}

	}

	@Configuration
	protected static class ThymeleafDefaultConfiguration {

		private final ThymeleafProperties properties;

		private final Collection templateResolvers;

		private final ObjectProvider dialects;

		public ThymeleafDefaultConfiguration(ThymeleafProperties properties,
				Collection templateResolvers, ObjectProvider dialectsProvider) {
			this.properties = properties;
			this.templateResolvers = templateResolvers;
			this.dialects = dialectsProvider;
		}

		@Bean
		@ConditionalOnMissingBean
		public SpringTemplateEngine templateEngine() {
			SpringTemplateEngine engine = new SpringTemplateEngine();
			engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
			this.templateResolvers.forEach(engine::addTemplateResolver);
			this.dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.SERVLET)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafWebMvcConfiguration {

		@Bean
		@ConditionalOnEnabledResourceChain
		@ConditionalOnMissingFilterBean(ResourceUrlEncodingFilter.class)
		public FilterRegistrationBean resourceUrlEncodingFilter() {
			FilterRegistrationBean registration = new FilterRegistrationBean<>(
					new ResourceUrlEncodingFilter());
			registration.setDispatcherTypes(DispatcherType.REQUEST, DispatcherType.ERROR);
			return registration;
		}

		@Configuration
		static class ThymeleafViewResolverConfiguration {

			private final ThymeleafProperties properties;

			private final SpringTemplateEngine templateEngine;

			ThymeleafViewResolverConfiguration(ThymeleafProperties properties, SpringTemplateEngine templateEngine) {
				this.properties = properties;
				this.templateEngine = templateEngine;
			}

			@Bean
			@ConditionalOnMissingBean(name = "thymeleafViewResolver")
			public ThymeleafViewResolver thymeleafViewResolver() {
				ThymeleafViewResolver resolver = new ThymeleafViewResolver();
				resolver.setTemplateEngine(this.templateEngine);
				resolver.setCharacterEncoding(this.properties.getEncoding().name());
				resolver.setContentType(
						appendCharset(this.properties.getServlet().getContentType(), resolver.getCharacterEncoding()));
				resolver.setProducePartialOutputWhileProcessing(
						this.properties.getServlet().isProducePartialOutputWhileProcessing());
				resolver.setExcludedViewNames(this.properties.getExcludedViewNames());
				resolver.setViewNames(this.properties.getViewNames());
				// This resolver acts as a fallback resolver (e.g. like a
				// InternalResourceViewResolver) so it needs to have low precedence
				resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
				resolver.setCache(this.properties.isCache());
				return resolver;
			}

			private String appendCharset(MimeType type, String charset) {
				if (type.getCharset() != null) {
					return type.toString();
				}
				LinkedHashMap parameters = new LinkedHashMap<>();
				parameters.put("charset", charset);
				parameters.putAll(type.getParameters());
				return new MimeType(type, parameters).toString();
			}

		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.REACTIVE)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafReactiveConfiguration {

		private final ThymeleafProperties properties;

		private final Collection templateResolvers;

		private final ObjectProvider dialects;

		ThymeleafReactiveConfiguration(ThymeleafProperties properties, Collection templateResolvers,
				ObjectProvider dialectsProvider) {
			this.properties = properties;
			this.templateResolvers = templateResolvers;
			this.dialects = dialectsProvider;
		}

		@Bean
		@ConditionalOnMissingBean(ISpringWebFluxTemplateEngine.class)
		public SpringWebFluxTemplateEngine templateEngine() {
			SpringWebFluxTemplateEngine engine = new SpringWebFluxTemplateEngine();
			engine.setEnableSpringELCompiler(this.properties.isEnableSpringElCompiler());
			engine.setRenderHiddenMarkersBeforeCheckboxes(this.properties.isRenderHiddenMarkersBeforeCheckboxes());
			this.templateResolvers.forEach(engine::addTemplateResolver);
			this.dialects.orderedStream().forEach(engine::addDialect);
			return engine;
		}

	}

	@Configuration
	@ConditionalOnWebApplication(type = Type.REACTIVE)
	@ConditionalOnProperty(name = "spring.thymeleaf.enabled", matchIfMissing = true)
	static class ThymeleafWebFluxConfiguration {

		private final ThymeleafProperties properties;

		ThymeleafWebFluxConfiguration(ThymeleafProperties properties) {
			this.properties = properties;
		}

		@Bean
		@ConditionalOnMissingBean(name = "thymeleafReactiveViewResolver")
		public ThymeleafReactiveViewResolver thymeleafViewResolver(ISpringWebFluxTemplateEngine templateEngine) {
			ThymeleafReactiveViewResolver resolver = new ThymeleafReactiveViewResolver();
			resolver.setTemplateEngine(templateEngine);
			mapProperties(this.properties, resolver);
			mapReactiveProperties(this.properties.getReactive(), resolver);
			// This resolver acts as a fallback resolver (e.g. like a
			// InternalResourceViewResolver) so it needs to have low precedence
			resolver.setOrder(Ordered.LOWEST_PRECEDENCE - 5);
			return resolver;
		}

		private void mapProperties(ThymeleafProperties properties, ThymeleafReactiveViewResolver resolver) {
			PropertyMapper map = PropertyMapper.get();
			map.from(properties::getEncoding).to(resolver::setDefaultCharset);
			resolver.setExcludedViewNames(properties.getExcludedViewNames());
			resolver.setViewNames(properties.getViewNames());
		}

		private void mapReactiveProperties(Reactive properties, ThymeleafReactiveViewResolver resolver) {
			PropertyMapper map = PropertyMapper.get();
			map.from(properties::getMediaTypes).whenNonNull().to(resolver::setSupportedMediaTypes);
			map.from(properties::getMaxChunkSize).asInt(DataSize::toBytes).when((size) -> size > 0)
					.to(resolver::setResponseMaxChunkSizeBytes);
			map.from(properties::getFullModeViewNames).to(resolver::setFullModeViewNames);
			map.from(properties::getChunkedModeViewNames).to(resolver::setChunkedModeViewNames);
		}

	}

	@Configuration
	@ConditionalOnClass(LayoutDialect.class)
	protected static class ThymeleafWebLayoutConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public LayoutDialect layoutDialect() {
			return new LayoutDialect();
		}

	}

	@Configuration
	@ConditionalOnClass(DataAttributeDialect.class)
	protected static class DataAttributeDialectConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public DataAttributeDialect dialect() {
			return new DataAttributeDialect();
		}

	}

	@Configuration
	@ConditionalOnClass({ SpringSecurityDialect.class })
	protected static class ThymeleafSecurityDialectConfiguration {

		@Bean
		@ConditionalOnMissingBean
		public SpringSecurityDialect securityDialect() {
			return new SpringSecurityDialect();
		}

	}

	@Configuration
	@ConditionalOnClass(Java8TimeDialect.class)
	protected static class ThymeleafJava8TimeDialect {

		@Bean
		@ConditionalOnMissingBean
		public Java8TimeDialect java8TimeDialect() {
			return new Java8TimeDialect();
		}

	}

}

If you are building a web application, thymeleaf template will be your default template engine.  Spring boot will load this class if TemplateMode and SpringBootEngine are loaded. We can see the use of @Conditional annotation.

How to exclude Spring Boot AutoConfiguration?

Spring Boot does offer an option to exclude any of the autoconfiguration you don’t want to include in your project.


@SpringBootApplication(exclude = {BatchAutoConfiguration.class)
public class HomeApplication 
{
    public static void main(String[] args) {
        SpringApplication.run(HomeApplication.class, args);
    }
}

One thing to remember here is that you must know why you are excluding a certain bean and if you are ok that it might exclude some dependent configurations.

Conclusion

In this post, I showed

  • How Spring Boot works and how you can build the Spring Boot application with some of the dependencies.
  • What Auto Configuration is and what it includes.

If you enjoyed this post or have any other questions, subscribe to my blog.

References

  1. Spring Boot Guide
  2. Spring Boot Official Documentation
  3. What is Spring Boot?

 

 

Key concepts about using JProfiler

JProfiler is a tool for analyzing what is going on inside a JVM. E-J Technologies has developed this tool. The most common usage is for CPU Profiling or Memory analyzing. In this post, I want to detail a few key concepts about using JProfiler.

Every software developer has to use JProfiler or similar tools VisualVM or JDK bundled tools when improving the performance of an enterprise application. Previously, I had shown how to use JProfiler for performance testing.

JProfiler focuses on three different topics that can help developers to analyze their application.

  • Method Calls – This shows what your application is doing with method calls and where the performance can be improved.
  • Thread and Lock – To find any multi-threading issues, you should check the thread and lock analysis that JProfiler provides.
  • Allocations – You can analyze the objects on the heap, reference chains, and garbage collection. Therefore, it helps to fix memory leak issues.

JProfiler also provides database analysis that can help in understanding if there are any performance issues with database queries. I will not be covering that in this post.

JProfiler Settings

There are two ways you can collect data while using JProfiler.

  • Sampling – This does not necessarily provide the accuracy of the collected data. This feature doesn’t provide method-level statistics. The advantage of using this feature is that system performance is not affected when running JProfiler.
  • Instrumentation – This mode collects the entire data, but also heavy on performance affecting the application. The advantage is the accuracy of the data. If you are doing performance analysis, this should be your default choice even with the drawback, it provides large data that is helpful to analyze performance.

How to run JProfiler on a running application

JProfiler offers two modes to start the profiling of an application. If you run in local mode, you have to choose data collection mode when starting the JProfiler. In this mode, the application and JProfiler are running on the same machine. Usually not recommended.

In remote mode, you will start your JProfiler agent on a remote machine, probably the same machine where the application is running. And from your local JProfiler installation, you will connect to the JProfiler agent. To do this, you will need the IP address of the remote machine as well as the port where the JProfiler agent is running. In short, you are connecting JProfiler GUI to a remote JProfiler agent that is profiling your application.

How to analyze application performance using JProfiler

If you are running a performance test on your application, you will stop the profiling when the performance test stops. Once the profiling is complete, JProfiler GUI should provide a snapshot of the profiled data.

In this snapshot, we will have data for performance analysis number of objects generated, method calls, garbage collection, memory leaks. We will go through these key concepts from JProfiler snapshot data now.

Overview page on Telemetries as shown below will give you an idea about metrics such as garbage collection activity, memory, threads, classes, and CPU load.

Key Concepts about using JProfiler

CPU Views

CPU views display the number of executions, call relationships of each method in the application. In CPU Views, you can find any specific method you suspect might be causing performance issues. This is a great view to analyze what might be the culprit in your application.

Call Tree

Call Tree shows hierarchical view of all the methods calls during the execution of your application. JProfiler helps with sorting the methods with their total execution times.

As shown above, we can see method to read data from Redis Cache is taking the most time.

Hot Spots

In the same view of CPU Views, there is an option for Hot Spots. This view can sort the methods based on various factors, such as individual execution time, total execution time, average execution time, and number of calls.

Live Memory

Live memory view shows memory allocation. This is where we can find if there is any memory leak.

All Objects

All objects view will show all the objects that were created during execution of the application and size of these objects.

 

Conclusion

In this post, I showed how to some key concepts about using JProfiler for performance analysis. JProfiler is a powerful tool if used effectively. There are a lot of features in JProfiler and it can be overwhelming while using the tool. I hope this post will help you use JProfiler to analyze your application.

References

  1. JProfiler Introduction – JProfiler Introduction