Spring Boot Web 开发实战:第二天,从零搭个“会卖萌”的小项目

大家好,我是你们那个总爱在代码里插科打诨的博主。今天是我们 Spring Boot 学习之旅的第二天!昨天我们刚把 Spring Boot 项目跑起来,今天就要正式进入 Web 开发战场了。

如果你还在用原始的 Spring MVC 手动配一堆 XML,那我只能说:兄弟,时代变了。Spring Boot 就像一个超级贴心的室友,把厨房、客厅、卫生间都打扫得干干净净,你只要负责吃喝玩乐(写业务代码)就行。

今天我们从零开始,手把手创建一个完整的 Spring Boot Web 项目,把静态资源、Thymeleaf 模板、文件上传、拦截器、自定义配置......统统玩一遍。代码注释会多到让你怀疑我是不是在写小说,风格嘛,继续保持幽默,争取让你边学边笑。

准备好了吗?出发!


第一步:创建 Spring Boot 项目

  1. 打开 https://start.spring.io
  2. 配置项目信息:
    • Project: Maven(或者 Gradle,随你开心)
    • Language: Java
    • Spring Boot 版本:选最新的 2.x 或 3.x 都行(本文以 2.x 为例)
    • Group: com.renliang
    • Artifact: springboot-web-day2
  3. 依赖选择(重点!):
    • 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 都会去静态资源文件夹找

我们来试试:

  1. 在 src/main/resources/static 下放一个 css/style.css: CSS

    css 复制代码
    body { background: pink; } /* 今天心情好,粉红少女风 */
  2. 在 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 的自动配置真的太省心了,省下来的时间我们可以多喝几杯奶茶🥤。

下一天我们将继续深入,可能聊聊数据访问、事务、安全这些硬核内容。

喜欢的话点个赞、收藏、关注三连哦~你的鼓励是我更新的最大动力!

我们下次见!👋

相关推荐
郑州光合科技余经理2 小时前
可独立部署的Java同城O2O系统架构:技术落地
java·开发语言·前端·后端·小程序·系统架构·uni-app
笨蛋不要掉眼泪2 小时前
Spring Boot + RedisTemplate 数据结构的基础操作
java·数据结构·spring boot·redis·wpf
Remember_9932 小时前
Spring 事务深度解析:实现方式、隔离级别与传播机制全攻略
java·开发语言·数据库·后端·spring·leetcode·oracle
编程彩机3 小时前
互联网大厂Java面试:从分布式事务到微服务优化的技术场景解读
java·spring boot·redis·微服务·面试·kafka·分布式事务
Moshow郑锴3 小时前
Spring Boot Data API 与 Redis 集成:KPI/图表/表格查询的缓存优化方案
spring boot·redis·缓存
2301_815357703 小时前
如何将SSM项目通过tomcat部署到Linux云服务器上?
linux·服务器·tomcat
好好研究3 小时前
SpringBoot整合SpringMVC
xml·java·spring boot·后端·mvc
千寻技术帮3 小时前
10386_基于SpringBoot的外卖点餐管理系统
java·spring boot·vue·外卖点餐
曹轲恒3 小时前
SpringBoot整合SpringMVC(末)
java·spring boot·后端