Showing posts with label software engineer. Show all posts
Showing posts with label software engineer. Show all posts

Wednesday, August 5, 2020

How to build a helloworld springboot application in 5 minutes

We assume you have maven installed and know how to run basic command such as "mvn clean package". We also assume you have eclipse with maven plugin installed.

Now our goal is to build a spring application that can print out "hello springboot" in 5 minutes.

Ready?

step 1, Let's build an empty springboot project with spring io. Spring provided an online tool which will create the folder structures, pom file with artifact/group, maven dependencies, plugins, parent pom ready. It will also generate a dummy application.properties, springboot application main class file for us for free. Anyway, who want to write all those boiling plates for the helloworld program?

click generate to create a springboot application


  • go to https://start.spring.io/ fill in the group and artifact, click generate. 
  • once the website generated your spring boot code then downloaded to your local and unzip it.
  • open a shell command window, go to your unzipped directory, issue the following command

springboot>cd Downloads/mydemo
springboot>mvn clean package
springboot>tree

The maven will download all the dependencies specified in the pom.xml and have the project compiled.

step 2. import the project into eclipse. We will run the springboot application, make sure it is start without exceptions. 
import existing maven project

  • click File -> Import.. 
  • under Maven, select Existing Maven Project
  • navigate to the unzipped folder and click open
Step 3. Run the springboot. The entry point of spring boot is nothing but a main method.

package my.example.com.mydemo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MydemoApplication {

public static void main(String[] args) {
SpringApplication.run(MydemoApplication.class, args);
}

}

We right click the file, then select Run As Java Application.
We just ran the famous springboot application. Nothing special, the output should looks like the following.


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

2020-06-02 14:48:47.315  INFO 5407 --- [           main] my.example.com.mydemo.MydemoApplication  : Starting MydemoApplication on Meifangs-MBP.home with PID 5407 (/Users/homenetwork/Downloads/mydemo/target/classes started by homenetwork in /Users/homenetwork/Downloads/mydemo)
2020-06-02 14:48:47.319  INFO 5407 --- [           main] my.example.com.mydemo.MydemoApplication  : No active profile set, falling back to default profiles: default
2020-06-02 14:48:47.945  INFO 5407 --- [           main] my.example.com.mydemo.MydemoApplication  : Started MydemoApplication in 1.091 seconds (JVM running for 2.206)

Step 4. print hello springboot.

The default main method did many things though appears doing nothing. The spring framework is started, it looking for the @SpringBootApplication annotation then register this class as the entry point.  Next, we will add a springboot scheduler, so that it keep printing "hello springboot" every 10 seconds.

  • modify the MydemoApplication.java, at the class level add the @EnalbeScheduling annotation, then add a method with annotation @scheduled(fixedRate=5000)
package my.example.com.mydemo;

import java.time.LocalDateTime;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class MydemoApplication {

public static void main(String[] args) {
SpringApplication.run(MydemoApplication.class, args);
}
@Scheduled(fixedRate = 5000)
void updateStatus() {
    System.out.println("hello springboot at " + LocalDateTime.now());
}
}

Run the program again. This time, the spring boot will print hello springboot every 5 seconds until you stop it. The annotation @EnableScheduling tells the spring framework that this springboot application will invoke springboot schedulers to run periodic tasks. With this information, the spring framework (imaging spring framework as a magician and your annotations are spells) then searches methods with annotation @scheduled, the annotated method will be the task run periodically. The @scheduled annotation may also have information such as period, delay etc. The spring framework will read in those information, and schedule the task when the spring boot application start.

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

2020-06-02 15:00:10.434  INFO 5424 --- [           main] my.example.com.mydemo.MydemoApplication  : Starting MydemoApplication on Meifangs-MBP.home with PID 5424 (/Users/homenetwork/Downloads/mydemo/target/classes started by homenetwork in /Users/homenetwork/Downloads/mydemo)
2020-06-02 15:00:10.437  INFO 5424 --- [           main] my.example.com.mydemo.MydemoApplication  : No active profile set, falling back to default profiles: default
2020-06-02 15:00:11.194  INFO 5424 --- [           main] o.s.s.c.ThreadPoolTaskScheduler          : Initializing ExecutorService 'taskScheduler'
2020-06-02 15:00:11.236  INFO 5424 --- [           main] my.example.com.mydemo.MydemoApplication  : Started MydemoApplication in 1.284 seconds (JVM running for 2.292)
hello springboot at 2020-06-02T15:00:11.239
hello springboot at 2020-06-02T15:00:16.213
hello springboot at 2020-06-02T15:00:21.213

