Spring Boot is a reductive application framework of the Spring platform, using Spring Boot can be more convenient and concise development of applications based on Spring, this article through a practical example, step by step to demonstrate how to create a basic Spring Boot program.

Depend on the configuration

This example uses Maven to do package dependency management. In the pop.xml file we need to add the Spring Boot dependency:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.2. RELEASE</version>
        <relativePath/> <! -- lookup parent from repository -->
    </parent>
Copy the code

In the meantime, we’re building a Web application, so we need to add web dependencies:

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

OOM framework, we use spring’s own JPA, the database uses memory database H2:

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

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
Copy the code

Main program configuration

Next we need to create a main class for our application:

@SpringBootApplication
public class App {

    public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code

Here we use the annotation @SpringBootApplication. It is equivalent to three annotations: @Configuration, @enableAutoConfiguration, and @ComponentScan.

Finally, we need to add the properties file: application.properties in the Resources directory. Where we define the port on which the program starts:

server.port=8081
Copy the code

MVC configuration

Spring MVC can be used with many templating languages; here we use Thymeleaf.

First we need to add dependencies:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-thymeleaf</artifactId> 
</dependency>
Copy the code

Then add the following configuration to application.properties:

spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
 
spring.application.name=Bootstrap Spring Boot
Copy the code

Then create a home page:

<html>
<head><title>Home Page</title></head>
<body>
<h1>Hello !</h1>
<p>Welcome to <span th:text="${appName}">Our App</span></p>
</body>
</html>
Copy the code

Finally create a Controller pointing to this page:

@Controller
public class SimpleController {
    @Value("${spring.application.name}")
    String appName;
 
    @GetMapping("/")
    public String homePage(Model model) {
        model.addAttribute("appName", appName);
        return "home"; }}Copy the code

Security configuration

This example is mainly to build a basically complete framework, so the necessary security access control is also required. We use Spring Security for Security control, adding the following dependencies:

<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-security</artifactId> 
</dependency>
Copy the code

When spring-boot-starter-Security is added to the dependency, all entries to the application library are added to the permission control by default. In this example, we do not need these permission controls, so we need to define SecurityConfig to allow all requests:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Override
    protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .anyRequest() .permitAll() .and().csrf().disable(); }}Copy the code

In the above example, we permit all.

I’ll go through the Spring Security tutorial in more detail later. I’m not going to go into that.

storage

In this case, we define a Book class, so we need to define the corresponding Entity class:

@Entity
public class Book {
  
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
 
    @Column(nullable = false, unique = true)
    private String title;
 
    @Column(nullable = false)
    private String author;
}
Copy the code

And the corresponding Repository class:

public interface BookRepository extends CrudRepository<Book.Long> {
    List<Book> findByTitle(String title);
}
Copy the code

Finally, we need to let the application discover our configured storage class as follows:

@EnableJpaRepositories("com.flydean.learn.repository")
@EntityScan("com.flydean.learn.entity") @SpringBootApplication public class App { public static void main(String[] args) { SpringApplication.run(App.class, args); }}Copy the code

Here, we use @EnableJpaRepositories to scan the Repository class.

Use @Entityscan to scan the JPA Entity class.

For convenience, we use the in-memory database H2. Once H2 is in the dependency package, Spring Boot automatically detects it and uses it. We need to configure some H2 properties:

spring.datasource.driver-class-name=org.h2.Driver spring.datasource.url=jdbc:h2:mem:bootapp; DB_CLOSE_DELAY=-1 spring.datasource.username=sa spring.datasource.password=Copy the code

Like security, storage is a very important and complex topic, which we will discuss in a later article.

Web pages and Controllers

With the Book Entity, we need to write a Controller for the Book, which mainly does the operation of adding, deleting, modifying and checking, as shown below:

@RestController
@RequestMapping("/api/books")
public class BookController {

    @Autowired
    private BookRepository bookRepository;

    @GetMapping
    public可迭代findAll(a) {
        return bookRepository.findAll();
    }

    @GetMapping("/title/{bookTitle}")
    public List findByTitle(@PathVariable String bookTitle) {
        return bookRepository.findByTitle(bookTitle);
    }

    @GetMapping("/{id}")
    public Book findOne(@PathVariable Long id) {
        return bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public Book create(@RequestBody Book book) {
        return bookRepository.save(book);
    }

    @DeleteMapping("/{id}")
    public void delete(@PathVariable Long id) {
        bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
        bookRepository.deleteById(id);
    }

    @PutMapping("/{id}")
    public Book updateBook(@RequestBody Book book, @PathVariable Long id) {
        if(book.getId() ! = id) {throw new BookIdMismatchException("ID mismatch!");
        }
        bookRepository.findById(id)
                .orElseThrow(BookNotFoundException::new);
        returnbookRepository.save(book); }}Copy the code

Here we use the @RestController annotation to indicate that this Controller is an API and does not involve page jumps.

@restController is a collection of @Controller and @responseBody.

Exception handling

Basically our program is done, but in Controller, we define some custom exceptions:

public class BookNotFoundException extends RuntimeException {
 
