Bộ sưu tập

Một cách nhìn khác về ứng dụng web với Spring MVC, Spring Boot, Tomcat dạng nhúng và Thymeleaf


https://codersontrang.com/2017/09/25/mot-cach-nhin-khac-ve-ung-dung-web-voi-spring-mvc-spring-boot-tomcat-dang-nhung-va-thymeleaf

Bình thường chúng ta thường thấy các ứng dụng web sẽ cần phải được triển khai trên một web server nào đó. Ví dụ như khi viết các ứng dụng Java web tuân theo chuẩn Servlet, bao giờ chúng ta cũng cần một Servlet container như Tomcat hay Jetty để có thể chạy ứng dụng trên đó. Việc chạy ứng dụng trên một web server riêng biệt có một số các đặc điểm mà mình cảm thấy khá không thuận tiện mà mình có thể kể tên sau đây:

  • Không chỉ mỗi lần triển khai cần phải cài đặt lên một web server riêng rẽ, mà cách này còn cần rất nhiều các cấu hình loằng ngoằng cả ở phía ứng dụng lẫn phía web server để chúng có thể tương thích với nhau. Ví dụ như khi làm ứng dụng Java Web trên nền Servlet, mình thấy cũng có khá nhiều các ứng dụng thường dùng JNDI để gọi tới các tài nguyên được cấu hình trên các web server riêng rẽ như tài nguyên về kết nối database hay email server … Điều này có một chút không thuận tiện khi phải chuyển ứng dụng từ máy này sang máy khác, chúng ta cũng cần phải chuyển các web server riêng biệt kia cùng với những cấu hình của nó và điều này sẽ rất dễ dẫn đến lỗi lầm nếu dự án làm quản lý cấu hình không tốt.
  • Trong quá trình vận hành, khi quản lý tiến trình của ứng dụng thì đó sẽ là tiến trình của web server chứ không hoàn toàn là tiến trình của chính ứng dụng mà chúng ta viết ra. Đôi khi sẽ xảy ra trường hợp ứng dụng của chúng ta không hoạt động nhưng vì web server là riêng biệt và trạng thái của nó vẫn bình thường nên các hệ thống giám sát dễ bị đánh lừa và không đưa ra được cảnh báo.

Đứng trước tình hình trên, thì rõ ràng chúng ta cần có một giải pháp gì đó làm cho ứng dụng có thể được đóng gói một cách gọn gàng hơn, từ đó dễ dàng vận chuyển hơn. Thiên hạn bỗng nghĩ ra cách là thay vì gói ứng dụng của chúng ta vào một web server thì hãy làm ngược lại tức là gói web server vào chính ứng dụng. Từ đó mới sinh ra các web server dưới dạng nhúng. Với một cách nhìn khác như vậy thì

  • Nhúng web server vào bản thân ứng dụng thì khi riển khai tất cả những gì ta cần chỉ là một đóng gói của chính bản thân ứng dụng và các cấu hình sẽ không bị tản mác ở nhiều nơi mà chỉ nằm ở chính bản thân của ứng dụng mà thôi. Lúc đó sẽ dễ dàng hơn cho ta trong việc quản lý. Điều này rất có ý nghĩa khi thiết kế các hệ thống và xây dựng úng dụng theo mô hình Microservice.
  • Bản thân ứng dụng sẽ có tiến trình (process) của riêng nó và từ bên ngoài nhìn người ta sẽ nhìn thấy ứng dụng thay vì một web server. Từ đó dễ dàng hơn trong quá trình giám sát và vận hành hệ thống.

Để có thể minh họa rõ hơn về hai cách tiếp cận trong việc phát triển ứng dụng web, các bạn có thể nhìn xuống hình dưới đây

Bài viết này sẽ hướng dẫn cách để tạo ra một ứng dụng web bằng Spring MVC mà ở đó web server là Tomcat sẽ được sử dụng dưới dạng nhúng. Trong nội dung cũng sẽ nói về framework Spring Boot, một trong những công cụ giúp dễ dàng khởi tạo và kích hoạt ứng dụng Spring, cũng như là Thymeleaf là một trong những view engine giúp cho việc vẽ và hiển thị thông tin trong quá trình hoạt động của ứng dụng, tương tự như jsp, freemarker hay velocity vậy.