Step 5. add a little bit seasonings to the hello springboot.

We can make the helloworld program a little bit tasty by making it configurable. For example, we can set the time interval as a variable, then put it in a configuration file, so that we don't have to change the java class if we decided to use a shorter or longer interval later.
application.properties

  • set the properties file. Open application add content 
statusLoader.delay=PT10S
  • modify the main class.
package my.example.com.mydemo;

import java.time.LocalDateTime;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;

@SpringBootApplication
@EnableScheduling
public class MydemoApplication {

public static void main(String[] args) {
SpringApplication.run(MydemoApplication.class, args);
}
@Scheduled(initialDelay=3000L, fixedDelayString="${statusLoader.delay}")
void updateStatus() {
    System.out.println("hello springboot at " + LocalDateTime.now());
}
}
run the program again
This time, the time interval changes to 10 seconds.
You can change it to longer and shorter time interval and observe the changes. 

The spring framework by default load any properties defined in the application.properties file. When a variable is referenced as a meta data in an annotation, "${statusLoader.delay}, spring framework will check if any properties in its context matches, then replace the variable with the value string. Now the spell became concrete @Scheduled(initialDelay=3000L, fixedDelayString=PT10S), the spring framework can create a scheduler thread under the sleeve, run every 10 seconds with 3 seconds initial delay. The scheduled threadpool has similar effect as the following code:

    Executors.newScheduledThreadPool(1).scheduleAtFixedRate(() 
            -> System.out.println("hello springboot at" + LocalDateTime.now()), 
            3, 10, TimeUnit.SECONDS);

Hope this helps.


Friday, July 31, 2020

JVM optimization for java 8

Starting with Java 8, the Metaspace replaces the PermGen. 
So let's discuss JVM performance tuning in the context of JDK9+. 

Java 8 JVM model

The java 8 JVM model (in the light of Serial Garbage Collection model) looks like the following:

JVM


Heap is a dedicated memory space for the JVM objects.

The Heap is divided into 2 parts, Young Generation and Old Generation. 

Young Generation heap is further divided into Eden space and 2 survivor spaces. 

The metadata information of the JVM is stored in a native memory called "MetaSpace" (They are previously stored in PermGen space). This MetaSpace memory region is not a contiguous Java Heap memory. This space is used by JVM for Garbage collection, auto tuning, concurrent de-allocation of metadata.

The Young Generation heap stores the short lived java objects, while the Old Generation heap stores the long lived java objects. Most of the java objects are short lived. Iterator objects are example of short lived objects with lifespan of a single loop. Some objects live long, the object created in public static main() could live until the program exits.

A java object's life cycle starts with keyword new. It is referenced by other objects and it references other objects to get the work done. Once there is no reference to the object, it can be garbage collected. Once garbaged collected, the java object no longer exists in heap. The majority of the java objects dies in Eden space. A few java objects die in Old generation heap.

Garbage collection occurs in each generation heap when the generation fills up. 

When the Eden space fills up, it causes a minor garbage collection in which only a few live objects in young generation are saved into survivor space (no garbage collection happens in old generation heap). The costs of such collections are small, a young generation full of dead objects is garbage collected very quickly, because only few live objects are copied to survivor space, then the Eden space is marked clean. 

During each minor garbage collection, some fraction of the surviving objects from the young generation are moved to the old generation. Eventually, the old generation will fill up and must be collected, resulting in a major garbage collection, in which the entire heap is garbage collected. Major collections usually last much longer than minor collections because a significantly larger number of objects are involved.

The vassal from young generation to old generation are the two survivor spaces in young generation heap. Most objects are initially allocated in eden. One survivor space is empty at any time, and serves as the destination of any live objects in eden; the other survivor space is the destination during the next copying collection. Objects are copied between survivor spaces in this way until they are old enough to be tenured (copied to the old generation).

JVM performance tuning strategies


This is probably the most import picture of understanding JVM optimization. Now we can understand how these parameters control JVM performance:
  •  -XX:MinHeapFreeRatio=<minimum>
  • -XX:MaxHeapFreeRatio=<maximum>
  • -Xms<min>
  • -Xmx<max>
  • -XX:NewRation=3
  • -XX:SurvivorRatio=6

The above JVM model has a few basic aspects. 

