大家好,我是你们那个总爱在代码里插科打诨的博主。今天是我们 Spring Boot 学习之旅的第二天!昨天我们刚把 Spring Boot 项目跑起来,今天就要正式进入 Web 开发战场了。
如果你还在用原始的 Spring MVC 手动配一堆 XML,那我只能说:兄弟,时代变了。Spring Boot 就像一个超级贴心的室友,把厨房、客厅、卫生间都打扫得干干净净,你只要负责吃喝玩乐(写业务代码)就行。
今天我们从零开始,手把手创建一个完整的 Spring Boot Web 项目,把静态资源、Thymeleaf 模板、文件上传、拦截器、自定义配置......统统玩一遍。代码注释会多到让你怀疑我是不是在写小说,风格嘛,继续保持幽默,争取让你边学边笑。
准备好了吗?出发!
第一步:创建 Spring Boot 项目
- 打开 https://start.spring.io
- 配置项目信息:
- Project: Maven(或者 Gradle,随你开心)
- Language: Java
- Spring Boot 版本:选最新的 2.x 或 3.x 都行(本文以 2.x 为例)
- Group: com.renliang
- Artifact: springboot-web-day2
- 依赖选择(重点!):
- Spring Web(必须,Web 开发核心)
- Thymeleaf(模板引擎)
- Lombok(可选,但能少写 getter/setter,强烈推荐)
- DevTools(热部署,改代码不用重启,太香了)
点击 Generate,下载解压后用 IDEA 打开。
项目结构大概长这样:
text
src/main/java → 放 Java 代码
src/main/resources → 放配置文件和静态资源
src/main/resources/templates → 放 Thymeleaf 模板
src/main/resources/static → 放 css/js/img 等静态资源
第二步:静态资源处理------Spring Boot 的"贴心管家"
Spring Boot 默认已经把静态资源映射配置好了,我们几乎不用动手指。
默认规则(超级重要!记住了):
Java
java
// 来自 WebMvcAutoConfiguration 的源码,翻译成大白话就是:
// 1. 所有 /webjars/** 请求,去 classpath:/META-INF/resources/webjars/ 找资源
// 比如引入 jQuery 的 webjar,就能直接用 /webjars/jquery/3.3.1/jquery.js
// 2. 所有 /** 请求,去以下四个文件夹找(顺序如列表):
"classpath:/META-INF/resources/"
"classpath:/resources/"
"classpath:/static/"
"classpath:/public/"
// 还有当前项目根路径 "/"
// 3. 欢迎页:以上文件夹下的 index.html,会被 /** 映射
// 访问 http://localhost:8080/ 直接显示 index.html
// 4. 图标:所有 **/favicon.ico 都会去静态资源文件夹找
我们来试试:
-
在 src/main/resources/static 下放一个 css/style.css: CSS
cssbody { background: pink; } /* 今天心情好,粉红少女风 */ -
在 src/main/resources/templates 下放一个 index.html(欢迎页): HTML
html<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <meta charset="UTF-8"> <title>欢迎页</title> <link rel="stylesheet" th:href="@{/css/style.css}"> </head> <body> <h1>欢迎来到我的 Spring Boot 小世界!🎉</h1> </body> </html>
启动项目,访问 http://localhost:8080/,你会看到粉红背景的欢迎页。是不是毫无配置就成功了?Spring Boot 默默说:我太难了,都不夸我。
Webjars 试玩(引入 jQuery):
在 pom.xml 加依赖:
XML
XML
<dependency>
<groupId>org.webjars</groupId>
<artifactId>jquery</artifactId>
<version>3.6.0</version>
</dependency>
然后在 html 里直接用:
HTML
html
<script th:src="@{/webjars/jquery/3.6.0/jquery.min.js}"></script>
访问页面控制台看网络请求,妥妥加载成功!
第三步:Thymeleaf 模板引擎------让页面"活"起来
Spring Boot 推荐 Thymeleaf,语法简单又强大。
默认配置(来自 ThymeleafProperties):
- 前缀:classpath:/templates/
- 后缀:.html
- 编码:UTF-8
- 缓存:生产环境开启,开发关闭(我们用默认)
创建实体类 User(用 Lombok 偷个懒)
Java
java
package com.renliang.pojo;
import lombok.Data;
import java.util.Date;
@Data // 自动生成 getter/setter/toString 等,简直是懒人福音
public class User {
private String username;
private String password;
private int age;
private int score;
private int gender; // 1=男 2=女
private Date date;
}
创建 Controller
Java
java
package com.renliang.controller;
import com.renliang.pojo.User;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
@Controller
public class DemoController {
@RequestMapping("/success")
public String success(Model model) {
// 普通文本和 HTML 文本
model.addAttribute("hello", "<h1>任亮是最帅的!</h1>");
// 放一个用户对象
User user = new User();
user.setUsername("renliang");
user.setPassword("123456");
user.setAge(18); // 永远 18 岁,嘿嘿
user.setScore(78);
user.setGender(2);
user.setDate(new Date());
model.addAttribute("user", user);
// 放一个用户列表,用于 th:each 演示
List<User> userList = new ArrayList<>();
for (int i = 0; i < 10; i++) {
User u = new User();
u.setUsername("renliang" + i);
u.setPassword("pwd" + i);
userList.add(u);
}
model.addAttribute("uList", userList);
// 返回模板名(会自动找 templates/success.html)
return "success";
}
}
创建 success.html(Thymeleaf 语法大赏)
HTML
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Thymeleaf 演示</title>
</head>
<body>
<!-- 普通文本替换 -->
<div th:text="${hello}">这里会被替换</div>
<!-- 支持 HTML 的替换(不会转义) -->
<div th:utext="${hello}">这里会显示 h1 标签</div>
<!-- 对象属性取值两种方式 -->
<div th:object="${user}">
<p>用户名: <span th:text="*{username}"></span></p>
</div>
<p>年龄直接取: <span th:text="${user.age}"></span></p>
<!-- 条件判断 -->
<a th:if="${user.age == 18}" th:href="@{https://www.baidu.com}">我永远18岁!</a>
<a th:unless="${user.age > 18}">我还年轻!</a>
<!-- 三元运算符给 class -->
<p th:class="${user.score >= 60} ? 'pass' : 'fail'">
成绩:[[${user.score}]] (内联文本写法)
</p>
<!-- switch case -->
<div th:switch="${user.gender}">
<p th:case="1">帅哥</p>
<p th:case="2">美女</p>
<p th:case="*">未知生物</p>
</div>
<!-- 遍历列表 -->
<table border="1">
<thead>
<tr>
<th>序号</th>
<th>用户名</th>
<th>密码</th>
</tr>
</thead>
<tbody>
<tr th:each="u,stat : ${uList}">
<td th:text="${stat.index + 1}">序号</td> <!-- index 从0开始 -->
<td th:text="${u.username}">用户名</td>
<td th:text="${u.password}">密码</td>
</tr>
</tbody>
</table>
<!-- URL 表达式 -->
<a th:href="@{/upload}">去上传文件</a>
<!-- 日期格式化(实体类上加 @JSONField 也行,这里直接用 Thymeleaf) -->
<p>当前时间: <span th:text="${#dates.format(user.date, 'yyyy-MM-dd HH:mm:ss')}"></span></p>
</body>
</html>
访问 /success,你会看到各种动态内容展示得美滋滋。
第四步:文件上传功能
页面 upload.html(放在 templates)
HTML
html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>文件上传</title>
</head>
<body>
<h2>来上传一张帅照吧!😎</h2>
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
<input type="file" name="pic"><br><br>
<input type="submit" value="上传">
</form>
</body>
</html>
Controller 处理上传
Java
java
package com.renliang.controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.FileOutputStream;
@RestController // 这里用 RestController 是为了直接返回字符串,方便看结果
public class UploadController {
@PostMapping("/upload")
public String upload(@RequestParam("pic") MultipartFile file) {
if (file.isEmpty()) {
return "文件为空!别逗我玩";
}
String fileName = file.getOriginalFilename();
String filePath = "D:/imgup/"; // 改成你自己的路径,记得有写权限
try {
// 创建目录(如果不存在)
File targetDir = new File(filePath);
if (!targetDir.exists()) {
targetDir.mkdirs();
}
// 写文件
FileOutputStream out = new FileOutputStream(filePath + fileName);
out.write(file.getBytes());
out.flush();
out.close();
return "上传成功!文件名叫:" + fileName + " 😄";
} catch (Exception e) {
e.printStackTrace();
return "上传失败了,哭唧唧 😭";
}
}
}
上传大小限制可以在 application.properties 配置:
properties
# 单文件最大 50MB
spring.servlet.multipart.max-file-size=50MB
# 总请求大小 100MB
spring.servlet.multipart.max-request-size=100MB
第五步:扩展 Spring MVC(自定义配置)
创建一个配置类:
Java
java
package com.renliang.config;
import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.alibaba.fastjson.support.spring.FastJsonConfig;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.servlet.config.annotation.*;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
@Configuration
public class MyWebConfig implements WebMvcConfigurer {
// 1. 视图控制器:直接把 /tx 转发到 success.html
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/tx").setViewName("success");
}
// 2. 拦截器:所有请求都拦截,除了 /hello2
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MyInterceptor())
.addPathPatterns("/**")
.excludePathPatterns("/hello2");
}
// 3. Fastjson 替换默认 Jackson(先引入 fastjson 依赖)
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
FastJsonHttpMessageConverter converter = new FastJsonHttpMessageConverter();
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(
com.alibaba.fastjson.serializer.SerializerFeature.PrettyFormat
);
converter.setFastJsonConfig(config);
converters.add(0, converter); // 加到最前面优先使用
}
}
自定义拦截器:
Java
java
package com.renliang.config;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
System.out.println("【前置拦截】我来啦~ 请求路径:" + request.getRequestURI());
return true; // 返回 false 就拦住了
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, org.springframework.web.servlet.ModelAndView modelAndView) {
System.out.println("【后置拦截】处理完了哦~");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
System.out.println("【最终清理】视图渲染完了,收工!");
}
}
第六步:注册 Servlet、Filter、Listener(三大组件)
Java
java
package com.renliang.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletListenerRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.*;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
@Configuration
public class ServletConfig {
// 注册 Servlet
@Bean
public ServletRegistrationBean<MyServlet> myServlet() {
return new ServletRegistrationBean<>(new MyServlet(), "/myServlet");
}
// 注册 Filter
@Bean
public FilterRegistrationBean<MyFilter> myFilter() {
FilterRegistrationBean<MyFilter> bean = new FilterRegistrationBean<>();
bean.setFilter(new MyFilter());
bean.setUrlPatterns(Arrays.asList("/hello", "/myServlet"));
return bean;
}
// 注册 Listener
@Bean
public ServletListenerRegistrationBean<MyListener> myListener() {
return new ServletListenerRegistrationBean<>(new MyListener());
}
}
// 自定义 Servlet
class MyServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
resp.getWriter().write("我是手动注册的 Servlet,Spring Boot 也支持我哦~");
}
}
// 自定义 Filter
class MyFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
System.out.println("MyFilter 正在过滤...");
chain.doFilter(request, response);
}
}
// 自定义 Listener
class MyListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
System.out.println("ServletContext 初始化了,我听到了!");
}
}
最后一步:application.properties 配置汇总
properties
server.port=8080
# server.servlet.context-path=/tx # 项目路径前缀,可选
# Thymeleaf 缓存开发时关闭,生产开启
spring.thymeleaf.cache=false
# 文件上传大小
spring.servlet.multipart.max-file-size=50MB
spring.servlet.multipart.max-request-size=100MB
写在最后
两天时间,我们从一个空项目,玩到了静态资源、模板引擎、文件上传、拦截器、自定义配置,甚至手动注册三大组件。Spring Boot 的自动配置真的太省心了,省下来的时间我们可以多喝几杯奶茶🥤。
下一天我们将继续深入,可能聊聊数据访问、事务、安全这些硬核内容。
喜欢的话点个赞、收藏、关注三连哦~你的鼓励是我更新的最大动力!
我们下次见!👋