Ví dụ được lấy ra để minh họa trong bài viết này chính là một phiên bản được sửa đổi của ví dụ được nêu ở trong bài viết “Giới thiệu về Spring MVC“. Sự khác biệt trong ví dụ ở hai bài viết có thể điểm ra như sau:

  • Nếu như bài viết trước ví dụ tuân theo cấu trúc thuần túy của một dự án Java web thì ví dụ ở bài này cấu trúc sẽ là một dự án Maven.
  • Bài viết trước sử dụng IDE là Eclipse, thì bài viết này IDE sẽ là IntelliJ, tuy nhiên sự khác biệt này không quá quan trọng. Nếu dự án đã mang cấu trúc chuẩn Maven thì nó sẽ được hỗ trợ ở hầu hết các IDE.
  • Trong bài viết trước sẽ cần một web server riêng rẽ để triển khai ứng dụng lên, thì đến bài viết này ta sử dụng web server dưới dạng nhúng. Ứng dụng web sẽ được kích hoạt và khởi tạo qua một lớp với phương thức main() nhờ Spring Boot.
  • JSP là view engine được sử dụng trong bài viết trước cho việc hiển thị dữ liệu cho người dùng thì sang đến bài viết này ta sử dụng Thymeleaf với cú pháp khác trên nền của ngôn ngữ HTML.

Với tất cả các điểm khác biệt nêu trên, thì sang đến ví dụ này, cấu trúc project được tạo ra và hiển thị dưới IDE IntelliJ sẽ giống như hình dưới đây:

pom.xml


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.codersontrang</groupId>
    <artifactId>spring-mvc-with-boot</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <repositories>
        <repository>
            <id>spring-milestone</id>
            <name>spring milestone</name>
            <url>http://repo.spring.io/milestone/</url>
        </repository>
        <repository>
            <id>maven-central</id>
            <name>maven central</name>
            <url>http://central.maven.org/maven2/</url>
        </repository>
    </repositories>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.0.0.M3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
            <version>2.0.0.M3</version>
        </dependency>
    </dependencies>
</project>

Đối với file cấu hình của Maven pom.xml, ta không cần gì khác hơn là hai dependency là spring-boot-starter-thymeleafspring-boot-starter-web, từ hai dependency này Maven sẽ tự động tải về các file thư viện liên đới trong đó có Spring MVC và web server tomcat dưới dạng nhúng. Tính đến thời điểm viết bài này, phiên bản mới nhất của Spring Boot chưa phải là phiên bản chính thức mà là 2.0.0.M3, tức là phiên bản Milestone 3. Vì vậy để có thể tải được phiên bản này, ta cần cấu hình Maven để trỏ đến Spring Milestone repository với URL “http://repo.spring.io/milestone/“. Đi kèm với Spring Boot 2.0.0.M3, thì các dependency khác cũng là phiên bản mới nhất, ví dụ như Spring sẽ là 5.0.0.RC3, Tomcat nhúng sẽ là phiên bản 8, Thymeleaf là phiên bản 3.0.7… Ta có thể xem những thông tin này trong phần External Libraries của IDE như hình dưới đây:

Student.java


package springmvcdemo.model;

import java.util.List;

public class Student {
    private String name;
    private List<String> books;

