JavaWeb学习记录(黑马程序员2023版)
一、Maven
管理和构建java项目的工具,它基于项目对象模型pom(project object model)的概念,通过一小段描述信息来管理项目的构建。
作用:
1.依赖管理
无需手动导入jar包,只需要在pom.xml中,配置一块描述信息即可(artifactId中写依赖名,version中写版本),会自动联网下载对应的依赖,修改版本也是直接修改即可
先在本地仓库进行jar包的查找,若没有会到中央仓库(https://repo1.maven.org/maven2/)下载到本地之后进行导入。若有远程仓库(私服)会比中央仓库先进行,速度较快,目前阶段,我们通常会使用阿里云的私服来提高速度。
安装我就不记录了,因为我电脑上面已经安装过了,直接看视频安装maven08:00。
终端运行mvn -v查看是否配置完毕
2.统一项目结构
我们知道Java项目的开发工具非常多,如
其目录结构有差异,无法互相导入。通过Maven会统一他们的项目结构,完美解决此问题。
3.项目构建
标准跨平台的自动化项目构建方式 。即通过maven提供的一系列指令快速的进行以下一系列操作。
test:会生成target目标保存了编译之后的文件
1.依赖配置
在pom.xml中添加如下信息,点击浮动的小m即成功配置,在Maven面板中可以看到依赖项中含有这个依赖
2.依赖传递
依赖具有传递性
直接依赖:在当前项目中通过依赖配置建立的依赖关系
间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源
A项目要直接依赖B项目,直接配置B项目的坐标即可,然后B项目中直接配置C项目的坐标
如果A项目想要B项目的依赖,但是不想要C项目的依赖,使用排除依赖(主动断开依赖的资源)
<exclusions><exclusion>
3.依赖范围
依赖的jar包,默认情况下,可以在任何地方使用。可通过<scope>设置其作用范围
作用范围:
主程序范围有效(main文件夹范围内)
测试程序范围有效(test文件夹范围内)
是否参与打包运行(package指令范围内)
junit单元测试
4.生命周期
为了对所有的maven项目构建过程进行抽象和统一
主要
二、SpringBootWeb
Spring 官网spring.io形成一种开发生态圈,提供了若干个子项目,每个项目用于完成特定的功能。
入门需求:
基于SpringBoot开发一个web应用,浏览器发起请求后,给浏览器返回字符串hello world
1.创建springboot程序,并勾选web开发相关依赖
我用的是jdk22
pom.xml 中记录的这个父工程是所有SpringBoot需要继承的
2.定义HelloController类,添加方法hello,并添加注解
加上注解@RestController才能标识这是请求处理类
RequestMapping浏览器调用/hello就会调用下面的方法
3.运行测试
启动则是在自动自称类中main方法处启动
访问端口号8080/hello
打印了内容说明没有问题
三、http协议
超文本传输协议,规定了浏览器和服务器之间数据传输的规则
1.特点
基于tcp协议:面向连接,安全。每一次请求者之前要先进行三次握手,确定双方都有收发能力了,再传输数据。
基于请求-相应模型:一次请求对应一次响应
HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。=》会话技术解决
缺点:多次请求间不能共享数据。
优点:速度快
2.请求协议
3.响应协议
四、Tomcat
一个轻量级的web服务器,支持servlet、jsp等少量javaEE规范
也被称为web容器、servlet容器
(学过了)
springbootWeb开发中已经把tomcat集成进来了(内嵌)
这就是为什么在下面这个启动类就可以启动tomcat的原因
五、postman请求响应
用于接口测试需求 是一款功能强大的网页调试与发送网页HTTP请求的Chorme插件
controller 接受请求 设置响应
1.测试环境的创建
创建自己的workspace
选择请求方式 填入请求路径 Send即可看到响应回来的数据
ctrl+s保存路径 创建一个集合保存在里面
2.简单参数
原始方式
在Controller方法形参中声明HttpServletRequest对象
调用对象的getParameter(参数)获取请求参数
SpringBoot方式
简单参数:参数名与形参变量名相同,定义形参即可接收参数
调用一次返回了tom:18,成功接收到了参数
如果是post请求呢?上面我们学过post请求要在请求体中携带 通过body设置请求体的内容
如果方法形参名称与请求参数名称不匹配,可以使用@requestParam这个注解完成映射
一旦给name加上注解,就表示这个参数必须传递,不传递就会报错(400 客户端发生错误)
3.实体参数
如果前端传递的参数非常多,有十个二十怎么处理?故可以将所有的请求参数封装到一个实体类当中,同样请求参数名与形参对象属性名保持一致。
简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可
创建实体User类
实体参数
测试
如果是复杂的实体对象呢?按照对象层次结构关系即可接收嵌套POJO属性参数
toString一定要写,要不然adress这边就返回的是内部地址
测试
4.数组集合参数
很多form表单中的checkbox里面有多个值需要提交到服务端,同样是逐个提交
数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数
集合参数:请求参数名与形参集合名称相同且请求参数为多个,@requestParam 绑定参数关系
5.日期参数
日期参数:使用@DateTimeFormat注解完成日期参数格式转换
由于日期的格式有多样,所以需要在注解之后指定格式
我一开始测试的参数是
报错
2024-06-26T20:49:31.376+08:00 WARN 25092 --- [sprintboot-web] [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value [2024-6-26 20:50:00]]
是因为月份只有一个数字的时候忘记填0了
改正即可正确输出了
6.Json参数
Json格式的参数在前后端异步交互的时候使用是非常多的
Json参数:Json数据键名与形参对象属性名相同,定义pojo类型形参即可接收参数,需要使用@RequestBody标识
(1)postman发送请求的时候如何传递Json格式的请求参数?
需要将请求方式设置为post:因为Json格式的请求数据是需要放在请求体当中携带到服务端的
(2)在服务端controller当中怎么接收Json格式的请求参数?
往往通过实体对象来接收,用@RequestBody注解将Json格式的数据封装到实体类当中
成功响应!
7.路径参数
路径参数:通过请求URL直接传递参数,使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数
多个路径参数,就定义多个形参 都用@PathVariable获取路径参数后绑定给形参就可以了
8.响应数据
通过注解@ResponseBody响应
作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应
为什么我们以上例子没有看到这个注解?
因为@RestController = @Controller+@ResponseBody
统一所有接口的响应结果 用一个类封装
前端只会拿到一种格式的数据,这对项目的管理和维护都有大大的帮助
/**
* 统一响应结果封装类
*/
public class Result {
private Integer code ;//1 成功 , 0 失败
private String msg; //提示信息
private Object data; //数据 date
public Result() {
}
public Result(Integer code, String msg, Object data) {
this.code = code;
this.msg = msg;
this.data = data;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object getData() {
return data;
}
public void setData(Object data) {
this.data = data;
}
public static Result success(Object data){
return new Result(1, "success", data);
}
public static Result success(){
return new Result(1, "success", null);
}
public static Result error(String msg){
return new Result(0, msg, null);
}
@Override
public String toString() {
return "Result{" +
"code=" + code +
", msg='" + msg + '\'' +
", data=" + data +
'}';
}
}
案例:
在pom.xml文件中引入dom4j的依赖,用于解析XML文件
引入资料中提供的解析XML的工具类XMLParserUtils、对应的实体类Emp、XML文件emp.xml
引入资料中提供的静态页面文件,放在resources下的static目录
SpringBoot项目的静态资源默认的存放目录为:classpath:/static、classpath:/public、classpath:/resources (classpath:resources)
编写Controller程序,处理请求,响应数据
六、分层解耦
1.三层架构
实际开发过程中需要尽量让每一个类的职责单一(单一职责原则),使复杂性更低,可读性更强
三层架构包括:
controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。
service:业务逻辑层,处理具体的业务逻辑。
dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、查、改
2.思想!!
内聚:软件中各个功能模块内部的功能联系
耦合:衡量软件中各个层/模块之间的依赖、关联程度
比如说上面的代码中
Controller类要使用数据,需要先new一个Service层的实现类(Controller和Service类之间耦合)
就是一个代码改动,另一边也需要跟着改动
原则:高内聚低耦合
我们需要解耦!=》容器 这样即使是改动了 Controller层也不需要动
控制反转:IOC。对象的创建控制权由程序自身转移到外部(容器)
反转之前是程序自身来控制对象的创建(new),反转之后是由容器来控制。容器叫(IOC容器 or Spring容器)
依赖注入:DI。容器为应用程序提供运行时,所依赖的资源
Bean对象:IOC容器中创建、管理的对象
3.IOC & DI 入门
(1)将service层和dao层的实现类,交给IOC容器管理
=》加上注解@Component
(2)为Controller及Service注入运行时,依赖的对象
=》运行时,IOC为其提供Bean对象,并赋值给该变量,加上注解@Autowired
从实现类A变成B 直接把Component注解注释掉 给B添上
4.IOC控制反转详解
若某个类不归属于下面三个层,那么就用@Component进行
注意:@RestController中包含了@Controller
每一个Bean都有标识(就是Bean的名字)默认是类名首字母小写
若你需要自己设置名字 通过给value赋值即可
一般不用指定
Bean组件扫描
声明这四大注解想要生效还需要被组件扫描注解@ComponentScan扫描
默认包含在了启动类声明注解中(范围在启动类所在包及其子包 意思就是不要随便改变位置)
或者手动设置
5.DI 依赖注入详解
通过以下几种方案解决:
@Primary\
@Qualifier\ 按照类型注入
@Resource 按照名称注入
总结
七、SQL查缺补漏
1.分页查询limit
(mysql方言 limit)
起始索引=(页码-1)* 每页展示的记录数
2.流程控制函数
3.外键添加
foreign key属于物理外键
缺点:影响增删改的效率(检查外键关系)
仅用于单节点数据库,不适用于分布式、集群场景
容易引发数据库的死锁问题,消耗性能
4.一对一&多对多
一对一关系,用于单表拆分,基础字段和其他字段分开存储,以提升工作效率
实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)
多对多如下
可视化关系表
5.多表查询
(1)内连接
(2)外连接
左外 以前一个为标准 如果后一个是null就查不出来 右外反之
(3)行子查询
(4)表子查询
6.事务
回滚:恢复到操作之前
7.索引
往往我们考虑查询的优化比增删改查更多
一般默认B+树数据结构(多路平衡搜索树)
所有的数据都是在叶子结点保存的 所有的key也会在叶子结点出现
排序从小到大 双向链表
查询性能稳定
语法
注意事项
八、Mybatis
优秀的持久层框架,用于简化JDBC的开发
几乎免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。mybatis可以通过简单的XML或者注解来配置和映射原始类型、接口与Java POJO 为数据库中的记录。
1.简单步骤
1.返回的每一个数据都会封装成一个User对象,表中的字段会自动的封装成对象的属性
字段名和类中的属性名保持一致,框架会完成自动封装
定义实体类时,如果需要用到整型,不要用int,要用Integer Why?
如果用int类型 默认值为0,会影响数据的判断,用Integer 默认值null 不会给数据的判断带来干扰
2.引入mybatis框架的依赖和mysql驱动的依赖
配置mybatis
更改数据库的名字和密码
3.需要定义持久层的接口叫UserMapper
即数据访问层的接口
在MyBatis开发当中,实现接口即可,不需要完成实现类
因为程序在运行时,框架底层会自动生成接口的实现类对象,并且将该对象交给IOC容器管理
单元测试
UserMapper是一个接口 不可以直接new一个接口
但是上面提到自动生成接口的代理对象并且将对象交给了IOC容器管理
已经成为IOC容器中的Bean
所以此时用依赖注入 用@Autowired将Bean注入
我在这里卡了好久!!!!一直报错:
无法自动装配。找不到“UserMapper”类型的Bean
解决方法:在测试类上即@SpringBootTest上加上一个新的注解@MapperScan("com.test.mapper")
2.配置sql提示
默认在mybatis中编写sql语句是不识别的。做以下配置 再连接database(右边栏)
3.JDBC
sun公司官方定义的一套操作所有关系型数据库的规范,即接口
仅仅是一套规范或是接口
其实现由各个厂商提供 e.g: Mysql Oracle SqlServer
此实现称作数据库驱动(数据库驱动jar包)
我们使用这套接口编程,真正执行的代码是驱动jar包中的实现类
我在java课设中用到servlet实现了一个图书管理系统
每次我在dao层的实现类中与数据库建立连接都会执行以下步骤:
1.注册驱动
2.获取连接
3.执行sql语句
4.获取查询的结果集
5.封装结果数据
6.释放资源
以下是黑马的例子
弊端:
(1)出现硬编码 一旦此内容发生变化,需要重新编译打包之后才能运行
(2)解析结果封装结果需要一个字段一个字段的解析(一旦字段很多将会非常繁琐)
(3)频繁地获取连接和释放连接,会造成资源浪费,从而导致性能降低
所以我们用mybatis解决(完胜)
(1)数据库连接的四要素配置在了.properties文件中 无需操作java代码
(2)用@Mapper自动将查询的结果封装到集合中,返回的每一条记录封装成对象,所有的User对象封装到一个List集合当中
(3)用spring.datasource连接信息,springboot会自动采用数据库连接池技术统一管理分配连接
4.数据库连接池
是一个容器,负责分配管理数据库连接
允许重新使用一个现有的数据库连接,而不是重新建立一个
释放空闲空间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏
连接复用而不是每次创建新的连接
上面我的图书管理系统也是通过c3p0连接池连接的
现在更多用的是后两个
springboot自带连接池hikari 此连接池会提供datasource接口
如果我们不想用默认的连接池,换成druid连接池呢?
引入druid起步依赖和修改配置文件即可
我的springboot是3.x版本的,所以在引入依赖的时候
<artifactId>后接druid-spring-boot-3-starter
<version>后接1.2.20
5.lombok工具包
Java类库,通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率
比如,解决臃肿的实体类中getter和setter以及构造方法和toString()
只需在实体类上面加上注解@Data
要想使用lombok还需要引入依赖 无需版本号
6.环境准备
curd就是增删改查的简写
1.准备数据库表
员工表emp
部门表dept
2.创建一个新的springboot工程,引入起步依赖
3.application.properties引入数据库连接信息
4.创建对应的实体类Emp(实体类采用驼峰命名)
5.准备Mapper接口EmpMapper
7.删除操作
根据id删除数据也需要一个接口方法
如果mapper接口方法只有一个普通类型的参数。#{...}里面的属性名可以随便写,如#{id},#{value}
还是建议两者保持一致
单元测试
可以看到第17条记录已经删除
返回影响的记录数
再次执行刚才的操作返回的记录数为0
借助mybatis日志来看到删除的具体信息
直接输入mybatislog会有提示信息选择第一个
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
显示 上面两行就叫预编译sql 性能更高更安全 传入值之后会先检查缓存当中有无
如果在登录界面检查有无此用户的信息
就用select count(*) from emp where username = ' ' and password = ' '
返回结果为 0 说明没有找到 如果返回结果为 1 说明找到了
有一个信息安全问题 sql注入
所以不能直接编译 而是使用预编译 用问号进行
参数占位符
#{...}执行sql时,会把#{...}替换为?,生成预编译SQL
¥{...}是拼接sql。直接将参数拼接在sql语句中,存在sql注入问题
8.新增
新增操作中如果属性过多就用emp封装一下再传值 记住values后面一定要跟实体类中的属性名一样
新增成功
主键返回
在@insert注解之前加上这样的注解
@Options(keyProperty="id",useGeneratedKeys=true)
表示会自动生成的主键值,赋值给emp对象的id属性
9.更新
更新成功
10.查询(根据ID查询)
一般用于页面回显展示,查询到的封装起来
problem:未封装进来 显示null
mybatis封装中 如果实体类属性名和数据库表查询返回的字段名一致才会自动封装
怎样解决上面的问题呢?
solution1:
给字段起别名,让别名与实体类属性一致
sql语句不能再写* 需要把所有字段罗列出来
solution2:
通过@Results,@Result注释手动映射封装
对不一致的进行封装
solution3:
开启mybatis的驼峰命名自动映射开关
在.properties中配置
输入camel加上以下这句话
11.查询(条件查询)
员工姓名->模糊匹配
性别->精准查询
入职时间->范围查询
支持分页查询 并对查询结果根据最后的修改时间进行倒序排序
!!如果需要再sql中的字符串中写入传入的值 需要把 # 改成 $(拼接符号 不会生成预编译)
用拼接函数
12.XML映射文件
注解开发简单的sql,xml开发动态sql
配置文件在resource下
创建Directory要用 / 分隔
同时xml文件需要约束 可以在mybatis中文网copy过来
<?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">
resultType:单条记录所封装的类型
mybatisX
基于idea的快速开发mybatis的插件 会跳转定位
13.动态sql
ctrl+alt+l格式化
随着用户的输入或者外部条件的变化而变化的sql语句
(1)if
如果name是null就会出现问题=》
那么我们需要引入where标签 自动去除and和空
update中去掉多余的逗号<set>标签 字段不存在则不更新
(2)foreach
collection遍历的集合 item遍历出来的元素 separator分隔符 open遍历开始前拼接的SQL片段 close遍历结束后拼接的SQL片段
e.g批量删除员工
(3)sql&include
为了提高复用性
九、案例细节
1.记录日志
项目开发中尽量不用sout记录日志
用日志记录框架记录日志
声明日志记录对象
private static Logger log = LoggerFactory.getLogger(DeptController.class);
log.info("根据id删除部门:{}",id);
调用info方法输出日志
可以不添加第一行代码 直接在class前面添加@Slf4j
2.Get/Post等请求方式
3.参数占位符
4.注解
@RequestBody 将数据封装到实体类当中
@PathVariable 获取路径参数绑定给形参
/** + 回车 文档注释
5.简化
抽取
6.HTTP请求方式
7.设置默认值
@RequestParam 设置默认值
8.分页查询
分页插件PageHelper 需要添加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
代码 分页条件查询
PageBean类
package com.itheima.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
/**
* 分页查询结果封装类
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
private Long total;//总记录数
private List rows;//数据列表
}
EmpController
package com.itheima.controller;
import com.itheima.pojo.PageBean;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;
import java.time.LocalDate;
import java.util.List;
/**
* 员工管理Controller
*/
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {
@Autowired
private EmpService empService;
@GetMapping
public Result page(@RequestParam(defaultValue = "1") Integer page,
@RequestParam(defaultValue = "10") Integer pageSize,
String name, Short gender,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
@DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
log.info("分页查询, 参数: {},{},{},{},{},{}",page,pageSize,name,gender,begin,end);
//调用service分页查询
PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end);
return Result.success(pageBean);
}
}
EmpService
package com.itheima.service;
import com.itheima.pojo.PageBean;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.List;
/**
* 员工管理
*/
public interface EmpService {
/**
* 分页查询
* @param page
* @param pageSize
* @return
*/
PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end);
}
EmpServiceImpl
package com.itheima.service.impl;
//import com.github.pagehelper.Page;
//import com.github.pagehelper.PageHelper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.itheima.mapper.EmpMapper;
import com.itheima.pojo.Emp;
import com.itheima.pojo.PageBean;
import com.itheima.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.util.List;
@Service
public class EmpServiceImpl implements EmpService {
@Autowired
private EmpMapper empMapper;
@Override
public PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end) {
//1. 设置分页参数
PageHelper.startPage(page,pageSize);
//2. 执行查询
List<Emp> empList = empMapper.list(name, gender, begin, end);
Page<Emp> p = (Page<Emp>) empList;
//3. 封装PageBean对象
return new PageBean(p.getTotal(), p.getResult());
}
}
EmpMapper
package com.itheima.mapper;
import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import java.time.LocalDate;
import java.util.List;
/**
* 员工管理
*/
@Mapper
public interface EmpMapper {
/**
* 员工信息查询
* @return
*/
public List<Emp> list(String name, Short gender,LocalDate begin,LocalDate end);
}
EmpMapper.xml
<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">
<!--条件查询-->
<select id="list" resultType="com.itheima.pojo.Emp">
select *
from emp
<where>
<if test="name != null and name != ''">
name like concat('%',#{name},'%')
</if>
<if test="gender != null">
and gender = #{gender}
</if>
<if test="begin != null and end != null">
and entrydate between #{begin} and #{end}
</if>
</where>
order by update_time desc
</select>
</mapper>
9.查询回显
在原有的基础上进行修改(就是根据id查找信息)
10.配置文件
(1)参数配置化
硬编码 如果发生变化 需要重新进行代码的编译 =》 引入到配置文件当中
@Value注解 通常用于外部配置的属性注入,其具体用法为: @Value("${配置文件中的key}")
(2)yml配置文件(更推荐)
后缀可以是yml(更推荐)也可以是yaml
层级结构更清晰
替换
第一次测试没法获取到 发现给密码加上双引号就成功了
(3)@ConfigurationProperties
更加简化啦!!!
不需要单个给属性绑定
属性名得保持一致
引入以下依赖 在配置文件中会有提示(可选)
十、文件上传
将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程
比如发朋友圈、微博
常见的两种存储形式:
1.本地存储
2.云存储(阿里云OSS)
前端上传需要在form标签后 用post请求方式且
加上编码格式enctype=“multipart/form-data” 支持大型的二进制数据
如果用默认编码 会发现提交的仅仅是文件名并无文件内容
服务端接收文件:提交上来的文件在Controller中怎么接收 MultipartFile image
如果与绑定属性名不一致用注解 @RequestParam("image")MultipartFile file
1.本地存储
在服务器,接收到上传上来的文件之后,将文件存储在本地服务器磁盘当中
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
log.info("文件上传: {}, {}, {}",username,age,image);
//获取原始的文件名称
String originalFilename = image.getOriginalFilename();
//将文件存储在服务器的磁盘目录中D:\aTestImages
image.transferTo(new File("D:\\aTestImages\\"+originalFilename));
return Result.success();
}
}
此处将文件的原始名称作为存储会有问题:两个文件名有可能一样 =》 构造唯一文件名 uuid
(1)测试类中生成uuid
@Test
public void testUuid(){
for (int i = 0; i < 1000; i++) {
String uuid = UUID.randomUUID().toString();
System.out.println(uuid);
}
}
生成的uuid 长度固定(36)且唯一
同时需要截取后缀
@Slf4j
@RestController
public class UploadController {
@PostMapping("/upload")
public Result upload(String username, Integer age, MultipartFile image) throws Exception {
log.info("文件上传: {}, {}, {}",username,age,image);
//获取原始的文件名称
String originalFilename = image.getOriginalFilename();
//构造唯一的文件名
int index = originalFilename.lastIndexOf(".");
String extname = originalFilename.substring(index);
String newFileName = UUID.randomUUID().toString()+extname;
log.info("新的文件名: {}",newFileName);
//将文件存储在服务器的磁盘目录中D:\aTestImages
image.transferTo(new File("D:\\aTestImages\\"+newFileName));
return Result.success();
}
}
(2)注意
在springboot中,文件上传,默认单个文件允许最大大小为1M。如果需要上传大文件,可以在.properties中进行如下配置
#配置单个文件最大上传大小
logging.logback.rollingpolicy.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB
2.阿里云OSS
阿里云对象存储OSS(云存储服务) 可以通过网络随时存储和调用包括文本、图片、音频呢和视频等在内的各种文件
术语:
SDK 软件开发工具包 包括辅助软件开发的依赖(jar包)、代码示例等
Bucket 存储空间是用户用于存储对象的容器,所有的对象都必须隶属于某个存储空间
创建bucket
参考官方sdk 查看文档信息
(1)上传文件流
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.InputStream;
public class Demo {
public static void main(String[] args) throws Exception {
// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
// 填写Bucket名称,例如examplebucket。
String bucketName = "examplebucket";
// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
String objectName = "exampledir/exampleobject.txt";
// 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
// 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
String filePath= "D:\\localpath\\examplefile.txt";
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
InputStream inputStream = new FileInputStream(filePath);
// 创建PutObjectRequest对象。
PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
// 创建PutObject请求。
PutObjectResult result = ossClient.putObject(putObjectRequest);
} catch (OSSException oe) {
System.out.println("Caught an OSSException, which means your request made it to OSS, "
+ "but was rejected with an error response for some reason.");
System.out.println("Error Message:" + oe.getErrorMessage());
System.out.println("Error Code:" + oe.getErrorCode());
System.out.println("Request ID:" + oe.getRequestId());
System.out.println("Host ID:" + oe.getHostId());
} catch (ClientException ce) {
System.out.println("Caught an ClientException, which means the client encountered "
+ "a serious internal problem while trying to communicate with OSS, "
+ "such as not being able to access the network.");
System.out.println("Error Message:" + ce.getMessage());
} finally {
if (ossClient != null) {
ossClient.shutdown();
}
}
}
}
(2)集成
1.引入阿里云OSS上传文件工具类
/**
* 阿里云 OSS 工具类
*/
@Component
public class AliOSSUtils {
private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
private String accessKeyId = "LTAI5tLNNdkWD6p8yaKNCoxw";
private String accessKeySecret = "rMHRFree0oqbomc3HpI52Inp1xlwB9";
private String bucketName = "my-java-web-tlias";
/**
* 实现上传图片到OSS
*/
public String upload(MultipartFile file) throws IOException {
// 获取上传的文件的输入流
InputStream inputStream = file.getInputStream();
// 避免文件覆盖
String originalFilename = file.getOriginalFilename();
String fileName = UUID.randomUUID().toString() + originalFilename.substring(originalFilename.lastIndexOf("."));
//上传文件到 OSS
OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
ossClient.putObject(bucketName, fileName, inputStream);
//文件访问路径
String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
// 关闭ossClient
ossClient.shutdown();
return url;// 把上传到oss的路径返回
}
}
2.上传图片接口开发
@Slf4j
@RestController
public class UploadController {
@Autowired
private AliOSSUtils aliOSSUtils;
@PostMapping("/upload")
public Result upload(MultipartFile image) throws IOException {
log.info("文件上传,文件名:{}",image.getOriginalFilename());
//调用阿里云工具类进行文件上传
String url = aliOSSUtils.upload(image);
log.info("文件上传成功,文件访问的url:{}",url);
return Result.success(url);
}
}
十一、登录
1.登录功能
在postman中进行测试
code为1 表示登陆成功
若失败
2.登录校验
看员工是否登录 再进行其他操作
HTTP是无状态的 每一次请求都是独立的 下一次请求不会携带上一次请求的数据
故要存储标记 在业务操作前判断是否标记
(1)会话技术
会话: 浏览器和服务器之间的一次连接,我们就叫做会话
用户打开浏览器访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。一次会话中可以包含多次请求和响应。
会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便 在同一次会话的多次请求间共享数据
方案:
1.客户端会话跟踪技术: Cookie
HTTP协议中支持的技术 but移动端无法使用,不安全因为用户可以禁用,不能跨域(比如前后端分离的开发程序,需要独立部署)
2.服务端会话跟踪技术: Session
存值取值,存储在服务端,安全 but服务器集群环境下无法直接使用以及Cookie的缺点
3.令牌技术(主流)
支持PC端、移动端, 解决集群环境下的认证问题,减轻服务器端存储压力 but需要自己实现(还需要前端人员配合)
(2)JWT令牌
JSON WEB TOKEN
用于在通信双方以json数据格式安全的传输信息 进行了安全的封装。由于数字签名的存在,这些信息是可靠的。
由三部分组成:
Header(头) 记录类型以及签名算法等 Base64编码
Payload(有效载荷) 携带一些自定义信息、默认信息等
Signature(签名) 防止Token被篡改,保证安全性
生成:
/**
* 生成jwt令牌
*/
@Test
public void testGenJwt(){
Map<String, Object> claims = new HashMap<>();
claims.put("id",1);
claims.put("name","tom");
String jwt = Jwts.builder()
.signWith(SignatureAlgorithm.HS256, "itheima")
.setClaims(claims)
.setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置有效期为1h
.compact();
System.out.println(jwt);
}
生成的jwt可以在jwt官网解码
解析令牌
/**
* 解析jwt令牌
*/
@Test
public void testParseJwt(){
Claims claims = Jwts.parser()
.setSigningKey("itheima")
.parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyMDg3NTMyOX0.2KbFdYQN6TiJKIkp3ZpFWewO7FCU8TlNsTNW-cqtImA")
.getBody();
System.out.println(claims);
}
}
注意:秘钥需配套
用户登录成功后,系统会自动下发jwt令牌,然后再后续的每次请求中,都需要在请求头header中携带到服务器,请求头的名称为token,值为 登录时下发的jwt令牌
若检测到用户未登录,则会返回如下固定的错误信息
测试成功!
校验令牌的有效性 下面两个
(3)过滤器Filter
是JavaWeb三大组件之一(Servlet、Filter、Listener)
可以把对资源的请求拦截下来,从而实现一些特殊的功能
过滤器一般完成一些通用的操作,such as 登录校验、统一编码处理、敏感字符处理等
初始化和销毁的方法是有默认实现的 可以不写出来
FilterChain.doFilter之前可以执行放行前的逻辑 同时放行后还可以执行放行后的逻辑
Filter拦截路径 @WebFilter(urlPatterns = " ")
过滤器链
在一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链
优先级按类名的自然排序
登录校验
fastJSON 阿里巴巴提供的一个手动转换成json的依赖包
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.0</version>
</dependency>
try-catch 快捷键 ctrl+alt+t
关注其业务流程
@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
//强转
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse resp = (HttpServletResponse) servletResponse;
//获取请求的url
String url = req.getRequestURL().toString();
log.info("请求的url:{}",url);
//判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("login")){
log.info("登录操作,放行...");
filterChain.doFilter(servletRequest,servletResponse);
return;
}
//获取请求头的令牌(token)
String jwt = req.getHeader("token");
//判断令牌是否存在,如果不存在,返回错误结果(未登录)
if (!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
log.info("解析令牌失败,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return;
}
//放行
log.info("令牌合法,放行");
filterChain.doFilter(servletRequest,servletResponse);
}
}
(4)拦截器Interceptor
是一种动态拦截方法调用的机制,类似于过滤器,spring框架中提供的,用来动态拦截控制器方法的执行
作用即拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码
1.定义拦截器
ctrl+o 重写方法
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行,返回true放行,返回false不放行
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle ...");
return true;
}
@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override //视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
2.配置拦截器
@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
@Autowired
private LoginCheckInterceptor loginCheckInterceptor;
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
}
}
拦截路径
执行流程
若过滤器和拦截器同时存在
登录校验
此处无需强转 因为传入的值中就是HTTP的
@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
@Override //目标资源方法运行前运行,返回true放行,返回false不放行
public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
System.out.println("preHandle ...");
//获取请求的url
String url = req.getRequestURL().toString();
log.info("请求的url:{}",url);
//判断请求url中是否包含login,如果包含,说明是登录操作,放行
if(url.contains("login")){
log.info("登录操作,放行...");
return true;
}
//获取请求头的令牌(token)
String jwt = req.getHeader("token");
//判断令牌是否存在,如果不存在,返回错误结果(未登录)
if (!StringUtils.hasLength(jwt)){
log.info("请求头token为空,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//解析token,如果解析失败,返回错误结果(未登录)
try {
JwtUtils.parseJWT(jwt);
} catch (Exception e) {
log.info("解析令牌失败,返回未登录的信息");
Result error = Result.error("NOT_LOGIN");
String notLogin = JSONObject.toJSONString(error);
resp.getWriter().write(notLogin);
return false;
}
//放行
log.info("令牌合法,放行");
return true;
}
@Override //目标资源方法运行后运行
public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle...");
}
@Override //视图渲染完毕后运行,最后运行
public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion...");
}
}
3.异常处理
/**
* 全局异常处理器
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(Exception.class) //捕获所有异常
public Result ex(Exception ex){
ex.printStackTrace();
return Result.error("对不起,操作失败,请联系管理员");
}
}
十二、事务管理
事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败
1.spring事务管理
my
开启事务管理日志
logging:
level:
org.springframework.jdbc.support.jdbcTransactionManager: debug
2.rollbackFor属性
![](https://i-blog.csdnimg.cn/direct/4e2a82bd867e48e8a30e9611e2d8c4fc.png)
如果代码这样抛出异常,会发现还是会提交
是因为!!
默认情况下,只有出现RuntimeException才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务。
以下,所有异常都会进行事务的回滚
3.propagation属性
事务传播行为:指的就是一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制
因为delete和insert公用同一个事物 所以发生错误都会回滚 传播行为默认required 改成requires_new会把上面的事务挂起 然后开启一个新的事务 当此事物完成之后 又回到刚刚挂起的事务
此插件会高亮事务信息(日志的过滤筛选)
十三、AOP
1.基础
面向切面编程、面向方面编程(就是面向特定方法编程)
案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时
只需定义模版方法 把记录方法执行耗时这一部分公共的逻辑代码定义
实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程
(1)导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
(2)编写aop程序
核心概念
连接点:JoinPoint,可以被AOP控制的方法(暗含方法执行时的相关信息)
通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)
切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用
切面:Aspect,描述通知与切入点的对应关系
目标对象:Target,通知所应用的对象
功能增强
一旦执行了aop程序的开发 最终运行的就不再是原始的目标对象 而是基于目标对象所生成的代理对象
2.进阶
(1)通知类型
后俩通知类型互斥
e.g:
有大量一样的代码就抽取
@PointCut 将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。
若想在其它的切入类中也使用 需要将private改为public
(2)通知顺序
原始方法运行之前的类名排名越靠前越先执行
之后类名排名越靠后越先执行
(3)切入点表达式
描述切入点方法的一种表达式
主要用来决定项目中哪些方法需要加入通知
1.execution
e.g:
包名.类名还是不建议省略 因为匹配的范围过大 影响匹配的效率
还可以代替一部分例如*Service
描述两个不相干的方法
2.annotation
匹配标识有特定注解的方法
注解目前仅起到标识的作用 无需属性
给方法加上注解(想匹配什么方法只需要加上注解即可)
切面类
(4)连接点
获取方法运行时的相关信息
Around才有返回值
3.记录操作日志
(1)导入依赖 上面有
(2)准备数据库表结构,引入对应实体类
(3)自定义注解@Log
(4)切面类
@Slf4j
@Component
@Aspect
public class LogAspect {
@Autowired
private OperateLogMapper operateLogMapper;
@Autowired
private HttpServletRequest request;
@Around("@annotation(com.itheima.anno.Log)")
public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
//操作人ID -- 获取请求头中的jwt令牌,解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");
//操作时间
LocalDateTime operateTime = LocalDateTime.now();
//操作类名
String className = joinPoint.getTarget().getClass().getName();
//操作方法名
String methodName = joinPoint.getSignature().getName();
//操作方法参数
Object[] args = joinPoint.getArgs();
//数组转字符串
String methodParams = Arrays.toString(args);
//记录原始目标方法运行之前的毫秒值
long begin = System.currentTimeMillis();
//调用原始目标方法运行
Object result = joinPoint.proceed();
//记录原始目标方法运行之后的毫秒值
long end = System.currentTimeMillis();
//方法返回值
String returnValue = JSONObject.toJSONString(result);
//操作耗时
Long costTime = end - begin;
//记录操作日志
OperateLog OperateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
operateLogMapper.insert(OperateLog);
log.info("AOP记录操作日志:{}",OperateLog);
return result;
}
}
(5)最后由于我们对于增删改进行操作,所以直接给对应的操作Controller上加上注释@Log即可
测试成功
十四、SpringBoot原理
1.配置优先级&&打包
测试 生效的是.properties的文件 其次是.yml 再是.yaml
SpringBoot 除了支持配置文件属性配置,还支持java系统属性和命令行参数的方式进行属性配置
命令行参数的优先级比java系统属性高 java系统属性的优先级比三种配置文件优先级高
执行maven打包指令package可以把项目打包成jar包 然后执行java指令可以运行jar包
插件
双击即可
在target目录可以找到jar包 直接在cmd中运行java指令 就可以运行程序了 (默认端口号8080)
总结
2.Bean管理
(1)获取bean
spring项目启动时,会把bean都创建好放在IOC容器中(需要什么bean对象就依赖注入即可),如果想要主动获取这些bean,可以通过一下几种方式:
1.根据name获取bean
2.根据类型获取bean
3.根据name获取bean(带类型转换)
名称注意是首字母要小写 默认的是方法名!!!
三个都是一样 说明默认单例模式
(2)bean作用域
bean对象是单例还是多例是取决于作用域的
@Lazy注解 会延迟Controller实例化 延迟到第一次使用的时候(用构造方法进行测试)
本来默认容器启动的时候就实例化了
会发现每一次调用getBean拿到的都是一个全新的bean对象
(3)第三方bean
自己定义的cean声明非常简单,仅需在类前加上注解@Component以及三个衍生注解@Controller、@Service、@Repository
还有种情况,类不是自己定义的,而是引入的第三方依赖中提供的
比如 学习解析XML时引入的此依赖
@Bean注解
定义在了启动类当中,不建议,我们要尽量保证启动类的纯粹性
那为什么启动类可以直接声明Bean对象呢?
因为启动类也是一个配置类
可以单独定义一个配置类 通过@Configuration声明
最后注入对象即可
3.SpringBoot原理(高频面试题!)
(1)起步依赖 maven的依赖传递
(2)自动配置
即当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,无需手动声明,简化开发,省去配置操作。
原理:(如果面试问到springboot的原理,实际上就是在问自动配置的原理)
(1)导入第三方依赖包
@SpringBootApplication具有包扫描的作用 但是扫描范围只有当前包及其子包
那怎么把第三方依赖包导入呢?
solution1:@ComponentScan组件扫描
这种方法一旦包变多 会很繁琐 需要查看很多包以及其很多类
solution2:@Import导入。使用@Import导入的类会被Spring加载到IOC容器中,成为IOC容器中的Bean对象
以下是四种导入形式
1.
2.
3.
返回全类型 将哪些类交给IOC容器管理
4.第三方依赖自己来指定 √
(2)源码跟踪剖析
是不是自动装备的类全部注册为IOC容器的Bean?
No!! 有个注解是@ConditionalOnMissingBean 意思是达到条件才会注册装配
(3)@Conditional
(4)自定义starter
为什么有了起步依赖还要自定义呢?
因为在实际开发中,有很多的第三方技术,不是所有的第三方技术都提供了与springboot整合的starter起步依赖,但这些技术又非常通用,在很多项目中都在使用,比如阿里云OSS对象存储服务。所以,为了方便起见,经常会定义一些公共组件,提供给各个项目团队使用。而在springboot项目中,一般会将这些公共组件封装为springboot的starter(起步依赖)
怎么区分springboot官方提供的还是其他提供?
如果项目中没有找到.iml文件怎么处理?
找到starter的全路径之后cmd输入命令mvn idea:module即可
1.创建maven模块 starter依赖管理 autoConfigure自动配置
aliyun-oss-spring-boot-starter仅保存pom.xml和.iml文件
aliyun-oss-spring-boot-autoconfigure保存pom.xml和.iml文件和src目录
2.在starter中引入autoconfigure
3.在autoconfigure引入阿里云oss的相关依赖
4.将工具类复制到autoconfigure这边
因为无需扫描将这两个类的@Component注解删掉 同时注入@A..也不能用了,就会有以下的解决方法
5.将阿里云OSSUtils声明成了IOC容器中的bean 要想使用直接注入工具类即可
6.加入getter\setter方法 避免有空指针
7.在autoConfigure中需要用enable注解来声明把aliyunproperties交到容器中管理,才能从配置文件aliyunoss中读取到封装使用
8.这份文件是要定义在内路径下的
在resources目录下 创建一级目录
复制文件名在上面创建的目录下创建文件
4.Web后端总结
十五、Maven高级
1.分模块设计与开发
拆分为若干个子模块,方便项目的管理维护,组件复用
无需创建springboot项目因为仅仅存放的是实体类 创建Maven项目
将对应的类移动到这个项目中,再在组件中引入外部依赖
2.继承与聚合
继承
子工程继承父工程中的配置信息,各个子工程共有的依赖可以直接定义在父工程中,比如lombok依赖,为了简化依赖配置、统一管理依赖 通过<parent>标签指定父工程坐标
springboot项目都有一个统一的父工程 spring-boot-starter-parent
在maven中一个工程只能继承一个父工程(单继承)
所以在使所有子工程的都是父工程时,也要把父工程的父工程配置成spring-boot-starter-parent
1.创建maven模块tlias-parent,该工程为父工程,设置打包方式pom(如果不设置默认jar包)
常见的打包方式:
设置打包方式:
2.在子工程的pom.xml文件中配置继承关系
最后的标签指定父工程的pom.xml文件
3.在父工程中配置各个工程共有的依赖(子工程会自动继承父工程的依赖)
版本锁定
在maven中,可以在父工程的pom文件中通过<dependencyManagement>来统一管理依赖版本
并不会直接加入,在子工程中如果需要用到,则加入以下代码(就是无需版本号)修改版本号直接在父工程修改即可
自定义属性/引用属性
依赖零散处理,方便集中管理维护
聚合
将多个模块组织成一个整体,同时进行项目的构建
聚合工程 一个不具有业务功能的“空”工程(有且仅有一个pom文件)=》父工程就是
对聚合工程进行打包,那所有聚合的模块都会被打包
或者是安装模块到本地仓库,直接在聚合工程上install安装操作,那么所有的模块都会安装到本地仓库
作用: 快速的构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)
maven中可以通过<modules>标签设置当前聚合工程所包含的子模块名称
对聚合工程进行指令 会对所有子模块进行
3.私服
同一公司两个项目组之间通过私服进行资源的共享
是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题
查找顺序:本地仓库-》私服-》中央仓库
学会如何上传到私服和从私服中下载依赖即可
只要不是snapshot都会默认上传到release仓库
1.在maven中配置访问私服的用户名和密码(身份凭证)
手动找到maven的配置文件地址settings.xml
2.指定私服仓库的url地址
3.在maven中配置私服的url地址
仓库组就是将若干个仓库划到一个仓库组当中
4.从私服中下载jar包到本地仓库即可使用
直接在pom.xml导入
发布直接运行deploy命令