openplanning

Tạo ứng dụng đăng ký tài khoản với Spring Boot, Spring Form Validation

  1. Mục tiêu của ví dụ
  2. Tạo Spring Boot project
  3. Cấu hình pom.xml
  4. Security, MessageSource
  5. Model, DAO
  6. Form Bean, Validator
  7. Controller
  8. Thymeleaf Template
  9. Chạy ứng dụng

1. Mục tiêu của ví dụ

Tài liệu được viết dựa trên:
  • Eclipse 3.7 (Oxygen)

  • Spring Boot 2.x

  • Spring Validation

  • Thymeleaf

Trong bài viết này tôi hướng dẫn bạn tạo một ứng dụng đăng ký người dùng sử dụng Spring Boot + Spring Validation + Thymeleaf. Các chủ đề được đề cập trong bài viết này bao gồm:
  • Tạo một Form đăng ký trên Spring.
  • Sử dụng Spring Validator để xác thực (validate) thông tin người dùng đã nhập vào.
  • Giải thích nguyên tắc hoạt động của Spring Validator.
Xem trước ứng dụng:

2. Tạo Spring Boot project

Trên Eclipse tạo dự án Spring Boot.
Nhập vào:
  • Name: SbRegistrationFormValidationThymeleaf
  • Group: org.o7planning
  • Description: Spring Boot + Form Validation + Thymeleaf
  • Package: org.o7planning.sbformvalidation
Lựa chọn các công nghệ và các thư viện để sử dụng:
  • Security
  • Validation
  • Web
  • Thymeleaf
OK, Project đã được tạo ra.
SbRegistrationFormValidationThymeleafApplication.java
package org.o7planning.sbformvalidation;

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

@SpringBootApplication
public class SbRegistrationFormValidationThymeleafApplication {

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

3. Cấu hình pom.xml

Trong ví dụ này chúng ta sẽ sử dụng thư viện Commons Validation để kiểm tra email mà người dùng nhập vào có chính xác hay không. Vì vậy cần phải khai báo thư viện này trong pom.xml.
** Commons Validation **
<dependencies>
    .....

        <!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
    <dependency>
        <groupId>commons-validator</groupId>
        <artifactId>commons-validator</artifactId>
        <version>1.6</version>
    </dependency>

        .....    
</dependencies>
Toàn bộ nội dung tập tin pom.xml:
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>org.o7planning</groupId>
    <artifactId>SbRegistrationFormValidationThymeleaf</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>SbRegistrationFormValidationThymeleaf</name>
    <description>Spring Boot + Form Validation + Thymeleaf</description>

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

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        
        <!-- https://mvnrepository.com/artifact/commons-validator/commons-validator -->
        <dependency>
            <groupId>commons-validator</groupId>
            <artifactId>commons-validator</artifactId>
            <version>1.6</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

4. Security, MessageSource

Trong ví dụ này không tập trung vào vấn đề bảo mật của ứng dụng. Tuy nhiên chúng ta cần tới thư viện Spring Security để có thể mật mã hóa (encode) mật khẩu của người dùng trước khi lưu vào cơ sở dữ liệu. Và bạn cần phải khai báo một Spring BEAN cho việc mật mã hóa mật khẩu.
WebSecurityConfig.java
package org.o7planning.sbformvalidation.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	@Bean
	public PasswordEncoder passwordEncoder() {
		BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder();
		return bCryptPasswordEncoder;
	}

	// Trong ví dụ này chúng ta không sử dụng Security.
	// Ghi đè phương thức này với Code rỗng
	// để vô hiệu hóa Security mặc định của Spring Boot.
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		// Empty code!
	}
}
Trong ví dụ này chúng ta có tập tin validation.properties. Tập tin này chứa các mã lỗi (Error code), được sử dụng để thông báo cho người dùng khi họ nhập các thông tin không chính xác.
validation.properties
NotEmpty.appUserForm.userName=User name is required
NotEmpty.appUserForm.firstName=First Name is required
NotEmpty.appUserForm.lastName=Last name is required
NotEmpty.appUserForm.email=Email is required
NotEmpty.appUserForm.password=Password is required
NotEmpty.appUserForm.confirmPassword=Confirm Password is required
NotEmpty.appUserForm.gender=Gender is required
NotEmpty.appUserForm.countryCode=Country is required