    public List<String> getBooks() {
        return books;
    }
    public void setBooks(List<String> books) {
        this.books = books;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

Giống như ở bài viết trước, lớp Student không có gì thay đổi, nhắc lại là trong lớp này có các thuộc tính là name và một danh sách List<String> books.

DemoController.java


package springmvcdemo.controller;

import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.servlet.ModelAndView;
import springmvcdemo.model.Student;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.List;

@Controller
public class DemoController {
    @GetMapping(value="/home")
    public ModelAndView handleRequest(HttpServletRequest request,
                                      HttpServletResponse response) throws Exception {

        Student student = new Student();
        student.setName("Coder Tien Sinh");

        List books = new ArrayList();
        books.add("book1");
        books.add("book2");
        student.setBooks(books);

        ModelAndView mav = new ModelAndView("index", "model", student);

        return mav;
    }

    @PostMapping(value="/submitStudentInfo")
    public ModelAndView submitStudentInfo(ModelMap model, @ModelAttribute("model") Student student){

        List listBooks = student.getBooks();
        boolean bookSameName = checkSameBookName(listBooks);

        String message = "Update success !!!";
        if(bookSameName){
            message = "Books must have different name !!!";
        }
        model.addAttribute("message", message);

        ModelAndView mav = new ModelAndView("index", "model", student);
        return mav;
    }

    private boolean checkSameBookName(List listBooks) {
        for(int i =0; i<listBooks.size(); i++){
            String firstBookName = listBooks.get(i);
            for(int j = i+1; j<listBooks.size(); j++){
                String secondBookName = listBooks.get(j);
                if(firstBookName.equalsIgnoreCase(secondBookName)){
                    return true;
                }
            }
        }
        return false;
    }
}

Là một lớp định nghĩa một controller trong mô hình của Spring MVC, nội dung của file DemoController.java trong bài viết này có một số thay đổi nhỏ so với bài viết trước như sau

  • Việc sử dụng các annotation @GetMapping@PostMapping thay cho annotation @RequestMapping giúp cho code ngắn gọn hơn
  • Tên của thuộc tính view trong đối tượng ModelAndView sẽ chỉ là "index" mà không có đuôi là "index.jsp" giống như bài viết trước. Lý do rất đơn giản là bài viết này ta sử dụng Thymeleaf thay cho JSP, và Spring Boot bằng cấu hình mặc định của nó đã tự hiểu view đó là view nào.

index.html


<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><title>Example :: Spring Application</title></head>
<body>
    <h1>CoderSonTrang - Spring MVC Demo</h1>
    <h3>Student Book Show</h3>
    <form th:action="@{/submitStudentInfo}" th:method="POST" th:object="${model}">
        Your name: <span th:text="*{name}"></span>
        <input type="hidden" th:field="*{name}"/>

        <br/><br/>

        Edit your list of books:
        <input th:each="book,iterStat : *{books}" th:field="*{books[__${iterStat.index}__]}" />

        <input type="submit" value="Submit"/>
    </form>

    <div style="background:#972F2C;color: #FFF;" th:text="${message}"></div>

</body>
</html>

File HTML index.html được trộn vào với các cú pháp của Thymeleaf để hiển thị dữ liệu từ model trong mô hình MVC. Để ý thấy là cú pháp của Thymeleaf phần lớn là các custom attributes và “ký sinh” trong các thẻ HTML thông thường. Nếu bạn nào từng biết đến AngularJS thì có lẽ sẽ thấy cú pháp này rất giống với cách sử dụng các directive. Ta điểm qua một số cú pháp của Thymeleaf trong ví dụ này như sau:

  • xmlns:th="http://www.thymeleaf.org": Đầu tiên để sử dụng được Thymeleaf,
    ta phải khai báo XML schema cho một namespace mà ở đây ta đặt là th
  • th:action="@{/submitStudentInfo}": là thuộc tính action trong form hay nói cách chính là URL mà dữ liệu của form sẽ được gửi đến. @{} là cú pháp thể hiện link trong Thymeleaf, link này sẽ là tương đối tính từ context path của ứng dụng web.
  • th:method="POST": Chỉ ra phương thức HTTP (POST, GET…) khi mà thông tin của form được gửi lên.
  • th:object="${model}": chỉ ra đối tượng sẽ được sử dụng ở trong form, ${} là biểu thức để lấy ra giá trị của thuộc tính trong model của Spring MVC, giá trị này được đặt vào trong Controller mà ta đã nhìn ở phần trên
  • th:text="*{name}": thay thế thẻ HTML bằng đoạn text có giá trị ở trong biểu thức. *{} ngầm hiểu là lấy ra thuộc tính con của đối tượng được chỉ ra trong th:object ở trên. Trong trường hợp này ta đang lấy ra và hiển thị tên của đối tượng Student.
  • th:field="*{name}": Không chỉ phục vụ cho việc hiển thị như th:text bên trên, th:field gắn thuộc tính của đối tượng đến một trường <input /> trong HTML, từ đó chỉ định ra tên của trường input tương ứng, điều này giúp ích cả khi cần gửi dữ liệu từ form lên server, các dữ liệu sẽ được từ động ánh xạ vào model tương ứng.
  • th:each="book,iterStat : *{books}": Lặp qua một tập hợp nào đó mà trong trường hợp này chính là tập hợp tên sách. book chính là bản thân mỗi phần tử trong mỗi vòng lặp, iterStat là đối tượng bao gồm các thông số cho quá trình lặp ví dụ như chỉ số của phần tử trong vòng lặp. Trong ví dụ này thì ta lấy ra chỉ số của phần tử lặp qua thuộc tính index của đối tượng iterStat với cú pháp là __${iterStat.index}__

Bạn có thể thấy rằng file index.html nằm trong thư mục resources/templates. Đây là quy tắc của Spring Boot giúp nó có thể nhận diện phần View trong Spring MVC mà không cần các cấu hình loằng ngoằng khác (hay còn gọi là boilerplate code, nếu bạn cần tìm hiểu thêm về boilerplate code thì có thể đọc sang bài viết này)

Spring5MVCDemoApp.java


package springmvcdemo;

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

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

Là điểm bắt đầu của chương trình, annotation @SpringBootApplication chỉ thị đây là một chương trình của Spring Boot, và mọi thứ sẽ kích hoạt từ đây. Tiếp đó Spring Boot sẽ quét qua toàn bộ class path trong project, tìm các annotation ví dụ như @Controller… để khởi tạo các Spring bean tương ứng. Ưu điểm của việc sử dụng Spring Boot là nó đã cấu hình sẵn tất cả mọi thứ, lập trình viên sẽ không cần phải tốn công cho việc làm các cấu hình từ đó có thể tập trung vào logic chính của ứng dụng hơn. Nếu như bạn so sánh ví dụ trong bài viết này với ví dụ tương tự ở bài viết trước bạn sẽ thấy các phần cấu hình liên quan đến web.xml hay file cấu hình của spring là springmvcdemo-servlet.xml hoàn toàn không cần thiết nữa. Tuy nhiên không vì thế mà Spring Boot trở nên không linh hoạt trong trường hợp bạn muốn thay đổi một cấu hình mặc định của nó. Bạn hoàn toàn có thể thay đổi các cấu hình mặc định của Spring Boot bằng việc chỉnh lại giá trị các thuộc tính trong file /resources/application.properties. Trong ví dụ này ta giữ nguyên các cấu hình mặc định vì thế mà nội dung file application.properties sẽ để trống.

Bây giờ ta có thể chạy ứng dụng qua file Spring5MVCDemoApp.java với hàm main() giống như bất cứ một ứng dụng Java đơn giản nào khác, nhìn vào màn hình console của IDE ta có thể thấy biểu tượng của Spring Boot được vẽ lên, và khi ứng dụng khởi tạo thành công thì ta cũng thấy bản thân ứng dụng trở thành một web server và đang lắng nghe dưới cổng mặc định của Tomcat là 8080.

Mở cửa sổ trình duyệt và truy cập vào đường dẫn http://localhost:8080/home, ta sẽ thấy trang thông tin được hiển thị giống như hình dưới đây

Điền cùng tên một quyển sách là “book 1” vào 2 ô input và bấm vào nhấp vào nút Submit. Khi đó trên màn hình hiển thị lời nhắn trả về là “Books must have different name !!!

Khi nhập hai cái tên khác nhau là “book 1” và “book 5” và bấm nút Submit. Khi đó màn hình hiển thị lời nhắn là “Update success !!!

Vậy là trong bài viết này chúng ta đã đi qua một ví dụ nhỏ về ứng dụng Spring Boot, Spring MVC, Tomcat dạng nhúng và Thymeleaf để xây dựng ứng dụng web. Trái với các ứng dụng Java web truyền thống sẽ thường được đóng gói lại thành một file *.war, sau đó file *.war này sẽ được triển khai vào trong một web server riêng rẽ, ví dụ trong bài viết này là ứng dụng web dưới dạng một Executable Java App mà ẩn bên trong nó bản chất chính là một Tomcat web server dưới dạng nhúng. Về quy trình đóng gói ứng dụng kiểu này và triển khai khi chạy thật mình xin được phép hẹn các bạn sang bài viết sau. Hi vọng bài viết này cung cấp cho các bạn thông tin hữu ích về một mô hình khác, một cách nhìn khác về việc phát triển các ứng dụng Java web.

Good luck !!!

Advertisements

7 comments on “Một cách nhìn khác về ứng dụng web với Spring MVC, Spring Boot, Tomcat dạng nhúng và Thymeleaf

  1. Pingback: Giới thiệu về Spring MVC | Coder Sơn Trang

  2. Pingback: Đóng gói ứng dụng Java với Maven plugin | Coder Sơn Trang

  3. Pingback: Giới thiệu về Spring Webflux | Coder Sơn Trang

  4. Pingback: Tích hợp log4j2 với ứng dụng Spring MVC | Coder Sơn Trang

  5. Pingback: Giám sát ứng dụng với Spring Actuator | Coder Sơn Trang

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất /  Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất /  Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất /  Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất /  Thay đổi )

Connecting to %s