导航
[React 从零实践01-后台] 代码分割
[React 从零实践02-后台] 权限控制
[React 从零实践03-后台] 自定义hooks
[React 从零实践04-后台] docker-compose 部署react+egg+nginx+mysql
[React 从零实践05-后台] Gitlab-CI使用Docker自动化部署
[源码-webpack01-前置知识] AST抽象语法树
[源码-webpack02-前置知识] Tapable
[源码-webpack03] 手写webpack - compiler简单编译流程
[源码] Redux React-Redux01
[源码] axios
[源码] vuex
[源码-vue01] data响应式 和 初始化渲染
[源码-vue02] computed 响应式 - 初始化,访问,更新过程
[源码-vue03] watch 侦听属性 - 初始化和更新
[源码-vue04] Vue.set 和 vm.$set
[源码-vue05] Vue.extend
[源码-vue06] Vue.nextTick 和 vm.$nextTick
[部署01] Nginx
[部署02] Docker 部署vue项目
[部署03] gitlab-CI
[深入01] 执行上下文
[深入02] 原型链
[深入03] 继承
[深入04] 事件循环
[深入05] 柯里化 偏函数 函数记忆
[深入06] 隐式转换 和 运算符
[深入07] 浏览器缓存机制(http缓存机制)
[深入08] 前端安全
[深入09] 深浅拷贝
[深入10] Debounce Throttle
[深入11] 前端路由
[深入12] 前端模块化
[深入13] 观察者模式 发布订阅模式 双向数据绑定
[深入14] canvas
[深入15] webSocket
[深入16] webpack
[深入17] http 和 https
[深入18] CSS-interview
[深入19] 手写Promise
[深入20] 手写函数
[深入21] 数据结构和算法 - 二分查找和排序
[深入22] js和v8垃圾回收机制
[深入23] JS设计模式 - 代理,策略,单例
[前端学java01-SpringBoot实战] 环境配置和HelloWorld服务
[前端学java02-SpringBoot实战] mybatis + mysql 实现歌曲增删改查
[前端学java03-SpringBoot实战] lombok,日志,部署
[前端学java04-SpringBoot实战] 静态资源 + 拦截器 + 前后端文件上传
(一) 前置知识
(1) 一些单词
transactional 事务
pattern 模式
completion 完成 结束 n
implement 实现 ( class 类名 implements 接口名 )
abstract 抽象的 adj
override 覆盖 重写
leaf 叶子
servlet 小程序服务 小应用程序
consume 消耗
configuration 配置
(2) interface 接口
-
interface
- 描述类有什么功能,但不具体实现,实现是在类中去实现
- 在interface中方法因为没有方法体,所以是要加
abstract
来修饰 abstract 是抽象的意思adj
-
注意:
- 接口不能被实例化,所以没有构造方法
- 可以通过多态的方式实例化子类(实现类)
- 接口中只有成员常量,没有成员变量
-
接口于接口的关系是 extends
比如 => 接口1 extends 接口2
- 共性继承类,扩展实现类
-
implements
- ( 类和接口 ) 是 ( 实现关系 ),用
implements
表示,implement 就是实现的意思
-
单实现
class 实现类类名 implements 接口名
-
多实现
class 实现类类名 implements 接口1,接口2,接口3
- ( 类和接口 ) 是 ( 实现关系 ),用
-
实现类
- 在接口中也经定义了类的方法,但没有实现
- 实现类,就是去实现接口中的方法的类
- 通过
class 实现类类名 implements 接口名
的方式来写实现类
-
关系
- ( 接口与接口 ) 是 ( 继承 ) 关系
- ( 类与接口 ) 是 ( 实现 ) 关系
(3) springboot 的静态资源访问
- 静态资源相关-官方文档
-
静态资源目录
- 在 resources 文件夹下的 4 个文件夹
static
public
resources
META-INF/resources
- 在 resources 文件夹下的 4 个文件夹
- 访问 ( 当前项目根路径 ) 访问静态资源
根路径 + 静态资源名
-
原理
-
静态映射:
/**
- 请求进来,先找 ( controller ) 处理,不能处理的所有请求再又交给 ( 静态资源处理器 ) 来处理,再找不到就会404
-
问题:如果controller中有个api接口是
/a.jpg
,而我们的resources/static/a.jpg
也有同名的静态资源,会发生什么? - 答案:会返回controller的api方法的返回值,而不会访问静态资源
-
静态映射:
-
静态资源访问前缀
- 静态资源设置前缀:
spring.mvc.static-path-pattern=/resources/**
- 访问静态资源:
当前项目 + 前缀 + 静态资源
- 例子:
http://localhost:7777/resources/66.jpg
- 静态资源设置前缀:
-
指定自定义的静态资源目录 ( 默认静态文件夹路径 )
-
spring.web.resources.static-locations=[classpath:/7resources/]
-
(4) @Configuration @Bean
-
@Configuration
标注的类是 ( 配置类 ) -
@Bean
是向容器中注册组件,并且是单列的
src/main/java/com.example.demo/config/MyConfig.java
-------
/**
* 1. @Configuration 标注的类就是 配置类
* 2。配置类里,使用 @Bean 标注在方法上给容器注册组件,注册的组件默认也是单实例的
* 3. 注意:@Configuration标注的类,该类本身也是一个组件,即 MyConfig 也是一个组件,即 ( 配置类本身也是一个组件 )
*
* 4. proxyBeanMethods 代理bean的方法
* Full(proxyBeanMethods = true) 单例,可以用于 ( 组件依赖 )
* Lite(proxyBeanMethods = false)
*/
// 告诉 SpringBoot 这是一个 ( 配置类 ),等用于以前的配置文件
// @Configuration 配置类
@Configuration(proxyBeanMethods = true)
public class MyConfig {
// @Bean
// @Bean 给容器中添加组件
// 1. 以方法名作为组件的id => user1
// 2. 返回类型就是组件类型 => UserBean
// 3. 返回的值就是组件在容器中的实例 => new UserBean()
// @Bean("userX") 的参数可以自定义id
@Bean("userX")
public UserBean user1() {
return new UserBean("woow_wu7", 20);
}
}
(5) @ConfigurationProperties @Component
- 要让配置文件和bean对象进行绑定
-
第一种方法
- @ConfigurationProperties + @Component
-
@ConfigurationProperties
可以把application.properties
或application.yml
文件中的配置的参数
,注入到组件中
(1) src/main/java/bean/AppMessageBean.java
-------
// 只有在容器中的组件才能获取 SpringBoot 的强大功能
// 该 bean 对象主要是测试 @ConfigurationProperties 和 @Component 两个注解
@Data
@Component
@ConfigurationProperties(prefix = "myapp") // prefix="myapp" 这个前缀的值是在 application.yml 文件中配置的
public class AppMessageBean {
private String name;
private String email;
private String author;
}
(2) src/main/java/com.example.demo/controller/testController.java
-------
@RestController
@Slf4j
public class TestController {
@Autowired
AppMessageBean appMessageBean;
// (1)
// 测试: @ConfigurationProperties 和 @Component 两个注解
// 教程: https://www.cnblogs.com/jimoer/p/11374229.html
@GetMapping("/@ConfigurationProperties")
public AppMessageBean getAuthorName() {
System.out.println(appMessageBean);
String author = appMessageBean.getAuthor();
System.out.println(author);
return appMessageBean;
}
}
-
第二种方法
- 需要在配置类中
- @EnableConfigurationProperties(Car.class)
- 开启 Car 属性绑定功能
- 把 Car 自动注册到容器中
(6) @ConfigurationProperties 和 @Value的区别
- @Value
- 主要用于单个属性
-
并且配合要求较高,使用羊肉串模式,比如 my-app-name 这样
(1)src/main/java/com.example.demo/bean/ValueTestBean.java
-------
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component // @Value 同样需要使用 @Component 将组件注册到容器中,对比 @ConfigurationProperties
public class ValueTestBean {
private String name;
private String email;
@Value("${myapp.author}") // 用于单个属性
private String author;
}
(2) 在controller中测试
-------
@GetMapping("/@Value")
public ValueTestBean getValueAuthorName() {
System.out.println(valueTestBean);
String author = valueTestBean.getAuthor();
System.out.println(author);
return valueTestBean;
}
(二) 配置 mybatis 的 xml 形式
(1) 引入 mybatis-spring-boot-starter 的maven依赖 ( 也叫场景启动器 )
<!-- mysql -->
<!--- 利用mybatis操作mysql需要三个库 ( mysql + jdbc + mybatis ) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.21</version>
<scope>runtime</scope>
</dependency>
<!-- jdbc依赖,(Java Database Connectivity) java数据连接 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
(2) 在 ( application.yml ) 文件中设置 mybatis ( 全局配置文件 ) 和 ( sql映射文件 )
-
application.properties
和application.yml
都可以- 优先级
application.properties > application.yml
- 推荐使用
application.yml
- 优先级
- 第一步:在resources文件夹中新建mybatis文件夹
- 新建全局配置文件:
mybatis/mybatis-config.xml
- 新建mapper的sql映射文件:
mybatis/mapper/MybatisTestMapper.xml
- 新建全局配置文件:
- 第二步:在 application.yml 文件中配置 mybatis 的
全局配置文件
和sql映射文件
- 第一步:在resources文件夹中新建mybatis文件夹
- 新建全局配置文件:`mybatis/mybatis-config.xml`
- 新建mapper的sql映射文件:`mybatis/mapper/MybatisTestMapper.java`
(1) resources/mybatis/mybatis-config.yml
-------
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
</configuration>
(2) resources/mybatis/mapper/MybatisTestMapper.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.example.demo.mapper.MybatisTestMapper">
<!-- public MybatisTestBean getMybatisTest(int id); -->
<!-- id 是方法名 -->
<!-- resultType 是方法的返回值类型,通过 copy path => copy reference 可以快速生成 -->
<select id="getMybatisTest" resultType="com.example.demo.bean.MybatisTestBean">
select * from mybatis_test where id=#{id}
</select>
</mapper>
- 第二步:在 application.yml 文件中配置 mybatis 的 `全局配置文件` 和 `sql映射文件`
(3) application.yml
-------
# 配置 mybatis 规则
mybatis:
config-location: classpath:mybatis/mybatis-config.xml # mybatis全局配置文件
mapper-locations: classpath:mybatis/mapper/*.xml # mybatis的sql映射文件
(3) 正常写 controller => service => mapper
MybatisTestController
-------
@RestController
public class MybatisTestController {
@Autowired
MybatisTestService mybatisTestService;
@GetMapping("/mybatis")
public MybatisTestBean getMybatisTestMessage(@RequestParam(name="id") int id) {
return mybatisTestService.getMybatisTest(id);
}
}
MybatisTestService
-------
@Service
public class MybatisTestService {
@Autowired
MybatisTestMapper mybatisTestMapper;
public MybatisTestBean getMybatisTest(int id) {
System.out.println(id);
return mybatisTestMapper.getMybatisTest(id);
}
}
MybatisTestMapper.java
-------
@Mapper
public interface MybatisTestMapper {
public MybatisTestBean getMybatisTest(int id);
}
[图片上传失败...(image-64066d-1633576164164)]
(三) 拦截器
(1) HandlerInterceptor 拦截器的三个方法
-
preHandle
在目标方法执行前执行,即 controller 方法执行前执行 -
postHandle
在目标方法执行完成后执行 -
afterCompletion
在页面渲染后执行
(2) 具体的实现过程
(2.1) 编写一个拦截器, 实现 HandlerInterceptor 接口
- 注意是
implements HandlerInterceptor
src/main/java/com.exmaple.demo/interceptor/LoginInterceptor.java
-------
/**
* 拦截器
* 1. 编写一个拦截器,实现 HandlerInterceptor 接口
* 2. 把拦截器注册到容器中 ( 实现 WebMvcConfigurer 的 addInterceptors 方法)
* 3. 指定拦截规则 【如果拦截所有,静态资源也会被拦截,可以用 excludePathPatterns 方法放行】
*/
/**
* 登陆检查
* @Slf4j
* 1. @Slf4j 是 lombok提供的功能
* 2. @Slf4j 注入后就可以使用 log.info()
*/
@Slf4j
public class LoginInterceptor implements HandlerInterceptor {
// 目标方法执行之前执行
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
log.info("preHandle拦截器 - 拦截的路径是{}", request.getRequestURI());
return true; // true 表示放行,false表示拦截
}
// 目标方法执行完成之后执行
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
log.info("postHandle拦截器 - ModelAndView", modelAndView);
}
// 页面渲染以后执行
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
log.info("afterCompletion拦截器", ex);
}
}
(2.2) 把拦截器注册到容器中,( 实现 WebMvcConfigurer 的 addInterceptors 方法)
(2.3) 指定拦截规则 【如果拦截所有,静态资源也会被拦截,可以用 excludePathPatterns 方法放行】
src/main/java/com.example.demo/config/AdminWebConfig.java
-------
/**
* 拦截器
* 1. 编写一个拦截器,实现 HandlerInterceptor 接口
* 2. 把拦截器注册到容器中 ( 实现 WebMvcConfigurer 的 addInterceptors 方法)
* 3. 指定拦截规则 【如果拦截所有,静态资源也会被拦截,可以用 excludePathPatterns 方法放行】
*/
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
.addPathPatterns("/**") // 拦截 => 拦截所有请求,包括静态资源
.excludePathPatterns("/", "/login", "css/**", "/fonts/**", "/images/**", "/js/**"); // 放行,放行了static文件夹下的所有静态资源
// 问题:如何能访问到 resources/static/images/8.jpg
// 回答:http://localhost:7777/images/8.jpg
}
}
(四) 文件上传
(1) @RequestPart("input框的name属性对应的值")
-
@RequestPart
这个注解用在multipart/form-data
表单提交请求的方法上
(2) 实现步骤
(2.1) springboot中如果需要返回html页面,需要 spring-boot-starter-thymeleaf
场景启动器
<!-- spring-boot-starter-thymeleaf -->
<!-- 主要用于显示resources/templates中的html -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
(2.2) resources/templates/fileUpload.xml
<!DOCTYPE html>
<!--注意:xmls:th 的值-->
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div>测试页面</div>
<!-- th:action="@{/upload}" 提交的controller对应的path -->
<!-- enctype="multipart/form-data" -->
<form th:action="@{/upload}" method="post" enctype="multipart/form-data">
<div>
<span>单头像上传</span>
<input type="file" name="single">
</div>
<div>
<span>多头像上传</span>
<!-- multiple表示开启多个上传 -->
<input type="file" name="multiple" multiple>
</div>
<button type="submit">上传</button>
</form>
</body>
</html>
(2.3) src/main/java/com.example.demo/controller/FileUpload.java
@Controller
@Slf4j
public class FileUpload {
@GetMapping("/fileUpload")
public String handleFile(
) {
// 1. 这里返回的是 resources/templates/fileUpload.html
// 2. 需要安装 spring-boot-starter-thymeleaf 这个maven依赖
return "fileUpload";
}
// MultipartFile 会自动封装上传上来的文件
@PostMapping("/upload")
public String upload(
@RequestPart("single") MultipartFile single,
@RequestPart("multiple") MultipartFile[] multiple
) throws IOException {
log.info("上传的单文件{}", single);
log.info("上传的多文件{}", multiple);
if (!single.isEmpty()) {
String originalFilename = single.getOriginalFilename(); // 获取原始文件名
single.transferTo(new File("F:\\Java-workspace\\uploadFolder\\" + originalFilename));
// 保存到 ( F/java-workspace/uploadFolder ) 文件夹
}
if (multiple.length > 0) {
for (MultipartFile file : multiple) { // for循环
if (!file.isEmpty()) {
String originalFilename = file.getOriginalFilename(); // 原始文件名
long size = file.getSize()/1024; // 文件大小,默认但是为字节,1MB = 1024KB = 1024 * 1024 byte
log.info("文件名{}. 大小{}KB", originalFilename, size);
file.transferTo(new File("F:\\Java-workspace\\uploadFolder\\" + originalFilename));
}
}
}
return "fileUpload"; // 返回 fileUpload.html
}
// (3)
// 前后端分离的接口
// 注意点
// 1. consumes 一定要设置成 "multipart/form-data" 因为前端 antd 中的 Upload 组件是用的 form-data 方式在上传
// 2. 前端上传时 Upload 组件一定要设置 name 属性,因为 name 的值是和这里的 @RequestPart("前端name属性的值") 一一对应
@PostMapping(value = "/frontendUpload", consumes = "multipart/form-data")
public String frontendUpload(
// @RequestParam("file") MultipartFile avatars
@RequestPart("file") MultipartFile avatars
) {
System.out.println(avatars);
return "上传成功";
}
}
(2.4) 前后端分离 react页面的上传代码如下
<Upload
action="/api/frontendUpload" // action还是会走代理
listType="picture-card"
fileList={fileList}
onPreview={handlePreview}
onChange={handleChange}
name="file" // 注意,name一定要约束好,因为后端 @RequestPart("file") 是根据前端传递的name值来判断的
>