Pattern.appUserForm.email=Invalid email
Duplicate.appUserForm.email=Email has been used by another account
Duplicate.appUserForm.userName=Username is not available
Match.appUserForm.confirmPassword=Password does not match the confirm password
Bạn cần khai báo một MessageResource Spring Bean, để Spring tự động tải (load) nội dung của tập tin validation.properties vào bộ nhớ.
WebConfiguration.java
package org.o7planning.sbformvalidation.config;

import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {

	@Bean
	public MessageSource messageSource() {
		ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
		// Tải file: validation.properties
		messageSource.setBasename("classpath:validation");
		messageSource.setDefaultEncoding("UTF-8");
		return messageSource;
	}

}

5. Model, DAO

Lớp AppUser đại diện cho một bản ghi (record) của bảng APP_USER. Nó là một người dùng (user) đã đăng ký thành công vào hệ thống.
AppUser.java
package org.o7planning.sbformvalidation.model;

public class AppUser {
    

    private Long userId;
    private String userName;
    private String firstName;
    private String lastName;
    private boolean enabled;
    private String gender;
    private String email;
    private String encrytedPassword;
    
    private String countryCode;

    public AppUser() {

    }

    public AppUser(Long userId, String userName, String firstName, String lastName, //
            boolean enabled, String gender, //
            String email,String countryCode, String encrytedPassword) {
        super();
        this.userId = userId;
        this.userName = userName;
        this.firstName = firstName;
        this.lastName = lastName;
        this.enabled = enabled;
        this.gender = gender;
        this.email = email;
        this.countryCode= countryCode;
        this.encrytedPassword = encrytedPassword;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getEncrytedPassword() {
        return encrytedPassword;
    }

    public void setEncrytedPassword(String encrytedPassword) {
        this.encrytedPassword = encrytedPassword;
    }

    public String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode;
    }

}
Country.java
package org.o7planning.sbformvalidation.model;

public class Country {

    private String countryCode;
    private String countryName;

    public Country() {

    }

    public Country(String countryCode, String countryName) {
        this.countryCode = countryCode;
        this.countryName = countryName;
    }

    public String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode;
    }

    public String getCountryName() {
        return countryName;
    }

    public void setCountryName(String countryName) {
        this.countryName = countryName;
    }
}
Gender.java
package org.o7planning.sbformvalidation.model;

public class Gender {

    public static final String MALE = "M";
    public static final String FEMALE = "F";
}
Các lớp DAO (Data Access Object) được sử dụng để thao tác với nguồn dữ liệu, chẳng hạn như query, insert, update, delete. Các lớp này thường được chú thích (annotate) bởi @Repository để Spring quản lý chúng như các Spring BEAN.
AppUserDAO.java
package org.o7planning.sbformvalidation.dao;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.o7planning.sbformvalidation.formbean.AppUserForm;
import org.o7planning.sbformvalidation.model.AppUser;
import org.o7planning.sbformvalidation.model.Gender;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Repository;

@Repository
public class AppUserDAO {

    // Config in WebSecurityConfig
    @Autowired
    private PasswordEncoder passwordEncoder;

    private static final Map<Long, AppUser> USERS_MAP = new HashMap<>();

    static {
        initDATA();
    }

