[JAVA] [Comparison verification] How different is the development productivity of Spring Boot apps from the past?

** How different is the development productivity of Spring Boot-based applications compared to developing legacy web applications? ** **

With a web application "EasyBuggy" based on a little old technology and its clone (exactly the same function) Compare a Spring Boot-based "Easy Buggy Boot" with the following numbers that affect development productivity: I tried it.

--Time to build

Differences in configuration

By the way, the main differences in configuration are as follows.

Difference EasyBuggy EasyBuggy Boot
Base technology Servlet 3.0.1 Spring Boot 1.5.6 (Servlet 3.0.1)
Presentation layer unused(Some JSP 2.2 + JSTL 1.2) Thymeleaf 2.1.5 (Some JSP 2.3 + JSTL 1.2)
Java container Apache Tomcat/7.0.37 Apache Tomcat/8.5.16
DB client/server JDBC / Derby 10.8.3.0 Spring JDBC 4.3.9 / Derby 10.12.1.1 (For Java 7), Or 10.13.1.1 (For Java 8)
LDAP client/server Apache DS Client API 1.0.0 / Server 1.5.5 Spring LDAP 2.3.1 / unboundid-ldapsdk 3.2.1
Email JavaMail 1.5.1 JavaMail 1.5.1 (Java Mail introduced by Spring Boot Mail 1.5.Override 6)
Development tools None Spring Boot Developer Tools 1.5.6
Java Supports Java 6 and above Supports Java 7 and above

Time to build

First, the average build time. The result is as follows.

EasyBuggy

About 6 seconds

$ mvn package
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building easybuggy 1-SNAPSHOT
[INFO] ------------------------------------------------------------------------

・ ・ ・(Omitted because it is long)・ ・ ・

[INFO] --- tomcat7-maven-plugin:2.1:exec-war-only (tomcat-run) @ easybuggy ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.847s
[INFO] Finished at: Thu Aug 31 23:28:55 JST 2017
[INFO] Final Memory: 36M/220M
[INFO] ------------------------------------------------------------------------

EasyBuggy Boot

About 12 seconds

$ mvn package
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building easybuggy4sb 1.0.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 

・ ・ ・(Omitted because it is long)・ ・ ・

[INFO] --- spring-boot-maven-plugin:1.5.6.RELEASE:repackage (default) @ easybuggy4sb ---
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 12.448s
[INFO] Finished at: Thu Aug 31 23:26:55 JST 2017
[INFO] Final Memory: 26M/242M
[INFO] ------------------------------------------------------------------------

The former is faster by more than double the difference.

By the way, the environment used for verification is CentOS 6.9 on VMWare, CPU 4 cores (Intel® Xeon® X5680 @ 3.33GHz), and memory 4GB. All dependent libraries have been downloaded locally.

Start-up time

The average startup time is as follows.

EasyBuggy

About 3 seconds

$ java -jar easybuggy.jar
8 31, 2017 2:44:56 pm org.apache.coyote.AbstractProtocol init
information: Initializing ProtocolHandler ["http-bio-8080"]
8 31, 2017 2:44:56 pm org.apache.catalina.core.StandardService startInternal
information: Starting service Tomcat
8 31, 2017 2:44:56 pm org.apache.catalina.core.StandardEngine startInternal
information: Starting Servlet Engine: Apache Tomcat/7.0.37
8 31, 2017 2:44:59 pm org.apache.coyote.AbstractProtocol start
information: Starting ProtocolHandler ["http-bio-8080"]

EasyBuggy Boot

About 15 seconds (about 9 seconds for mvn spring-boot: run)

$ java -jar ROOT.war
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/C:/Users/ktamura/git/easybuggy4sb/target/ROOT.war!/WEB-INF/lib/logback-classic-1.1.11.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/C:/Users/ktamura/git/easybuggy4sb/target/ROOT.war!/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.5.6.RELEASE)
2017-08-31 15:08:46.454  INFO 5460 --- [           main] o.t.e.Easybuggy4sbApplication            : Starting Easybuggy4sbApplication v1.0.0-SNAPSHOT on ktamura-PC with PID 5460 (C:\Users\ktamura\git\easybuggy4sb\target\ROOT.war started by k
tamura in C:\Users\ktamura\git\easybuggy4sb)

・ ・ ・(Omitted because it is long)・ ・ ・

2017-08-31 15:09:01.198  INFO 5460 --- [           main] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-08-31 15:09:01.273  INFO 5460 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-08-31 15:09:01.276  INFO 5460 --- [           main] o.t.e.Easybuggy4sbApplication            : Started Easybuggy4sbApplication in 15.253 seconds (JVM running for 15.81)

