{"data":{"allMarkdownRemark":{"edges":[{"node":{"id":"f3a0fb4b-eb2f-5624-a408-d63e3cee5184","frontmatter":{"category":"Coding","title":"Spring batch integration","date":"2020-02-07","summary":"Usage cases for spring-batch.","thumbnail":null,"authorName":"Artur Yolchyan","authorDescription":"Artur is a Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/spring-batch-integration/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAACXBIWXMAAAsSAAALEgHS3X78AAAEfElEQVQ4yz2O2VMbBQDGlzJQSylXIEc3F0eODYGEJAQI5GBzbK49kt0cm703CSRQqfQCB9taap1qgY61VXqoPZxxbKe2dWo7tY6d0RdHX/0HfPLRF991qTPO/OZ7+33fB+RDFjruKMWhcgwqwnYqaiVhKwXbiolRgZiuVxJ1LrsgEg25sFilFqrFBblQFXNVgajyBMCkxiuIs4yMMoiDSTiwoC3kc/hdVpdVPzFqRua9MpupS7mqmK9LVF0u1CVFzssCIXMYwKZcTGpMyWLUOe2yjoxAQyNO1YCutbV9f1tbe1u7dcRY47GaMiXuVVSFvMTjModLLApwaRefHqeibqNm4EBnb/+A1mo2T9kHcZ9tGR5LQocPvdFmNusEOqv8lPicxBESi4kstifzabeMuj1Wnba7i/Q71vDgJ/Xc4zXhyWrxq1pyG/eWvEMd+9umfWMLAiFWshKD/g8goROV5LjdBJa9ls9r2edr4svTjRenG8836o9WK7e46EXM5zbpNGoVX0qKdEqk00qFgkBnAGrOko+Nz1jA94iZhyvFZ8fZ707JLzaWft48/vJ08/6b5Y+K4azHph5QlbCIXE7yJYRXco8U4Ffvn7ZoAiPgRSp8vU59s9Z89s7RJ2fXtxerbxGpo5nIOWxaCLr1BhOZDUqlOF+ICyVEQSwnAcuBFmtHi8ekRT0OMjBZQyI3lvgduVCLzvGx0HJi9nw+WPTbJ4dNdSLMUTBXiAnF+GsSgKN7n62zxWnQVAIuOjCxEJnaxuevleMfkMjJzPxGcuZTPs4FoA0U3uHIIhHkqKgCr1CIAx6wBeptTY3ZTiRnzmPhbTp9gyFuc+hNBnm6Kj48tXy1Ri7Brs8WxTMVvITN8fkIT8I8GRXIKFCAR11g1wSo3WSIb9dXbojEnUb1C5m5XkJ+ePvI4/WVHSm3BE98SKNCZpbBg6xyntzz92QxN+U39XYBQDMdfbTWvCpiF/Kpyyy1Wy/fapS+XC5tcclq0JGdMOViHhYP/Sdzyn5+HuBR3+xwXwcABG1DNxvkOgqfLxR3q4vvoujFQvzeSuGymKV8w5C+m4p7GTTIYEE2F+b2iABcZnLW2tsNAPrOg5f4zJVyYgfH7vHibY76epm8eyR/BpuU0inPNBFwW6RcSPGVfQYPcbl5gM14Ex69/gDQvW8f7rHuyujtGnm3yT84Id45Sp8lw0dwZHPrqX0s3N/Xnwl56UyAI0KvCQN02lOIOcfV7erWFvBgOwwZj+VSm0LleDlfR+aOsfSDV3+yS1udHV2aAZNaBQ4aByddTjQ6VcGCAJv1wX472K9S96gOHeyZmkHoWJiJ+Jsxz9ax1Ve//33p3k86tVmnM2s0JgWtdlCrHdKDw5AVAoio+7DW2NOj6+lRazQj71/99crayY8b+P3rd3/5459Hv/017o7otENGo82gt5jNo0rq9cMGg0UPWoBZj7O3Dxzo1/f1alyT6OL645Xm2cvXflw+93Rz93tx6YJaZRocHDMYIKMRAg9bQHDYZIIMBrvZ7PwXcPlkRtKKCQsAAAAASUVORK5CYII=","width":50,"height":50,"src":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png","srcSet":"/static/dc9516b9327e627890a1f5c7f84c004a/45876/avatar.png 1x,\n/static/dc9516b9327e627890a1f5c7f84c004a/eb85b/avatar.png 1.5x,\n/static/dc9516b9327e627890a1f5c7f84c004a/4f71c/avatar.png 2x,\n/static/dc9516b9327e627890a1f5c7f84c004a/9ec3e/avatar.png 3x"}}},"headerImage":null},"html":"<p><strong>Intro</strong></p>\n<p>In this article, we will take a look at spring-batch and how it could be used. We will walk through various configurations and we will create an application which reads from a CSV file &#x26; writes into a database with outstanding performance.</p>\n<p>I used the following: Java 11, Spring 5+, Spring Boot 2 and Maven-based project.</p>\n<p><strong>Create a Project</strong></p>\n<p>First, we need to create a Spring Boot 2 project. We recommend that you do so by visiting <a href=\"https://start.spring.io/\">spring initializer</a>, which is a useful tool to generate spring projects with required dependencies and configurations.</p>\n<p><strong>Dependencies</strong></p>\n<p>We need the dependencies below to run and test the project:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">&lt;dependency> \n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter&lt;/artifactId>\n&lt;/dependency>\n   \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n    &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n      &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n    &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-batch&lt;/artifactId>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-data-jpa&lt;/artifactId>\n&lt;/dependency>    \n\n&lt;dependency>\n &lt;groupId>org.projectlombok&lt;/groupId>\n &lt;artifactId>lombok&lt;/artifactId>\n &lt;version>1.18.10&lt;/version>\n &lt;scope>provided&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>com.h2database&lt;/groupId>\n &lt;artifactId>h2&lt;/artifactId>\n&lt;/dependency>\n    \n&lt;dependency>\n &lt;groupId>org.springframework.boot&lt;/groupId>\n &lt;artifactId>spring-boot-starter-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n &lt;exclusions>\n     &lt;exclusion>\n     &lt;groupId>org.junit.vintage&lt;/groupId>\n     &lt;artifactId>junit-vintage-engine&lt;/artifactId>\n     &lt;/exclusion>\n &lt;/exclusions>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.springframework.batch&lt;/groupId>\n &lt;artifactId>spring-batch-test&lt;/artifactId>\n &lt;scope>test&lt;/scope>\n&lt;/dependency>\n\n&lt;dependency>\n &lt;groupId>org.hamcrest&lt;/groupId>\n &lt;artifactId>hamcrest-all&lt;/artifactId>\n &lt;version>1.3&lt;/version>\n &lt;scope>test&lt;/scope>\n&lt;/dependency></code></pre></div>\n<p><em>spring-boot-starter-batch</em> dependency includes all the configurations to run the spring batch application. <a href=\"https://projectlombok.org/\">Lombok</a>  is just a helper dependency to write the code faster and cleaner. <a href=\"http://www.h2database.com/\">H2</a>  is used as an in-memory database. <em>spring-boot-starter-test</em> and <em>spring-batch-test</em> are included for test purposes.</p>\n<p><strong>Book Class</strong></p>\n<p>Let’s create a model class book, which will represent a book. This will just serve as a model class and will help us during implementation of spring batch.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n\npublic class Book {\nprivate String title;\nprivate String description;\nprivate String author;\n}</code></pre></div>\n<p><strong>Configuration</strong></p>\n<p>Let’s create a class called SpringBatchConfiguration. We will add all required configurations here. First, let’s annotate this class with <a href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/annotation/Configuration.html\">@Configuration</a> to be able to inject beans, and with <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/configuration/annotation/EnableBatchProcessing.html\">@EnableBatchProcessing</a> to enable spring batch processing. Additionally, we can add <a href=\"https://projectlombok.org/features/constructor\">@RequiredArgsConstructor</a> from Lombok which would help us to generate constructor with parameters. These parameters are the ones which are marked as final class properties. These properties will be injected as spring beans. Our class will be like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Configuration\n@EnableBatchProcessing\n@RequiredArgsConstructor\npublic class SpringBatchConfiguration</code></pre></div>\n<p>Now, in SpringBatchConfiguration class let’s add properties which need to be injected into the constructor:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">private final JobBuilderFactory jobBuilderFactory;\nprivate final StepBuilderFactory stepBuilderFactory;</code></pre></div>\n<p><em>jobBuilderFactory</em> and <em>stepBuilderFactory</em> are declared in spring batch jar as spring beans so that we can inject them in any class we want.</p>\n<p><strong>File Reader</strong></p>\n<p>Now, we need to declare and initialize spring beans to configure the batch process. The first bean which we will need will be responsible for reading from a file line by line. Spring Batch provides a default class for it. The class name is <em>FlatFileItemReader</em>. Similarly, spring has different default reader classes for reading from a relational database, mongodb and etc. However, if you need, you can create your own reader and implement it in a way you want.</p>\n<p>Let’s now see what <em>FlatFileItemReader</em> injection will look like.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\n@StepScope\npublic FlatFileItemReader&lt;Book> bookReader(@Value(\"#{jobParameters['filePath']}\") final String filePath\n) {\nreturn new FlatFileItemReaderBuilder&lt;Book>()\n    .name(\"personItemReader\")\n    .resource(new ClassPathResource(filePath))\n    .delimited()\n    .names(new String[]{\"title\", \"description\", \"author\"})\n    .fieldSetMapper(new BeanWrapperFieldSetMapper&lt;Book>() {{\n    setTargetType(Book.class);\n    }})\n    .build();\n}</code></pre></div>\n<p>We are configuring that the bean reader should read data from the given file path, which should be a CSV file having rows with title, description and author respectively.</p>\n<p>The <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/scope/StepScope.html\">StepScope</a> means to initialize this bean after each step, so file path could be dynamically set when the spring batch job is launched. We will come to that later, how to pass the file path later in this article.</p>\n<p><strong>Item Writer</strong></p>\n<p>Now, let’s create a writer class, which will take the data and write into the relational database.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic JdbcBatchItemWriter&lt;Book> writer(final DataSource dataSource) {\n    return new JdbcBatchItemWriterBuilder&lt;Book>()\n        .itemPreparedStatementSetter((book, preparedStatement) -> {\n            preparedStatement.setString(1, book.getTitle());\n            preparedStatement.setString(2, book.getDescription());\n            preparedStatement.setString(3, book.getAuthor());\n        })\n        .sql(\"INSERT INTO books (title, description, author) VALUES (title, description, author_surname)\")\n        .dataSource(dataSource)\n        .build();\n} </code></pre></div>\n<p>In the writer bean, we are setting item processors and adding values inside <em>preparedStatement</em> accordingly, which book property should be inserted for each db table column.  </p>\n<p><strong>Step Configuration</strong></p>\n<p>Now, let’s configure a step which will be executed in the batch process. In our case, the configuration will look like this:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Step step1(final ItemWriter&lt;Book> writer, final ItemReader&lt;Book> reader) {\n    return stepBuilderFactory.get(\"step1\")\n        .&lt;Book, Book> chunk(100)\n        .reader(reader)\n        .processor((ItemProcessor&lt;Book, Book>) book -> book)\n        .writer(writer)\n        .build();\n}</code></pre></div>\n<p>In our scenario, we have only one step, but it's also possible to configure multiple steps. Here, we are creating a spring batch step with the name <em>step1</em> and setting reader and writer accordingly. These are the readers and writers which we created as spring beans earlier.</p>\n<p>We are setting chunk as 100, which means the items chunk proceeded is 100. We can make this configurable too. The processor is for converting the current object to the one which the writer should proceed with. In our case, it is the same object.</p>\n<p><strong>Job Configuration</strong></p>\n<p>Last but not least, let’s configure the job which should be executed.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Bean\npublic Job importUserJob(final Step step1) {\n    return jobBuilderFactory.get(\"bookReaderJob\")\n        .incrementer(new RunIdIncrementer())\n        .flow(step1)\n        .end()\n        .build();\n}</code></pre></div>\n<p>Here, we create a job with the name <em>bookReaderJob</em>. We add an incrementer <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/support/RunIdIncrementer.html\">RunIdIncrementer</a>, which is an id generator for the tables which are specifically designed to store the details about job executions in the database. After each time the spring batch job is executed, it will save details about the execution. To check the schema structure for storing this data, take a look at <a href=\"https://github.com/spring-projects/spring-batch/blob/master/spring-batch-core/src/main/resources/org/springframework/batch/core/schema-mysql.sql\">this sql</a>. It is for MySql, but other SQL scripts are available too.</p>\n<p>Additionally, we add flow by which the job should be executed. Currently, we have only one step in our flow, so we add it.  </p>\n<p>Please also add this config: <code class=\"language-text\">spring.batch.job.enabled=false</code> in your properties config file so that spring batch job won’t be executed automatically with no parameters when the application is started.</p>\n<p><strong>Execution</strong></p>\n<p>To execute the job, we need to declare <a href=\"https://docs.spring.io/spring-batch/docs/current/api/org/springframework/batch/core/launch/JobLauncher.html\">JobLauncher</a> and launch the job. To do so, we need to create the below class:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Component\n@RequiredArgsConstructor\npublic class SpringBatchExecutor {  \n    private final JobLauncher jobLauncher;\n    private final Job job;  \n\n    @SneakyThrows\n    public void execute(final String filePath) {\n        JobParameters parameters = new JobParametersBuilder()\n            .addString(\"filePath\", filePath)\n            .toJobParameters();  \n        jobLauncher.run(job, parameters);\n    }  \n}</code></pre></div>\n<p>Now, we can call the execute method from whenever we want, with the file path from which the data should be read.</p>\n<p><strong>Additional Classes</strong></p>\n<p>There are a few additional classes which you will need to execute your code.</p>\n<p>Create a class <em>BookEntity</em> so that spring data JPA will automatically create the book table for you. Then you can create repository.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Data\n@Entity\n@Table(name = \"books\")\npublic class BookEntity {\n    @Id\n    @GeneratedValue(strategy = GenerationType.IDENTITY)\n    private Long id;\n    private String title;\n    private String description;\n    private String authorFullName;\n}</code></pre></div>\n<p>Then create the interface <em>BookRepository</em> and extend it from <em>JpaRepository</em>. At the moment, we will need this only for testing.  </p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Repository\npublic interface BookRepository extends JpaRepository&lt;BookEntity, Long> {\n} </code></pre></div>\n<p><strong>Testing</strong></p>\n<p>No one likes a code which is not tested. So, let’s write a few test cases for our class.</p>\n<p>Here is a code sample for test cases:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Slf4j\n\n@SpringBootTest\nclass SpringBatchSampleApplicationIntegrationTest {\n\n    @Autowired\n    private SpringBatchExecutor springBatchExecutor;\n    \n    @Autowired\n    private BookRepository bookRepository;\n\n    @Test\n    public void testExecution() {\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"sample-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(7L));\n    } \n\n    @Test\n    public void testLargeData() {\n        long startTime = System.currentTimeMillis();\n        long initialCount = bookRepository.count();\n        assertThat(initialCount, equalTo(0L));\n        springBatchExecutor.execute(\"large-data.csv\");\n        long count = bookRepository.count();\n        assertThat(count, equalTo(60000L));\n        long endTime = System.currentTimeMillis();\n        log.info(\"executed in miles: {}\", endTime - startTime);\n    }\n}</code></pre></div>\n<p>The second test executes in ≈ <strong>3500 ms</strong> in a machine with a 2.2 Ghz Intel Core i7 and 16GB ram. It proceeds 60K lines of CSV file, and saves it into a relational database.</p>\n<p>You can check out the working sample code in <a href=\"https://github.com/yolch-yolchyan/spring-batch-sample\">github</a>,</p>","fields":{"slug":"/spring-batch-integration/","tags":["auto1","engineering","spring","code","java"]}}},{"node":{"id":"7efdac28-b97f-53e0-adf8-48d022f3eecc","frontmatter":{"category":"Coding","title":"Integration Test Speedup with Spring","date":"2019-03-11","summary":"Here is what we found on speeding up your integration tests in spring based applications.","thumbnail":null,"authorName":"Boris Faniuk","authorDescription":"Boris is a former member of Engineering of AUTO1 Group.","authorAvatar":{"relativePath":"pages/integration-test-speedup/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/integration-test-speedup/header.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAPABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAABQABAv/EABYBAQEBAAAAAAAAAAAAAAAAAAIAAf/aAAwDAQACEAMQAAABJ6Uw4FJSv//EABsQAAICAwEAAAAAAAAAAAAAAAECAAMEFCEx/9oACAEBAAEFAl9tuawzH6WUPNYT/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFREBAQAAAAAAAAAAAAAAAAAAARD/2gAIAQIBAT8BJ//EABsQAAEEAwAAAAAAAAAAAAAAAAABEBIhIjFR/9oACAEBAAY/AjJo9SizZ//EABwQAQEAAgIDAAAAAAAAAAAAAAERAHEQMUFR0f/aAAgBAQABPyFMdusqfAgeuBQwbbGHwF1jT4z/2gAMAwEAAgADAAAAELsf/8QAFhEBAQEAAAAAAAAAAAAAAAAAARBB/9oACAEDAQE/EA2f/8QAFxEAAwEAAAAAAAAAAAAAAAAAAAERIf/aAAgBAgEBPxDSpD//xAAbEAEBAQADAQEAAAAAAAAAAAABEQAhMXFBYf/aAAgBAQABPxAMiBZ241OQL4wZbyughIofpPEuZhdFS61EeZ//2Q==","width":1024,"height":768,"src":"/static/9f60dcc74069c89c91db86dc33946167/9f594/header.jpg","srcSet":"/static/9f60dcc74069c89c91db86dc33946167/9f594/header.jpg 1x"}}}},"html":"<h2>Problem statement</h2>\n<p>Over time we realized that our microservices built on top of Spring framework take tons of time on the integration test step. Every single PR runs all tests and this takes 10-20 minutes for some services. To decrease PR build time we decided to research why this happens.\nOne of the problems that this research highlighted is ramping up heavy spring context several times during execution of all module’s tests.</p>\n<p>As a proof of concept we have taken 3 modules and cleaned up their tests to achieve better integration test performance.</p>\n<p>The results are pretty much self-explanatory: </p>\n<ol>\n<li>Module A now takes 3 minutes to test versus 11 before</li>\n<li>Module B now takes 3 minutes to test versus 8 before</li>\n<li>Module C now takes 4 minutes to test versus 12 before</li>\n</ol>\n<p>Below I list all factors that produce many spring contexts and how we managed to overcome them.</p>\n<h2>Spring features to be avoided</h2>\n<h3><code class=\"language-text\">@ActiveProfiles</code> and <code class=\"language-text\">@ContextConfiguration</code> with different attributes</h3>\n<p>Our spring-based microservices actively use spring profiles and configuration classes.\nMany components from common libraries are hidden under profiles and configuration classes to not be active for every single module / environment without need. So, integration test authors define profiles and configuration classes that should be activated for their tests using these annotations:</p>\n<ol>\n<li><code class=\"language-text\">@ActiveProfiles({\"integration-test\", \"pg-test\", \"mongo-test-two-nodes\"})</code></li>\n<li><code class=\"language-text\">@ContextConfiguration(classes = {Application.class, AwsTestConfiguration.class})</code></li>\n</ol>\n<p>Here <em>mongo-test-two-nodes</em> activates special mode for mongo based services to operate with two separate mongo instances. As not all tests need this, the profile is turned off by default and only those tests that need this behaviour would explicitly turn it on. But this, unfortunately, leads to multiple spring contexts being created.</p>\n<p>The same happens with <em>AwsTestConfiguration</em> that deploys special test-component to mock AWS calls.</p>\n<p>The solution was to make all tests use the same set of active profiles and context configuration classes for every single integration test.\nThe drawback is that some components are deployed, even for the tests that do not need it. But deploying single component takes O(1) time comparing with the whole spring context.</p>\n<p>Another point to keep in mind is that order of profiles (configuration classes) matters.\n<code class=\"language-text\">@ActiveProfiles({\"integration-test\", \"pg-test\"})</code>\n<code class=\"language-text\">@ActiveProfiles({\"pg-test\", \"integration-test\"})</code></p>\n<p>Two settings mentioned above result in 2 different spring contexts.</p>\n<h3><code class=\"language-text\">@SpyBean</code> and <code class=\"language-text\">@MockBean</code></h3>\n<p>One interesting feature in spring-boot-test is mocking / spying real spring beans.\nThis was mostly used when we don’t care what some method does in test but we need to check that it was called by parent components.\nUnfortunately this also makes test to create separate spring context.\nWhat we’ve done here is replacing <code class=\"language-text\">@SpyBean</code> annotation with configuration class that wraps real spring bean into <code class=\"language-text\">Mockito.spy(..)</code>:</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">@Configuration\npublic class SomeComponentSpyFactory {\n\t@Autowired\n\tprivate SomeComponent someComponent;\n\n\t@Bean\n\t@Primary\n\tpublic SomeComponent someComponentSpy() {\n    \t\treturn Mockito.spy(someComponent);\n\t}\n}</code></pre></div>\n<p>Another option is to use the combination of <code class=\"language-text\">@Autowired</code> and <code class=\"language-text\">@Spy</code> annotations.</p>\n<div class=\"gatsby-highlight\" data-language=\"text\"><pre class=\"language-text\"><code class=\"language-text\">\t@InjectMocks\n\t@Autowired\n\tprivate ParentComponent parentComponent;\n\n\t@Spy\n\t@Autowired\n\tprivate ChildComponent childComponent;</code></pre></div>\n<p>However, the drawback of the last solution is that, <em>parentComponent</em> needs to have a setter for <em>childComponent</em> which is not good in terms of encapsulation. But if you don’t use autowiring on constructor level it is fine. Anyway, one can choose whatever fits the situation better.</p>\n<h3><code class=\"language-text\">@DirtiesContext</code></h3>\n<p>One of the services used this annotation to clear the caches between different tests.\nClearing a cache is really a problem (see section below), but this solution comes to separate spring context per test class or even test method (depending on annotation parameters). Context per method was actually the case for a few test classes. Instead in this modules we added methods to clear the caches programmatically without deploying the whole new context.\nOne can use java reflection API instead of adding special production-level methods, again whatever fits you better.</p>\n<h2>Challenge: test execution order and cleaning collections (caches)</h2>\n<p>After all this “multiple context” factors are gone one may face (and we did) a challenge to fix or re-organize some tests. Before above enhancements were done all tests were running in separate spring contexts, therefore used separate instances of every component and therefore those problems could be hidden.</p>\n<p>As an example some tests in mongo-based service were testing <code class=\"language-text\">getAll()</code>-like operations. Those tests were running functions that fetched all records from some collections. This was not a problem as embedded mongo DB was different instance. After all tests started to exploit the same spring context some of them started to fail intermittently. The failures were intermittent because test execution order is different. One service may break another, but not vice versa.\nSo, to overcome this, we added missing <code class=\"language-text\">@Before</code> operations that cleaned those collections.</p>\n<p>The rule that I would suggest here is that every test should clean up whatever it needs to be clean and not rely on fairness of other tests. That was a problem in our case. Some tests had <code class=\"language-text\">@After</code> operations that cleaned up, but not everything produced by the test was cleaned up. This caused intermittent failures of other tests.</p>\n<h2>Tools we created to reduce test execution time</h2>\n<p>So, now after we know all this, we created two tools (and may create more in future) that help to identify those problems.</p>\n<h3>Jenkins PR pipeline step</h3>\n<p>After every PR commit we run one additional step that analyzes test execution log.\nIf more than one spring context is created this leaves some traces and a notification is sent to the author of the commit via slack</p>\n<p>As an enhancement, the jenkins jobs could be modified to fail the build if the number of spring contexts is greater than a certain threshold. The threshold could start with a high number and can gradually be reduced as the team cleans up the existing tests by adopting the aforementioned practices/fixes</p>\n<h3>IntelliJ IDEA plugin</h3>\n<p>We wrote an IntelliJ plugin to identify and solve the aforementioned problems. There will be a separate blog post about this plugin coming very soon.</p>","fields":{"slug":"/integration-test-speedup/","tags":["auto1","engineering","spring","java","testing"]}}},{"node":{"id":"99158851-0237-5904-98ca-02b5e2f45684","frontmatter":{"category":"Coding","title":"Spring 5 candidate component index case study","date":"2019-02-05","summary":"Analysis of Spring 5.X candidate component index applicability to boost our application startup time","thumbnail":{"relativePath":"pages/spring-5-indexer/thumbnailImage.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAZABQDASIAAhEBAxEB/8QAFwABAQEBAAAAAAAAAAAAAAAAAAIBBf/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAeBWySoZgAf/xAAaEAACAgMAAAAAAAAAAAAAAAAAARASICEx/9oACAEBAAEFAisLg95//8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAwEBPwEf/8QAFBEBAAAAAAAAAAAAAAAAAAAAIP/aAAgBAgEBPwEf/8QAFBABAAAAAAAAAAAAAAAAAAAAMP/aAAgBAQAGPwJP/8QAGhAAAwADAQAAAAAAAAAAAAAAAAERECAxIf/aAAgBAQABPyFK8Kx5obfaNVLt/9oADAMBAAIAAwAAABBTBzz/xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAEDAQE/EB//xAAUEQEAAAAAAAAAAAAAAAAAAAAg/9oACAECAQE/EB//xAAcEAEAAgMBAQEAAAAAAAAAAAABABEhMUFRYSD/2gAIAQEAAT8QR0FfCY14dprVxKUeSiVurchzX3sU1d7c6ZqWULr2CBDvz9f/2Q==","width":265,"height":325,"src":"/static/85788e7deb4eaccc84801ebd1f7b77a8/a2998/thumbnailImage.jpg","srcSet":"/static/85788e7deb4eaccc84801ebd1f7b77a8/a2998/thumbnailImage.jpg 1x,\n/static/85788e7deb4eaccc84801ebd1f7b77a8/99cce/thumbnailImage.jpg 1.5x"}}},"authorName":"Mariusz Sondecki","authorDescription":"Mariusz is Senior Software Engineer at AUTO1 Group.","authorAvatar":{"relativePath":"pages/spring-5-indexer/avatar.png","childImageSharp":{"resolutions":{"base64":"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAYAAACNiR0NAAAACXBIWXMAAAsSAAALEgHS3X78AAACyElEQVQ4y4VUO2gUURR9mZmN8YcItoKVjeXuzsxu9jOzm92NkBBTqDEmjZAEgtqIpSgI1gp+uhS6kEI7wc7GQsFCLERiISGCaGHhF02yu+M5s/cujyC4cJY379573r3n3XdNvV51xsYiJ4rKbqlUyBj8wjAYyWazp4BVYB3YAraBDeAhMBcE/l76lkrFTLVachuNyCGXEbJMrVbx6ADnaWAtn88nvu8nWBMdQcJ9Auv3wCxjWq2aC9IMSF3DzCyyG1YAs9pSYkL2melmLpfT71tKGkUlz2iZJAuCgA5dEjGAwPoV0AbuAy+tfR7YkZjb5CiXR1OutEw5jWQdCfgCnEiSxNg/7LWAj+KTSiHSzBvrAtascrriPCmnDkP4AYQ0Em3TGPHf8P38ARpP/4PsHQ7aU69Hqdi4OJeI47JXLg9IX1tVbUuWCzSsygcJO+L0goGNRpySoB1colarerhEvcCn4tuxCB/TsC5ZdS3Ct2Ho75uZmRoigWbIbgCxt7Q0O8TLsgg17o2R2+JHzzJcV/1IoBlybel42SLsCcc3I6UqoaZ+lUHFYjjCzJSQa+4J4UVLKiX8ScOGVXJXDN+xd7SfZXFYS65U+n2Wz+cOw+ezHadSkfCRdVJiZdlmMG9ZtHMpgWR3Z0eMSvWExjmrbXoCbZ951dIim7Yk6u1IYtlwavChW51Pp01tXOwf01eC9RHs/Rbbnx2Hf4IUh/Q5zdol8H3KG22Pj9edOK54LF1876pd37TELhqdZ+J4MwxDHUvXgDOFQrB7YqLpjI4WdhG8GLYNbCeBK/1+TWNWRG+Pfy5Hj5Dek3IXzX9+8DkL/AIeLC/PDU1NHXdSQk5altNsDkjPAT+ArzKyFnK5bB2IxbYik4hanmfM5GTL6Q/Y2DUc25y0HLToubR8TI39cL4APJOxr/35AXgOXEJnHKQvepP6eiQj119kDrZjqLo8HAAAAABJRU5ErkJggg==","width":50,"height":50,"src":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png","srcSet":"/static/26a7a327ccc4b335712e5a7086f2b26d/45876/avatar.png 1x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/eb85b/avatar.png 1.5x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/4f71c/avatar.png 2x,\n/static/26a7a327ccc4b335712e5a7086f2b26d/9ec3e/avatar.png 3x"}}},"headerImage":{"relativePath":"pages/spring-5-indexer/headerImage.jpg","childImageSharp":{"resolutions":{"base64":"data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAANABQDASIAAhEBAxEB/8QAFwAAAwEAAAAAAAAAAAAAAAAAAAECBv/EABQBAQAAAAAAAAAAAAAAAAAAAAD/2gAMAwEAAhADEAAAAc/UgxB//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQABBQJf/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPwE//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPwE//8QAFBABAAAAAAAAAAAAAAAAAAAAIP/aAAgBAQAGPwJf/8QAGBAAAwEBAAAAAAAAAAAAAAAAAAERIEH/2gAIAQEAAT8hElKxzmP/2gAMAwEAAgADAAAAEPPP/8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAwEBPxA//8QAFBEBAAAAAAAAAAAAAAAAAAAAEP/aAAgBAgEBPxA//8QAGxAAAgIDAQAAAAAAAAAAAAAAAREAECExQVH/2gAIAQEAAT8Qm5fABBZ6Hh5TKTxX/9k=","width":1280,"height":853,"src":"/static/430c76d0d1e83aad3065e86e11366218/966a5/headerImage.jpg","srcSet":"/static/430c76d0d1e83aad3065e86e11366218/966a5/headerImage.jpg 1x,\n/static/430c76d0d1e83aad3065e86e11366218/aa36d/headerImage.jpg 1.5x"}}}},"html":"<h2>The situation</h2>\n<p>Since day one at AUTO1, we strongly believe that the microservice architecture helps us run business at scale not only due to the technical benefits it brings but also, if not foremost by allowing many teams to work independently from each other. Today, we’re running more than 250 microservices in production and on average add one more to our platform every week. All of them are powered by the Spring Framework technology stack, which we value very much for its productivity, but at the same time consider it to be a bit heavy in terms of bootstrapping time. The typical startup time of our Spring applications varies between 40 and 80 seconds. Since we do around 30 deployments per day, our engineering teams spend roughly 30 minutes daily waiting for the deployments to be completed. As we value the time of our engineering teams, we wanted to find a way to decrease these numbers. </p>\n<h2>A new hope</h2>\n<p>While investigating the startup times of our applications, we have identified classpath component scanning as a potential place to look for improvements. Luckily for us, some of our tech savvy engineers suggested a not so prominent Spring 5.X feature to come at help here - the build time component candidate index.\nThe build time component index is an alternative for regular component scanning, which skips the classpath search operation in favor of using a compile time pre-generated index of component candidates during the spring application context building. However, it comes with a small print from the Spring Framework experts saying that this functionality should have mostly a visible impact on startup times for applications with large amount of beans and that are operating in environments where IO operations are expensive (e.g. remote file systems) or where JVM security managers are in use. Although, neither of this is our use case, we have decided to proceed with the feature evaluation and measurements, as here at AUTO1, we like our decisions to be data driven.</p>\n<h2>Demystifying the indexer</h2>\n<p>The key part of the indexer feature is a pre-generated index file located in <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em> one per JAR. Every index entry in this file is a fully qualified name of a candidate component as a key and comma separated stereotypes as value. So for example “X=Y, Z” can be read simply as register a candidate component X with following stereotypes Y, Z. Below is an example of a <em><span style=\"font-family:Courier New;\">spring.components</span></em> file:</p>\n<div class=\"gatsby-highlight\" data-language=\"shell\"><pre class=\"language-shell\"><code class=\"language-shell\">com.auto1.playground.service.SpringDummyService=org.springframework.stereotype.Component\ncom.auto1.playground.domain.SpringDummy=javax.persistence.Entity,javax.persistence.Table,javax.persistence.EntityListeners\ncom.auto1.playground.repository.SpringDummyRepository=org.springframework.stereotype.Component,org.springframework.data.repository.Repository\ncom.auto1.playground.configuration.LocalConfiguration=org.springframework.stereotype.Component\ncom.auto1.playground.service.IndexedCustomService=com.auto1.playground.service.IndexedCustomService\ncom.auto1.playground.Application=org.springframework.stereotype.Component\ncom.auto1.playground.SpringDummyController=org.springframework.stereotype.Component\ncom.auto1.playground.service.IndexedCustomExample=com.auto1.playground.annotation.IndexedCustom</code></pre></div>\n<p>The first question that arises here is how to create such a file and keep it up-to-date. The Spring engineers thought about this problem as well and came up with an annotation processor tool called <a href=\"https://github.com/spring-projects/spring-framework/tree/master/spring-context-indexer\">spring indexer</a> that hooks in during the project build phase and generates the file. Making use of it is fairly simple, assuming you are using your favorite build automation tool, in our case maven and want our IDE to keep this file up to date, simply add the spring indexer annotation processor to your pom file:</p>\n<div class=\"gatsby-highlight\" data-language=\"xml\"><pre class=\"language-xml\"><code class=\"language-xml\"><span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>build</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>plugins</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>plugin</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>groupId</span><span class=\"token punctuation\">></span></span>org.apache.maven.plugins<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>groupId</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>artifactId</span><span class=\"token punctuation\">></span></span>maven-compiler-plugin<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>artifactId</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>version</span><span class=\"token punctuation\">></span></span>3.5<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>version</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>configuration</span><span class=\"token punctuation\">></span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>annotationProcessorPaths</span><span class=\"token punctuation\">></span></span>\n                    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>path</span><span class=\"token punctuation\">></span></span>\n                        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>groupId</span><span class=\"token punctuation\">></span></span>org.springframework<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>groupId</span><span class=\"token punctuation\">></span></span>\n                        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;</span>artifactId</span><span class=\"token punctuation\">></span></span>spring-context-indexer<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>artifactId</span><span class=\"token punctuation\">></span></span>\n                    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>path</span><span class=\"token punctuation\">></span></span>\n                <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>annotationProcessorPaths</span><span class=\"token punctuation\">></span></span>\n            <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>configuration</span><span class=\"token punctuation\">></span></span>\n        <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>plugin</span><span class=\"token punctuation\">></span></span>\n    <span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>plugins</span><span class=\"token punctuation\">></span></span>\n<span class=\"token tag\"><span class=\"token tag\"><span class=\"token punctuation\">&lt;/</span>build</span><span class=\"token punctuation\">></span></span></code></pre></div>\n<p>Once this is done, what happens during project build time is the spring context indexer (precisely speaking the <a href=\"https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/index/CandidateComponentsIndex.html\">CandidateComponentsIndexer</a> class) while going through the project identifies annotated components and outputs them to <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em>. The type of components it includes in the candidate list are:</p>\n<ol>\n<li>\n<p>Classes annotated directly or indirectly with spring <em><span style=\"font-family:Courier New;\">@Component</span></em> annotation (which by itself is meta annotated with <em><span style=\"font-family:Courier New;\">@Indexed</span></em>) and other stereotypes for which <em><span style=\"font-family:Courier New;\">@Component</span></em> is a meta annotation such as <em><span style=\"font-family:Courier New;\">@Repository</span></em>, <em><span style=\"font-family:Courier New;\">@Controller</span></em>, <em><span style=\"font-family:Courier New;\">@Service</span></em>, <em><span style=\"font-family:Courier New;\">@Configuration</span></em>.</p>\n</li>\n<li>\n<p>Classes and interfaces annotated with annotations from <em><span style=\"font-family:Courier New;\">javax</span></em> package, including <em><span style=\"font-family:Courier New;\">CDI</span></em> annotations (<em><span style=\"font-family:Courier New;\">@Named<span></em>, <em><span style=\"font-family:Courier New;\">@ManagedBean</span></em>), <em><span style=\"font-family:Courier New;\">JPA</span></em> annotations (<em><span style=\"font-family:Courier New;\">@Entity</span></em>, <em><span style=\"font-family:Courier New;\">@EntityListeners</span></em>) or even <em><span style=\"font-family:Courier New;\">Servlet</span></em> annotations (<em><span style=\"font-family:Courier New;\">@WebListener</span></em>)</p>\n</li>\n<li>\n<p>Any custom classes and interfaces annotated with <em><span style=\"font-family:Courier New;\">@Indexed</span></em> annotation. </p>\n</li>\n</ol>\n<p>It should be noted that applying <em><span style=\"font-family:Courier New;\">@Indexed</span></em> on a class/interface where no other standard stereotype (as from points 1 and 2) is present will not make by itself the bean managed, thus not making them eligible for being autowired, but merely provide a hint that it represents a stereotype to be considered in the index. </p>\n<p>Example of the <em><span style=\"font-family:Courier New;\">@Indexed</span></em> annotation usage:</p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@Indexed</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">IndexedCustomService</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span></code></pre></div>\n<p>Or as meta annotated as below: </p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@Target</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">ElementType</span><span class=\"token punctuation\">.</span>TYPE<span class=\"token punctuation\">)</span>\n<span class=\"token annotation punctuation\">@Retention</span><span class=\"token punctuation\">(</span><span class=\"token class-name\">RetentionPolicy</span><span class=\"token punctuation\">.</span>RUNTIME<span class=\"token punctuation\">)</span>\n<span class=\"token annotation punctuation\">@Documented</span>\n<span class=\"token annotation punctuation\">@Indexed</span>\n<span class=\"token keyword\">public</span> <span class=\"token annotation punctuation\">@interface</span> <span class=\"token class-name\">IndexedCustom</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span>\n\n<span class=\"token annotation punctuation\">@IndexedCustom</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">IndexedCustomExample</span> <span class=\"token punctuation\">{</span> <span class=\"token punctuation\">}</span></code></pre></div>\n<p>In order to make them available in the application context, thus autowirable, we could for example include them in the component scan filters as shown below: </p>\n<div class=\"gatsby-highlight\" data-language=\"java\"><pre class=\"language-java\"><code class=\"language-java\"><span class=\"token annotation punctuation\">@SpringBootApplication</span>\n<span class=\"token annotation punctuation\">@ComponentScan</span><span class=\"token punctuation\">(</span>basePackages<span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token string\">\"com.auto1\"</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">,</span> includeFilters <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span>\n  <span class=\"token annotation punctuation\">@ComponentScan.Filter</span><span class=\"token punctuation\">(</span>type<span class=\"token operator\">=</span> ANNOTATION<span class=\"token punctuation\">,</span> value <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token class-name\">IndexedCustomExample</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span><span class=\"token punctuation\">,</span>\n  <span class=\"token annotation punctuation\">@ComponentScan.Filter</span><span class=\"token punctuation\">(</span>type<span class=\"token operator\">=</span> ASSIGNABLE_TYPE<span class=\"token punctuation\">,</span> value <span class=\"token operator\">=</span> <span class=\"token punctuation\">{</span><span class=\"token class-name\">IndexedCustomService</span><span class=\"token punctuation\">.</span><span class=\"token keyword\">class</span><span class=\"token punctuation\">}</span><span class=\"token punctuation\">)</span>\n  <span class=\"token punctuation\">}</span>\n<span class=\"token punctuation\">)</span>\n<span class=\"token keyword\">public</span> <span class=\"token keyword\">class</span> <span class=\"token class-name\">Application</span> <span class=\"token keyword\">implements</span> <span class=\"token class-name\">WebMvcConfigurer</span> <span class=\"token punctuation\">{</span>\n    <span class=\"token comment\">// ...</span>\n<span class=\"token punctuation\">}</span></code></pre></div>\n<p>There is one big pitfall that comes with the feature though, that is by default the index is automatically enabled when a <em><span style=\"font-family:Courier New;\">META-INF/spring.components</span></em> file is found on the classpath during application start-up and this in turn, disables completely the regular classpath component scanning. In other words, both the index and classpath scanning cannot be used at the same time. The consequence of this is that if an index is available for some JARs, but is incomplete due to including other JARs that come with potential bean candidates but don’t have the index file generated, those beans will not be discovered, thus application startup might spectacularly fail with a well known error: <em><span style=\"font-family:Courier New;\">“X required a bean of type 'Y’ that could not be found”</span></em>.\nFortunately, Spring engineers, as thoughtful as they are, provided a feature flag <em><span style=\"font-family:Courier New;\">spring.index.ignore</span></em> that can be passed either as a system property or set in the <em><span style=\"font-family:Courier New;\">spring.properties</span></em> file at the root of the classpath allowing us to disable the usage of the candidate component index altogether. </p>\n<h2>Test me one more time</h2>\n<p>Like mentioned before, the aim of our investigation was to understand if the Spring indexer feature is of any help to us in regards to boosting the application start-up times. In order to make the test fairly reliable and control as many confounding factors as possible, we have decided to run the test firstly, using a “dummy” Spring 5 application (in two flavors - with 120 and 5000 beans) using all the features a production microservice would use, on a bare metal machine i.e. a <span style=\"font-family:Courier New;\">MacBook Pro late 2017 i7</span>. Secondly, running a real life production service in a less controlled but close to production environment i.e. on a <span style=\"font-family:Courier New;\">r4.8xlarge</span> EC2 instance with other services running concurrently. The applications themselves were using <span style=\"font-family:Courier New;\">Spring 5.0.10.RELEASE</span> with <span style=\"font-family:Courier New;\">Spring Cloud Finchley.SR2</span> release train.\nAfter making a couple of hundred measurements of application bootstrapping times with candidate component index enabled / disabled and verifying in the logs that Spring was indeed using (or not) the component candidate list, we ended up with following averaged results:</p>\n<p><a\n    class=\"gatsby-resp-image-link\"\n    href=\"/static/30322fc0fcb831c8c776c8ac7df43562/a0e60/measurements.jpg\"\n    style=\"display: block\"\n    target=\"_blank\"\n    rel=\"noopener\"\n  >\n    <span\n    class=\"gatsby-resp-image-wrapper\"\n    style=\"position: relative; display: block;  max-width: 590px; margin-left: auto; margin-right: auto;\"\n  >\n    <span\n      class=\"gatsby-resp-image-background-image\"\n      style=\"padding-bottom: 70%; position: relative; bottom: 0; left: 0; background-image: url('data:image/jpeg;base64,/9j/2wBDABALDA4MChAODQ4SERATGCgaGBYWGDEjJR0oOjM9PDkzODdASFxOQERXRTc4UG1RV19iZ2hnPk1xeXBkeFxlZ2P/2wBDARESEhgVGC8aGi9jQjhCY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2NjY2P/wgARCAAOABQDASIAAhEBAxEB/8QAGAAAAgMAAAAAAAAAAAAAAAAAAAMCBAX/xAAVAQEBAAAAAAAAAAAAAAAAAAABAv/aAAwDAQACEAMQAAAB3qb1i4kM/wD/xAAaEAACAwEBAAAAAAAAAAAAAAABAgAREhMD/9oACAEBAAEFArnUxDaP56PGKMr/AP/EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQMBAT8BP//EABQRAQAAAAAAAAAAAAAAAAAAABD/2gAIAQIBAT8BP//EABgQAQEAAwAAAAAAAAAAAAAAAAABEDFB/9oACAEBAAY/AnEuNpH/xAAYEAEBAQEBAAAAAAAAAAAAAAABEQAhMf/aAAgBAQABPyFB6husmO16mK1mi2t3Gw3/2gAMAwEAAgADAAAAEJM//8QAGhEAAgIDAAAAAAAAAAAAAAAAABEBIYHh8P/aAAgBAwEBPxCF2dENWf/EABoRAAICAwAAAAAAAAAAAAAAAAARASGB4fD/2gAIAQIBAT8Ql9jZKdH/xAAdEAEBAAEEAwAAAAAAAAAAAAABEQAhMUFhccHR/9oACAEBAAE/EF55xmDyDZWb6p6yXcazAScAaHdwDquO37giISrzn//Z'); background-size: cover; display: block;\"\n    ></span>\n    <img\n        class=\"gatsby-resp-image-image\"\n        style=\"width:100%;height:100%;margin:0;vertical-align:middle;position:absolute;top:0;left:0;box-shadow:inset 0px 0px 0px 400px white;\"\n        alt=\"Measurements\"\n        title=\"\"\n        src=\"/static/30322fc0fcb831c8c776c8ac7df43562/f8fb9/measurements.jpg\"\n        srcset=\"/static/30322fc0fcb831c8c776c8ac7df43562/e8976/measurements.jpg 148w,\n/static/30322fc0fcb831c8c776c8ac7df43562/63df2/measurements.jpg 295w,\n/static/30322fc0fcb831c8c776c8ac7df43562/f8fb9/measurements.jpg 590w,\n/static/30322fc0fcb831c8c776c8ac7df43562/a0e60/measurements.jpg 800w\"\n        sizes=\"(max-width: 590px) 100vw, 590px\"\n      />\n  </span>\n  </a></p>\n<h2>The expected disappointment</h2>\n<p>As the above results have consistently shown, there was no improvement or even insignificant worsening (which will be a subject of our analysis in a separate article) of the bootstrapping time of the Spring applications, regardless of the number of candidate beans present, when using the pre-generated candidate component index. This was expected, as explained in the opening section, the conditions for which this feature was designed (slow or expensive IO operations) are simply not met in our case. One more important factor to consider here, is that a great deal of the beans in our Spring applications, is created using bean definition factory methods, which are not subject to the component classpath scanning, thus are not impacted by this feature. </p>\n<p>All in all, we deemed this feature not to be useful for our purposes and moved on to the next item on our list.</p>","fields":{"slug":"/spring-5-indexer/","tags":["spring","spring-indexer","bootstrapping","performance"]}}}]}},"pageContext":{"slug":"/tags/spring","tag":"spring","categories":["Architecture","Coding","DevOps","Engineering","ProjectManagement","QA","Social","TechRadar"]}}