    private static void initDATA() {
        String encrytedPassword = "";

        AppUser tom = new AppUser(1L, "tom", "Tom", "Tom", //
                true, Gender.MALE, "tom@waltdisney.com", encrytedPassword, "US");

        AppUser jerry = new AppUser(2L, "jerry", "Jerry", "Jerry", //
                true, Gender.MALE, "jerry@waltdisney.com", encrytedPassword, "US");

        USERS_MAP.put(tom.getUserId(), tom);
        USERS_MAP.put(jerry.getUserId(), jerry);
    }

    public Long getMaxUserId() {
        long max = 0;
        for (Long id : USERS_MAP.keySet()) {
            if (id > max) {
                max = id;
            }
        }
        return max;
    }

    //

    public AppUser findAppUserByUserName(String userName) {
        Collection<AppUser> appUsers = USERS_MAP.values();
        for (AppUser u : appUsers) {
            if (u.getUserName().equals(userName)) {
                return u;
            }
        }
        return null;
    }

    public AppUser findAppUserByEmail(String email) {
        Collection<AppUser> appUsers = USERS_MAP.values();
        for (AppUser u : appUsers) {
            if (u.getEmail().equals(email)) {
                return u;
            }
        }
        return null;
    }

    public List<AppUser> getAppUsers() {
        List<AppUser> list = new ArrayList<>();

        list.addAll(USERS_MAP.values());
        return list;
    }

    public AppUser createAppUser(AppUserForm form) {
        Long userId = this.getMaxUserId() + 1;
        String encrytedPassword = this.passwordEncoder.encode(form.getPassword());

        AppUser user = new AppUser(userId, form.getUserName(), //
                form.getFirstName(), form.getLastName(), false, //
                form.getGender(), form.getEmail(), form.getCountryCode(), //
                encrytedPassword);

        USERS_MAP.put(userId, user);
        return user;
    }

}
CountryDAO.java
package org.o7planning.sbformvalidation.dao;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.o7planning.sbformvalidation.model.Country;
import org.springframework.stereotype.Repository;

@Repository
public class CountryDAO {

    private static final Map<String, Country> COUNTRIES_MAP = new HashMap<>();

    static {
        initDATA();
    }

    private static void initDATA() {

        Country vn = new Country("VN", "Vietnam");
        Country en = new Country("EN", "England");
        Country fr = new Country("FR", "France");
        Country us = new Country("US", "US");
        Country ru = new Country("RU", "Russia");

        COUNTRIES_MAP.put(vn.getCountryCode(), vn);
        COUNTRIES_MAP.put(en.getCountryCode(), en);
        COUNTRIES_MAP.put(fr.getCountryCode(), fr);
        COUNTRIES_MAP.put(us.getCountryCode(), us);
        COUNTRIES_MAP.put(ru.getCountryCode(), ru);
    }

    public Country findCountryByCode(String countryCode) {
        return COUNTRIES_MAP.get(countryCode);
    }

    public List<Country> getCountries() {
        List<Country> list = new ArrayList<>();
        list.addAll(COUNTRIES_MAP.values());
        return list;
    }

}

6. Form Bean, Validator

Lớp AppUserForm đại diện cho dữ liệu mà người dùng cần phải nhập trên Form đăng ký.
AppUserForm.java
package org.o7planning.sbformvalidation.formbean;

public class AppUserForm {

    private Long userId;
    private String userName;
    private String firstName;
    private String lastName;
    private boolean enabled;
    private String gender;
    private String email;
    private String password;
    private String confirmPassword;
    private String countryCode;

    public AppUserForm() {

    }