First of all, the total heap size the JVM controls. This decide how much resources are allocated to this particular JVM. More memory the JVM has, more capable it is. The total heap size is bounded below by -Xms<min> and above by -Xmx<max>. JVM shrinks and expands heap size between -Xms and -Xmx, reserve some memory to itself in case Young Generation or Old Generation need reinforcement.  JVM decides when to re-enforce a generation according to the ratio of free space to live objects. This target range is set as a percentage by the parameters -XX:MinHeapFreeRatio=<minimum> and -XX:MaxHeapFreeRatio=<maximum>. For example, for range 40% to 70%, if the percent of free space in a generation falls below 40%, then JVM will use the reserved memory to expand the generation to maintain 40% free space. Similarly, if the free space of a generation exceeds 70%, then the JVM will take some space away from the generation as JVM reservation. Setting -Xms and -Xmx to the same value increases predictability, however, the JVM is then unable to compensate in bad times. 

The second most important aspects of JVM model is the ratio of heap dedicated to the young generation and old generation. The bigger a generation, the less often garbage collections occur. XX:NewRatio controls this aspects. For example, setting -XX:NewRatio=3 means that the ratio between the young and old generation is 1:3. In other words, the combined size of the eden and survivor spaces will be one-fourth of the total heap size. Increase the ratio, for example, means bigger young generation and smaller old generation, which implies less minor GC and more full GC. 

Guess what does -XX:SurvivorRatio=6 mean? It sets the ratio between eden and a survivor space to 1:6. In other words, each survivor space will be one-sixth the size of eden, and thus one-eighth the size of the young generation (not one-seventh, because there are two survivor spaces). If survivor spaces are too small, copying collection overflows directly into the old generation. If survivor spaces are too large, they will be uselessly empty. 

The above generation based garbage collection model and parameters are the essence of JVM performance tuning. 

JVM Garbage Collector Options


The rest of this article covers the techniques JVM used to augment this generation based GC model with multi-cores.

Actually, JVM has 3 modes for generation based garbage collection. They can be selected with one of the following flags:

  1. -XX:+UseSerialGC

  2. -XX:+UseParallelGC

  3. -XX:+UseG1GC

The XX:+UseConcMarkSweepGC is not in this list, because it is replaced by -XX:+UseG1GC in JDK9 and now is practically obsolete. 

We can skip -XX:+UseSerialGC, all we have to know is, it tells JVM to use Serial Garbage Collector, which is what we have talked so far.

Parallel Garbage Collector


-XX:+UseParallelGC tells JVM to use Parallel Garbage Collector mode. While the serial garbage collector uses a single thread to perform all garbage collection work, the parallel garbage collector, by default, execute both minor and major collections with multiple threads. ParallelGC usually performs significantly better than the serialGC when more than two processors are available. The number of garbage collector threads can be controlled with the command-line option -XX:ParallelGCThreads=<N>. Because multiple garbage collector threads are participating in a minor collection, some fragmentation is possible due to promotions from the young generation to the old generation during the collection. Each garbage collection thread involved in a minor collection reserves a part of the old generation for promotions and the division of the available space into these "promotion buffers" can cause a fragmentation effect. Reducing the number of garbage collector threads and increasing the size of the old generation will reduce this fragmentation effect.

The ParallelGC allows automatic tuning by specifying specific behaviors instead of generation sizes and other low-level tuning details. 

-XX:MaxGCPauseMillis=<N> hints JVM that pause times of <N> milliseconds or less are desired.
Put emphasis on this goal could reduce the overall throughput of the application and the desired pause time goal may not be met.

-XX:GCTimeRatio=<N> sets the ratio of garbage collection time to application running time to 1/(1+<N>). This flag hints JVM to put emphasis on meeting the throughput goal. Again, this goal can harm the pause goal and may not be achieved.  

The ParallelGC has an implicit goal of minimizing the size of the heap set by -Xmx<N> as long as the other goals are being met.

There are other flags to control other aspects of parallel garbage collector. Generation size adjustments, for example, are controlled by 
XX:YoungGenerationSizeIncrement=<Y>  
-XX:TenuredGenerationSizeIncrement=<T> 
-XX:AdaptiveSizeDecrementScaleFactor=<D>

G1 Garbage Collector