    public BookNotFoundException(String message, Throwable cause) {
        super(message, cause);
    }
    // ...
}
Copy the code

So how do you handle these exceptions? We can block these exceptions using @controllerAdvice:

@ControllerAdvice
public class RestExceptionHandler extends ResponseEntityExceptionHandler {
 
    @ExceptionHandler({ BookNotFoundException.class })
    protected ResponseEntity<Object> handleNotFound( Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, "Book not found".new HttpHeaders(), HttpStatus.NOT_FOUND, request);
    }
 
    @ExceptionHandler({ BookIdMismatchException.class, 
      ConstraintViolationException.class, 
      DataIntegrityViolationException.class })
    public ResponseEntity<Object> handleBadRequest( Exception ex, WebRequest request) {
        return handleExceptionInternal(ex, ex.getLocalizedMessage(), 
          newHttpHeaders(), HttpStatus.BAD_REQUEST, request); }}Copy the code

This kind of exception catching is also called global exception catching.

test

Now that our Book API is written, we need to write a test program to test it.

Here we use @springboottest:

@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringContextTest {

    @Test
    public void contextLoads(a) {
        log.info("contextLoads"); }}Copy the code

WebEnvironment = SpringBootTest. WebEnvironment. DEFINED_PORT is said test using Spring The Boot application port uses the custom port in application.properties.

Next we use RestAssured to test the BookController:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
public class SpringBootBootstrapTest {

    private static final String API_ROOT
            = "http://localhost:8081/api/books";

    private Book createRandomBook(a) {
        Book book = new Book();
        book.setTitle(randomAlphabetic(10));
        book.setAuthor(randomAlphabetic(15));
        return book;
    }

    private String createBookAsUri(Book book) {
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);
        return API_ROOT + "/" + response.jsonPath().get("id");
    }


    @Test
    public void whenGetAllBooks_thenOK(a) {
        Response response = RestAssured.get(API_ROOT);

        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
    }

    @Test
    public void whenGetBooksByTitle_thenOK(a) {
        Book book = createRandomBook();
        createBookAsUri(book);
        Response response = RestAssured.get(
                API_ROOT + "/title/" + book.getTitle());

        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertTrue(response.as(List.class)
                .size() > 0);
    }
    @Test
    public void whenGetCreatedBookById_thenOK(a) {
        Book book = createRandomBook();
        String location = createBookAsUri(book);
        Response response = RestAssured.get(location);

        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertEquals(book.getTitle(), response.jsonPath()
                .get("title"));
    }

    @Test
    public void whenGetNotExistBookById_thenNotFound(a) {
        Response response = RestAssured.get(API_ROOT + "/" + randomNumeric(4));

        assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode());
    }

    @Test
    public void whenCreateNewBook_thenCreated(a) {
        Book book = createRandomBook();
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);

        assertEquals(HttpStatus.CREATED.value(), response.getStatusCode());
    }

    @Test
    public void whenInvalidBook_thenError(a) {
        Book book = createRandomBook();
        book.setAuthor(null);
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .post(API_ROOT);

        assertEquals(HttpStatus.BAD_REQUEST.value(), response.getStatusCode());
    }

    @Test
    public void whenUpdateCreatedBook_thenUpdated(a) {
        Book book = createRandomBook();
        String location = createBookAsUri(book);
        book.setId(Long.parseLong(location.split("api/books/") [1]));
        book.setAuthor("newAuthor");
        Response response = RestAssured.given()
                .contentType(MediaType.APPLICATION_JSON_VALUE)
                .body(book)
                .put(location);

        assertEquals(HttpStatus.OK.value(), response.getStatusCode());

        response = RestAssured.get(location);

        assertEquals(HttpStatus.OK.value(), response.getStatusCode());
        assertEquals("newAuthor", response.jsonPath()
                .get("author"));
    }

    @Test
    public void whenDeleteCreatedBook_thenOk(a) { Book book = createRandomBook(); String location = createBookAsUri(book); Response response = RestAssured.delete(location); assertEquals(HttpStatus.OK.value(), response.getStatusCode()); response = RestAssured.get(location); assertEquals(HttpStatus.NOT_FOUND.value(), response.getStatusCode()); }}Copy the code

Once you’ve written your test class, run it.

conclusion

Your first Spring Boot program is complete, and we will continue to enrich and improve the basic framework in future articles. Stay tuned.

For example code for this article, see Github: bootstrap-sample-app

See flydean’s blog for more tutorials