    public AppUserForm(Long userId, String userName, //
            String firstName, String lastName, boolean enabled, //
            String gender, String email, String countryCode, //
            String password, String confirmPassword) {
        this.userId = userId;
        this.userName = userName;
        this.firstName = firstName;
        this.lastName = lastName;
        this.enabled = enabled;
        this.gender = gender;
        this.email = email;
        this.countryCode = countryCode;
        this.password = password;
        this.confirmPassword = confirmPassword;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getCountryCode() {
        return countryCode;
    }

    public void setCountryCode(String countryCode) {
        this.countryCode = countryCode;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getConfirmPassword() {
        return confirmPassword;
    }

    public void setConfirmPassword(String confirmPassword) {
        this.confirmPassword = confirmPassword;
    }
}
Lớp AppUserValidator được sử dụng để phê chuẩn (validate) các thông tin mà người dùng đã nhập vào Form. Như vậy AppUserValidator sẽ phê chuẩn (validate) giá trị các trường (field) của đối tượng AppUserForm.
AppUserValidator.java
package org.o7planning.sbformvalidation.validator;

import org.apache.commons.validator.routines.EmailValidator;
import org.o7planning.sbformvalidation.dao.AppUserDAO;
import org.o7planning.sbformvalidation.formbean.AppUserForm;
import org.o7planning.sbformvalidation.model.AppUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.validation.Errors;
import org.springframework.validation.ValidationUtils;
import org.springframework.validation.Validator;

@Component
public class AppUserValidator implements Validator {

	// common-validator library.
	private EmailValidator emailValidator = EmailValidator.getInstance();

	@Autowired
	private AppUserDAO appUserDAO;

	// Các lớp được hỗ trợ bởi Validator này.
	@Override
	public boolean supports(Class<?> clazz) {
		return clazz == AppUserForm.class;
	}

	@Override
	public void validate(Object target, Errors errors) {
		AppUserForm appUserForm = (AppUserForm) target;

		// Kiểm tra các field của AppUserForm.
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "userName", "NotEmpty.appUserForm.userName");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "NotEmpty.appUserForm.firstName");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "NotEmpty.appUserForm.lastName");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "email", "NotEmpty.appUserForm.email");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty.appUserForm.password");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "confirmPassword", "NotEmpty.appUserForm.confirmPassword");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "gender", "NotEmpty.appUserForm.gender");
		ValidationUtils.rejectIfEmptyOrWhitespace(errors, "countryCode", "NotEmpty.appUserForm.countryCode");

		if (!this.emailValidator.isValid(appUserForm.getEmail())) {
			// Email không hợp lệ.
			errors.rejectValue("email", "Pattern.appUserForm.email");
		} else if (appUserForm.getUserId() == null) {
			AppUser dbUser = appUserDAO.findAppUserByEmail(appUserForm.getEmail());
			if (dbUser != null) {
				// Email đã được sử dụng bởi tài khoản khác.
				errors.rejectValue("email", "Duplicate.appUserForm.email");
			}
		}

		if (!errors.hasFieldErrors("userName")) {
			AppUser dbUser = appUserDAO.findAppUserByUserName(appUserForm.getUserName());
			if (dbUser != null) {
				// Tên tài khoản đã bị sử dụng bởi người khác.
				errors.rejectValue("userName", "Duplicate.appUserForm.userName");
			}
		}

		if (!errors.hasErrors()) {
			if (!appUserForm.getConfirmPassword().equals(appUserForm.getPassword())) {
				errors.rejectValue("confirmPassword", "Match.appUserForm.confirmPassword");
			}
		}
	}

}

7. Controller

MainController.java
package org.o7planning.sbformvalidation.controller;

import java.util.List;

import org.o7planning.sbformvalidation.dao.AppUserDAO;
import org.o7planning.sbformvalidation.dao.CountryDAO;
import org.o7planning.sbformvalidation.formbean.AppUserForm;
import org.o7planning.sbformvalidation.model.AppUser;
import org.o7planning.sbformvalidation.model.Country;
import org.o7planning.sbformvalidation.validator.AppUserValidator;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
// import org.springframework.transaction.annotation.Transactional;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.InitBinder;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

@Controller
public class MainController {

	@Autowired
	private AppUserDAO appUserDAO;

	@Autowired
	private CountryDAO countryDAO;

	@Autowired
	private AppUserValidator appUserValidator;

