# 环境
Spring Boot 3.2.0-SNAPSHOT 需要 Java 17 ,并且可以兼容到 Java 20,包括 Java 20。还需要 Spring Framework 6.1.0-M1 或以上版本。
Maven 3.6.3 及其以上
Gradle 7.x (7.5 及其以上) 和 8.x
java17 及以上
idea
# 入门
在 idea 中新建项目
选择 Spring Initializr
填入相关内容后(类型选择 Maven)
点击下一步
Web 中选择 Spring Web
点击创建
# 项目创建成功后
# 目录
.idea 为 idea 的文件目录不用管
.mvm 为 maven 的文件目录
src 为代码目录,以后的编码环境都在此目录下
.gitignore 为 git 的排除目录
HELP.md 为描述文档
mvnw Maven Wrappe
mvnw.cmd 适用于 Windows 环境
pom.xml 管理 maven 的配置文件
# Hello World!
在 src/main/java/com.*.* 下新建 Controller 目录
(* 号分别为组名和工件名)
在 Controller 新建文件 "TestController.java"
1 2 3 4 5 6 7 8 9 10 11 12 import org.springframework.web.bind.annotation.RequestMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestController { @RequestMapping("/home") String home () { return "Hello World!" ; } }
点击运行
浏览器使用 get 或者 post 访问 http://127.0.0.1:8080/home
即输出 Hello Werld!
# 规范书写
# 目录结构书写
src/main/java: 存放 Java 源代码文件的目录。这里通常包括以下子目录:
com/example/yourproject: 项目的主要 Java 包。这里存放应用程序的核心类和逻辑。
config: 存放应用程序的配置类。
controller: 存放 Spring MVC 控制器类。
service: 存放业务逻辑层的接口和实现类。
mapper/repository: 存放数据访问层 (如 JPA 或 JDBC) 的接口和实现类。
model: 存放实体类或数据传输对象 (DTO)。
src/main/resources: 存放应用程序的配置文件和其他资源文件。这里通常包括以下内容:
application.properties 或 application.yml: 应用程序的主要配置文件。
static: 存放静态资源文件,如 CSS、JavaScript、图片等。
templates: 存放 Thymeleaf、JSP 或其他模板引擎的视图文件。
data.sql: 初始化数据库的 SQL 脚本。
src/test/java: 存放单元测试和集成测试的 Java 源代码文件。
pom.xml: Maven 项目的 POM (Project Object Model) 文件,用于管理依赖项和构建过程。
application.properties 或 application.yml: 应用程序的主要配置文件,用于设置数据源、服务器端口、日志级别等。
mvnw 和 mvnw.cmd: Maven Wrapper 文件,用于在没有安装 Maven 的环境下构建和运行项目。
# 详细目录
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 servicex // 项目名 |- servicex-auth // 模块1 |- servicex-common // 模块2 |- servicex-gateway // 模块3 |- servicex-system // 模块4 |- src |- main // 业务逻辑 |- assembly // 基于maven assembly插件的服务化打包方案 |- bin // 模块脚本(启动、停止、重启) |- sbin // 管理员角色使用的脚本(环境检查、系统检测等等) |- assembly.xml // 配置文件 |- java // 源码 |- com |- hadoopx |- servicex |- annotation // 注解 |- aspect // 面向切面编程 |- config // 配置文件POJO |- filter // 过滤器 |- constant // 存放常量 |- utils // 工具 |- exception // 异常 |- controller // 控制层(将请求通过URL匹配,分配到不同的接收器/方法进行处理,然后返回结果) |- service // 服务层接口 |- impl // 服务层实现 |- mapper/repository // 数据访问层,与数据库交互为service提供接口 |- model/entity/domain // 实体对象 |- dto // 持久层需要的实体对象(用于服务层与持久层之间的数据传输对象) |- vo // 视图层需要的实体对象(用于服务层与视图层之间的数据传输对象) |- *Application.java // 入口启动类 |- resources // 资源 |- static // 静态资源(html、css、js、图片等) |- templates // 视图模板(jsp、thymeleaf等) |- mapper // 存放数据访问层对应的XML配置 |- *Mapper.xml |- ... |- application.yml // 公共配置 |- application-dev.yml // 开发环境配置 |- application-prod.yml // 生产环境配置 |- banner.txt |- logback.xml // 日志配置 |- test // 测试源码 |- java |- com |- hadoopx |- servicex |- system |- 根据具体情况按源码目录结构存放编写的测试用例 |- target // 编译打包输出目录(自动生成,不需要创建) |- pom.xml // 该模块的POM文件 |- sql // 项目需要的SQL脚本 |- doc // 精简版的开发、运维手册 |- .gitignore // 哪些文件不用传到版本管控工具中 |- pom.xml // 工程总POM文件 |- README.md // 注意事项 External Libraries // 相关JAR包依赖
# restful 风格
即
URL 路由中只使用名词来定位资源,用 HTTP 协议里的动词(GET、POST、PUT、DELETE)来实现资源的增删改查操作。
举例
增加一个 id /id 请求协议:POST
删除一个 id /id 请求协议:DELETE
修改一个 id /id 请求协议:PUT
查找一个 id /id 请求协议:GET
# 完整访问数据库的例子
# mysql
# 创建数据库
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 DROP DATABASE IF EXISTS `test`;CREATE DATABASE IF NOT EXISTS `test` ;USE `test`; DROP TABLE IF EXISTS `user `;CREATE TABLE IF NOT EXISTS `user ` ( `id` int NOT NULL AUTO_INCREMENT, `user_name` varchar (50 ) NOT NULL , `pass_word` varchar (50 ) NOT NULL , `name` varchar (50 ) NOT NULL , PRIMARY KEY (`id`), UNIQUE KEY `user_pk_2` (`user_name`) ) ENGINE= InnoDB DEFAULT CHARSET= utf8mb4 COLLATE = utf8mb4_0900_ai_ci;
创建好表后往里面添加几条数据
# 导包
1 2 3 4 5 6 <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.33</version > </dependency >
# 配置
在 resources 文件夹下新建 application.yml
1 2 3 4 5 6 7 8 spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&useSSL=true username: root password: abab
# 书写实体类
即表的模型
# 导包
lombok 可以直接使用注解写 get、set、toString 等方法
1 2 3 4 5 6 7 <dependency > <groupId > org.projectlombok</groupId > <artifactId > lombok</artifactId > <version > 1.18.32</version > <scope > provided</scope > </dependency >
在 model 包下创建 User.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 package com.test.test.model;import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;@Data @AllArgsConstructor @NoArgsConstructor public class User { public Integer Id; public String UserName; public String PassWord; public String Name; }
# mybatis
mybatis 是用于操作数据的框架,可以使开发更方便
我们这个例子使用 Mybatis + 注解的形式
# 导包
1 2 3 4 5 6 <dependency > <groupId > org.mybatis.spring.boot</groupId > <artifactId > mybatis-spring-boot-starter</artifactId > <version > 3.0.3</version > </dependency >
# 配置
因为我们的实体类使用的驼峰命名法,而数据库使用的下划线分割法,所以要加转化配置
application.yml
1 2 3 4 5 mybatis: configuration: map-underscore-to-camel-case: true
# 编码
于 Controller 创建 TestController 控制器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;@RestController public class TestController { @Autowired private TestService testService; @GetMapping("id") User getId () { return testService.getId(); } }
@RestController 标注为控制器
@Autowired 自动导入
@GetMapping (“id”) 接收请求类型为 get 链接为 /id
Service 中创建 TestService
1 2 3 4 public interface TestService { public User getId () ; }
这里面写的就是你的接口
Service/Impl 中写实现类 TestServiceImpl
1 2 3 4 5 6 7 8 9 10 11 12 13 import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;@Service public class TestServiceImpl implements TestService { @Autowired private TestMapper testMapper; @Override public User getId () { return testMapper.getId(); } }
@Service 标注为 Service
Mapper 中创建接口 TestMapper
1 2 3 4 5 6 7 @Mapper @Repository public interface TestMapper { @Select("select * from user where id=1") public User getId () ; }
@Mapper 当我们在 Mapper 接口上使用 @Mapper 注解时,MyBatis 会自动为这个接口生成一个实现类
@Select 使用 @Select 注解可以直接在 Mapper 接口方法上定义对应的 SQL 语句
写好后点击运行
在浏览器访问链接 (http://127.0.0.1:8080/id )
就可以访问到数据库里的数据了!
流程为浏览器发送请求,Controller 通过 @GetMapping (“id”) 接收请求到 getId 方法中,
在调用 Service 层的 getId () 方法,在通过 Service 调用 Mapper 层的接口操作数据库返回成 User 实体
提问:为什么不直接 Controller 层调用 Mapper 层返回实体呢?要 Service 有什么用呢?
因为 Service 层是实现业务逻辑的主要地方。这里不仅包括具体的业务操作,比如计算、数据处理等,还包括对数据的校验、业务规则的应用等。
# mybatis plus (访问数据库)
# 过滤器 (Filter) 拦截器 (Interceptor)
过滤器 (Filter) 阶段:
过滤器是基于 Java Servlet 规范的一部分。它们在请求到达 Spring 的前端控制器 DispatcherServlet 之前运行。
过滤器可以对进入的请求进行预处理,也可以对发送回客户端的响应进行后处理。它们通常用于日志记录、身份验证、编码请求体、跨站点请求伪造 (CSRF) 保护等。
如果有多个过滤器,它们的执行顺序根据在 web 配置中的声明顺序或通过 @Order 注解指定的顺序。
拦截器链 (Interceptor Chain) 的 preHandle 方法执行:
请求通过所有过滤器后,接下来会到达 Spring 的 DispatcherServlet。
在 DispatcherServlet 将请求路由到相应的 Controller 之前,配置的拦截器的 preHandle 方法会被调用。
拦截器可以进行更细粒度的控制,比如权限检查、日志记录、事务管理等。如果 preHandle 返回 false,则停止请求的进一步处理。
请求到达 Controller:
如果拦截器的 preHandle 方法返回 true,请求继续向下执行,最终到达目标 Controller。
Controller 处理请求,并返回相应的模型视图或响应体。
拦截器链的 postHandle 和 afterCompletion 方法执行:
一旦 Controller 处理完请求,拦截器链中的 postHandle 方法会被调用,接着是视图渲染。
最后,在响应发送回客户端之后,afterCompletion 方法被调用,这是进行资源清理的好时机。
整个流程如下:请求进入 → 过滤器 → DispatcherServlet → 拦截器的 preHandle → Controller → 拦截器的 postHandle → 视图渲染 → 拦截器的 afterCompletion。
过滤器和拦截器虽然在功能上有所重叠,但它们各自有不同的用途和适用场景。过滤器更接近于底层的请求处理,而拦截器提供了更细粒度的控制,且完全集成在 Spring 的请求处理流程中。
# 过滤器 (filter) 的应用
举例:发送的 /id 请求验证 token,/ab 验证请求的请求头中携带 "req-name" 的值是否为 "abcde"
# 代码书写
@WebFilter+@ServletComponentScan(“com.zhengxl.filterdemo.filter”)
@WebFilter (urlPatterns = “/*”) 为拦截所有请求
@ServletComponentScan (“com.zhengxl.filterdemo.filter”) 放在启动器类上
1 2 3 4 5 6 7 8 9 10 @SpringBootApplication @ServletComponentScan("com.zhengxl.filterdemo.filter") public class TestApplication { public static void main (String[] args) { SpringApplication.run(TestApplication.class, args); } }
于 filter 中创建 TokenFilter
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 import jakarta.servlet.*;import jakarta.servlet.annotation.WebFilter;import jakarta.servlet.http.HttpServletRequest;import org.springframework.stereotype.Component;import java.io.IOException;@Component @WebFilter(urlPatterns = "/*") public class TokenFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { HttpServletRequest httpRequest = (HttpServletRequest) servletRequest; String path = httpRequest.getServletPath(); switch (path) { case "/id" : String tokenValue = httpRequest.getHeader("token" ); if (isValidToken(tokenValue)) { filterChain.doFilter(servletRequest, servletResponse); } else { sendInvalidResponse(servletResponse, "Invalid token" ); } break ; case "/ab" : String reqNameValue = httpRequest.getHeader("req-name" ); if ("abcdef" .equals(reqNameValue)) { filterChain.doFilter(servletRequest, servletResponse); } else { sendInvalidResponse(servletResponse, "Invalid req-name value" ); } break ; default : filterChain.doFilter(servletRequest, servletResponse); break ; } } private boolean isValidToken (String token) { return token != null && token.equals("validToken" ); } private void sendInvalidResponse (ServletResponse servletResponse, String message) throws IOException { servletResponse.setContentType("text/plain" ); servletResponse.setCharacterEncoding("UTF-8" ); servletResponse.getWriter().write(message); } }
# 拦截器 (Interceptor) 的应用
preHandle
在 Controller 方法处理之前
postHandle
调用前提:preHandle 返回 true
在 Controller 方法处理之后,DispatcherServlet 进行试图的渲染之前
afterCompletion
调用前提:preHandle 返回 true
DispatcherServlet 进行试图的渲染之后
在 filter 中新建文件 TestInterceptor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 @Component public class TestInterceptor implements HandlerInterceptor { @Override public boolean preHandle (HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String header = request.getHeader("req-name" ); if ("yihuihui" .equals(header)) { return true ; } System.out.println("请求头错误: {" +header+"}" ); return false ; } @Override public void postHandle (HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception { System.out.println("执行完毕!" ); response.setHeader("res" , "postHandler" ); } @Override public void afterCompletion (HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception { System.out.println("回收" ); } }
然后加入到 spring 的配置中
在 config 中新建 TestConfig 文件
实现接口,实现 addInterceptors 方法
1 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class TestConfig implements WebMvcConfigurer { @Autowired TestInterceptor testInterceptor; @Override public void addInterceptors (InterceptorRegistry registry) { registry.addInterceptor(testInterceptor) .addPathPatterns("/**" ) .excludePathPatterns("/static/**" ); } }
# 跨域请求
在 config 中创建 TestConfig
1 2 3 4 5 6 7 8 9 10 11 @Configuration public void addCorsMappings (CorsRegistry registry) { registry.addMapping("/**" ) .allowCredentials(true ) .allowedOrigins("https://example.com" , "https://another-example.com" ) .allowedMethods(new String []{"GET" ,"POST" ,"PUT" ,"DELETE" }) .allowedHeaders("*" ) .exposedHeaders("*" ); }
# spring 邮件系统
# 导包
1 2 3 4 5 6 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-mail</artifactId > <version > 3.3.0</version > </dependency >
# 开启邮件服务
以 outlook 邮件为例子
# 添加配置
application.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 spring: mail: username: 123456 @outlook.com password: '123456' host: smtp-mail.outlook.com properties: mail: smtp: auth: true starttls: enable: true required: true port: 587 default-encoding: UTF-8
# 编写工具类
在 utils 中创建 EmailUtils
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 @Component public class EmailUtils { @Autowired private JavaMailSender javaMailSender; @Value("${spring.mail.username}") private String from; public void sendMail (String to,String subject,String text) { SimpleMailMessage message=new SimpleMailMessage (); message.setFrom(from); message.setTo(to); message.setSubject(subject); message.setText(text); message.setSentDate(new Date ()); javaMailSender.send(message); } }
在需要使用的地方调用这个方法就可以了
# 定时任务管理
# 实现
首先在启动器上加 @EnableScheduling 注解
1 2 3 4 5 6 7 8 @SpringBootApplication @EnableScheduling public class TestApplication { public static void main (String[] args) { SpringApplication.run(TestApplication.class, args); } }
然后使用 @Scheduled 注解
1 2 3 4 5 6 7 8 9 @Component public class TaskUtils { public static int i=0 ; @Scheduled(cron = "0/5 * * * * ? ") public void execute () { i+=5 ; System.out.println("时间:" +i+"秒" ); } }
@Scheduled (cron = "0/5 * * * * ?") 代表每 5 秒执行
# cron
cron 表达式语法
格式:秒 分 时 日 月 周 年
说明
是否必填
允许值
通配符
秒
是
0-59
, - * /
分
是
0-59
, - * /
时
是
0-23
, - * /
日
是
1-31
, - * ? / L W
月
是
1-12 or JAN-DEC
, - * /
周
是
1-7 or SUM-SAT
, - * ? / L #
年
否
empty or 1970-2099
, - * /
通配符:
* 表示所有值。例如:在秒的字段上设置 “*”, 即每一秒钟都会触发。
? 表示不指定值。使用的场景为不需要关心当前设置这个字段的值。例如:不关心周几 "2 3 * * * ?"
- 表示区间。例如:在小时上设置 “1-3”, 表示 1,2,3 点都会触发。
, 表示指定多个值,例如:在周字段上设置 “MON,WED,FRI” 表示周一,周三和周五触发
/ 用于递增触发。如在秒上面设置 "5/15" 表示从 5 秒开始,每增 15 秒触发 (5,20,35,50)。在月字段上设置’1/3’所示每月 1 号开始,每隔三天触发一次。
L 表示最后的意思。在日字段设置上,表示当月的最后一天 (依据当前月份,如果是二月还会依据是否是润年 [leap]), 在周字段上表示星期六,相当于 "7" 或 "SAT"。如果在 "L" 前加上数字,则表示该数据的最后一个。例如在周字段上设置 "6L" 这样的格式,则表示 “本月最后一个星期五 "
W 表示离指定日期的最近那个工作日 (周一至周五). 例如在日字段上设置 "15W",表示离每月 15 号最近的那个工作日触发。如果 15 号正好是周六,则找最近的周五 (14 号) 触发,如果 15 号是周未,则找最近的下周一 (16 号) 触发。如果 15 号正好在工作日 (周一至周五),则就在该天触发。如果指定格式为 “1W”, 它则表示每月 1 号往后最近的工作日触发。如果 1 号正是周六,则将在 3 号下周一触发。(注,“W"前只能设置具体的数字,不允许区间”-").
'L’和 'W’可以一组合使用。如果在日字段上设置 "LW", 则表示在本月的最后一个工作日触发(一般指发工资) 。
# 序号 (表示每月的第几个周几),例如在周字段上设置 "6#3" 表示在每月的第三个周六。注意如果指定 "#5", 正好第五周没有周六,则不会触发该配置 (用在母亲节和父亲节再合适不过了)
周字段的设置,若使用英文字母是不区分大小写的 MON 与 mon 相同。
可通过在线生成 Cron 表达式的工具:http://cron.qqe2.com/ 来生成自己想要的表达式
# zone
zone: 与 cron 参数一起使用,指定 cron 表达式的时间区域。默认情况下,它使用服务器的本地时区。
1 2 3 4 @Scheduled(cron = "0 0 * * * *", zone = "America/New_York") public void executeHourlyInNY () {}
# fixedRate
fixedRate:以固定的间隔执行任务,单位为毫秒。这个间隔是指任务开始之间的时间间隔。
1 2 3 4 5 @Scheduled(fixedRate = 5000) public void taskWithFixedRate () { System.out.println("每5秒执行一次:" + new Date ()); }
fixedRateString: 与 fixedRate 相同,但允许使用字符串表达式。
1 2 3 4 @Scheduled(fixedRateString = "${fixed.rate.in.milliseconds}") public void executeWithFixedRateString () {}
# fixedDelay
fixedDelay:在任务执行完成后,等待固定的延迟时间再执行下一次任务,单位也是毫秒。这个间隔是指任务完成之后到下一次开始之间的时间间隔。
1 2 3 4 5 @Scheduled(fixedDelay = 5000) public void taskWithFixedDelay () { System.out.println("任务完成后5秒再执行:" + new Date ()); }
fixedDelayString: 与 fixedDelay 相同,但允许使用字符串表达式,这在从配置文件中读取值时非常有用。
1 2 3 4 @Scheduled(fixedDelayString = "${fixed.delay.in.milliseconds}") public void executeWithFixedDelayString () {}
# initialDelay
initialDelay:这不是单独使用的参数,而是与 fixedRate 或 fixedDelay 一起使用,来延迟任务的首次执行时间。
1 2 3 4 5 @Scheduled(fixedRate = 5000, initialDelay = 10000) public void taskWithInitialDelay () { System.out.println("首次延迟10秒后,每5秒执行一次:" + new Date ()); }
initialDelayString: 与 initialDelay 相同,但允许使用字符串表达式。
1 2 3 4 @Scheduled(fixedRate = 5000, initialDelayString = "${initial.delay.in.milliseconds}") public void executeWithInitialDelayString () {}
# 执行 python
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 public String py2 () { try { ProcessBuilder processBuilder = new ProcessBuilder ("python" ,"D:/demo/python/gitvideo/getURL.py" ); Process process = processBuilder.start(); BufferedReader reader = new BufferedReader (new InputStreamReader (process.getInputStream(), "GBK" )); String line; while ((line = reader.readLine()) != null ) { System.out.println(line); } System.out.println(1233 ); int exitCode = process.waitFor(); if (exitCode != 0 ) { return "Error executing script" ; } return line; } catch (IOException | InterruptedException e) { e.printStackTrace(); return "Error: " + e.getMessage(); } }
# 打包 jar
在 pox.xml 中添加 spring-boot-maven-plugin 插件
1 2 3 4 5 6 7 8 9 <build > <plugins > <plugin > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-maven-plugin</artifactId > </plugin > </plugins > </build >
保存后于命令行运行
查看 target 目录,你应该看到.jar 后缀结尾的文件
可使用下面命令运行
# 注解
# 类注解
# @SpringBootApplication
@SpringBootApplication。这个注解被称为元注解,它结合了 @SpringBootConfiguration、@EnableAutoConfiguration 和 @ComponentScan。
# @RestController
标示为 Controller 控制器
# 方法注解
# @RequestMapping
标示为路由
1 2 3 4 5 6 @RequestMapping("/home") String home () { return "Hello World!" ; }
浏览器使用 get 或者 post 访问 http://127.0.0.1:8080/home
即输出 Hello Werld!