EasyBuggy starts up very quickly, so it feels slow even in 15 seconds. Looking at the EasyBuggy Boot log, there isn't an extremely time-consuming process, so there may not be much room for improvement. I think that the startup time of Spring Boot will take at least that much.

This result is the result of the built-in Tomcat, but the result is almost the same even when the war file is deployed to the non-built-in Tomcat.

However, EasyBuggy Boot takes about 9 seconds when started with the mvn spring-boot: run command. I compared the logs of both to see why this difference occurs, but it seems that there is no big difference in the processing being executed, and I did not know the clear reason.

Time from source code modification to operation check

The average time from modifying the source code to being able to check the operation is as follows.

EasyBuggy

About 15 seconds

EasyBuggy uses the mvn install command to create a jar file and start it all at once. It takes about 15 seconds to restart, including the time required for this and the stop. Of course, the JVM hot swap will reflect the fixes while debugging, so it won't take much time in actual development ... (and even faster with JRebel).

EasyBuggy Boot

About 3 seconds

2017-09-01 12:04:55.414  INFO 2800 --- [     Thread-104] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@27231264: startup date [Fri Sep 01 12:04:21 JST 2017]; root of context hierarchy
2017-09-01 12:04:55.417  INFO 2800 --- [     Thread-104] ationConfigEmbeddedWebApplicationContext : Closing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@4274ec33: startup date [Fri Sep 01 12:04:23 JST 2017]; parent: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@27231264
2017-09-01 12:04:55.532  INFO 2800 --- [     Thread-104] o.s.c.support.DefaultLifecycleProcessor  : Stopping beans in phase 0
2017-09-01 12:04:55.537  WARN 2800 --- [     Thread-104] o.s.b.f.support.DisposableBeanAdapter    : Invocation of destroy method failed on bean with name 'inMemoryDatabaseShutdownExecutor': java.sql.SQLSyntaxErrorException: Syntax error: Encountered "SHUTDOWN" at line 1, column 1.
2017-09-01 12:04:55.538  INFO 2800 --- [     Thread-104] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown

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

・ ・ ・(Omitted because it is long)・ ・ ・

2017-09-01 12:04:57.895  INFO 2800 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8081 (http)
2017-09-01 12:04:57.897  INFO 2800 --- [  restartedMain] o.s.c.support.DefaultLifecycleProcessor  : Starting beans in phase 0
2017-09-01 12:04:57.923  INFO 2800 --- [  restartedMain] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2017-09-01 12:04:57.925  INFO 2800 --- [  restartedMain] o.t.e.Easybuggy4sbApplication            : Started Easybuggy4sbApplication in 2.125 seconds (JVM running for 828.681)

EasyBuggy Boot has introduced Spring Boot Developer Tools 1.5.6, so the fixes are reflected immediately and you can develop with almost no waiting. This sense of speed is very comfortable.

Number of lines of source code

Let's compare the number of lines in the source code.

EasyBuggy

Type Blank line Comment line Code Number of files
Java 838 359 4881 98
JSP 11 10 810 6
XML 5 0 47 2
HTML 2 0 35 7
properties 71 325 799 6
Total 927 694 6572 119

EasyBuggy Boot

Type Blank line Comment line Code Number of files
Java 663 131 3500 92
HTML 8 39 1034 46
JSP 0 4 149 2
XML 0 1 32 4
SQL 0 0 18 3
JSON 0 0 17 1
DTD 0 0 3 1
LDIF 0 5 46 1
properties 86 321 830 4
Total 757 501 5629 154

While the number of lines of EasyBuggy source code is 6,572, EasyBuggy Boot is 5,629, so using Spring Boot can reduce the number by about 15%. There is a difference of about 1.3 times in the number of files, but I think that it is because EasyBuggy did not separate the screen and logic into separate files (JSP etc.) (* I wanted to explain one bug in one file, so I made it like that I made it).

[Bonus] Readability of source code

How has the readability of the source code changed? For the following functions

xsspage.png

The source code of each is as follows.

EasyBuggy

XSSServlet.java


package org.t246osslab.easybuggy.vulnerabilities;

import java.io.IOException;
import java.util.Locale;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.t246osslab.easybuggy.core.utils.HTTPResponseCreator;
import org.t246osslab.easybuggy.core.utils.MessageUtils;

@SuppressWarnings("serial")
@WebServlet(urlPatterns = { "/xss" })
public class XSSServlet extends HttpServlet {