	// Set a form validator
	@InitBinder
	protected void initBinder(WebDataBinder dataBinder) {
		// Form mục tiêu
		Object target = dataBinder.getTarget();
		if (target == null) {
			return;
		}
		System.out.println("Target=" + target);

		if (target.getClass() == AppUserForm.class) {
			dataBinder.setValidator(appUserValidator);
		}
		// ...
	}

	@RequestMapping("/")
	public String viewHome(Model model) {

		return "welcomePage";
	}

	@RequestMapping("/members")
	public String viewMembers(Model model) {

		List<AppUser> list = appUserDAO.getAppUsers();

		model.addAttribute("members", list);

		return "membersPage";
	}

	@RequestMapping("/registerSuccessful")
	public String viewRegisterSuccessful(Model model) {

		return "registerSuccessfulPage";
	}

	// Hiển thị trang đăng ký.
	@RequestMapping(value = "/register", method = RequestMethod.GET)
	public String viewRegister(Model model) {

		AppUserForm form = new AppUserForm();
		List<Country> countries = countryDAO.getCountries();

		model.addAttribute("appUserForm", form);
		model.addAttribute("countries", countries);

		return "registerPage";
	}

	// Phương thức này được gọi để lưu thông tin đăng ký.
	// @Validated: Để đảm bảo rằng Form này
	// đã được Validate trước khi phương thức này được gọi.
	@RequestMapping(value = "/register", method = RequestMethod.POST)
	public String saveRegister(Model model, //
			@ModelAttribute("appUserForm") @Validated AppUserForm appUserForm, //
			BindingResult result, //
			final RedirectAttributes redirectAttributes) {

		// Validate result
		if (result.hasErrors()) {
			List<Country> countries = countryDAO.getCountries();
			model.addAttribute("countries", countries);
			return "registerPage";
		}
		AppUser newUser= null;
		try {
			newUser = appUserDAO.createAppUser(appUserForm);
		}
		// Other error!!
		catch (Exception e) {
			List<Country> countries = countryDAO.getCountries();
			model.addAttribute("countries", countries);
			model.addAttribute("errorMessage", "Error: " + e.getMessage());
			return "registerPage";
		}

		redirectAttributes.addFlashAttribute("flashUser", newUser);
		
		return "redirect:/registerSuccessful";
	}

}

8. Thymeleaf Template

_menu.html
<div xmlns:th="http://www.thymeleaf.org"
     style="border: 1px solid #ccc;padding:5px;margin-bottom:20px;">

  <a th:href="@{/}">Home</a>

     | &nbsp;

   <a th:href="@{/members}">Members</a>

     | &nbsp;

   <a th:href="@{/register}">Register</a>
  

</div>
membersPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <title th:utext="${title}"></title>
   </head>
   
   <style>
      table th, table td {
         padding: 5px;
      }
      .message {
         color: blue;
      }
   </style>
   
   <body>
   
      <!-- Include _menu.html -->
      <th:block th:include="/_menu"></th:block>  
      
      <h2>Members</h2>
      
   
      
      <table border="1">
            <tr>
               <th>User Name</th>
               <th>First Name</th>
               <th>Last Name</th>
               <th>Email</th>
               <th>Gender</th>
            </tr>
            <tr th:each ="member : ${members}">
               <td th:utext="${member.userName}">...</td>
               <td th:utext="${member.firstName}">...</td>
               <td th:utext="${member.lastName}">...</td>
               <td th:utext="${member.email}">...</td>
               <td th:utext="${member.gender}">...</td>
            </tr>
      </table>
      
   </body>