G1 Garbage Collector is the default GC in JDK9. It has the shortest pause time among the 3 GC options.

  1. SerialGC has a smallest GC overhead, if the application has a small data set (up to approximately 100 MB), or it is running on a single core and there are no pause time requirements, then option -XX:+UseSerialGC is the best choice. 
  2. If peak application performance is the first priority and (b) there are no pause time requirements or pauses of 1 second or longer are acceptable, then select the parallel collector with -XX:+UseParallelGC. 
  3. If response time is more important than overall throughput and garbage collection pauses must be kept shorter than approximately 1 second, then select the concurrent collector with -XX:+UseG1GC.
Now let's study how G1 Garbage collector works and how it achieve short garbage collection pauses.

Recall that SerialGC divides the heap into 3 regions: eden space, survivor space and old generation space. SerialGC perform many minor garbage collections in eden space. After each minor GC, it saves the survivor objects into survivor space. Eventually fraction of the survivor space objects are saved into old generation space. Once the old generation space is slowly filled up, a stop of the world full garbage collection is performed across the whole heap.

G1GC also divide the heap into not 3 regions but 3 types of regions: eden regions, survivor regions and old generation regions. (4 types of regions if you like, 3 types plus un-allocated empty slots.) The region size depends on the heap size, 2000 regions (or empty slots) per heap is quite typical.

Young Collections


Young Collections

At the beginning, most of the regions are un-allocated empty slots, JVM starts to allocate some regions and create new objects in them, we call those regions eden regions. The short lived java objects quickly born, die and fill up these initially empty eden regions. After a certain number of eden regions are allocated, the JVM will perform minor garbage collections to copy survivor objects to a small number of allocated regions which we call survivor regions. The few objects survived many minor garbage collections, thus are copied from survivor regions into some allocated regions, which we call old generation regions. These process happened in many regions in the heap concurrently.

Young Collection + Concurrent Mark

As the heap ages, after numerous minor garbage collections, more and more survivors entered old generation regions. Eventually, the original empty heap are now full of old generation regions. It is time for the JVM to prepare the whole heap clean up. In the phase of Young Collection + Concurrent Mark, JVM scans the old generation regions in the heap to mark the live objects in them, concurrently. While the JVM perform the concurrent live objects mark, the application is running, the minor young garbage collection is continuing. The world does not stop, it is running as usual, maybe just a little bit slower than the Young collections phase.

Mixed Collections

Young Collection + Concurrent Mark is just a short phase, in no time, JVM finished marking the live objects in the old generation regions, the heap now entered the final phase of a heap life cycle -- mixed collections.

Mixed Collections


During this phase, the normal Eden regions -> survivor regions and survivor regions -> old generation regions copies continue as usual. However these normal minor Young generation GCs are now mixed with whole heap old generation GCs. Additionally, JVM compactly copies the live objects in most of the old generation regions into a small number of old generation regions, thus put more and more old generation regions back to empty slot. 

This process continues until most of the old generation regions are returned back to empty slot, the few old generation regions left are full of live objects and not worth further garbage collecting.

The heap is now reborn, JVM stops Mixed Collections phase, and starts Young Collections phase, the beginning of a new cycle. 

As you can see, in the 3 phases of heap life cycle, the regions are concurrently updated, there are always regions for creating new objects and there is no stop the world events happening. That is the reason G1GC has the lowest pause time among the 3 JVM GC options. 

Wednesday, July 29, 2020

mysql pagenation and penalty of it

we usually use mysql select query to get a list of database entity:
select 'id', 'name' from client;

what happens if there are 1 million rows to query?

General strategy is don't query a table without where clause. Always add a filter

select 'id', 'name' from client where 'created' between '2012-03-11 00:00:00' and '2012-05-11 23:59:00';

if created is indexed, the above query will run fast and return a small number of rows.

Mysql technically provides a pagination function.

The following query will return the first 500 rows of the 1 million rows in client table.
select 'id', 'name' from client limit 500;

In order to get the second 500 rows, we can add an offset
select 'id', 'name' from client limit 500 offset 500;

next 500, we increase the offset
select 'id', 'name' from client limit 500 offset 1000;
...
The same query has a shortcut
select 'id', 'name' from client limit 500 offset 100000;
is equivalent to
select 'id', 'name' from client limit 1000000, 500;

This approach has a penalty, if we ran the query
explain select 'id', 'name' from client limit 100000, 500;

the result shows, the sql actually read 100500 rows by the primary key, then throw away the first 100000 rows. What a waste. The memory is occupied, lots of rows are read. It is ok for small offset, but bad for large offset. This kind of query should not happen in production, it has danger of draining the database resource.

