Showing posts with label springboot. Show all posts
Showing posts with label springboot. 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 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.

MarketAxess

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