SpringBoot 開發 Web 系統,快速入門指南!
01、背景介紹
在之前的文章中,我們簡單的介紹了 SpringBoot 項目的創建過程,了解了 Spring Boot 開箱即用的特性,本篇文章接著上篇的內容繼續介紹 Spring Boot 用于 web 工程開發時的其它特性。
廢話不多說了,上代碼!
02、應用實踐
當將 SpringBoot 框架用于傳統的 web 項目開發時,通常分為以下三個過程來實現。
- 第一步:連接數據庫,實現對表進行 CRUD 操作
- 第二步:引入模板引擎來開發頁面
- 第三步:使用一些常見的 web 特性來滿足其它的功能開發
最后源碼目錄結構如下!
springboot-hello
├── src
│ └── main
│ ├── java
│ ├── com
│ ├── example
│ ├── springboot
│ ├── Application.java
│ ├── entity
│ ├── User.java
│ ├── service
│ ├── UserService.java
│ ├── web
│ ├── UserController
│ └── resources
│ ├── application.properties
│ ├── templates
│ └─── index.html
└── pom.xml
下面我們依次來看看相關的實踐過程。
2.1、數據庫操作
這里我們以 Mysql 數據庫為例,采用 Spring 的 JdbcTemplate 模板來操作數據的的增刪改查,過程如下。
2.1.1、準備數據庫表
先創建tb_user表,包含屬性id、name、age,可以通過執行下面的建表語句。
CREATE TABLE `tb_user` (
`id` bigint(20) unsigned NOT NULL,
`name` varchar(30) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
2.1.2、添加相關依賴包
在我們訪問數據庫的時候,需要先配置一個數據庫驅動包,通常采用 JDBC 方式方式訪問,需要在pom.xml中引入相關依賴包。
<!--spring jdbc-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--mysql 驅動-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
2.1.3、添加數據源配置
與此同時,還需要在application.properties文件中配置相關的數據源訪問地址。
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
2.1.4、編寫領域對象
根據數據庫中創建的 User 表,創建對應的領域對象。
package com.example.springboot.entity;
public class User {
/**
* 用戶ID
*/
private Long id;
/**
* 用戶名稱
*/
private String name;
/**
* 用戶年齡
*/
private Integer age;
// set、get方法等...
}
2.1.5、編寫數據訪問對象
通過JdbcTemplate實現對tb_user表中的數據訪問操作。
package com.example.springboot.service;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 查詢用戶
* @return
*/
public List<User> getAll() {
List<User> users = jdbcTemplate.query("select id, name, age from tb_user", (resultSet, i) -> {
User user = new User();
user.setId(resultSet.getLong("id"));
user.setName(resultSet.getString("name"));
user.setAge(resultSet.getInt("age"));
return user;
});
return users;
}
/**
* 通過ID查詢用戶
* @param id
* @return
*/
public User getById(Long id) {
User target = jdbcTemplate.queryForObject("select id, name, age from tb_user where id = ?", new BeanPropertyRowMapper<User>(User.class),id);;
return target;
}
/**
* 創建用戶
* @param entity
* @return
*/
public int create(User entity){
return jdbcTemplate.update("insert into tb_user(id, name, age) values(?, ?, ?)", entity.getId(), entity.getName(), entity.getAge());
}
/**
* 修改用戶
* @param entity
* @return
*/
public int updateById(User entity){
return jdbcTemplate.update("update tb_user set name = ?, age = ? where id = ? ", entity.getName(), entity.getAge(), entity.getId());
}
/**
* 刪除用戶
* @param id
* @return
*/
public int deleteById(Long id) {
return jdbcTemplate.update("delete from tb_user where id = ?", id);
}
}
2.1.6、編寫單元測試用例
在src/test/java目錄下,編寫單元測試用例,驗證代碼中的增、刪、改、查操作的正確性,包名與主目錄中保持一致。
package com.example.springboot;
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Autowired
private UserService userService;
@Test
public void test(){
// 插入5條數據
userService.create(new User(1L, "張三", 20));
userService.create(new User(2L, "李四", 21));
userService.create(new User(3L, "王五", 22));
// 查詢全部數據
List<User> dbList1 = userService.getAll();
System.out.println("第一次全量查詢結果:" + dbList1.toString());
// 修改數據
userService.updateById(new User(2L, "趙六", 21));
// 查詢指定數據
User updateObj = userService.getById(2l);
System.out.println("查詢[id=2]結果:" + updateObj.toString());
// 刪除數據
userService.deleteById(2L);
userService.deleteById(3L);
// 查詢全部數據
List<User> dbList2 = userService.getAll();
System.out.println("第二次全量查詢結果:" + dbList2.toString());
}
}
單元測試,運行后的輸出結果:
第一次全量查詢結果:[User{id=1, name='張三', age=20}, User{id=2, name='李四', age=21}, User{id=3, name='王五', age=22}]
查詢[id=2]結果:User{id=2, name='趙六', age=21}
第二次全量查詢結果:[User{id=1, name='張三', age=20}]
此時操作數據庫中的表數據,已經正常流通了。
上面介紹的JdbcTemplate只是最基本的幾個操作,更多其他數據訪問操作的使用可以參考:JdbcTemplate API
2.2、Thymeleaf 模板
在傳統的 Java web 工程中,通常會采用 JSP 來編寫頁面并進行數據展示。而在 Spring Boot 框架中,推薦使用 Thymeleaf 模板引擎來開發 Web 頁面。
Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 內容的模板引擎,與 JSP、Velocity、FreeMarker 等類似,都可以輕易的與 Spring MVC 等 Web 框架進行集成作為 Web 應用的模板引擎。
下面我們一起來看下簡單的頁面集成應用。
2.2.1、添加相關依賴包
在 SpringBoot 項目中使用 Thymeleaf 時,只需要添加所需的模板引擎模塊依賴包即可,內容如下。
<!--thymeleaf模板引擎-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
2.2.2、添加相關的配置參數
與此同時,還需要在application.properties文件中配置 thymeleaf 模版掃描路徑,比如如下配置。
# thymelea模板配置
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.mode=HTML5
spring.thymeleaf.encoding=UTF-8
spring.thymeleaf.cache=false
spring.thymeleaf.enabled=true
其中spring.thymeleaf.prefix就是模板引擎掃描的路徑。
2.2.3、創建頁面模板
根據上一步映射的模板路徑, 在模板路徑src/main/resources/templates下新建模板文件index.html,內容如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Home Page</title>
</head>
<body>
<h1>Hello !</h1>
<table>
<thead>
<tr>
<th>用戶ID</td>
<th>用戶名稱</td>
<th>用戶年齡</td>
</tr>
</thead>
<tbody>
<tr th:each="prod:${allUsers}">
<td th:text="${prod.id}">100</td>
<td th:text="${prod.name}">張三豐</td>
<td th:text="${prod.age}">99</td>
</tr>
</tbody>
</table>
</body>
</html>
2.2.4、編寫頁面請求對象
最后編寫一個頁面請求對象,用來處理路徑的請求,將數據渲染到index頁面上,具體實現如下:
@Controller
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/")
public String index(ModelMap map) {
// 查詢所有的用戶
List<User> users = userService.getAll();
map.addAttribute("allUsers", users);
return "index";
}
}
將上文中的三條數據插入到數據庫,以便展示。
2.2.5、測試頁面展示情況
最后將服務啟動,在瀏覽器發起請求,地址為http://localhost:8080/,展示結果如下:
圖片
說明頁面渲染正常,符合預期效果。
更多 Thymeleaf 的頁面語法,可以訪問 Thymeleaf 的官方文檔來深入學習使用。
2.3、web 基本特性
除了以上功能,SpringBoot 還有幾個常用特性功能,比如 SpringMVC 中的接口開發、過濾器、攔截器、aop 代理、異常處理等。
下面,我們一起簡要的看看相關特性的用法。
2.3.1、接口開發
當與其它項目對接的時候,通常會采用 json 數據格式進行請求和返回,在傳統的 SpringMVC 項目中,我們通常需要在每個接口方法上加@ResponseBody注解,以便數據以 json 格式返回給用戶。
在 Spring Boot 框架中,我們只需要在接口類上添加@RestController注解,即可實現@Controller和@ResponseBody一樣的效果。
示例如下。
@RestController
public class ApiController {
@Autowired
private UserService userService;
@GetMapping("/getUsers")
public List<User> getUsers() {
// 查詢所有的用戶
System.out.println("收到查詢用戶的請求");
List<User> users = userService.getAll();
return users;
}
}
將服務啟動,訪問http://localhost:8080/getUsers,看看控制臺輸出結果。
圖片
可以看到,與預期一致。
如果是頁面開發,只要使用@Controller注解即可,以免無法渲染數據。
2.3.2、過濾器
過濾器在 web 項目開發過程中經常會用到,比如用于收集調用日志、排除有 XSS 威脅的字符等,過濾器本質不屬于 SpringBoot 自帶的功能,而是 Servlet 提供的功能,SpringBoot 對此做了集成管理,實現方式也很簡單。
首先創建一個過濾器實現類,示例如下。
public class LogFilter implements Filter {
@Override
public void init(javax.servlet.FilterConfig filterConfig) throws ServletException {
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
System.out.println("日志過濾器,request url :"+request.getRequestURI());
filterChain.doFilter(servletRequest, servletResponse);
}
@Override
public void destroy() {
}
}
然后將過濾器注冊到 SpringBoot 中,示例如下。
@Configuration
public class FilterConfig {
/**
* 添加過濾器
* @return
*/
@Bean
public FilterRegistrationBean helloFilterRegistrationBean() {
FilterRegistrationBean registration = new FilterRegistrationBean();
registration.setName("logFilter"); // 指定過濾器名稱
registration.setFilter(new LogFilter()); // 指定過濾器實現類
registration.setUrlPatterns(Collections.singleton("/*"));// 指定攔截路徑
registration.addInitParameter("paramName", "paramValue");// 指定初始化參數
registration.setOrder(1);// 指定順序
return registration;
}
}
將服務啟動,訪問http://localhost:8080/getUsers,看看控制臺輸出結果。
日志過濾器,request url :/getUsers
收到查詢用戶的請求
說明過濾器已經正常工作了。
2.3.3、攔截器
攔截器在 web 項目開發過程中也經常會用到,比如用于用戶權限的攔截等等。攔截器屬于 SpringMVC 自帶的功能,因此 SpringBoot 默認就支持,實現方式也很簡單。
首先創建一個攔截器實現類,示例如下。
public class SignInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
System.out.println("方法前攔截,request url:" + request.getRequestURI());
return HandlerInterceptor.super.preHandle(request, response, handler);
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
ModelAndView modelAndView) throws Exception {
System.out.println("方法中攔截(不能攔截異常),request url:" + request.getRequestURI());
HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
System.out.println("方法后攔截(能攔截異常),request url:" + request.getRequestURI());
}
}
然后,將攔截器注冊到攔截器鏈中。
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
// 將SignInterceptor添加到攔截器鏈中,并且指定攔截路徑和攔截順序
registry.addInterceptor(new SignInterceptor()).addPathPatterns("/*").order(1);
}
}
將服務啟動,訪問http://localhost:8080/getUsers,看看控制臺輸出結果。
方法前攔截,request url:/getUsers
收到查詢用戶的請求
方法中攔截(不能攔截異常),request url:/getUsers
方法后攔截(能攔截異常),request url:/getUsers
可以發現,過濾器的執行順序在攔截器之前。
其中攔截器中postHandle()和afterCompletion()方法,都可以實現對接口執行后進行攔截,兩者不同點在于:
- postHandle()方法無法攔截異常;
- afterCompletion()方法可以攔截異常;
可以新增一個getUsersError()方法,增加運行時異常。
@GetMapping("/getUsersError")
public List<User> getUsersError() {
// 查詢所有的用戶
System.out.println("收到查詢用戶的請求");
if(1==1){
throw new NullPointerException("異常測試");
}
List<User> users = userService.getAll();
return users;
}
再次請求訪問http://localhost:8080/getUsersError,控制臺輸出結果如下。
方法前攔截,request url:/getUsersError
收到查詢用戶的請求
方法后攔截,request url:/getUsersError
當出現異常時,可見postHandle()方法,沒有被執行。
2.3.4、aop 代理
aop 動態代理也是 web 項目開發過程中常用的功能特性,熟悉 Spring 的同學可能知道,Spring 的動態代理技術使用了 aspectj 框架的注解來實現切面技術,因此在使用的時候,需要添加相關的依賴包。
首先在pom.xml文件中添加 aspectj 依賴包,示例如下。
<!--添加 aspectj 依賴包 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
</dependency>
編寫動態代理類,代理com.example.springboot.web包下所有類的public方法。
@Order(1)
@Component
@Aspect
public class ControllerAspect {
/***
* 定義切入點
*/
@Pointcut("execution(public * com.example.springboot.web..*.*(..))")
public void methodAdvice(){}
/**
* 方法調用前通知
*/
@Before(value = "methodAdvice()")
public void before(JoinPoint joinPoint){
System.out.println("代理-> 來自Before通知,方法名稱:" + joinPoint.getSignature().getName());
}
/**
* 方法調用后通知
*/
@After(value = "methodAdvice()")
public void after(JoinPoint joinPoint){
System.out.println("代理-> 來自After通知,方法名稱:" + joinPoint.getSignature().getName());
}
/**
* 方法調用后通知,方法正常執行后,有返回值,會通知;如果拋異常,不會通知
*/
@AfterReturning(value = "methodAdvice()", returning = "returnVal")
public void afterReturning(JoinPoint joinPoint,Object returnVal){
System.out.println("代理-> 來自AfterReturning通知,方法名稱:" + joinPoint.getSignature().getName() + ",返回值:" + returnVal.toString());
}
/**
* 方法環繞通知
*/
@Around(value = "methodAdvice()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("代理-> 來自Around環繞前置通知,方法名稱:" + joinPoint.getSignature().getName());
Object returnValue = joinPoint.proceed();
System.out.println("代理-> 來自Around環繞后置通知,方法名稱:" + joinPoint.getSignature().getName());
return returnValue;
}
/**
* 拋出異常通知
*/
@AfterThrowing(value = "methodAdvice()", throwing = "ex")
public void afterThrowing(JoinPoint joinPoint, Exception ex) {
System.out.println("代理-> 來自AfterThrowing通知,方法名稱:" + joinPoint.getSignature().getName() + ",錯誤信息:" + ex.getMessage());
}
}
將服務啟動,訪問http://localhost:8080/getUsers,控制臺輸出結果如下。
代理-> 來自Around環繞前置通知,方法名稱:getUsers
代理-> 來自Before通知,方法名稱:getUsers
收到查詢用戶的請求
代理-> 來自Around環繞后置通知,方法名稱:getUsers
代理-> 來自After通知,方法名稱:getUsers
代理-> 來自AfterReturning通知,方法名稱:getUsers,返回值:[User{id=1, name='張三', age=20}, User{id=2, name='李四', age=21}, User{id=3, name='王五', age=22}]
訪問http://localhost:8080/getUsersError,控制臺輸出結果如下。
代理-> 來自Around環繞前置通知,方法名稱:getUsersError
代理-> 來自Before通知,方法名稱:getUsersError
收到查詢用戶的請求
代理-> 來自After通知,方法名稱:getUsersError
代理-> 來自AfterThrowing通知,方法名稱:getUsersError,錯誤信息:異常測試
可以很清晰的看到,當出現異常時AfterReturning()通知方法和Around環繞后置通知方法都不會執行,異常信息會進入到AfterThrowing() 通知方法中。
2.3.5、異常處理
Spring Boot 對異常處理也做了很多的支持,開發者可以通過@ExceptionHandler注解來全局代理異常信息,實現方式也很簡單。
編寫一個全局異常處理類,示例如下。
@ControllerAdvice
public class GlobalExceptionHandler {
/**
* 處理Exception異常
* @param ex
* @return
*/
@ExceptionHandler(value = Exception.class)
@ResponseBody
public Map exceptionHandler(Exception ex){
Map<String,Object> errorMap = new HashMap<>();
errorMap.put("code","500");
errorMap.put("message",ex.getMessage());
return errorMap;
}
}
將服務啟動,訪問http://localhost:8080/getUsersError,控制臺輸出結果如下。
可以看到,異常請求被成功接管。
03、參考
1、https://springdoc.cn/spring-boot/index.html