</html>
registerPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <title th:utext="${title}"></title>
      <style>
         th, td {
         padding: 5px;
         }
         td span  {
         font-size:90%;
         font-style: italic;
         color: red;
         }
         .error {
         color: red;
         font-style: italic;
         }
      </style>
   </head>
   <body>
      <!-- Include _menu.html -->
      <th:block th:include="/_menu"></th:block>
      
      <h2>Register</h2>
      
      <div th:if="${errorMessage != null}"
           th:utext="${errorMessage}" class="error">...</div>
      
      <form th:action="@{/register}" th:object="${appUserForm}" method="POST">
         <table>
            <tr>
               <td>User Name</td>
               <td><input type="text" th:field="*{userName}" /></td>
               <td>
                  <span th:if="${#fields.hasErrors('userName')}" th:errors="*{userName}">..</span>
               </td>
            </tr>
            <tr>
               <td>Password</td>
               <td><input type="password" th:field="*{password}" /> </td>
               <td>
                  <span th:if="${#fields.hasErrors('password')}" th:errors="*{password}">..</span>
               </td>
            </tr>
            <tr>
               <td>Confirm</td>
               <td><input type="password" th:field="*{confirmPassword}" /> </td>
               <td>
                  <span th:if="${#fields.hasErrors('confirmPassword')}" th:errors="*{confirmPassword}">..</span>
               </td>
            </tr>
            <tr>
               <td>Email</td>
               <td><input type="text" th:field="*{email}" /> </td>
               <td>
                  <span th:if="${#fields.hasErrors('email')}" th:errors="*{email}">..</span>
               </td>
            </tr>
            <tr>
               <td>First Name</td>
               <td><input type="text" th:field="*{firstName}" /> </td>
               <td>
                  <span th:if="${#fields.hasErrors('firstName')}" th:errors="*{firstName}">..</span>
               </td>
            </tr>
            <tr>
               <td>Last Name</td>
               <td><input type="text" th:field="*{lastName}" /> </td>
               <td>
                  <span th:if="${#fields.hasErrors('lastName')}" th:errors="*{lastName}">..</span>
               </td>
            </tr>
            <tr>
               <td>Gender</td>
               <td>
                  <select th:field="*{gender}">
                     <option value=""> -- </option>
                     <option value="M">Male</option>
                     <option value="F">Female</option>
                  </select>
               </td>
               <td>
                  <span th:if="${#fields.hasErrors('gender')}" th:errors="*{gender}">..</span>
               </td>
            </tr>
            <tr>
               <td>Country</td>
               <td>
                  <select th:field="*{countryCode}">
                     <option value=""> -- </option>
                     <option th:each="country : ${countries}"
                        th:value="${country.countryCode}"
                        th:utext="${country.countryName}"/>
                  </select>
               <td><span th:if="${#fields.hasErrors('countryCode')}" th:errors="*{countryCode}">..</span></td>
            </tr>
            <tr>
               <td>&nbsp;</td>
               <td>
                  <input type="submit" value="Submit" />
                  <a th:href="@{/}">Cancel</a>
               </td>
               <td>&nbsp;</td>
            </tr>
         </table>
      </form>
      
   </body>
</html>
registerSuccessfulPage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <title>Successfully registered</title>
      <style>
        span {color: blue;}
      </style>
   </head>
   
   <body>
   
      <!-- Include _menu.html -->
      <th:block th:include="/_menu"></th:block>  
      
      <h2>You have successfully registered!</h2>
      
      <div th:if="${flashUser != null}">
         <ul>
            <li>User Name: <span th:utext="${flashUser.userName}">..</span></li>
            <li>Email: <span th:utext="${flashUser.email}">..</span></li>
         </ul>
      </div>
     
      
   </body>
</html>
welcomePage.html
<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
   <head>
      <title th:utext="${title}"></title>
   </head>
   
   <body>
   
      <!-- Include _menu.html -->
      <th:block th:include="/_menu"></th:block>  
      
      <h2>Home Page!</h2>
      
   </body>
</html>

9. Chạy ứng dụng

Nhấn phải chuột vào project chọn:
  • Run As/Spring Boot App

Các hướng dẫn Spring Boot

Show More