Alternatively, we can avoid large offset query by redesign the UI. We allow user to access the first a few pages, the query has small offset anyway. If the user have to click into deeper pages, we'd better to redesign the data model. Redirect the user to a different page, where a different data model is used. Say, we can partition the table:
CREATE TABLE client (
id INT NOT NULL,
name VARCHAR(20) NOT NULL,
created DATETIME NOT NULL )
PARTITION BY RANGE( YEAR(created) )(
    PARTITION from_2013_or_less VALUES LESS THAN (2014),
    PARTITION from_2014 VALUES LESS THAN (2015),
    PARTITION from_2015 VALUES LESS THAN (2016),
    PARTITION from_2016_and_up VALUES LESS THAN MAXVALUE

For the first a few pages, we use
SELECT * FROM client PARTITION (from_2016_and_up) WHERE created >= '2016-01-01' limit N, 500;

Then for more pages we just use a different query
SELECT * FROM client PARTITION (from_2015) WHERE created between '2015-01-01 00:00:00' and '2015-12-31 23:59:00' limit N, 500;

If we makes more partitions, we can change to use more queries.

This way, we don't have to read a huge amount of rows, we just read from the relevant partition.

Another good question to ask is: why we need to present this large amount of data to the user? Should we spit the tables or add some filter to limit the total dataset, such as date range? Does the user really want to click lots of pages to find the information instead of refining the filtering criteria? If they want to do that, let's don't allow the random page jump, just provide prev and next (maybe 1 to 10 page jump) hyperlinks, that will reasonably encourage the user to think harder. If the amount of rows need to display have to be large, why not use reactive http client with reactive mysql db support? Let's use webflux to have the db entity stream to the client side, so that while user scroll down the page, new rows get loaded dynamically.



Friday, July 17, 2020

How springboot logging works

Spring is an opinionated framework, which means it chooses a reasonable default for you if you don't choose it yourself.

What logging facility the springboot chooses for you by default?

The answer is slf4j + logback

When you set spring boot dependency in the pom

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

That dependency depends on (thus brings in) spring-boot-starter-logging, which depends on sping-jcl (spring commons logging bridge), which is the logging facility springs framework use to do the log.

In the code, we just have to do the log as the following:
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.example.my.mydemo.handler.ClientHandler;
import com.example.my.mydemo.model.Client;

@RestController
public class ClientController {
    Logger logger = LoggerFactory.getLogger(ClientController.class);

    @Autowired
    Environment env;
    
    private final ClientHandler clientHandler;
    @Autowired
    public ClientController(ClientHandler clientHandler) {
        this.clientHandler = clientHandler;
    }
    
    @GetMapping("/client")
    public Client getClient(@RequestParam(value = "id", defaultValue = "1") String id) {
        logger.debug("/client requested with parameter {}", id);
        return clientHandler.handle(id);
    } 
    
    @GetMapping("/")
    public String test() {
        return env.getProperty("Greetings.Visit");
    }
    
    @Override
    public String toString() {
        return "ClientController, use id to lookup client information, url: http://localhost:8080/client?id=1";
    }

}

The slf4j is just an interface, the jar classes that implement these interfaces are log4j, which is in the class path of your maven dependencies. As you already guessed, spring did the default config for you.

mvn dependency:tree
...
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.3.0.RELEASE:compile
[INFO] |  +- org.springframework.boot:spring-boot-starter:jar:2.3.0.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot:jar:2.3.0.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-autoconfigure:jar:2.3.0.RELEASE:compile
[INFO] |  |  +- org.springframework.boot:spring-boot-starter-logging:jar:2.3.0.RELEASE:compile
[INFO] |  |  |  +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] |  |  |  |  \- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] |  |  |  +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.13.2:compile
[INFO] |  |  |  |  \- org.apache.logging.log4j:log4j-api:jar:2.13.2:compile
[INFO] |  |  |  \- org.slf4j:jul-to-slf4j:jar:1.7.30:compile
...