    private static final Logger log = LoggerFactory.getLogger(XSSServlet.class);

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {

        String string = req.getParameter("string");
        Locale locale = req.getLocale();

        StringBuilder bodyHtml = new StringBuilder();

        bodyHtml.append("<form action=\"xss\" method=\"post\">");
        bodyHtml.append(MessageUtils.getMsg("description.reverse.string", locale));
        bodyHtml.append("<br><br>");
        bodyHtml.append(MessageUtils.getMsg("label.string", locale) + ": ");
        bodyHtml.append("<input type=\"text\" name=\"string\" size=\"100\" maxlength=\"100\">");
        bodyHtml.append("<br><br>");
        bodyHtml.append("<input type=\"submit\" value=\"" + MessageUtils.getMsg("label.submit", locale) + "\">");
        bodyHtml.append("<br><br>");

        if (!StringUtils.isBlank(string)) {
            // Reverse the given string
            String reversedName = StringUtils.reverse(string);
            bodyHtml.append(MessageUtils.getMsg("label.reversed.string", locale) + " : "
                + reversedName);
        } else {
            bodyHtml.append(MessageUtils.getMsg("msg.enter.string", locale));
        }
        bodyHtml.append("<br><br>");
        bodyHtml.append(MessageUtils.getInfoMsg("msg.note.xss", locale));
        bodyHtml.append("</form>");

        HTTPResponseCreator.createSimpleResponse(req, res, MessageUtils.getMsg("title.xss.page", locale),
                bodyHtml.toString());
    }
}

EasyBuggy Boot

xss.html


<!DOCTYPE html SYSTEM "http://www.thymeleaf.org/dtd/xhtml1-strict-thymeleaf-spring4-4.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org" th:with="lang=${#locale.language}" th:lang="${lang}">
<div th:replace="head"></div>
<body style="margin-left: 20px; margin-right: 20px;">
	<div th:replace="header"></div>
	<form action="xss" method="post">
		<p th:text="#{description.reverse.string}" /><br />
		<label th:text="#{label.string}"></label><span>: </span>
			<input type="text" name="string" size="100" maxlength="100" /><br /><br /> 
			<input type="submit" /><br /><br />
			<p th:utext="${msg}" /><br />
		<div class="alert alert-info" role="alert">
			<span class="glyphicon glyphicon-info-sign" th:utext="#{msg.note.xss}"></span>
		</div>
	</form>
</body>
</html>

XSSController.java


package org.t246osslab.easybuggy4sb.vulnerabilities;

import java.util.Locale;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class XSSController {

    @Autowired
    MessageSource msg;

    @RequestMapping(value = "/xss")
    public ModelAndView process(@RequestParam(value = "string", required = false) String string, ModelAndView mav,
            Locale locale) {
        mav.setViewName("xss");
        mav.addObject("title", msg.getMessage("title.xss.page", null, locale));
        if (!StringUtils.isBlank(string)) {
            // Reverse the given string
            String reversedName = StringUtils.reverse(string);
            mav.addObject("msg", msg.getMessage("label.reversed.string", null, locale) + " : " + reversedName);
        } else {
            mav.addObject("msg", msg.getMessage("msg.enter.string", null, locale));
        }
        return mav;
    }
}

Since the function itself is simple, there may not be a big difference in readability, but the latter is easier for programmers and designers to share the work. Of course, if you use JSP for the former, it will be close to that.

Recommended Posts

[Comparison verification] How different is the development productivity of Spring Boot apps from the past?
[Verification] Comparison of Spring Boot vs Micronaut boot speed
The story of raising Spring Boot from 1.5 series to 2.1 series part2
[Swift] The color of the Navigation Bar is different (lighter) from the specified color.
[Spring Boot] I investigated how to implement post-processing of the received request.
How to set environment variables in the properties file of Spring boot application
What is different from the PHP language. [Note]
The story of raising Spring Boot 1.5 series to 2.1 series
Let's check the feel of Spring Boot + Swagger 2.0
The official name of Spring MVC is Spring Web MVC
[Spring Boot] How to refer to the property file
How to write Scala from the perspective of Java
The comparison of enums is ==, and equals is good [Java]
Access the built-in h2db of spring boot with jdbcTemplate
05. I tried to stub the source of Spring Boot
I tried to reduce the capacity of Spring Boot
How to use CommandLineRunner in Spring Batch of Spring Boot
How to boot by environment with Spring Boot of Maven
A record of studying the Spring Framework from scratch
How is the next value of the Time object correct?