SprintBoot笔记
1、IOC 模式
在 Java 中,IOC(Inversion of Control,控制反转)是一种设计原则,用于降低代码间的耦合度,是 Spring 等主流框架的核心思想之一。
简单来说,控制反转指的是将对象的创建、依赖关系的管理等 “控制权” 从代码本身转移到外部容器(如 Spring 容器)。
- 传统方式:对象 A 需要依赖对象 B 时,通常会在 A 的代码中直接创建 B 的实例(如
B b = new B()
),此时 A 完全控制着 B 的创建,耦合度高。 - IOC 方式:对象的创建和依赖注入由外部容器负责,A 只需要声明需要 B,容器会自动将 B 的实例 “注入” 到 A 中,无需 A 主动创建。
- 核心思想
- 反转控制权:将对象的创建、组装、生命周期管理等控制权交给容器,而非由代码主动控制。
- 依赖注入(DI):IOC 的实现方式之一,容器通过构造函数、setter 方法等方式,将对象依赖的组件自动注入到对象中。
- 解耦:减少类与类之间的直接依赖,提高代码的灵活性、可维护性和可测试性。
2、AOP编程
(Aspect-Oriented Programming,面向切面编程)。一种编程范式,旨在通过 “切面” 思想解决代码中横切关注点(Cross-cutting Concerns)的问题,与 OOP(面向对象编程)相辅相成。
核心概念
- 横切关注点:指那些在应用中多处重复出现的功能,如日志记录、事务管理、权限验证、异常处理等。这些功能若分散在业务代码中,会导致代码冗余且难以维护。
- 切面(Aspect):将横切关注点模块化封装的组件,例如一个日志切面类专门处理所有日志相关逻辑。
- 连接点(Join Point):程序执行过程中的特定点(如方法调用、异常抛出等),是切面可以插入的位置。
- 切入点(Pointcut):定义切面要作用的连接点集合(通过表达式指定),例如 “所有 Service 层的 save 方法”。
- 通知(Advice):切面在特定连接点执行的代码,分为 5 种类型:
- 前置通知(Before):目标方法执行前执行
- 后置通知(After):目标方法执行后执行(无论是否异常)
- 返回通知(AfterReturning):目标方法正常返回后执行
- 异常通知(AfterThrowing):目标方法抛出异常后执行
- 环绕通知(Around):包裹目标方法,可自定义执行逻辑
- 目标对象(Target Object):被切面增强的对象(原始业务对象)。
- 代理对象(Proxy):AOP 框架创建的、包含原始对象功能和切面逻辑的代理对象。
工作原理
AOP 通过动态代理实现:在不修改原始代码的情况下,生成代理对象,将切面逻辑织入(Weaving)到目标方法的执行过程中,实现横切关注点与业务逻辑的分离。
3、SprintBoot注解
@SpringBootConfiguration:标注在某个类上 , 表示这是一个SpringBoot的配置类;
@EnableAutoConfiguration :开启自动配置功能
@SpringBootApplication:表示该类是启动类
这个类主要做了以下四件事情:
1、推断应用的类型是普通的项目还是Web项目
2、查找并加载所有可用初始化器 , 设置到initializers属性中
3、找出所有的应用程序监听器,设置到listeners属性中
4、推断并设置main方法的定义类,找到运行的主类
4、配置文件
application.properties
-
- 语法结构 :key=value
application.yml
-
- 语法结构 :key:空格 value
yaml注入配置文件
@Component //注册bean,将Dog类创建放入到IOC容器中
public class Dog {
@Value("阿黄") //给Bean注入属性值
private String name;
@Value("18")
private Integer age;
}
//从yaml文件中读取数据自动配置属性值
@Component
@ConfigurationProperties(prefix = "person") //这里在Yaml写的有对应的数据
public class Person {
private String name;
private Integer age;
}
//需要文件处理器
<!-- 导入配置文件处理器,配置文件进行绑定就会有提示,需要重启 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
加载指定的配置文件
//在项目目录下创建一个新的配置文件,然后加上注解就可以指定加载
@PropertySource(value = "classpath:person.properties")
@Component //注册bean
public class Person {
@Value("${name}")
private String name;
......
}
配置文件占位符
//在这里可以使用${}进行展占位,里面放一些函数或变量
person:
name: qinjiang${random.uuid} # 随机uuid
age: ${random.int} # 随机int
happy: false
birth: 2000/01/01
maps: {k1: v1,k2: v2}
lists:
- code
- girl
- music
dog:
name: ${person.hello:other}_旺财
age: 1
5、JSR303数据校验
Springboot中可以用@validated来校验数据,如果数据异常则会统一抛出异常,方便异常中心统一处理。
//在类上面加入@Validated注解可以启动数据校验,在需要校验的字段上面加入注解就可以检验字段的值格式是否正确
@Component //注册bean
@ConfigurationProperties(prefix = "person")
@Validated //数据校验
public class Person {
@Email(message="邮箱格式错误") //name必须是邮箱格式
private String name;
}
常见校验规则
@NotNull(message="名字不能为空")
private String userName;
@Max(value=120,message="年龄最大不能查过120")
private int age;
@Email(message="邮箱格式错误")
private String email;
空检查
@Null 验证对象是否为null
@NotNull 验证对象是否不为null, 无法查检长度为0的字符串
@NotBlank 检查约束字符串是不是Null还有被Trim的长度是否大于0,只对字符串,且会去掉前后空格.
@NotEmpty 检查约束元素是否为NULL或者是EMPTY.
Booelan检查
@AssertTrue 验证 Boolean 对象是否为 true
@AssertFalse 验证 Boolean 对象是否为 false
长度检查
@Size(min=, max=) 验证对象(Array,Collection,Map,String)长度是否在给定的范围之内
@Length(min=, max=) string is between min and max included.
日期检查
@Past 验证 Date 和 Calendar 对象是否在当前时间之前
@Future 验证 Date 和 Calendar 对象是否在当前时间之后
@Pattern 验证 String 对象是否符合正则表达式的规则
6、整合JDBC
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
编写配置文件
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver # MySQL 8+ 必须显式声明
测试连接
@SpringBootTest
class SpringbootDataJdbcApplicationTests {
//DI注入数据源
@Autowired
DataSource dataSource;
@Test
public void contextLoads() throws SQLException {
//看一下默认数据源
System.out.println(dataSource.getClass());
//获得连接
Connection connection = dataSource.getConnection();
System.out.println(connection);
//关闭连接
connection.close();
}
}
JDBCTemplate
- execute方法:可以用于执行任何SQL语句,一般用于执行DDL语句;
- update方法及batchUpdate方法:update方法用于执行新增、修改、删除等语句;batchUpdate方法用于执行批处理相关语句;
- query方法及queryForXXX方法:用于执行查询相关语句;
- call方法:用于执行存储过程、函数相关语句。
7、整合Druid
添加依赖
<!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.23</version>
</dependency>
切换数据源,Spring Boot 2.0 以上默认使用 com.zaxxer.hikari.HikariDataSource 数据源,但可以 通过 spring.datasource.type 指定数据源。
spring:
datasource:
username: root
password: 123456
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource # 自定义数据源
设置Druid的基础配置信息
spring:
datasource:
username: root
password: 123456
#?serverTimezone=UTC解决时区的报错
url: jdbc:mysql://localhost:3306/springboot?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
#Spring Boot 默认是不注入这些属性值的,需要自己绑定
#druid 数据源专有配置
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 60000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
#配置监控统计拦截的filters,stat:监控统计、log4j:日志记录、wall:防御sql注入
#如果允许时报错 java.lang.ClassNotFoundException: org.apache.log4j.Priority
#则导入 log4j 依赖即可,Maven 地址:https://mvnrepository.com/artifact/log4j/log4j
filters: stat,wall,log4j
maxPoolPreparedStatementPerConnectionSize: 20
useGlobalDataSourceStat: true
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
自定义Druid配置
@Configuration
public class DruidConfig {
/*
将自定义的 Druid数据源添加到容器中,不再让 Spring Boot 自动创建
绑定全局配置文件中的 druid 数据源属性到 com.alibaba.druid.pool.DruidDataSource从而让它们生效
@ConfigurationProperties(prefix = "spring.datasource"):作用就是将 全局配置文件中
前缀为 spring.datasource的属性值注入到 com.alibaba.druid.pool.DruidDataSource 的同名参数中
*/
@ConfigurationProperties(prefix = "spring.datasource")
@Bean
public DataSource druidDataSource() {
return new DruidDataSource();
}
//配置 Druid 监控管理后台的Servlet;
//内置 Servlet 容器时没有web.xml文件,所以使用 Spring Boot 的注册 Servlet 方式
@Bean
public ServletRegistrationBean statViewServlet() {
ServletRegistrationBean bean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
// 这些参数可以在 com.alibaba.druid.support.http.StatViewServlet
// 的父类 com.alibaba.druid.support.http.ResourceServlet 中找到
Map<String, String> initParams = new HashMap<>();
initParams.put("loginUsername", "admin"); //后台管理界面的登录账号
initParams.put("loginPassword", "123456"); //后台管理界面的登录密码
//后台允许谁可以访问
//initParams.put("allow", "localhost"):表示只有本机可以访问
//initParams.put("allow", ""):为空或者为null时,表示允许所有访问
initParams.put("allow", "");
//deny:Druid 后台拒绝谁访问
//initParams.put("kuangshen", "192.168.1.20");表示禁止此ip访问
//设置初始化参数
bean.setInitParameters(initParams);
return bean;
}
//配置 Druid web 监控 filter 过滤器
//配置 Druid 监控 之 web 监控的 filter
//WebStatFilter:用于配置Web和Druid数据源之间的管理关联监控统计
@Bean
public FilterRegistrationBean webStatFilter() {
FilterRegistrationBean bean = new FilterRegistrationBean();
bean.setFilter(new WebStatFilter());
//exclusions:设置哪些请求进行过滤排除掉,从而不进行统计
Map<String, String> initParams = new HashMap<>();
initParams.put("exclusions", "*.js,*.css,/druid/*,/jdbc/*");
bean.setInitParameters(initParams);
//"/*" 表示过滤所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
8、整合MyBatis
添加依赖
<!-- MyBatis-Plus 核心依赖 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3.1</version>
</dependency>
创建实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String sex;
}
创建mapper目录、创建mapper类
//@Mapper : 表示本类是一个 MyBatis 的 Mapper
//@Repository 是 Spring 的注解,主要用于让 Spring 识别该接口为数据访问层组件,并进行管理。
@Mapper
@Repository
public interface UserMapper extends BaseMapper<User> {
//获取所有用户信息
List<User> getAllUsers();
}
创建对应的Mapper.xml文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.daitools.learn.mapper.UserMapper">
<select id="getAllUsers" resultType="com.daitools.learn.entity.User">
select * from user;
</select>
</mapper>
Maven配置资源过滤
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
<filtering>true</filtering>
</resource>
</resources>
测试代码
@Autowired
UserMapper userMapper;
@Test
public void getUsers(){
List<User> allUsers = userMapper.getAllUsers();
for(User user:allUsers)
System.out.println(user);
}
9、配置静态资源
配置静态资源路径src/main/resources/static
spring:
# 配置静态资源
resources:
static-locations=classpath: /static
SpringBoot默认在static 目录中存放静态资源,而 templates 中放动态页面。
先添加thymeleaf依赖
<!--thymeleaf-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
在resources目录下创建templates目录,在这个目录中创建动态页面
创建controller,这个controller是可以直接返回模板页面的
@Controller
public class thymeleafController {
//直接返回模板目录下的test页面
@RequestMapping("/t1")
public String test1(){
return "test";
}
}
想要在页面中增加数据,那么需要修改controller中的方法,以及模板页面
//方法中传入Model实例化对象
//使用Model中的方法添加数据
@RequestMapping("/t1")
public String test1(Model model){
// 向模板文件中添加数据
model.addAttribute("msg","我是在模板中添加的数据。");
return "test";
}
在模板文件中添加thymeleaf的命名空间约束,然后使用thymeleaf语法取出model中的数据
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>test</title>
</head>
<body>
<div>将添加到模板中的数据取出来显示</div>
<div th:text="${msg}"></div>
</body>
</html>
10、整合Swagger
Swagger号称世界上最流行的API框架,用于生成API文档,还可以直接在线运行测试
好像是只能找到有@RestController注解的接口,普通Controller注解的接口无法显示到Swagger中
添加Swagger依赖
<!-- SpringDoc OpenAPI 依赖 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-ui</artifactId>
<version>1.6.15</version> <!-- 此版本兼容 Spring Boot 2.7.x -->
</dependency>
添加路径匹配策略配置(解决 Spring Boot 2.6+ 后的路径匹配问题)
# Swagger路径匹配策略
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
直接访问 http://localhost:8080/swagger-ui.html
查看自动生成的文档
11、异步任务
在 Spring Boot 中,异步任务是处理耗时操作的重要方式,它可以避免主线程阻塞,提高应用的并发处理能力。@Async
注解是实现异步任务的核心工具。
适用于:发送邮件、文件上传、数据同步等耗时操作。
开启异步支持,在 Spring Boot 主类或配置类上添加 @EnableAsync
注解
@SpringBootApplication
@EnableAsync
@MapperScan("com.daitools.learn.mapper")
public class LearnApplication {
public static void main(String[] args) {
SpringApplication.run(LearnApplication.class, args);
}
}
在需要异步执行的方法上面添加@Async注解
@Service
public class AsyncService {
// 异步执行该方法
@Async
public void asyncTask() {
try {
// 模拟耗时操作(如:发送邮件)
Thread.sleep(3000);
System.out.println("异步任务执行完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在controller类中调用异步方法,在这里直是直接为异步方法开启一个子线程进行,而主线程会继续向下进行。
//调用异步方法
@Autowired
private AsyncService asyncService;
@GetMapping("/testAsync")
public String testAsync() {
// 调用异步方法(主线程不等待)
asyncService.asyncTask();
// 立即返回结果,无需等待异步任务完成
return "请求已接收,异步任务正在执行...";
}
12、定时任务
在 Spring 框架中,定时任务是实现周期性执行特定逻辑的重要机制,而 @EnableScheduling
和 @Scheduled
是实现这一功能的核心注解。
@EnableScheduling
:开启定时任务支持,标记在配置类或主类上,用于开启 Spring 的定时任务调度功能。
@SpringBootApplication
@EnableScheduling // 开启定时任务支持
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
@Scheduled
:定义定时任务执行规则,标记在方法上,指定该方法为定时任务,并定义执行时机(如固定间隔、固定延迟、Cron 表达式等)。
- 方法必须是 无返回值(void) 的。
- 方法不能有参数(若需参数,需通过其他方式注入)。
- 方法所在类必须被 Spring 管理(如标注
@Component
、@Service
等)。
@Component
public class ScheduledTasks {
// 每 5 秒执行一次(单位:毫秒)
@Scheduled(fixedRate = 5000)
public void taskWithFixedRate() {
System.out.println("固定间隔任务执行:" + new Date());
}
}
常用注解
// 每 5 秒执行一次(单位:毫秒)
//在 Spring 中使用 @Scheduled(fixedRate = 5000) 注解的定时任务,项目启动后不会立刻执行,而是会等待第一个间隔时间(5 秒)后才执行第一次。
@Scheduled(fixedRate = 5000)
// 上一次任务完成后,间隔 3 秒再执行(单位:毫秒)
@Scheduled(fixedDelay = 3000)
// 应用启动后延迟 10 秒,之后每 5 秒执行一次
@Scheduled(initialDelay = 10000, fixedRate = 5000)
//Cron 表达式(cron)
// 每天凌晨 2 点执行
@Scheduled(cron = "0 0 2 * * ?")
// 每周一上午 10:30 执行
@Scheduled(cron = "0 30 10 ? * MON")
// 每小时的第 15 分钟执行(如 1:15、2:15...)
@Scheduled(cron = "0 15 * * * ?")
Cron 表达式特殊字符说明:
*:匹配所有值(如分钟字段为 * 表示每分钟)。
?:仅用于日和周字段,标识不指定值(避免日和周冲突)。
-:指定范围(如小时字段 9-17 表示 9 点到 17 点)。
,:指定多个值(如周字段 MON,WED,FRI 表示周一、三、五)。
/:指定增量(如秒字段 0/10 表示每 10 秒)。
定时任务的执行机制
- 默认单线程:Spring 定时任务默认使用单线程调度,若一个任务执行时间过长,会阻塞后续所有任务。
- 自定义线程池
@Configuration
public class SchedulerConfig {
@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5); // 线程池大小
scheduler.setThreadNamePrefix("Scheduled-"); // 线程名前缀
scheduler.setAwaitTerminationSeconds(60); // 关闭时等待时间
scheduler.setWaitForTasksToCompleteOnShutdown(true); // 关闭时等待任务完成
return scheduler;
}
}
定时任务和前端请求是用的两个独立的线程池,如果定时任务执行过程中,前端发起请求,则定时任务和请求的任务独立进行,互不影响。
13、邮件任务
邮件发送基于 SMTP 协议(简单邮件传输协议),接收则基于 POP3 或 IMAP 协议。SpringBoot 通过 spring-boot-starter-mail
starter 封装了 JavaMail 的操作,简化了配置和发送流程。
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-mail</artifactId>
</dependency>
在 application.properties
或 application.yml
中配置 SMTP 服务器参数。
spring:
mail:
# SMTP服务器地址(QQ邮箱为smtp.qq.com,163为smtp.163.com)
host: smtp.qq.com
# 发送者邮箱账号
username: your-qq@qq.com
# 授权码(非登录密码,需在邮箱设置中开启SMTP并获取)
password: your-auth-code
# 端口(SSL加密端口通常为465,非加密为25)
port: 465
# 协议
protocol: smtp
# 额外配置(启用SSL加密)
properties:
mail:
smtp:
ssl:
enable: true
auth: true # 开启认证
debug: false # 是否开启调试模式(打印发送日志)
编写测试函数
@Autowired
private JavaMailSender javaMailSender;
/**
* 发送简单文本邮件
*/
public void sendSimpleEmail() {
// 创建简单邮件消息对象
SimpleMailMessage message = new SimpleMailMessage();
// 设置发送者(需与配置文件中username一致)
message.setFrom("your-qq@qq.com");
// 收件人(可多个,用逗号分隔)
message.setTo("recipient1@example.com", "recipient2@example.com");
// 抄送(可选)
message.setCc("cc@example.com");
// 密送(可选)
message.setBcc("bcc@example.com");
// 主题
message.setSubject("【简单文本邮件】测试主题");
// 内容
message.setText("这是一封简单的文本邮件,来自SpringBoot。");
// 发送邮件
javaMailSender.send(message);
}
Html邮件
@Component
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
/**
* 发送HTML邮件
*/
public void sendHtmlEmail() throws MessagingException {
// 创建MimeMessage对象
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
// 创建MimeMessageHelper(第二个参数为true表示支持多部分内容)
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
// 设置基本信息
helper.setFrom("your-qq@qq.com"); // 发送者
helper.setTo("recipient@example.com"); // 收件人
helper.setSubject("【HTML邮件】测试主题"); // 主题
// HTML内容(支持CSS样式)
String htmlContent = "<h3>这是一封HTML邮件</h3>" +
"<p style='color: red;'>红色文本内容</p>" +
"<a href='https://www.baidu.com'>百度链接</a>";
helper.setText(htmlContent, true); // 第二个参数为true表示启用HTML
// 发送
javaMailSender.send(mimeMessage);
}
}
带附件的邮件
@Component
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
/**
* 发送带附件的邮件
*/
public void sendEmailWithAttachment() throws MessagingException {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
// 第二个参数true表示支持多部分(附件属于多部分内容)
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
// 基本信息
helper.setFrom("your-qq@qq.com");
helper.setTo("recipient@example.com");
helper.setSubject("【带附件邮件】测试主题");
helper.setText("这是一封带附件的邮件,请查收。", false); // 纯文本内容
// 添加附件(本地文件)
File attachmentFile = new File("D:/test.pdf"); // 附件路径
FileSystemResource resource = new FileSystemResource(attachmentFile);
String attachmentName = "测试附件.pdf"; // 附件显示名称
helper.addAttachment(attachmentName, resource);
// 发送
javaMailSender.send(mimeMessage);
}
}
带图片的邮件
@Component
public class EmailService {
@Autowired
private JavaMailSender javaMailSender;
/**
* 发送带内嵌图片的HTML邮件
*/
public void sendHtmlWithImageEmail() throws MessagingException {
MimeMessage mimeMessage = javaMailSender.createMimeMessage();
MimeMessageHelper helper = new MimeMessageHelper(mimeMessage, true, "UTF-8");
helper.setFrom("your-qq@qq.com");
helper.setTo("recipient@example.com");
helper.setSubject("【带图片的HTML邮件】测试主题");
// HTML内容,通过cid:imageId引用内嵌图片
String htmlContent = "<h3>这是一封带内嵌图片的邮件</h3>" +
"<p>图片展示:</p>" +
"<img src='cid:imageId' width='300' height='200'/>";
helper.setText(htmlContent, true);
// 内嵌图片(本地图片)
File imageFile = new File("D:/test.jpg");
FileSystemResource imageResource = new FileSystemResource(imageFile);
// 第一个参数为资源ID(需与HTML中的cid:imageId一致),第二个参数为资源
helper.addInline("imageId", imageResource);
// 发送
javaMailSender.send(mimeMessage);
}
}