With these logging facilities in place, we just have to follow the spring's convention to configure the logging facility if you don't like the default. The convention is:
as long as you put a file named logback.xml or spring-logback.xml under directory src/main/resources, spring will use the file to configure logback.
Here is an example logback.xml content. By default, logback log level is INFO, the reconfiguration want to log DEBUG for class ClientController and JdbcTemplate. While the log of  ClientController is print to the console, which is the default, the JdbcTemplate is write to /logs.sql with dates postfix in the file name logs/sql.20-06-30_19.log, the file name changes with time, and the content looks like:
"2020-06-30 19:12:44,252 DEBUG[http-nio-8080-exec-1] o.s.j.c.JdbcTemplate - Executing prepared SQL statement [select name, email, id from Client where id = ?]"

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<include resource="org/springframework/boot/logging/logback/base.xml"/>
<appender name="MyFile" class="ch.qos.logback.core.rolling.RollingFileAppender">
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>
logs/sql.%d{yy-MM-dd_HH}.log
</fileNamePattern>
</rollingPolicy>
<encoder>
<pattern>
%date{ISO8601} %-5level[%thread] %logger{1} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="com.example.my.mydemo.controller.ClientController" level="DEBUG"/>
<logger name="org.springframework.jdbc.core.JdbcTemplate" level="DEBUG">
<appender-ref ref="MyFile"/>
</logger>
</configuration>

Now setback and enjoy the springboot logging for free (almost).

FYI.
If you are a fan of lombok. The one line 
Logger logger = LoggerFactory.getLogger(ClientController.class);
can be replaced by lombok annotation @Slf4j
the annotation will generate one line of code for you
Logger log = LoggerFactory.getLogger(ClientController.class);

then you can use variable log in later code.

Actually, to use lombok you have to add maven dependency, then add one extra import line in java code, so it is just a stylish choice for some developers.
...
import com.example.my.mydemo.model.Client;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
public class ClientController { 
...
    @GetMapping("/client")
    public Client getClient(@RequestParam(value = "id", defaultValue = "1") String id) {
        log.debug("/client requested with parameter {}"id);
        return clientHandler.handle(id);
    }
...
For more information about lombok, check out How Lombok save developer's time.

Sunday, July 12, 2020

How Lombok saves developers' time

Nowadays, all the cool kids use lombok to make beautiful java code. So let's talk about it.

Project Lombok is a java library that automatically plugs into your editor and build tools to generate boilerplate code such as class constructors, field getter and setter, hashCode etc.

In order to use it, set lombok as one of the maven dependencies.

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>

With the dependency, a lombok.jar will be introduced in the project compilation classpath. The lombok.jar contains a file named /META-INF/services/javax.annotation.processing.Processor. When javac sees this file in a compilation classpath, it runs annotation processors defined there during compilation. As a result, the lombok annotations such as @Getter is replaced by java getter code blocks. As you can see, lombok is a jar that works at compilation time.

The javac can understand the lombok annotations doesn't mean your IDE can understand them. Eclipse, for example, need to have a the lombok.jar registered, so that it can use it to compile your code before running it. The Eclipse installation is easy, lombok.jar did that for you. You just have to download the jar, run it with
java -jar lombok.jar

the rest of the work is handled by the jar. It scan your computer to find the eclipse and install itself into your chosen eclipse programs.

Now you can write code such as
package com.example.my.mydemo.dao;
import lombok.Getter;
import lombok.Setter;

@Setter @Getter
public class Client {
    private String name;
    private String email;
    private long id;
}

With the lombok annotation @Getter @Setter, javac or eclipse treat them as equivalent to the following code

package com.example.my.mydemo.dao;
public class Client {
    private String name;
    private String email;
    private long id;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getEmail() {
        return email;
    }
    public void setEmail(String email) {
        this.email = email;
    }
    public long getId() {
        return id;
    }
    public void setId(long id) {
        this.id = id;
    }
}

So, when other java code need to access getter and setters, it won't fail.
package com.example.my.mydemo.dao;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;

import com.example.my.mydemo.model.Client;

public class ClientDao {
    private final JdbcTemplate jdbcTemplate;
    private static final String CLIENTSELECT = "select name, email, id from "
            + "Client where id = ?";
    public ClientDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    
    public Client getClient(String clientId) {
        Client client = jdbcTemplate.queryForObject(CLIENTSELECT, new Object[]{clientId},
                new BeanPropertyRowMapper<Client>(Client.class));
        return client;
    }

}

That is easy.

Even better, we can use @Data annotation to get many code for free, it is equivalent to @Getter @Setter @RequiredArgsConstructor @ToString @EqualsAndHashCode all together.


MarketAxess

MarketAxess: The Leader in e-Trading for Global Fixed Income MarketAxess Holdings Inc. (MarketAxess) is an international financial technol...