黑马程序员JavaWeb开发教程,实现javaweb企业开发全流程(涵盖Spring+MyBatis+SpringMVC+SpringBoot等)

来源:Web开发-导学课程_哔哩哔哩_bilibili

注意

有*和--,有注意点详看 

蓝色注意,黄色更注意

导学

Web开发-介绍

访问地址,向前端服务器发送请求,响应基础页面(无数据)

在请求后端服务器,后端服务器访问数据库服务器,返回数据,填充到基础页面。

此课程所学内容有:

 浏览器

 所有的内核,都会符合一个标准Web标准

前端课程安排

 超文本标记语言

由浏览器来解析出来。 

javaScript

跨平台、面向对象的脚本语言。脚本语言的意思就是写完的代码可以直接浏览器解析使用,而不需要编译。

js核心内容如下

函数:相当于java的方法

两个核心对象 : bom与dom

js事件监听:例如点击事件

vscode插件

Day01-04. HTML-VSCode安装_哔哩哔哩_bilibili

有md文档

h5 css js文档

w3school 在线教程

JS

 运算符== 与===

 ==会转换后在进行比较。

对象

主要分为三类:

Array String JSON 都是基础对象,bom是对浏览器操作的对象,dom文档对象模型 。

h5的一个标签,都是封为了一个dom对象,例如:<a>

自定义对象

js中自定义对象

 以前有错误理解,以为这就是json对象。其实这是自定义js对象。

json对象

json对象

 json是通过javascript对象标记法书写的文本,他的本质是文本,以键值对形式存在,key必须由双引号包围。

数字类型直接写数字 20 , 数组类型直接写 【】,字符串类型用" ",详情如下。

基础概念

示例:理解parse和stringify方法

 bom对象

bom 主要封为5个对象

java程序员 需了解window对象(窗口对象)和location对象(地址栏对象)

 window对象

 alert就是调用了window对象的方法。确认取消按钮提示框也是window对象的confirm方法。

 location对象

 可以获取,也可以设置地址。

dom对象

 事件监听挂钩,可以操作dom

复习一下:document是整个文档对象,element是元素对象例如每个标签,attribute是属性标签例如href,text是文本对象 例如传智教育。

操作dom

 返回值是对象,如果是数组就是[XXX]  要认识。

 获取后如何操作dom对象

查文档

HTML DOM Element 对象

 1

 2

3

js事件监听

 

js中事件绑定的两种方式

 常见事件

 监听事件案例

 聚焦与失去焦点进行大小写转换

Vue 

 免除原生js操作dom

生命周期 

mounted挂在完成后,发送请求,加载数据。 

插值表达式 

 插值表达式可以是以上几种形式。

常用指令

v-on是为标签绑定事件的 也就是@

 Ajax介绍

 

就是XMLHttpRequest的简写,代表所有的异步请求 

Axios介绍 

对原生ajax进行了封装 

axios的使用

请求方式别名 

 response.data.data

  response.data是返回的整个json数据

一般情况,我们只需要拿到json中的data的数组数据,所以要书写为 response.data.data

前后端分离开发 

API接口管理平台 

YApi-高效、易用、功能强大的可视化接口管理平台

Yapi不好用就用apifox 

前端工程化 

 操作流程

工程化开发依赖环境Node.js就相当于java程序依赖jdk 

 资料中有md的安装文档

Nodejs需要配置变量(前端要以管理员运行)

 1.安装Nodejs,配置变量 (在md文档中)

2.npm install -g @vue/cli  需要联网,安装脚手架

执行vue --version可以查看是否安装成功

3. 安装镜像

Vue创建

 有两种方式。

 目录结构

 修改端口号  8080->7000

在vue.config文件中 

 Vue组件库Element

 Element - The world's most popular Vue UI framework

快速入门 

 1. npm install element-ui@2.15.3

2.Element - The world's most popular Vue UI framework

有步骤,在main.js中导入element-ui

import ElementUI from 'element-ui';

import 'element-ui/lib/theme-chalk/index.css';

Vue.use(ElementUI);

组件中的细节 

log后显示[Object object]时,怎么办?

object时一个js对象,这时只需要将他转化为字符串即可

JSON.stringify(object)  就可以输出出来了。 

当组件中的属性不清楚时,可以查看文档,拉到最下面查看。

Element案例 

 主页面访问

 实在App.vue中

1.引入emp-view组件

布局页面 

在组件库中,有对应的布局组件(布局容器),找到相应的。 

继续添加表格表单组件。

完成组件实现

vue中使用axios 异步加载

 

yarn add axios

性别展示

拿到信息后,性别是数字,而不是男女

去element找到复杂的表格

 

scope.row拿到了这一行的数据,此处需要拿到gender字段 

使用三元运算符来展示男女 

图片展示 

需要展示的是图片,而此处却是地址 。

解决方法同理,使用插槽。

 优化细节

以前没边框

只需给aside添加border: 1px solid #eee

Vue路由

 

hash对应不同组件。

 介绍

原理 

 使用方式

  1.安装

yarn add vue-router@3.5.1 

2.定义路由表

两种导入方式都可以。

 3.添加路由出口

4.添加展示位置

优化细节 

路由首次访问时,会是默认为 /

让他重定向到 dept   http://localhost:8080/#/dept

不配置会找不到/,就会显示空白页。 

打包部署 **

1.打包 

npm run build

yarn run build 

打包后,就会出现dist文件。

2.部署

 

 nginx要放在没中文没空格的目录下。

 注意

 

 操作步骤

将dist下的文件,复制到html文件下。

 运行nginx.exe。查看任务管理器-详细信息,查看是否启动成功

如果没成功,操作如下

 没成功,查看log文件夹下的日志。

80端口被占用了,所以没启动成功 

 

4是进程id 

去任务管理器,查看是谁占用

 解决方法

1.停止进程

2.改端口号 

 

启动成功后,直接访问localhost:端口号  即可

Maven介绍

 是一款构建工具

不用手动导入jar包文件,直接就可以联网下载。

统一目录结构。

跨平台

进行编译

 在target目录下生成字节码文件

 Maven概述

 介绍

顺序:想查看本地仓库是否存在,再查找私服,如果没有,去中央仓库下载(国外很慢),下载到私服。

我们配置的镜像,就是远程仓库阿里云的私服,就会很快。

仓库: 

 在仓库中找到对应位置

 

Maven安装 

 

 springboot自带,也要自己配置仓库。

操作步骤

   配置本地仓库存放位置

 配阿里云私服

 配置环境变量

 配置完成后,在任意目录下,都可以运行maven指令。

测试是否安装成功 

 

输入mvn -v.

注意:maven的运行是依赖jdk的,采用的是jdk11版本。 

maven-idea集成配置使用

配置Maven环境 

 操作步骤

 项目结构下

 setting下

jre版本 

字节码版本

 idea创建Maven项目

 坐标

 idea导入Maven项目

 

操作步骤

 方法1-------------------------------:

打开文件资源管理器,就是文件位置。 

 

将项目粘贴进来。

选择新导入进来项目02的pom文件。

 方法2-------------------------------: 

 

 

在选择pom文件,即可。

依赖配置

 依赖

我们仓库中,有这个依赖时,就会有提示信息。

如果没有提示信息,可以到网址查看。

mvnrepository.com 

 

依赖传递 

 

查看依赖传递的方法

 排除依赖

A依赖B ,B依赖JAR。如果A不需要JAR,就可以将其断掉。 

 案例

因为依赖传递,A项目中是依赖junit的。

 

已无junit依赖。 

依赖范围 

 

 Maven生命周期

 

只需关注5个阶段

 运行install时,clean是 不会运行的。因为需要在同一套声明周期中。

使用方式

 如果没有target文件夹,可以这样操作。

执行clean后,会清除以前的字节码文件,target文件就会删除。

执行 package 后,就会打成jar包,install命令会把jar包导入到本地仓库中。别的模块需要用到此模块,只需导入对应的坐标依赖即可。

总结 

Web入门

SpringBootWeb入门 

jdk与springboot版本一定要对应上。 

新建模块。 

启动springboot项目时报错

报错 

java: 无法访问org.springframework.web.bind.annotation.RequestMapping
  错误的类文件: /D:/A_develop/maven/maven-repository/org/springframework/spring-web/6.0.18/spring-web-6.0.18.jar!/org/springframework/web/bind/annotation/RequestMapping.class
    类文件具有错误的版本 61.0, 应为 55.0
    请删除该文件或确保该文件位于正确的类路径子目录中。 

就是springboot与jdk版本不对应。

jdk11   springboot 2.7.17. 

web入门-http协议-概述

登录时请求,与进入管理模块时的请求无法共享数据,所以就不知道当前用户是否登录。java已经通过别的方式实现解决了。 

 http协议-请求协议

请求行就是第一行,请求体只有post请求有,get是拼接。

请求头常见参数

 Content-Type是参数的数据类型。json和http形式字符串

 

 

响应协议

  

常见状态码

 

 协议解析

 

响应数据,浏览器都自带解析工具。

Tomcat这些服务器提供了http协议的解析工具。 

 Tomcat介绍

 

 

 Tomcat基本使用

 

常见问题

  冲突也可以修改Tomcat的端口号

 注意

 Tomcat的部署

SpringBootWeb-入门程序解析

 starts 是起步依赖 

 

创建项目后,pom中的Web依赖,因为依赖传递,所以内嵌了Tomcat 

内嵌了Tomcat。 原理是循环依赖,就是依赖传递

请求响应-概述

 

Tomcat无法识别我们写的java,Controller代码,但是Tomcat可以识别javaEE规范中的技术,servlet ,Tomcat也是servlet容器。SpringBoot的DispatcherServlet继承于Servlet。

httpServletRequest与httpServletResponse两个对象负责 获取 和 响应 规定(符合http协议)的数据 。

 请求响应-请求

 

postman软件的使用。

 简单参数

  

 原始方式

代码:在git中A1

package com.neuedu.controller;

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
@RestController
public class simpleParam {
    @RequestMapping("/simpleParam")
    public String simpleParam(HttpServletRequest httpServletRequest){
        String name = httpServletRequest.getParameter("name");
        String ageStr = httpServletRequest.getParameter("age");
        int age = Integer.parseInt(ageStr);
        System.out.println("name:"+name+"   age:"+age);
        return "ok";
    }
}

 springboot方式


post请求,参数在请求体中携带,所以选择body 

对于简单参数,不管get还是post请求,只要传输与接收的参数名字对应上 就可以解析成功。 

 参数名不一致的情况

需要用到RequestParam注解。 

注意

required 默认为true,如果不传会报错。

控制台会输出:Required request parameter 'name' for method parameter type String is not present] 

总结 

实体参数

代码在git 

 简单实体参数

 复杂实体参数

 address.city的形式传参

 

 数组集合参数

参数名要保持一致 

参数名要一样。数组和集合区分,加RequestParam注解。

  数组参数

 例如复选框,可以选多个值。

sout(hobby)  ->   [Ljava.lang.String;@4c284867

Arrays.toString(hobby) 输出数组  ->[java, C, PHP]

 集合参数

多个参数名一样,默认会封成数组,所以要加@RequestParam来进行区分 

 总结

日期参数 

因为日期参数多种多样,所以要在服务端声明格式类型 

 json参数

 json参数是要放在请求体中传输的

键名与对象属性名要一致。json格式要求key必须"" 。

路径参数

 参数总结

 请求响应

 

RestController = Controller + ResponseBody 

他会先将数据转换为json,再响应回去。

统一处理 

 

不同的接口,返回的数据都不同。例如字符串、json格式数据、json数组 等等,这不方便前端统一管理维护。

 

统一封装起来,方便维护。 

总结 

响应数据-案例

 

 

类路径指的就是当前的resources路径。 

前端页面路径是 localhost:8080/emp.html 就行   static路径不用加,因为他就是用来存放静态资源的。

完成结果:

 技巧***---------------------

动态加载路径 

 利用类加载器获取文件。

分层解耦-三层架构

 

细节基础知识:因为dao的数据,有数据库查出来的,有别的接口返回的,有xml解析出来的,不固定,所以要面向接口编程。

类似,为了增加灵活性,service层也要创建接口。

面向接口编程 

 分层解耦***----------概念理解

 

private EmpService empService = new EmpServiceImplA();

这个过程已经耦合了,如果重新写了一个 EmpServiceImplB,这时候Controller中就需要重新new出B,service改动了,controller也就会发生改动,这就是耦合。

不再去new对象,实现解耦。

把实现类对象交给容器来管理起来    控制反转(不在new,交给容器)

类型相同的可以直接拿到用     依赖注入(拿来直接用)

IOC&DI入门

 

当去掉new 对象时,程序会报空指针异常,因为没有赋值。这时候就需要交给容器来管理,成为Bean对象,加上Component注解。 AutoWired实现注入。

细节基本知识

 接口的实现类有A 和 B。如果想用B,就在B上添加Component注解,把B交给容器管理。

 

IOC详解***

component多用于工具类中。controller、service、repository都对component进行了封装。

 

 

  

value属性可以指定名字。

 

 DI详解*** 

当实现类A和B,都加上Service注解,都交给容器管理时。再自动装配时,就会报错 

 

Autowired默认是按照 数据类型装配的。 

解决方案 3种

Autowired是spring提供的,Resource是jdk提供的,而且默认是名字装配。 

 总结

MySql课程简介

Mysql概述-安装

参考网盘资料

Day06-02. MySQL-概述-安装配置_哔哩哔哩_bilibili 

 

mysql- 数据模型&SQL介绍

data文件夹存放的就是MySQL数据库的数据。

 

 

 

数据库的操作

 

 DDL-数据库操作

  

 select database()   查看当前正在使用的数据库。

 

show schema s   原来有s+s。

 DDL-图形化操作

在资料中有操作文档。 

idea集成的也可以使用。 

DDL-表结构操作-创建 

 

DDL-表结构操作-数据类型*

 定义无符号,需要在类型后方添加 unsigned。

 根据格式来选择合适的类型。

时间类型,根据需求,根据格式,来选择合适的。 

对于金钱,要求精准,就需要decimal类型。

price decimal(8, 2) not null comment '套餐价格',

DDL-表结构操作-创建***------

 代码

CREATE TABLE `tb_emp` (
  `id` int NOT NULL AUTO_INCREMENT,
  `column_name` varchar(20) NOT NULL COMMENT '用户名',
  `password` varchar(32) DEFAULT '123456' COMMENT '密码',
  `name` varchar(10) DEFAULT NULL COMMENT '员工姓名',
  `gender` tinyint unsigned NOT NULL COMMENT '性别 1男 2女',
  `image` varchar(300) DEFAULT NULL COMMENT '图像url',
  `job` tinyint unsigned DEFAULT NULL COMMENT '职位 1班主任 2讲师 3 学工主管 4教研主管',
  `entrydate` date DEFAULT NULL COMMENT '入职日期',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `update_time` datetime NOT NULL COMMENT '修改时间',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COMMENT='员工表';

注意

 性别gender 用 tinyint类型 ,且可以添加unsigned 无符号。有很多可以添加无符号

 DDL-表结构操作-查询

 

DML-添加数据-insert

 

 DML-更新数据-update

 

 DML-删除数据-delete

 

总结

 DQL-基本查询

 

 DQL-条件查询* 

__表示查询2个字的员工(两个杠) 

 DQL-聚合函数*

聚合函数不对null进行运算 。聚合函数是对数据纵向进行处理 。

推荐使用count(*),数据库底层对*有优化处理。 

DQL-分组查询*

分组后,查询的字段,一般为聚合函数和分组字段,其他字段无意义。

having是groupby 的条件,就是分组后的过滤条件。

where不能对聚合函数进行判断。

DQL-排序查询

 

DQL-分页查询

 

DQL-案例*

案例1

 

 案例2

  

 if函数,gengder查询出来的数据是 1 和 2 

参数1条件,参数2 为true的数据,参数3 为false的数据

案例3 

 

运用到 case when 语法 。最后end结束 

 

 总结

 

多表设计1对多

多表设计1对多 外键*

 

场景在线

 部门表和员工表。

部门有 1 2 3 4,员工去关联部门,当直接删除部门为1的行数据,此时员工里关联部门为1的数据依然存在。此时数据就会有矛盾,就会不完整。  此时只是我们逻辑上进行关联,物理上并没有关联。

所以外键的作用就是保持,数据的一致性。有了外键后,当员工有关联部门=1的,此时再删除部门为1的数据,就会删除失败。


推荐使用逻辑外键,就是删除外键,在代码层面上进行查询。 

 多表设计1对1 多对多*

真是开发中,物理外键基本不用

1对1

将大表拆分为两种小表

将用户表,拆分为  基本信息表   和    身份信息表。提高效率(场景:基本信息查询很多,但是身份信息查询就比较少,此时就需要进行拆分提高效率。)

此时外键还加了unique唯一约束,这样 就不会有重复值,保证1对1

 多对多

 

多对多时,就需要借助第三张表来处理。例如选课,师生。

 

第三张表中,创建2个外键来分别关联两张表的主键。 

多表设计案例 关系分析

 

多表设计案例 表结构

-- 瑞吉点餐页面原型 , 设计表结构
-- 分类表
create table category(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(20) not null unique comment '分类名称',
    type tinyint unsigned not null comment '类型 1 菜品分类 2 套餐分类',
    sort tinyint unsigned not null comment '顺序',
    status tinyint unsigned not null default 0 comment '状态 0 禁用,1 启用',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '更新时间'
) comment '菜品及套餐分类' ;

-- 菜品表
create table dish(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(20) not null unique comment '菜品名称',
    category_id int unsigned not null comment '菜品分类ID',
    price decimal(8, 2) not null comment '菜品价格',
    image varchar(300) not null comment '菜品图片',
    description varchar(200) comment '描述信息',
    status tinyint unsigned not null default 0 comment '状态, 0 停售 1 起售',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '更新时间'
) comment '菜品';

-- 套餐表
create table setmeal(
    id int unsigned primary key auto_increment comment '主键ID',
    name varchar(20) not null unique comment '套餐名称',
    category_id int unsigned not null comment '分类id',
    price decimal(8, 2) not null comment '套餐价格',
    image varchar(300) not null comment '图片',
    description varchar(200) comment '描述信息',
    status tinyint unsigned not null default 0 comment '状态 0:停用 1:启用',
    create_time datetime not null comment '创建时间',
    update_time datetime not null comment '更新时间'
)comment '套餐' ;


-- 套餐菜品关联表
create table setmeal_dish(
    id int unsigned primary key auto_increment comment '主键ID',
    setmeal_id int unsigned not null comment '套餐id ',
    dish_id int unsigned not null comment '菜品id',
    copies tinyint unsigned not null comment '份数'
)comment '套餐菜品关系';


 unsigned   tinyint   unique  decimal(8, 2)8位2位小数  datetime  的使用。

总结

MYSQL-多表查询-内连接

 查询出交集的数据,没有关联的数据,不会查出来,例如 员工表中部门为null的数据。部门表中还没有null。

MYSQL-多表查询-外连接*

可以查询出基表的所有数据。因为有时候需要全部数据。 

 这时候使用内连接,就会查不到如下数据。

查询出基表的数据 & 交集的数据

MYSQL-多表查询-子查询(标量、列)

 标量子查询

  

 

列子查询

 

MYSQL-多表查询-子查询(行、表)*

行子查询 

 

 表子查询

 没事看看

 

MYSQL-多表查询-案例1和2***

没有特殊的说明,就使用内连接 。详看5,自己想复杂了。

技巧 

使用工具,将表结构关系列出来。写语句前,先进行分析用什么表,什么关联,在书写。 


 

代码:

-- 1. 查询价格低于 10元 的菜品的名称 、价格 及其 菜品的分类名称 .
-- 表:dish , category
select d.name 名称, d.price 价格, c.name 分类名称
from dish d
inner join category c
on c.id = d.category_id
where d.price < 10;

-- 2. 查询所有价格在 10元(含)到50元(含)之间 且 状态为'起售'的菜品名称、价格 及其 菜品的分类名称 (即使菜品没有分类 , 也需要将菜品查询出来).
-- 表:dish , category
select d.name 名称, d.price 价格, c.name 分类名称
from dish d
left join category c
on c.id = d.category_id
where d.price between 10 and 50 and d.status = 1;

-- 3. 查询每个分类下最贵的菜品, 展示出分类的名称、最贵的菜品的价格 .
-- 表:dish , category
select (case category_id when 1 then '酒水饮料' when 2 then '传统主食'
                           when 3 then '人气套餐'when 4 then '商务套餐'
                           when 5 then '经典川菜'when 6 then '新鲜时蔬'else '汤类' end) 分类名称
,max(price) 价格
from dish
group by category_id
having max(price);

-- 4. 查询各个分类下 状态为 '起售' , 并且 该分类下菜品总数量大于等于3 的 分类名称 .
select (case d.category_id when 1 then '酒水饮料' when 2 then '传统主食'
                         when 3 then '人气套餐'when 4 then '商务套餐'
                         when 5 then '经典川菜'when 6 then '新鲜时蔬'else '汤类' end) 分类名称
, count(*)
from dish d
join category c on c.id = d.category_id
where c.status = 1
group by d.category_id
having count(*) >= 3;

-- 5. 查询出 "商务套餐A" 中包含了哪些菜品 (展示出套餐名称、价格, 包含的菜品名称、价格、份数).
-- 表:dish , setmeal , setmeal_dish
-- 方法1想复杂了
-- (1.1)先查询出套餐的基本配置(套餐名称、菜品id、价格、份数)
select sd.dish_id , s.name , s.price , sd.copies from setmeal s
join setmeal_dish sd
on s.id = sd.setmeal_id
where s.name = '商务套餐A';
-- (1.2)将上表作为基表,在与菜品进行关联,将dish_id转换为汉字
select d.name 菜品名称 , 套餐名称 ,价格 , 份数 from (select sd.dish_id , s.name as '套餐名称', s.price as '价格', sd.copies as '份数'from setmeal s
join setmeal_dish sd
on s.id = sd.setmeal_id
where s.name = '商务套餐A') as jb
left join dish d
on jb.dish_id = d.id;
-- 方法2(视频方法)***
select s.name , s.price , d.name , d.price , sd.copies
from dish d , setmeal_dish sd , setmeal s
where d.id = sd.dish_id and  sd.setmeal_id = s.id
and s.name = '商务套餐A';


-- 6. 查询出低于菜品平均价格的菜品信息 (展示出菜品名称、菜品价格).
-- 表:dish
select avg(price)
from dish;

select d.name , d.price
from dish d
where d.price < (select avg(price)
                 from dish);


 总结:

MYSQL-事务-介绍与操作*

 

 事务控制

代码如下:(事务相对隔离

-- 开启事务
start transaction ;

-- 删除部门
delete from tb_dept where id = 3;

-- 删除部门下的员工

delete from tb_emp where dept_id = 3;

-- 提交事务
commit ;

-- 回滚事务
rollback ;

select * from tb_dept;
select * from tb_emp;

 这整个控制台也相当于一个事务。当没有提交前,执行下面两个查询,数据已经伪删除

 打开新的控制台,查看数据

数据依然存在。 

如果执行提交,将真正进行执行,进行删除。如果一条语句成功一条失败,则可以进行回滚恢复。

MYSQL-事务-四大特性*

MYSQL-索引-介 绍*

原理 

 索引就相当于一本书的目录。

 优缺点*

他是一种数据结构,这种二叉树查询很快。

没有索引时:就是直接插入即可。

存在索引时:就需要将插入的数据维护进二叉树这个数据结构中,保持平衡(插入、修改、删除,同理)。

MYSQL-索引 -结构*

因为大数据情况下,二叉搜索树和红黑树,层级深,不利于查询。 

结构

理解**(图片)

最终形成的是一个矮胖的树。第三层为叶子节点(存储真诚数据),前两层是非叶子节点(存储索引数据)。

MYSQL-索引-操作语法 ****

-- 创建 : 为tb_emp表的name字段建立一个索引 .
create index idx_emp_name on tb_emp(name);

-- 查询 : 查询 tb_emp 表的索引信息 .
show index from tb_emp;

-- 删除: 删除 tb_emp 表中name字段的索引 .
drop index idx_emp_name on tb_emp;

 注意

创建主键,默认就会创建主键索引 ,主键索引时性能最高的。

字段添加唯一约束时 unique,也会创建唯一索引。

总结:

MyBatis-入门-课程介绍

MyBatis-入门-快速入门程序

准备工作 

 代码:

#驱动类名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#数据库连接的url
spring.datasource.url=jdbc:mysql://localhost:3306/web02
#连接数据库的用户名
spring.datasource.username=root
#连接数据库的密码
spring.datasource.password=root


package com.neuedu.mapper;

import com.neuedu.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper//在运行时,会自动生成该接口的实现类对象(代理对象),并且将该对象交给IOC容器管理
public interface UserMapper {

    //查询全部信息
    @Select("select * from user")
    public List<User> list();
}

MyBatis-入门-配置SQL提示*

配置sql提示

MyBatis-入门-JDBC

  

在使用spring.datasource数据源时,mybatis会把jdbc这种连接对象封装到数据库连接池中,使用的时候直接拿,就不需要向jdbc还需要手动释放了。

 只需关注两点

  

MyBatis-入门-数据库连接池

 

 

springboot默认的 时 Hikari追光者 

 如何切换连接池

 只需要引入druid的起步依赖,配置的连接信息不用动,都是统一的。当然也可以使用德鲁伊的方式

<dependency>
   <groupId>com.alibaba</groupId>
   <artifactId>druid-spring-boot-starter</artifactId>
   <version>1.2.8</version>
</dependency>

 总结:

自己写一个数据库连接池,就需要实现接口DataSource。

MyBatis-入门-lombok工具包

 

 

lombok依赖

<dependency>
	<groupId>org.projectlombok</groupId>
	<artifactId>lombok</artifactId>
</dependency>


Mybatis-基础操作-环境准备*

 

 

注意点 

 数据库                java                的                对应关系

 date                  LocalDate

datetime             LocalDateTime

LocalDate类型对应数据表中的date类型

 LocalDateTime类型对应数据表中的datetime类型

Mybatis-基础操作-删除 (--基于注解方式--)

 当只有一个参数时,#{}中的形参可以随便写,因为只有这1个参数,放在这里。

代码

package com.itheima.mapper;
import org.apache.ibatis.annotations.*;

/*@Mapper注解:表示当前接口为mybatis中的Mapper接口
  程序运行时会自动创建接口的实现类对象(代理对象),并交给Spring的IOC容器管理
*/
@Mapper
public interface EmpMapper {

    //根据id删除数据
    @Delete("delete from emp where id = #{id}")
    public int delById(Integer id);

}
package com.itheima;

import com.itheima.mapper.EmpMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class SpringbootMybatisCrudApplicationTests {

    @Autowired //从Spring的IOC容器中,获取类型是EmpMapper的对象并注入
    EmpMapper empMapper;

    @Test
    public void test123_1(){
        int i = empMapper.delById(17);
        System.out.println(i);
    }

}

package com.itheima.pojo;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDate;
import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp {
    private Integer id;
    private String username;
    private String password;
    private String name;
    private Short gender;   //1男 2 女
    private String image;
    private Short job;   //1班主任 2 讲师 3 学工
    private LocalDate entrydate; // 入职日期  年月日   LocalDate类型对应数据表中的date类型
    private Integer deptId;
    private LocalDateTime createTime;  //创建日期 年月日 时分秒   LocalDateTime类型对应数据表中的datetime类型
    private LocalDateTime updateTime;
}

Mybatis-基础操作-删除(预编译)*

 

 添加日志myabtis

#指定mybatis输出日志的位置, 输出控制台
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

 

Mybatis-基础操作-新增

 

#{}中的值,是属性名,小驼峰和实体类中的一样。不是数据库_的形式。 

代码: 

//新增员工
    @Insert("insert into emp (username, name, gender, image, job, entrydate, dept_id, create_time, update_time) " +
            "values (#{username}, #{name}, #{gender}, #{image}, #{job}, #{entrydate}, #{deptId}, #{createTime}, #{updateTime})")
    public void insertOne(Emp emp);
@Test
    public void test125_1(){//测试添加数据
        //创建员工对象
        Emp emp = new Emp();
        emp.setUsername("tom");
        emp.setName("汤姆");
        emp.setImage("1.jpg");
        emp.setGender((short)1);
        emp.setJob((short)1);
        emp.setEntrydate(LocalDate.of(2000,1,1));
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(1);
        empMapper.insertOne(emp);
    }

Mybatis-基础操作-新增(主键返回)*

  特定业务场景,添加完数据后,主键要返回回来。

 例如这里的套餐,先创建套餐信息,在添加套餐与餐品关联表(这里需要用到套餐id)

操作:添加Options注解

//会自动将生成的主键值,赋值给emp对象的id属性
    @Options(useGeneratedKeys = true,keyProperty = "id") 


代码:

执行完添加,直接就可以拿到id了。 

Mybatis-基础操作-更新

 代码

//更新操作
    @Update("update emp set username=#{username}, name=#{name}, gender=#{gender}, image=#{image}," +
            " job=#{job}, entrydate=#{entrydate}, dept_id=#{deptId}, update_time=#{updateTime} " +
            "where id=#{id}")
    public void updateOne(Emp emp);
 @Test
    public void test127_1(){//测试更新数据
        Emp emp = new Emp();
        emp.setId(18);
        emp.setUsername("songdaxia");
        emp.setPassword(null);
        emp.setName("老宋");
        emp.setImage("2.jpg");
        emp.setGender((short)1);
        emp.setJob((short)2);
        emp.setEntrydate(LocalDate.of(2012,1,1));
        emp.setCreateTime(null);
        emp.setUpdateTime(LocalDateTime.now());
        emp.setDeptId(2);
        empMapper.updateOne(emp);
    }

Mybatis-基础操作-查询(根据ID)*

 

因为名称不一致导致没有自动封装成功。 

 数据展示(后几个属性没有封装成功)

Emp(id=1, username=jinyong, password=123456, name=金庸, gender=1, image=1.jpg, job=4, entrydate=2000-01-01, deptId=null, createTime=null, updateTime=null)


解决方案 

//查询操作
    @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, " +
            "create_time, update_time from emp where id=#{id}")
    public Emp getById(Integer id);

    //方法1:取别名,让别名与实体类属性一致
//    @Select("select id, username, password, name, gender, image, job, entrydate, " +
//            "dept_id AS deptId, create_time AS createTime, update_time AS updateTime " +
//            "from emp " +
//            "where id=#{id}")
//    public Emp getById(Integer id);

    //方法2:手动映射封装
//    @Results({
//            @Result(column = "dept_id", property = "deptId"),
//            @Result(column = "create_time", property = "createTime"),
//            @Result(column = "update_time", property = "updateTime")
//    })
//    @Select("select id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time from emp where id=#{id}")
//    public Emp getById(Integer id);

    //方法3:开启mybatis的驼峰命名自动映射开关--->在配置文件中配置
//    mybatis.configuration.map-underscore-to-camel-case=true

总结 3种方式

 _&驼峰映射

 # 在application.properties中添加:
mybatis.configuration.map-underscore-to-camel-case=true

Mybatis-基础操作-查询(条件查询)**

 

字符串中是不能用#{}的,但是可以用${}. 

${}和#{}  拼接与预编译

推荐方式

 @param

 在Springboot1.x版本中,编译时,不会编译形参,形参是以var1,var2形式存在这样与sql语句中的#{参数} 对应不上,所以要使用param注解。

在springboot2.x版本中,有内置编译的插件,就可以解决这个问题。

代码

    //条件查询
    //方式1:使用${}存在注入风险
//    @Select("select * FROM emp " +
//            "where name like '%${name}%'" +
//            "and gender = #{gender} " +
//            "and entrydate between #{begin} and #{end} " +
//            "order by update_time desc")
//    public List<Emp> list(String name , Short gender , LocalDate begin , LocalDate end);

    //方式2:使用mysql的concat()函数 拼接
    @Select("select * FROM emp " +
            "where name like concat('%',#{name},'%') " +
            "and gender = #{gender} " +
            "and entrydate between #{begin} and #{end} " +
            "order by update_time desc")
    public List<Emp> list(String name , Short gender , LocalDate begin , LocalDate end);
@Test
    public void test129_1(){//条件查询
//        empMapper.list("张",1,"2000-01-01","2024-01-01");
        List<Emp> list = empMapper.list("张", (short) 1, LocalDate.of(2000, 1, 1), LocalDate.of(2024, 1, 1));
        list.stream().forEach(e->{
            System.out.println(e);
        });

入职日期是LocalDate类型,所以要用LocalDate.of静态方法来设置值,不能用字符串。 

Mybatis-基础操作-XML映射文件(--基于XML方式--)**

resultType:单条记录所封装的类型 

遵守3条规范

注解就不用担心,因为执行注解那,语句与方法都在一起。

xml,需要找到EmpMaaper接口的list的方法。规范就是建立连接。 

 注意

在resources下创建包,要以 / 分隔,如果以.分隔,在文件中 会变成一个目录。


代码

//方式3:使用xml
    public List<Emp> list(String name , Short gender , LocalDate begin , LocalDate end);
<?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.itheima.mapper.EmpMapper">
<!--    resultType:单条记录所封装的类型-->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select * from emp
        where name like concat('%',#{name},'%')
          and gender = #{gender}
          and entrydate between #{begin} and #{end}
        order by update_time desc
    </select>
</mapper>

 上面的约定直接从官网复制。xml要遵守3条规范

    @Test
    public void test129_1(){//条件查询
//        empMapper.list("张",1,"2000-01-01","2024-01-01");
        List<Emp> list = empMapper.list("张", (short) 1, LocalDate.of(2000, 1, 1), LocalDate.of(2024, 1, 1));
        list.stream().forEach(e->{
            System.out.println(e);
        });

    }

Mybatis-基础操作-动态SQL-if*

 where标签可以去除and/or

为什么使用if标签***

 一行数据10个字段,如果只封装3个字段,那么其他字段就会是null,这样直接修改的时候,就会将其他字段修改为null。


 

 

 <where>标签,可以去除多余的and/or,并且where中有条件成立,才会添加where,无满足条件的就不会动态渲染where。

 代码

<?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.itheima.mapper.EmpMapper">
    <!--    resultType:单条记录所封装的类型-->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select *
        from emp
        <where>
            <if test="name != null">
                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>
//方式3:使用xml
    public List<Emp> list(String name , Short gender , LocalDate begin , LocalDate end);
    @Test
    public void test129_1(){//条件查询
//        List<Emp> list = empMapper.list("张", (short) 1, LocalDate.of(2000, 1, 1), LocalDate.of(2024, 1, 1));
        List<Emp> list = empMapper.list("张", (short) 1, null,null);
        System.out.println(list);

    }

Mybatis-基础操作-动态SQL-if - 案例

遇到语法错误,找near。框架报错,从后网前看。

set标签可以去除, 

总结

 Mybatis-基础操作-动态SQL-foreach*****

foreach标签一般用于批量操作。 

批量删除

 出现了一个小问题

删除时,永远删除的都是id为 0 1 2  的数据。

原因:

写成index 了,正确时item。 

Mybatis-基础操作-动态SQL-include

  

sql中出现重复代码。 

抽离出来,方便复用。

 

 

代码 

 <sql id="commonSelect">
        id, username, password, name, gender, image, job, entrydate, dept_id, create_time, update_time
    </sql>

    <!--    resultType:单条记录所封装的类型-->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select
        <include refid="commonSelect"/>
        from emp
        <where>
            <if test="name != null">
                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>

总结 

 

案例-准备工作

 就是员工和部门的增删改查。

 基础操作

 

 

 

知识*********************

log记录日志

//创建log对象,记录日志   或者使用 @slf4j 注解
    private static Logger log = LoggerFactory.getLogger(DeptController.class);

或者在类上添加@Slf4j注解

log.info("添加部门:{}"+dept);

要查看参数,要添加{}占位。 要不然不会输出出来。


@RequestParam注解

@RequestParam(defaultValue = "1")  设置默认值

package com.neuedu.controller;

import com.neuedu.pojo.Emp;
import com.neuedu.pojo.PageBean;
import com.neuedu.pojo.Result;
import com.neuedu.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.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDate;
import java.util.List;

@RestController
@RequestMapping("/emps")
@Slf4j
public class EmpController {
    @Autowired
    EmpService empService;

    @GetMapping
    public Result listEmp(@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:{}",page,pageSize,name,gender,begin,end);
        PageBean pageBean = empService.listEmp(page, pageSize,name,gender,begin,end);
        return Result.success(pageBean);
    }
}

参数判断

name不填写时,传进来的是“”空字符串,这样的话,在xml中就需要 双层判断 !=null & !='' 


案例-部门管理-查询

查看页面原型

查看接口文档

自己敲代码,代码在git中保存了。tlias-web-management模块。

apifox接口测试,也完成了。

案例部门管理-前后端联调

 直接访问http://localhost:90/,查看接口是否返回数据成功。

案例-部门管理-删除

 

案例-部门管理-更新

问题

代码

### Error updating database.  Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'create_time = '2024-04-06 16:22:17'
            
            
                up' at line 5
### The error may exist in com/neuedu/mapper/DeptMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: update dept          SET name = ?                                           create_time = ?                                           update_time = ?           WHERE id = ?
### Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'create_time = '2024-04-06 16:22:17'
            
            
                up' at line 5
; bad SQL grammar []; nested exception is java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'create_time = '2024-04-06 16:22:17'
            
            
                up' at line 5] with root cause

java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'create_time = '2024-04-06 16:22:17'
            
            
                up' at line 5

 

以前没加 , 


案例-部门管理-新增

问题

Data truncation: Data too long for column 'name' at row 1     

数据库varchar太小


 总结**

优化 

 抽离出来。

 案例-员工管理-分页查询-分析

看接口文档与页面原型

总记录数可能有点多,所以实体中定义Long类型

package com.neuedu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class PageBean {
    private Long total;
    private List rows;
}

属性 与接口文档中对应

案例-员工管理-分页查询-实现

手搓分页 

PageHelper、@RequestParam(defaultValue = "1")  设置默认值

代码在git,已经自己手写完毕

案例-员工管理-分页查询插件-PageHelper**

 Mybatis插件分页

先加载依赖 

两步走:1.设置分页参数。2.封装结果集

分页插件,会自动执行count(*) 和 limit。

分页插件,已经有封装的Page对象,数据要放在Page中。

所以我们只需要正常的执行查询就行。

代码

package com.neuedu.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.neuedu.mapper.EmpMapper;
import com.neuedu.pojo.Emp;
import com.neuedu.pojo.PageBean;
import com.neuedu.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
    EmpMapper empMapper;
    @Override
    public PageBean listEmp(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
//        无分页插件时
//        Integer start = (page - 1) * pageSize;
//        List<Emp> emps = empMapper.listEmp(start, pageSize);
//        Long total = this.listCount();
//        return new PageBean(total,emps);

        //使用PageHelper
        PageHelper.startPage(page,pageSize);
        List<Emp> emps = empMapper.listEmp(page, pageSize,name,gender,begin,end);
        Page<Emp> p = (Page<Emp>) emps;
        return new PageBean(p.getTotal(),p.getResult());
    }

    @Override
    public Long listCount() {
        return empMapper.listCount();
    }
}
<?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.neuedu.mapper.EmpMapper">
<!--    select * from emp limit #{start},#{pageSize};    无分页插件时-->
    <select id="listEmp" resultType="com.neuedu.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>

 name不填写时,传进来的是“”空字符串,这样的话,在xml中就需要 双层判断 !=null & !=''

案例-员工管理-删除员工

  

 

@DeleteMapping("/{ids}")
    public Result delEmp(@PathVariable List<Integer> ids){
        log.info("删除员工:{}",ids);
        empService.delEmp(ids);
        return Result.success();
    }

 批量删除。

 案例-员工管理-新增员工

 

查看需求文档与页面原型开发

<insert id="addEmp">
        insert into emp (username,name,gender,image,dept_id,entrydate,job,create_time,update_time)
        values (#{username},#{name},#{gender},#{image},#{deptId},#{entrydate},#{job},#{createTime},#{updateTime})
    </insert>
@PostMapping
    public Result addEmp(@RequestBody Emp emp){
        log.info("新增员工:{}",emp);
        empService.addEmp(emp);
        return Result.success();
    }

 

数据库密码有默认约束为123456,所以代码上不用设置值为123456. 

案例-文件上传-简介***

前端操作

 前端页面3要素

post请求,传输大小比较大,type类型是file,enctype是multipart/form-data(提交方式)是上传大的二进制文件。

enctype属性**

 1.正常是application/x-www-form-urlencoded

他的解析传输形式

2.文件形式multipart/form-data 

他的传输形式,会携带名称类型内容。

可以采用火狐浏览器查看。此处用的谷歌,没有看到内容(谷歌对数据进行了抓包包装,已经看不到了)。

火狐效果:

只有文本文件才能看到这个文字效果。图片视频什么的,看到的是二进制乱码。


 

服务端操作

 

 操作

 提交

 进入断点

 image下有个localtion,直接复制值。

C:\Users\ayu\AppData\Local\Temp\tomcat.8080.14935340758480073979\work\Tomcat\localhost\ROOT 

在本地打开

就会有3个临时文件,正好就是传过来的3个参数,相对应。 

只要请求响应完毕,临时文件就会消失,所以接下来,就要对临时文件进行处理。

 

案例-文件上传-本地存储***

@PostMapping("/upload")
    public Result upload(String username , Integer age , MultipartFile image) throws IOException {
        log.info("文件上传:{},{},{}",username,age,image);
        //转存的文件信息,都在MultipartFile image对象中
        //获取原始文件名
        String originalFilename = image.getOriginalFilename();
        //将文件存储在服务器的磁盘目录中E:uploadfile heima
        image.transferTo(new File("E:\\uploadfile\\heima\\"+originalFilename));

        return Result.success();
    }

转存的文件信息,都在MultipartFile image对象中 

存在问题:文件名

相同的文件名上传会进行覆盖。


接下来用uuid来确定唯一性。

新出问题

 上传文件超过1M

 

配置一下即可解决。

#配置单个文件上传大小限制  记住multipart
spring.servlet.multipart.max-file-size=10MB
#配置单个请求最大大小的限制(1次请求中是可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

本地存储最终代码 

@PostMapping("/upload")
    public Result upload(String username , Integer age , MultipartFile image) throws IOException {
        log.info("文件上传:{},{},{}",username,age,image);
        //转存的文件信息,都在MultipartFile image对象中
        //获取原始文件名
        String originalFilename = image.getOriginalFilename();

        //构建新的文件名  uuid+原文件尾椎
        String fileName = this.createFileName(originalFilename);

        //将文件存储在服务器的磁盘目录中E:uploadfile heima
        image.transferTo(new File("E:\\uploadfile\\heima\\"+fileName));

        return Result.success();
    }

    public String createFileName(String originalFilename){
        //生成uuid并去除'-'
        String uuid = UUID.randomUUID().toString().replace("-","");
        //获取文件.的索引
        int index = originalFilename.lastIndexOf(".");
        //截取.索引以后的内容
        String suffix = originalFilename.substring(index);
        return uuid+suffix;
    }

如果上传多个文件,就和批量删除一个道理,用集合来接收,在进行操作。 

 常用方法**

 

存储在本地有很多弊端,前端也无法直接访问资源,基本不会存储在本地。可以选择存储在云服务器上,百度云阿里云......

案例-文件上传-阿里云OSS-准备

 基础流程

 

案例-文件上传-阿里云OSS-入门***

安装OSS Java SDK_对象存储(OSS)-阿里云帮助中心

参考官方文档

这些代码需要改动,剩下的就是核心代码了(不需要改动)

sdk地址,就是开发工具包,java代码

 上传文件、图片、视频等资源到OSS_对象存储(OSS)-阿里云帮助中心

 endpoint

进入创建的bucket -概览-往下找

accessKeyId&accessKeySecret 

在创建的时候需要保存。 

 bucketName

自己创建的bucket 

sdk新旧有差异

视频中的,旧版

String accessKeyId = "LTAI4GCH1vX6DKqJWxd6nEuW";
String accessKeySecret = "yBshYweHOpqDuhCArrVHwIiBKpyqSL";

是直接暴露的。

package com.itheima;

import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
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";
        // 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
        String accessKeyId = "LTAI4GCH1vX6DKqJWxd6nEuW";
        String accessKeySecret = "yBshYweHOpqDuhCArrVHwIiBKpyqSL";
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "web-tlias";

        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "1.jpg";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件流。
        String filePath= "C:\\Users\\Administrator\\Pictures\\Camera Roll\\1.jpg";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObject请求。
            ossClient.putObject(bucketName, objectName, inputStream);
        } 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();
            }
        }
    }
}

新版

需要配置环境变量,和jdk一样。

package com.neuedu;

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.File;

//https://help.aliyun.com/zh/oss/user-guide/simple-upload?spm=a2c4g.11186623.0.0.34f25168FSwzPj
public class Demo {

    public static void main(String[] args) throws Exception {
        // Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。
        String endpoint = "https://oss-cn-beijing.aliyuncs.com";
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Bucket名称,例如examplebucket。
        String bucketName = "web-hmgzy";
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = "1.png";
        // 填写本地文件的完整路径,例如D:\\localpath\\examplefile.txt。
        // 如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
        String filePath= "E:\\uploadfile\\heima\\1.png";

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);

        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(filePath));
            // 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。
            // ObjectMetadata metadata = new ObjectMetadata();
            // metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());
            // metadata.setObjectAcl(CannedAccessControlList.Private);
            // putObjectRequest.setMetadata(metadata);

            // 上传文件。
            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();
            }
        }
    }
}


 运行,查看 上传成功

上传的文件,会分配地址,直接浏览器就可以访问。 

 案例-文件上传-阿里云OSS-集成**

代码 

package com.neuedu.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyuncs.exceptions.ClientException;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;

/**
 * 阿里云 OSS 工具类
 */
@Component  //交给IOC容器管理
public class AliOSSUtils {

    private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
//    private String accessKeyId = "LTAI4GCH1vX6DKqJWxd6nEuW";
//    private String accessKeySecret = "yBshYweHOpqDuhCArrVHwIiBKpyqSL";
    private String bucketName = "web-hmgzy";

    /**
     * 实现上传图片到OSS
     */
    public String upload(MultipartFile file) throws Exception {
        // 获取上传的文件的输入流
        InputStream inputStream = file.getInputStream();

        // 避免文件覆盖
        String originalFilename = file.getOriginalFilename();
        String fileName = UUID.randomUUID().toString().replace("-","") + originalFilename.substring(originalFilename.lastIndexOf("."));

        //上传文件到 OSS
        //采用配置变量的形式,获取accessKeyId&accessKeySecret
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
        ossClient.putObject(bucketName, fileName, inputStream);

        //文件访问路径
        String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
        // 关闭ossClient
        ossClient.shutdown();
        return url;// 把上传到oss的路径返回
    }

}
package com.neuedu.controller;

import com.aliyuncs.exceptions.ClientException;
import com.neuedu.pojo.Result;
import com.neuedu.util.AliOSSUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.IOException;
import java.util.UUID;

@RestController
@Slf4j
public class UploadController {

    //本地存储
//    @PostMapping("/upload")
//    public Result upload(String username , Integer age , MultipartFile image) throws IOException {
//        log.info("文件上传:{},{},{}",username,age,image);
//        //转存的文件信息,都在MultipartFile image对象中
//        //获取原始文件名
//        String originalFilename = image.getOriginalFilename();
//
//        //构建新的文件名  uuid+原文件尾椎
//        String fileName = this.createFileName(originalFilename);
//
//        //将文件存储在服务器的磁盘目录中E:uploadfile heima
//        image.transferTo(new File("E:\\uploadfile\\heima\\"+fileName));
//
//        return Result.success();
//    }
//
//    public String createFileName(String originalFilename){
//        //生成uuid并去除'-'
//        String uuid = UUID.randomUUID().toString().replace("-","");
//        //获取文件.的索引
//        int index = originalFilename.lastIndexOf(".");
//        //截取.索引以后的内容
//        String suffix = originalFilename.substring(index);
//        return uuid+suffix;
//    }

    @Autowired
    AliOSSUtils aliOSSUtils;
    @PostMapping("/upload")
    public Result upload(MultipartFile image) throws Exception {
        log.info("文件上传,文件名:{}",image.getOriginalFilename());
        //调用aliyun工具类进行文件上传
        String url = aliOSSUtils.upload(image);
        log.info("文件上传完成,url:{}",url);
        return Result.success(url);
    }

}

案例-修改员工-查询回显&修改员工

看页面原型与接口文档。

@GetMapping("/{id}")
    public Result getOne(@PathVariable Integer id){
        log.info("查询单条信息:{}",id);
        Emp emp = empService.getOne(id);
        return Result.success(emp);
    }

    @PutMapping
    public Result updateEmp(@RequestBody Emp emp){
        log.info("修改数据:{}",emp);
        empService.updateEmp(emp);
        return Result.success();
    }

 update操作,要修改更新时间。

<update id="updateEmp">
        update emp
        <set>
            <if test="username !=null and username != ''">
                username = #{username},
            </if>
            <if test="name != null and name != '' ">
                name = #{name},
            </if>
            <if test="gender != null">
                gender = #{gender},
            </if>
            <if test="entrydate != null">
                entrydate = #{entrydate},
            </if>
            <if test="deptId != null">
                dept_id = #{deptId},
            </if>
            <if test="job != null">
                job = #{job},
            </if>
        </set>
        <where>
            id = #{id}
        </where>
    </update>

案例-配置文件-参数配置化***

 解决办法

技巧alt+鼠标左键,一起操作 

 

已经注入成功 

总结

 

案例-配置文件-yml配置文件

 

案例-配置文件-@ConfigurationProperties*

 

创建一个新的实体来保存 配置的属性。再将此新实体注入到所需的地方,调用get方法获取值。

代码

 yml配置文件,配置参数

aliyun:
  oss:
    endpoint: https://oss-cn-beijing.aliyuncs.com
    bucketName: web-hmgzy

新建实体封装

package com.neuedu.util;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

@Component
@Data
@ConfigurationProperties(value = "aliyun.oss")
public class AliyunProperties {
//    private String accessKeyId = "LTAI4GCH1vX6DKqJWxd6nEuW";
//    private String accessKeySecret = "yBshYweHOpqDuhCArrVHwIiBKpyqSL";
    private String endpoint;
    private String bucketName;
}

使用

package com.neuedu.util;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.common.auth.CredentialsProviderFactory;
import com.aliyun.oss.common.auth.EnvironmentVariableCredentialsProvider;
import com.aliyuncs.exceptions.ClientException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.util.UUID;

/**
 * 阿里云 OSS 工具类
 */
@Component  //交给IOC容器管理
public class AliOSSUtils {
    //原始写法
//    private String endpoint = "https://oss-cn-beijing.aliyuncs.com";
//    private String accessKeyId = "LTAI4GCH1vX6DKqJWxd6nEuW";
//    private String accessKeySecret = "yBshYweHOpqDuhCArrVHwIiBKpyqSL";
//    private String bucketName = "web-hmgzy";

    //配置文件 注入
//    @Value("${aliyun.oss.endpoint}")
//    private String endpoint;
//    @Value("${aliyun.oss.bucketName}")
//    private String bucketName;

    //再次优化  使用@ConfigurationProperties注解,实体封装参数,利用get方法获得。
    @Autowired
    AliyunProperties aliyunProperties;
    /**
     * 实现上传图片到OSS
     */
    public String upload(MultipartFile file) throws Exception {
        //获取阿里云OSS参数
        String endpoint = aliyunProperties.getEndpoint();
        String bucketName = aliyunProperties.getBucketName();

        // 获取上传的文件的输入流
        InputStream inputStream = file.getInputStream();

        // 避免文件覆盖
        String originalFilename = file.getOriginalFilename();
        String fileName = UUID.randomUUID().toString().replace("-","") + originalFilename.substring(originalFilename.lastIndexOf("."));

        //上传文件到 OSS
        //采用配置变量的形式,获取accessKeyId&accessKeySecret
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
        ossClient.putObject(bucketName, fileName, inputStream);

        //文件访问路径
        String url = endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + fileName;
        // 关闭ossClient
        ossClient.shutdown();
        return url;// 把上传到oss的路径返回
    }

}


 在实体中,会出现这个,并不影响程序的运行。

他只是需要引入依赖,就会出现相应的提示

 这项依赖的作用,就是输入的时候有提示。

总结

属性少用value。

基础登录功能

根据需求接口文档编写登录。

package com.neuedu.controller;


import com.neuedu.pojo.Emp;
import com.neuedu.pojo.Result;
import com.neuedu.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@Slf4j
public class UserController {
    @Autowired
    UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("登录emp:{}",emp);
        Emp e = userService.login(emp);
        return e != null?Result.success():Result.error("用户名或密码错误");
    }
}

--------理解------- 

登录校验-概述*

http协议是无状态的,意思是,每次请求都是相对独立的。

 登录成功后,会有登录标记,每次请求都去统一拦截,判断是否有标记,才可放行。

登录认证-登录校验-会话技术*

一次会话可以包含多次请求,直到浏览器或服务器 一方关闭,会话结束。

进入浏览器访问域名,这时候就建立会话了。

3次会话,3个浏览器。

会话跟踪

判断请求是否来自同一个浏览器,这样方便共享数据。

例如:

验证码展示,向服务端发送请求

当输入验证码登录时,又是一次请求,这就涉及到会话跟踪,进行共享数据。

 会话跟踪技术

 因为http是无状态,每次请求相对独立,无法携带状态,这也保证了,他的效率高。那怎么携带状态?就需要用到会话跟踪技术。

会话跟踪方案对比-Cookie 注重理解**

Cookie (Headers) - HTTP 中文开发手册 - 开发者手册 - 腾讯云开发者社区-腾讯云

服务器端在给客户端在响应数据的时候,会自动的将 cookie 响应给浏览器,浏览器接收到响应回来的 cookie 之后,会自动的将 cookie 的值存储在浏览器本地。接下来在后续的每一次请求当中,都会将浏览器本地所存储的 cookie 自动地携带到服务端。

 

访问服务端后,服务端把cookie相应给浏览器,存储在浏览器。

以后的请求中, cookie会跟随请求携带到服务端。

 代码

package com.neuedu.controller;

import com.neuedu.pojo.Result;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@RestController
@Slf4j
public class SessionController {
    @GetMapping("/c1")
    public Result cookie1(HttpServletResponse response){
        response.addCookie(new Cookie("user_name","itg"));
        return Result.success();
    }

    @GetMapping("c2")
    public Result cookie2(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        for (Cookie cookie : cookies){
            if (cookie.getName().equals("user_name")){
                System.out.println("user_name"+cookie.getValue());
            }
        }
        return Result.success();
    }
}

 访问  http://localhost:8080/c1

 

在响应头头中,可以看到服务器响应的cookie。 Set-Cookie是设置cookie

访问 http://localhost:8080/c2

 请求头内容,  cookie随请求携带。

 优缺点

  

 cookie跨域了,就不能使用了。

会话跟踪方案对比-Session 注重理解**

session的底层,就是基于cookie实现的。 

 如果我们现在要基于 Session 来进行会话跟踪,浏览器在第一次请求服务器的时候,我们就可以直接在服务器当中来获取到会话对象Session。如果是第一次请求Session ,会话对象是不存在的,这个时候服务器会自动的创建一个会话对象Session 。而每一个会话对象Session ,它都有一个ID(示意图中Session后面括号中的1,就表示ID),我们称之为 Session 的ID。

Set-Cookie的名字JSESSIONID 是固定的,传输的是 session的id。

接下来,服务器端在给浏览器响应数据的时候,它会将 Session 的 ID 通过 Cookie 响应浏览器。其实在响应头当中增加了一个 Set-Cookie 响应头。

 

接下来,在后续的每一次请求当中,都会将 Cookie 的数据获取出来,并且携带到服务端。接下来服务器拿到JSESSIONID这个 Cookie 的也就是 Session 的ID。拿到 ID 之后,就会从众多的 Session 当中来找到当前请求对应的会话对象Session

 代码

//网session中设置值
    @GetMapping("/s1")
    public Result session1(HttpSession session){
        log.info("HttpSession-s1: {}", session.hashCode());

        session.setAttribute("loginUser", "tom"); //往session中存储数据
        return Result.success();
    }

    @GetMapping("/s2")
    public Result session2(HttpServletRequest request){
        HttpSession session = request.getSession();
        log.info("HttpSession-s2: {}", session.hashCode());

        //如果能取出来,就证明可以在1次会话中共享数据了。
        Object loginUser = session.getAttribute("loginUser"); //从session中获取数据
        log.info("loginUser: {}", loginUser);
        return Result.success(loginUser);
    }

访问 http://localhost:8080/s1 

向服务器发送请求,服务器给出响应Set-Cookie,存储session的id

已经存储在浏览器中了。

访问 http://localhost:8080/s2

浏览器又会把cookie携带到服务器,服务器进行查找对应的id,这样就可以共享数据了。

 优缺点

  

集群环境下无法直接使用session。

首次访问时,有负载均衡到第一台服务器,生产session。返回给浏览器,下次访问时,不知是哪台服务器存储的session了,这样就无法共享数据。

 会话跟踪方案对比-令牌技术 注重理解**

 本质就是一个字符串

服务端生成令牌,相应给客户端,保存在浏览器,后续每次请求都会携带令牌,服务端来校验令牌有效性。

(想象生活中,上班打卡)入职,录入人脸,每天上班打卡机打卡。

如果通过令牌技术来跟踪会话,我们就可以在浏览器发起请求。在请求登录接口的时候,如果登录成功,我就可以生成一个令牌,令牌就是用户的合法身份凭证。接下来我在响应数据的时候,我就可以直接将令牌响应给前端

接下来我们在前端程序当中接收到令牌之后,就需要将这个令牌存储起来。这个存储可以存储在 cookie 当中,也可以存储在其他的存储空间(比如:localStorage)当中。

接下来,在后续的每一次请求当中,都需要将令牌携带到服务端。携带到服务端之后,接下来我们就需要来校验令牌的有效性。如果令牌是有效的,就说明用户已经执行了登录操作,如果令牌是无效的,就说明用户之前并未执行登录操作。

此时,如果是在同一次会话的多次请求之间,我们想共享数据,我们就可以将共享的数据存储在令牌当中就可以了。

 --------理解-------

登录校验-JWT令牌-介绍

 

 

登录校验-JWT令牌-生成&校验**

 

前两个部分都是base64编码,可以解码解析出来,第三部分是算法算出来的,不能解析出来。

细节:


<!--            JWT-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>0.9.1</version>
        </dependency>

 

 代码

 生成jwt

@Test
    public void testGenJwt(){
        Map<String, Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("username","gzy");

        String jwt = Jwts.builder()
                .setClaims(claims)  //自定义内容(负载)
                .signWith(SignatureAlgorithm.HS256, "ithG")   //设置签名算法(官网查看)和自定义的签名秘钥(解析的时候用)
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000))   //过期时间(有效期) 单位:毫秒
                .compact();

        System.out.println(jwt);
    }

 秘钥要大于3位,要不然生成时会报错

这就是签名算法 

解析令牌jwt

/**
     * 解析令牌
     */
    @Test
    public void parseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("ithG")  指定签名密钥(必须保证和生成令牌时使用相同的签名密钥) 上面的itG
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJpZCI6MSwiZXhwIjoxNzEyNTc0MjYwLCJ1c2VybmFtZSI6Imd6eSJ9.M-Lp6UHFB1Hx-oEBBEvRaA-v_8Ecmbzm2dOc2rQdriU")  //jwt字符串
                .getBody();

        System.out.println(claims);
    }

exp为过期时间。id和username为传输的内容。


输出的结果就是生成的JWT令牌,,通过英文的点分割对三个部分进行分割,我们可以将生成的令牌复制一下,然后打开JWT的官网,将生成的令牌直接放在Encoded位置,此时就会自动的将令牌解析出来。  

网上的base64解码,也可以解析出来。第三部分是算法,不是编码,不可解析。

 登录校验-JWT令牌-登录下发令牌**

根据接口文档,在登录时,成功生成令牌,失败返回提示信息。

操作

 1.导入工具类

package com.neuedu.util;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import java.util.Date;
import java.util.Map;

public class JwtUtils {

    private static String signKey = "itheima";
    private static Long expire = 43200000L;

    /**
     * 生成JWT令牌
     * @param claims JWT第二部分负载 payload 中存储的内容
     * @return
     */
    public static String generateJwt(Map<String, Object> claims){
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    /**
     * 解析JWT令牌
     * @param jwt JWT令牌
     * @return JWT第二部分负载 payload 中存储的内容
     */
    public static Claims parseJWT(String jwt){
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

 2.成功生成jwt

package com.neuedu.controller;


import com.neuedu.pojo.Emp;
import com.neuedu.pojo.Result;
import com.neuedu.service.UserService;
import com.neuedu.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@Slf4j
public class UserController {
    @Autowired
    UserService userService;

    @PostMapping("/login")
    public Result login(@RequestBody Emp emp){
        log.info("登录emp:{}",emp);
        Emp e = userService.login(emp);

        //登录成功,生成令牌。
        if (e != null){
            //存储信息info
            Map<String, Object> info = new HashMap<>();
            info.put("id",e.getId());
            info.put("username",e.getUsername());
            info.put("name",e.getName());

            //调用工具类生成jwt令牌
            String jwt = JwtUtils.generateJwt(info);
            return Result.success(jwt);
        }

        //失败,返回提示信息。
        return Result.error("用户名或密码错误");
    }
}

前端会进行存储,存储在local storage中。JWT令牌存储在浏览器的本地存储空间local storage中了。 local storage是浏览器的本地存储,在移动端也是支持的。

演示

 登录成功,jwt已经返回。

 已经存储到local  storage中。

我们在发起一个查询部门数据的请求,此时我们可以看到在请求头中包含一个token(JWT令牌),后续的每一次请求当中,都会将这个令牌携带到服务端。

登录校验-filter入门* 

 

在Filter类上要添加@WebFilter注解,来配置拦截的路径

 Filter是javaWeb的三大组件,不是springboot提供的的,如果想在springboot中使用javaWe的三大组件就要在启动类上添加注解@ServletComponentScan(注解表示,当前项目是支持javaweb3大组件的)

 两步走,1.定义Filter 2.配置Filter

过滤器中有非常重要的一步,就是放行。

 代码

package com.neuedu.filter;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import java.io.IOException;

//定义一个类,实现一个标准的Filter过滤器的接口
@WebFilter(urlPatterns = "/*")
public class DemoFilter implements Filter {
    @Override //初始化方法, 只调用一次
    public void init(FilterConfig filterConfig) throws ServletException {
        System.out.println("init 初始化方法执行了-------------------------");
    }

    @Override //拦截到请求之后调用, 调用多次
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        System.out.println("Demo 拦截到了请求...放行前逻辑-------------------------");
        //放行
        chain.doFilter(request,response);
    }

    @Override //销毁方法, 只调用一次
    public void destroy() {
        System.out.println("destroy 销毁方法执行了-------------------------");
    }
}

在定义完Filter之后,Filter其实并不会生效,还需要完成Filter的配置,Filter的配置非常简单,只需要在Filter类上添加一个注解:@WebFilter,并指定属性urlPatterns,通过这个属性指定过滤器要拦截哪些请求  

package com.neuedu;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.ServletComponentScan;

@ServletComponentScan
@SpringBootApplication
public class TliasWebManagementApplication {

    public static void main(String[] args) {
        SpringApplication.run(TliasWebManagementApplication.class, args);
    }

}

 支持使用servlet组件

 登录校验-Filter详解-执行流程

 

执行流程

 请求 -  filter - 访问controller相关逻辑 - 拿到响应 - filter - 返回给浏览器

 拦截路径

 登录校验-Filter详解-过滤器链

  

 -------------重要------------

 登录校验过滤器*********

因为登录的时候,还没有令牌。 

 判断是否登录,判断令牌是否存在,判断解析是否通过.

基于上面的业务流程,我们分析出具体的操作步骤:

  1. 获取请求url

  2. 判断请求url中是否包含login,如果包含,说明是登录操作,放行

  3. 获取请求头中的令牌(token)

  4. 判断令牌是否存在,如果不存在,返回错误结果(未登录)

  5. 解析token,如果解析失败,返回错误结果(未登录)

  6. 放行

 代码:详看,盲打

<!--        转换为json字符串-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.76</version>
        </dependency>

package com.neuedu.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.neuedu.pojo.Result;
import com.neuedu.util.JwtUtils;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Slf4j
@WebFilter("/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        //强转
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resq = (HttpServletResponse) servletResponse;

        //1 获取请求url
        String url = req.getRequestURL().toString();
        log.info("请求的url:{}",url);

        //2 判断请求url中是否包含login,如果包含,说明是登录操作,放行
        boolean contain = url.contains("login");
        if (contain) {
            log.info("登录操作,放行...");
            filterChain.doFilter(req, resq);
            //放行后,不在继续往下操作。
            return;
        }

        //3 获取请求头中的令牌(token)
        String token = req.getHeader("token");

        //4 判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(token)){//spring提供的StringUtils
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");//接口文档规定的是NOT_LOGIN,返回给前端
            //在controller中,RestController会自动将对象转换为json传输。这里需要手动传输,借助阿里巴巴fastJson
            //先去引入pom,在使用。
            String notLogin = JSONObject.toJSONString(error);
            //响应给浏览器,调用Writer输出流
            resq.getWriter().write(notLogin);
            return;
        }

        //5 解析token,如果解析失败,返回错误结果(未登录)
        try {
            //ctrl + alt + t 快捷键   捕获异常
            JwtUtils.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json,阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            //响应给浏览器,调用Writer输出流
            resq.getWriter().write(notLogin);
            return;
        }


        //6 放行
        log.info("令牌合法,放行");
        filterChain.doFilter(req, resq);
    }
}

细节

1.FilterChain主要是进行放行操作的。

2.强转,因为tomcat中使用的是ServletRequest/response,传输需要时httpServletRequest/Response。

3.在controller中,RestController会自动将对象转换为json传输。这里需要手动传输,借助阿里巴巴fastJson

4.手动响应给浏览器,调用Writer输出流 resq.getWriter().write(notLogin);

5.校验解析令牌是否成功,就看是否有异常?try catch进行捕获。

5.判断String是否是空,StringUtils.hasLength(token)  spring提供的。

测试 

 

测试登录。

登录成功,返回令牌。

执行查询操作,将token放在请求头中。

最终请求成功,返回正确结果。

尝试修改令牌,测试错误token。

解析失败,返回NOT_LOGIN。

测试查询,不携带令牌。

token判断为不存在,返回NOT_LOGIN。


 -------------重要------------

登录校验-interceptor-入门**

定义拦截器,就要实现拦截器的接口,成为他,就得实现它。然后把他交给容器来管理。

@configuration 标明这是一个配置类

代码

package com.neuedu.interceptor;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {//ctrl + o 调出重写页面

    @Override//目标资源方法(controller)执行前执行。 返回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..");
    }
}

 交给IOC容器管理。

package com.neuedu.config;

import com.neuedu.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginCheckInterceptor loginCheckInterceptor;

    @Override //注册拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

实现WebMvcConfigurer接口,并重写addInterceptors方法  

将拦截器注入到配置类中,再进行注册拦截器。

细节

ctrl+o调出界面,重写方法

目标资源方法就是controller

preHandle方法,返回true,是放行,然后才会执行controller。

拦截器,拦截所以,路径是/** 两个星星。过滤器是一个星

 -------------重要------------ 

登录校验-interceptor-详解***

/*  和  /**   一级和两级路径,注意。

 

package com.neuedu.config;

import com.neuedu.interceptor.LoginCheckInterceptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    LoginCheckInterceptor loginCheckInterceptor;

    @Override //注册拦截器
    public void addInterceptors(InterceptorRegistry registry) {
        //excludePathPatterns 排除路径  .  /* 是一级,只能拦截1级的  /emp /dept, /dept/1 这样就拦截不了
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

 excludePathPatterns 排除路径 . /* 是一级,只能拦截1级的 /emp /dept, /dept/1 这样就拦截不了

执行流程 ***重要!重要!重要!

filter是tomcat的servlet组件,interceptor是spring提供的组件。filter可以拦截所有的请求,而interceptor只能拦截进去spring的请求。因为tomcat无法识别contorller的请求,所以有dispatcherServlet中央调度器,去对接controller。 

  • 当我们打开浏览器来访问部署在web服务器当中的web应用时,此时我们所定义的过滤器会拦截到这次请求。拦截到这次请求之后,它会先执行放行前的逻辑,然后再执行放行操作。而由于我们当前是基于springboot开发的,所以放行之后是进入到了spring的环境当中,也就是要来访问我们所定义的controller当中的接口方法。

  • Tomcat并不识别所编写的Controller程序,但是它识别Servlet程序,所以在Spring的Web环境中提供了一个非常核心的Servlet:DispatcherServlet(前端控制器),所有请求都会先进行到DispatcherServlet,再将请求转给Controller。

  • 当我们定义了拦截器后,会在执行Controller的方法之前,请求被拦截器拦截住。执行preHandle()方法,这个方法执行完成后需要返回一个布尔类型的值,如果返回true,就表示放行本次操作,才会继续访问controller中的方法;如果返回false,则不会放行(controller中的方法也不会执行)。

  • 在controller当中的方法执行完毕之后,再回过来执行postHandle()这个方法以及afterCompletion() 方法,然后再返回给DispatcherServlet,最终再来执行过滤器当中放行后的这一部分逻辑的逻辑。执行完毕之后,最终给浏览器响应数据。

它们之间的区别主要是两点:

  • 接口规范不同:过滤器需要实现Filter接口,而拦截器需要实现HandlerInterceptor接口。

  • 拦截范围不同:过滤器Filter会拦截所有的资源,而Interceptor只会拦截Spring环境中的资源。

 当filter和interceptor同时都开启后,执行流程和图片一样。

  -------------重要------------

 登录校验-interceptor-登录校验*

 

和filter的流程一样,只不过换成interceptor。

代码

逻辑一样,所以将filter中代码,直接复制到Interceptor的preHandle方法中,进行修改就行

true放行, false不放行

package com.neuedu.interceptor;

import com.alibaba.fastjson.JSONObject;
import com.neuedu.pojo.Result;
import com.neuedu.util.JwtUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;


@Component
@Slf4j
public class LoginCheckInterceptor implements HandlerInterceptor {//ctrl + o 调出重写页面

    @Override//目标资源方法(controller)执行前执行。 返回true:放行    返回false:不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        System.out.println("preHandle..");
        

        //1 获取请求url
        String url = req.getRequestURL().toString();
        log.info("请求的url:{}",url);

        //2 判断请求url中是否包含login,如果包含,说明是登录操作,放行
        boolean contain = url.contains("login");
        if (contain) {
            log.info("登录操作,放行...");
            //true 为放行
            return true;
        }

        //3 获取请求头中的令牌(token)
        String token = req.getHeader("token");

        //4 判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(token)){//spring提供的StringUtils
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");//接口文档规定的是NOT_LOGIN,返回给前端
            //在controller中,RestController会自动将对象转换为json传输。这里需要手动传输,借助阿里巴巴fastJson
            //先去引入pom,在使用。
            String notLogin = JSONObject.toJSONString(error);
            //响应给浏览器,调用Writer输出流
            resp.getWriter().write(notLogin);
            //令牌不存在,不能放行  返回false
            return false;
        }

        //5 解析token,如果解析失败,返回错误结果(未登录)
        try {
            //ctrl + alt + t 快捷键   捕获异常
            JwtUtils.parseJWT(token);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("解析令牌失败,返回未登录错误信息");
            Result error = Result.error("NOT_LOGIN");
            //手动转换 对象--json,阿里巴巴fastJSON
            String notLogin = JSONObject.toJSONString(error);
            //响应给浏览器,调用Writer输出流
            resp.getWriter().write(notLogin);
            //不能放行,false
            return false;
        }


        //6 放行
        log.info("令牌合法,放行");
        return true;//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..");
    }
}

@Configuration  
public class WebConfig implements WebMvcConfigurer {
    //拦截器对象
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
       //注册自定义拦截器对象
        registry.addInterceptor(loginCheckInterceptor)
                .addPathPatterns("/**")
                .excludePathPatterns("/login");
    }
}


异常处理

添加部门 

页面没有变化

进行添加部门,因为name有唯一约束,所以报错了。

所以前端页面并没有变化,而后台已经报错了。就因为前端不发识别spring给出的响应。(controller继续向上抛,抛给了spring,这是spring的处理方式。)

 再抛给全局异常处理器,进行处理返回Result(前后端规定好的对象)这样前端就可以解析出来。

@RestContorllerAdvice 意思是这是一个全局异常处理器。传输时是一个json字符串,那么Result是怎么转换的呢,是因为 注解底层 有@ResponseBody注解 + @ControllerAdvice注解。

@ExceptionHandler 表明 处理什么类型的异常。

总结

两个注解 

事务管理-事务回顾

@Transactional

spring事务管理 。出现运行时异常,事务会自动进行回滚。 

 事务管理配置文件

#spring事务管理日志
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

代码 

@Override
    public void delById(Integer id) { //删除部门和部门员工
        deptMapper.delById(id);

        //伪造异常
        int i = 1/0;

        empMapper.delByDeptId(id);
    }

 

1号部门,学工部被删除。 

员工表却没有正常删除。 

所以要添加事务,保证数据的完整性。

@Transactional
    @Override
    public void delById(Integer id) {
        deptMapper.delById(id);

        //伪造异常
        int i = 1/0;

        empMapper.delByDeptId(id);
    }

 

事务管理-事务进阶-rollbackFor***

1/0 伪造异常,他是一个运行时异常,运行时异常是可以正常回滚的。

 rollbackFor就是指定哪样的异常可以回滚。

默认情况下,只有出现运行时异常才能回滚。

设置注解属性 rollbackFor=Exception.class 表明所有异常都回滚。

操作

 手动抛出1个异常

异常下面的empMapper.xxx方法有提示,让删除。是因为上面出现异常,那么下面的代码都不会执行了。所以加个判断即可。

在service中向上抛出一个Exception编译时异常之后,由于是controller调用service,所以在controller中要有异常处理代码,此时我们选择在controller中继续把异常向上抛。

启动程序,开始测试。

 

 

部门id为1的员工,并没有删除掉。 @Transactional事务并没有回滚数据,失效了。

接下来恢复数据

使用rollbackFor来指定处理异常的范围,来解决问题

@Transactional(rollbackFor = Exception.class) //指定Exception范围,就是所有异常,都会进行处理。
    @Override
    public void delById(Integer id) throws Exception {
        deptMapper.delById(id);

        //伪造异常
//        int i = 1/0;
        //手动抛出一个异常,来伪造异常
        if (true){
            throw new  Exception("出错了...");
        }
        empMapper.delByDeptId(id);
    }

没用rollbackFor,以前使用的1/0操作时,因为是运行时异常,所以好使,可以回滚。注解的作用范围默认是运行时异常。

 @Transactional(rollbackFor = Exception.class)

重启服务,测试

事务生效了,并没有删除掉,执行了回滚操作。


事务管理-事务进阶-propagation***

插件 :控制台过滤插件

Grep Console 可以对控制台日志实行高亮。

查看事务管理的日志


 主要内容

 

 主需要关注两个

案例需求

 下面这个是有问题的,想一想???

insert的事务是默认值,默认值是有则加入,无则创建。他有啊,所以就会加入到delete的事务中。所以delete方法里的代码都共用一个事务。虽然记录日志的代码在finally中,会执行,因为共用1个事务,try中代码出现异常,那么整个事务中的代码都会回滚,所以日志也会被回滚掉。 

将insert事务设置为REQUIRES_NEW ,意思是需要一个新的事物,无论调用方是否有事务,他都会创建1个新事物。所以当delete方法中出现异常,并不会影响insert的提交。

 以为异常,所以进行了回滚。

数据库已没有插进数据,也给回滚了。

@Transactional(propagation = Propagation.REQUIRES_NEW) 给insert加入传播行为xxxnew

 

挂起:将当前正在执行的事务暂停执行,直接执行新创建的事务

数据加入成功

代码

 DeptServiceImpl

 /**
     * 删除部门
     * @param id
     */
    @Transactional(rollbackFor = Exception.class) //指定Exception范围,就是所有异常,都会进行处理。
    @Override
    public void delById(Integer id) throws Exception {
        try {
            deptMapper.delById(id);

            //伪造异常
//        int i = 1/0;
            //手动抛出一个异常,来伪造异常
            if (true){
                throw new  Exception("出错了...");
            }
            empMapper.delByDeptId(id);
        } finally {
            DeptLog log = new DeptLog();
            log.setCreateTime(LocalDateTime.now());
            log.setDescription("执行了解散部门的操作,此次解散的是"+id+"号部门");
            logService.insert(log);
        }
    }

DeptLogServiceImpl

package com.neuedu.service.impl;

import com.neuedu.mapper.DeptLogMapper;
import com.neuedu.pojo.DeptLog;
import com.neuedu.service.DeptLogService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class DeptLogServiceImpl implements DeptLogService {

    @Autowired
    private DeptLogMapper deptLogMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    @Override
    public void insert(DeptLog deptLog) {
        deptLogMapper.insert(deptLog);
    }
}

总结

AOP基础-快速入门

概述 

 

粉色:模版方法

 快速入门

 

@component交给spring容器管理,@Aspect 切面AOP类 , @Around 指定特定方法根据业务编程。 参数里的 两个 * 分别是 类/接口 和 方法。

代码

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Slf4j
public class TimeAspect {

    @Around("execution(* com.itheima.service.*.*(..))") //切入点表达式
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        //1.记录开始时间
        long startTime = System.currentTimeMillis();
        //2.调用原始方法运行
        Object result = proceedingJoinPoint.proceed(); //原始方式也会有返回值,要返回回去
        //3.记录结束时间,计算方法耗时时间
        long endTime = System.currentTimeMillis();
        //proceedingJoinPoint.getSignature()拿到方法的签名
        log.info(proceedingJoinPoint.getSignature()+"方法执行耗时:{}ms",endTime-startTime);

        return result;
    }

}

 proceedingJoinPoint.getSignature()拿到方法的签名

proceedingJoinPoint.proceed() 调用原始方法运行

 切入点表达式,先了解一下即可,后续详说。

使用场景

  • 事务管理:我们前面所讲解的Spring事务管理,底层其实也是通过AOP来实现的,只要添加@Transactional注解之后,AOP程序自动会在原始方法运行前先来开启事务,在原始方法运行完毕之后提交或回滚事务

 优势

  • 代码无侵入:没有修改原始的业务方法,就已经对原始的业务方法进行了功能的增强或者是功能的改变

  • 减少了重复代码

  • 提高开发效率

  • 维护方便

AOP基础-核心概念***

1. 连接点:JoinPoint可以被AOP控制的方法(暗含方法执行时的相关信息)

连接点指的是可以被aop控制的方法。例如:入门程序当中所有的业务方法都是可以被aop控制的方法

2. 通知:Advice,指哪些重复的逻辑,也就是共性功能(最终体现为一个方法)

在入门程序中是需要统计各个业务方法的执行耗时的,此时我们就需要在这些业务方法运行开始之前,先记录这个方法运行的开始时间,在每一个业务方法运行结束的时候,再来记录这个方法运行的结束时间。

但是在AOP面向切面编程当中,我们只需要将这部分重复的代码逻辑抽取出来单独定义。抽取出来的这一部分重复的逻辑,也就是共性的功能。

 

共性的内容,要执行的逻辑,封为1个方法。

3. 切入点:PointCut匹配连接点的条件,通知仅会在切入点方法执行时被应用

在通知当中,我们所定义的共性功能到底要应用在哪些方法上?此时就涉及到了切入点pointcut概念。切入点指的是匹配连接点的条件。通知仅会在切入点方法运行时才会被应用。

在aop的开发当中,我们通常会通过一个切入点表达式来描述切入点(后面会有详解)。

 

 假如:切入点表达式改为DeptServiceImpl.list(),此时就代表仅仅只有list这一个方法是切入点。只有list()方法在运行的时候才会应用通知。

4. 切面:Aspect,描述通知与切入点的对应关系(通知+切入点

当通知和切入点结合在一起,就形成了一个切面。通过切面就能够描述当前aop程序需要针对于哪个原始方法,在什么时候执行什么样的操作。

这个方法统称为切面 通知+切入点 

切面所在的类,我们一般称为切面类(被@Aspect注解标识的类)

5. 目标对象:Target通知所应用的对象

目标对象指的就是通知所应用的对象,我们就称之为目标对象。

 执行流程

 执行流程。找到目标对象,根据切面代码逻辑,去生成代理对象,这个代理对象具备目标对象的内容,在内容上下添加切面的逻辑。最终把代理对象注入到controller中。

 Spring的AOP底层是基于动态代理技术来实现的,也就是说在程序运行的时候,会自动的基于动态代理技术为目标对象生成一个对应的代理对象。在代理对象当中就会对目标对象当中的原始方法进行功能的增强。

测试

 访问部门管理。

controller中注入的是CGLIB代理对象。

代理对象开始,有个代理类,将切面中的代码进行封装。这都是自动的,只是我们看不见

执行完毕,返回结果。


 一但使用aop开发,那么注意,注入的对象不是原来的对象,而是代理对象。

AOP进阶-通知类型***

5种通知类型&@Pointcut注解

 

 @AfterReturning   @AfterThrowing  互斥。一个正常执行,一个异常执行。

 环绕通知比较特殊,是ProceedingJoinPoint类型,需自己调用proceed方法来执行。

而且环绕通知必须指定返回值类型。

代码

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect1 {
    //前置通知
    @Before("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))") //第一个*是返回值 代码任意
    public void before(JoinPoint joinPoint){
        log.info("before ...");
    }

    //环绕通知
    @Around("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

 环绕和前置 通知执行了。

返回后通知,后置,环绕  通知 都执行了。         异常后通知没执行。

伪造异常 

 

 异常通知执行,返回后通知没执行   互斥。

抽取接入点

execution(* com.itheima.service.impl.DeptServiceImpl.*(..))  抽取出来复用。 

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect1 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void pd(){}

    //前置通知
    @Before("pd()") //第一个*是返回值 代码任意
    public void before(JoinPoint joinPoint){
        log.info("before ...");
    }

    //环绕通知
    @Around("pd()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();

        //原始方法如果执行时有异常,环绕通知中的后置代码不会在执行了

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("pd()")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("pd()")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("pd()")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

@Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
public void pd(){}

如果是public修饰,那么别的切面类也可以使用pd()。


总结

 

AOP进阶-通知顺序

插件细节-过滤*


 内容

 

和类名字母顺序排序有关。

 @Order

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(1)
@Slf4j
@Component
@Aspect
public class MyAspect4 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.*(..))")
    public void pd(){}

    //前置通知
    @Before("pd()") //第一个*是返回值 代码任意
    public void before(JoinPoint joinPoint){
        log.info("before4 ...");
    }

    //后置通知
    @After("pd()")
    public void after(JoinPoint joinPoint){
        log.info("after4 ...");
    }

}

 AOP进阶-切入点表达式execution

 

类似模糊查询匹配。

切入点表达式示例
  • 省略方法的修饰符号

    execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
  • 使用*代替返回值类型

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))
  • 使用*代替包名(一层包使用一个*

    execution(* com.itheima.*.*.DeptServiceImpl.delete(java.lang.Integer))
  • 使用..省略包名

    execution(* com..DeptServiceImpl.delete(java.lang.Integer))    
  • 使用*代替类名

    execution(* com..*.delete(java.lang.Integer))   
  • 使用*代替方法名

    execution(* com..*.*(java.lang.Integer))   
  • 使用 * 代替参数

    execution(* com.itheima.service.impl.DeptServiceImpl.delete(*))
  • 使用..省略参数

    execution(* com..*.*(..))
注意事项:
  • 根据业务需要,可以使用 且(&&)、或(||)、非(!) 来组合比较复杂的切入点表达式。

    execution(* com.itheima.service.DeptService.list(..)) || execution(* com.itheima.service.DeptService.delete(..))
 事例

 

 使用切入点表达式,通配list 和 delete 方法。

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect5 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.list(..)) ||" +
            "execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    public void pp(){}

    @Before("pp()")
    public void testListAndDel(JoinPoint joinPoint){
        log.info(joinPoint.getSignature()+"切面执行...");
    }

}

 尽量基于接口描述,而不是实现类,增强扩展性。

使用 || 可以写多个表达式。 

 总结

 AOP进阶-切入点表达式-@annotation**

基于注解的方式来描述。可以自定义注解来实现。

代码 

package com.itheima.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)  //作用目标->方法上
@Retention(RetentionPolicy.RUNTIME) //作用范围 运行时
public @interface MyZDY {
}

自定义注解。 

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Slf4j
@Component
@Aspect
public class MyAspect5 {

    @Pointcut("execution(* com.itheima.service.impl.DeptServiceImpl.list(..)) ||" +
            "execution(* com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")
    public void pp(){}

//    @Before("pp()")
    @Before("@annotation(com.itheima.aop.MyZDY)")   //添加MyZDY注解的,就会生效
    public void testListAndDel(JoinPoint joinPoint){
        log.info(joinPoint.getSignature()+"切面执行...");
    }

}

 AOP切面类。

@MyZDY
    @Override
    public List<Dept> list() {
//        int i = 1/0;
        List<Dept> deptList = deptMapper.list();
        return deptList;
    }

    @MyZDY
    @Override
    public void delete(Integer id) {
        //1. 删除部门
        deptMapper.delete(id);
    }

ServiceImpl实现类,添加注解,要aop编程的方法。

总结

 

注解比较灵活,但是需要自定义注解。当不好匹配时,可以使用注解。 

AOP进阶-连接点**

 

 代码

package com.itheima.aop;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 测试连接点
 */
@Slf4j
@Component
@Aspect
public class MyAspect7 {
    @Pointcut("@annotation(com.itheima.aop.MyZDY)")
    private void pt(){}

    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
    }

    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);

        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);

        //获取方法执行时需要的参数
        Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));

        //执行原始方法
        Object returnValue = pjp.proceed();

        return returnValue;
    }

}

before通知,拿不到参数,因为主要逻辑代码还没有执行。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

 AOP案例-记录操作日志********

 

 

代码

<!--        AOP-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>

 引入aop依赖

-- 操作日志表
create table operate_log(
    id int unsigned primary key auto_increment comment 'ID',
    operate_user int unsigned comment '操作人ID',
    operate_time datetime comment '操作时间',
    class_name varchar(100) comment '操作的类名',
    method_name varchar(100) comment '操作的方法名',
    method_params varchar(1000) comment '方法参数',
    return_value varchar(2000) comment '返回值',
    cost_time bigint comment '方法执行耗时, 单位:ms'
) comment '操作日志表';



建日志表

package com.neuedu.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class OperateLog {
    private Integer id; //ID
    private Integer operateUser; //操作人ID
    private LocalDateTime operateTime; //操作时间
    private String className; //操作类名
    private String methodName; //操作方法名
    private String methodParams; //操作方法参数
    private String returnValue; //操作方法返回值
    private Long costTime; //操作耗时
}

建实体

package com.neuedu.mapper;

import com.neuedu.pojo.OperateLog;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface OperateLogMapper {

    //插入日志数据
    @Insert("insert into operate_log (operate_user, operate_time, class_name, method_name, method_params, return_value, cost_time) " +
            "values (#{operateUser}, #{operateTime}, #{className}, #{methodName}, #{methodParams}, #{returnValue}, #{costTime});")
    public void insert(OperateLog log);

}

记录日志的插入语句。

package com.neuedu.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.METHOD)     //作用的范围
@Retention(RetentionPolicy.RUNTIME)   //什么时候有效
public @interface Log { //自定义日志注解

}

自定义注解

package com.neuedu.aop;

import com.alibaba.fastjson.JSONObject;
import com.neuedu.mapper.OperateLogMapper;
import com.neuedu.pojo.OperateLog;
import com.neuedu.util.JwtUtils;
import io.jsonwebtoken.Claims;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.util.Arrays;

@Component
@Aspect
public class LogAspect {
    @Autowired  //注入操作日志mapper
    OperateLogMapper mapper;
    @Autowired
    HttpServletRequest request;

    @Around("@annotation(com.neuedu.aop.Log)")
    public Object insertLog(ProceedingJoinPoint pjp) throws Throwable {
        OperateLog operateLog = new OperateLog();
        //记录开始时间
        long start = System.currentTimeMillis();

        //操作放行
        Object result = pjp.proceed();

        //记录结束时间
        long end = System.currentTimeMillis();

        //操作人id
        String jwt = request.getHeader("token");
        Claims claims = JwtUtils.parseJWT(jwt);//解析令牌内容
        Integer userId = (Integer) claims.get("id");
        operateLog.setOperateUser(userId);

        //操作时间
        operateLog.setOperateTime(LocalDateTime.now());
        //操作类名
        operateLog.setClassName(pjp.getTarget().getClass().getName());
        //操作方法名
        operateLog.setMethodName(pjp.getSignature().getName());
        //操作方法参数
        operateLog.setMethodParams(Arrays.toString(pjp.getArgs()));
        //操作方法返回值
        operateLog.setReturnValue(JSONObject.toJSONString(result));
        //操作耗时
        operateLog.setCostTime(end-start);
        mapper.insert(operateLog);
        return result;
    }
}

定义切面类 。

拿到操作人id:

通过jwt令牌,以前实在controller中 方法参数列表中,声明一个HttpServletRequest req,在aop中可以直接注入HttpServletRequest。

返回值:

返回值是一个Object类型,插入到数据库中需要的是String,需要转换为json字符串 阿里巴巴

package com.neuedu.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.neuedu.aop.Log;
import com.neuedu.mapper.EmpMapper;
import com.neuedu.pojo.Emp;
import com.neuedu.pojo.PageBean;
import com.neuedu.service.EmpService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;

@Service
public class EmpServiceImpl implements EmpService {
    @Autowired
    EmpMapper empMapper;
    @Override
    public PageBean listEmp(Integer page, Integer pageSize, String name, Short gender, LocalDate begin, LocalDate end) {
//        无分页插件时
//        Integer start = (page - 1) * pageSize;
//        List<Emp> emps = empMapper.listEmp(start, pageSize);
//        Long total = this.listCount();
//        return new PageBean(total,emps);

        //使用PageHelper
        PageHelper.startPage(page,pageSize);
        List<Emp> emps = empMapper.listEmp(page, pageSize,name,gender,begin,end);
        Page<Emp> p = (Page<Emp>) emps;
        return new PageBean(p.getTotal(),p.getResult());
    }

    @Override
    public Long listCount() {
        return empMapper.listCount();
    }

    @Log
    @Override
    public void delEmp(List<Integer> ids) {
        empMapper.delEmp(ids);
    }

    @Log
    @Override
    public void addEmp(Emp emp) {
        emp.setCreateTime(LocalDateTime.now());
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.addEmp(emp);
    }

    @Override
    public Emp getOne(Integer id) {
        return empMapper.getOne(id);
    }

    @Log
    @Override
    public void updateEmp(Emp emp) {
        emp.setUpdateTime(LocalDateTime.now());
        empMapper.updateEmp(emp);
    }
}

给方法添加自定义注解

注意 

 记录的类名,看自定义注解加在什么位置,controller和impl,就是切入点的位置,从哪个位置切入。

因为impl的返回值就是null,所以是空。加在controller的自定义注解,那么返回值就是Result。

操作方法 获取属性

 //操作类名
 String className = joinPoint.getTarget().getClass().getName();

@Autowired
private HttpServletRequest request;

//操作人ID - 当前登录员工ID
//获取请求头中的jwt令牌, 解析令牌
String jwt = request.getHeader("token");
Claims claims = JwtUtils.parseJWT(jwt);
Integer operateUser = (Integer) claims.get("id");

//操作方法参数
Object[] args = joinPoint.getArgs();
String methodParams = Arrays.toString(args);


------原理篇要理解-------

springboot-配置优先级

优先级:命令行参数>java系统属性

打包上线的项目,如何修改

如何修改 命令行参数&java系统属性 

演示:

 执行生命周期的package。

到target文件中,找打jar包。

在当前路径进行cmd,打开窗口,输入java,看提示命令

options 是 java系统属性   args 是命令行参数。

执行 java -jar springboot-web-config-0.0.1-SNAPSHOT.jar

没指定,启动默认是8080

执行 java -jar -Dserver.port=9000  springboot-web-config-0.0.1-SNAPSHOT.jar

指定了系统属性,启动的就是9000

执行 java -jar -Dserver.port=9000  springboot-web-config-0.0.1-SNAPSHOT.jar --server.port=10010

同时指定了, 命令行参数优先级高,启动的是10010.

注意

 基于官方骨架创建,都会有


 配置优先级

bean的管理-bean的获取

 

代码

package com.itheima;

import com.itheima.controller.DeptController;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;

@SpringBootTest
class SpringbootWebConfig2ApplicationTests {

    //springboot的IOC容器对象
    @Autowired
    ApplicationContext applicationContext;

    //获取bean对象
    @Test
    public void testGetBean(){
//        getBean有很多重载方法

        //根据bean的名称获取
        DeptController bean1 = (DeptController) applicationContext.getBean("deptController");
        System.out.println(bean1);

        //根据bean的类型获取
        DeptController bean2 = applicationContext.getBean(DeptController.class);
        System.out.println(bean2);

        //根据bean的名称 及 类型获取
        DeptController bean3 = applicationContext.getBean("deptController", DeptController.class);
        System.out.println(bean3);

    }


    //bean的作用域
    @Test
    public void testScope(){


    }


    //第三方bean的管理
    @Test
    public void testThirdBean() throws Exception {
        SAXReader saxReader = new SAXReader();

        Document document = saxReader.read(this.getClass().getClassLoader().getResource("1.xml"));
        Element rootElement = document.getRootElement();
        String name = rootElement.element("name").getText();
        String age = rootElement.element("age").getText();

        System.out.println(name + " : " + age);
    }

}

一个bean没有声明名称,那就是类名首字母小写。 

默认是单例。

bean的管理-bean的作用域

使用@Scope注解来指定。

 IOC容器当中,默认bean对象是单例模式(只有一个实例对象)。那么如何设置bean对象为非单例呢?需要设置bean的作用域。

重点是前两种。

默认情况下,项目启动时,就会实例化对象,加载到容器中。

 首次实例化后,以后的对象,都是拿来直接用。

当指定@Scope("prototype")  非单例的时候,就会初始化10次,每次都不一样。

实例化了10次。

当指定@Lazy 懒加载时。

启动,控制台并没有输出构造方法的内容,还没有加载初始化。

当进入断点,首次调用getBean的时候,进行了加载。

总结 

 

bean的管理-第三方bean*

第三方是通过pom引入的,都是只读的,无妨使用@component注解,交给spring容器来管理。这时候就需要使用@Bean注解。 

演示

传统方法来解析xml文件,获取里面的值。

交给spring容器管理后,一样可以。 

代码

package com.itheima.config;

import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration  //配置类
public class CommonConfig {
    @Bean  //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
    public SAXReader saxReader(){
        return new SAXReader();
    }
}

要单独定义一个类,放在config配置包下,来统一管理第三方bean。

@Test
    public void testBean2(){
        Object bean = applicationContext.getBean("saxReader");
        System.out.println(bean);
    }

这里的saxReader就是上面代码中的方法名称。 

  • 通过@Bean注解的name或value属性可以声明bean的名称,如果不指定,默认bean的名称就是方法名。

可以拿到bean对象

package com.itheima.config;

import com.itheima.service.DeptService;
import org.dom4j.io.SAXReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration  //配置类
public class CommonConfig {
    @Bean  //将当前方法的返回值对象交给IOC容器管理, 成为IOC容器bean
    //通过@Bean注解的name/value属性指定bean名称, 如果未指定, 默认是方法名
    public SAXReader saxReader(DeptService deptService){
        System.out.println(deptService);
        //给第三方bean注入对象,无法使用@AutoWired,但可以使用形参的方式给第三方bean注入。类似构造
        return new SAXReader();
    }
}

 给第三方bean注入对象,无法使用@AutoWired,但可以使用形参的方式给第三方bean注入。类似构造

  • 如果第三方bean需要依赖其它bean对象,直接在bean定义方法中设置形参即可,容器会根据类型自动装配。

在启动的过程中,已经注入成功,输出出来了。默认是通过类型装配,就DeptService类型。

springboot原理-起步依赖*

springboot就是简化sping框架的。 

SpringBoot框架之所以使用起来更简单更快捷,是因为SpringBoot框架底层提供了两个非常重要的功能:一个是起步依赖,一个是自动配置

假如我们没有使用SpringBoot,用的是Spring框架进行web程序的开发,此时我们就需要引入web程序开发所需要的一些依赖。

那么这些依赖都需要手动去引入,而且版本要对应,不对应就会出现问题。

如果使用springboot

只需要引入对应依赖,一个依赖就行。 其他依赖会通过maven的依赖传递,自动传递下来。

A需要B,B需要C,C需要D。那么引入A,BCD就会传递下载下来。

原理

很简单,起步依赖的原理就是 maven的依赖传递

springboot原理-自动配置-概述

 spring是如何将bean加入到IOC容器中的。

 

大家会看到有两个CommonConfig,在第一个CommonConfig类中定义了一个bean对象,bean对象的名字叫reader。

在第二个CommonConfig中它的bean名字叫commonConfig,为什么还会有这样一个bean对象呢?原因是在CommonConfig配置类上添加了一个注解@Configuration,而@Configuration底层就是@Component

在IOC容器中除了我们自己定义的bean以外,还有很多配置类,这些配置类都是SpringBoot在启动的时候加载进来的配置类。这些配置类加载进来之后,它也会生成很多的bean对象。

比如:配置类GsonAutoConfiguration里面有一个bean,bean的名字叫gson,它的类型是Gson。

com.google.gson.Gson是谷歌包中提供的用来处理JSON格式数据的。

当我们想要使用这些配置类中生成的bean对象时,可以使用@Autowired就自动注入了:

import com.google.gson.Gson;
import com.itheima.pojo.Result;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
​
@SpringBootTest
public class AutoConfigurationTests {
​
    @Autowired
    private Gson gson;
​
​
    @Test
    public void testJson(){
        String json = gson.toJson(Result.success());
        System.out.println(json);
    }
}

添加断点,使用debug模式运行测试类程序:

问题:在当前项目中我们并没有声明谷歌提供的Gson这么一个bean对象,然后我们却可以通过@Autowired从Spring容器中注入bean对象,那么这个bean对象怎么来的?

答案:SpringBoot项目在启动时通过自动配置完成了bean对象的创建。

体验了SpringBoot的自动配置了,下面我们就来分析自动配置的原理。其实分析自动配置原理就是来解析在SpringBoot项目中,在引入依赖之后是如何将依赖jar包当中所定义的配置类以及bean加载到SpringIOC容器中的。

springboot原理-自动配置-方案****

 

 文件导入,不是真的导入,要把项目结构都导入进来import。

itheima这个项目模拟第三方依赖。使用的是springboot-web-config2演示。

在springboot-web-config2的pom文件中,引入itheima-utils的依赖。

<!--        引入第三方提供依赖-->
        <dependency>
            <groupId>com.example</groupId>
            <artifactId>itheima-utils</artifactId>
            <version>0.0.1-SNAPSHOT</version>
        </dependency>

 

都是一些自定义的类,类上也加了@Component的注解。 

bean并没有获取到。为什么???

思考

思考:引入进来的第三方依赖当中的bean以及配置类为什么没有生效?

  • 原因在我们之前讲解IOC的时候有提到过,在类上添加@Component注解来声明bean对象时,还需要保证@Component注解能被Spring的组件扫描到。(没有扫描到组件)

  • SpringBoot项目中的@SpringBootApplication注解,具有包扫描的作用,但是它只会扫描启动类所在的当前包以及子包。(两个项目,测试类这个扫描,扫不到引入的@Component)

  • 当前包:com.itheima, 第三方依赖中提供的包:com.example(扫描不到)


解决办法

那么如何解决以上问题的呢?

  • 方案1:@ComponentScan 组件扫描

  • 方案2:@Import 导入(使用@Import导入的类会被Spring加载到IOC容器中)

 方案1*简单

 @ComponentScan只要指定参数,那么原来就会被覆盖掉,所以也要扫描当前项目。

value是个数组,所以要加{ }

 因为@SpringBootApplication注解中包含@ComponentScan,添加@ComponentScan相当于重写,所以原来的会失效。重写后,就可以拿到bean了。


在springboot的自动配置,并没有采用这种方案,如果引入很多第三方依赖

 就会写的很长,使用繁琐,性能也低。

联想:Mybatis、MP....第三方,就没有这么配置,就是因为没有采用这种方式。

方案2****

第四种方式,就是springboot所采用的方式。

这种导入@Import的方式,及时类上没有@Component注解,也好使,他是直接将类加载到容器中。

@EnableXxxx注解,内部就封装了@Import。

普通类:就是自定义的基础类。

package com.example;

import org.springframework.stereotype.Component;

@Component
public class TokenParser {

    public void parse(){
        System.out.println("TokenParser ... parse ...");
    }

}

配置类:配置类中,封装了一些自定义的基础类。(就是封装了很懂普通类)

package com.example;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class HeaderConfig { //配置类

    @Bean
    public HeaderParser headerParser(){
        return new HeaderParser();
    }

    @Bean
    public HeaderGenerator headerGenerator(){
        return new HeaderGenerator();
    }
}

 ImportSelector接口实现类:可以对 配置类普通类进行封装。

package com.example;

import org.springframework.context.annotation.ImportSelector;
import org.springframework.core.type.AnnotationMetadata;

public class MyImportSelector implements ImportSelector {
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
        //返回值字符串数组(数组中封装了全限定名称的类)
        return new String[]{"com.example.HeaderConfig"};
    }
}

返回值字符串数组(数组中封装了全限定名称的类)

package com.itheima;

import com.example.HeaderConfig;
import com.example.MyImportSelector;
import com.example.TokenParser;
import com.github.pagehelper.dialect.helper.MySqlDialect;
import org.dom4j.io.SAXReader;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;


@SpringBootApplication
//第一种方式
//@ComponentScan(value = {"com.itheima","com.example"})
//第二种方式
//@Import(TokenParser.class)  //1.导入普通类
//@Import(HeaderConfig.class)  //2.导入配置类
//@Import(MyImportSelector.class)   //3.导入ImportSelector接口实现类

public class SpringbootWebConfig2Application {

    public static void main(String[] args) {
        SpringApplication.run(SpringbootWebConfig2Application.class, args);
    }


}

主启动类

package com.itheima;

import com.example.HeaderParser;
import com.example.TokenParser;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;

@SpringBootTest
public class AutoConfigurationTests {
    @Autowired
    ApplicationContext applicationContext;

    @Test
    public void testTokenParser(){
        TokenParser tokenParserBean = applicationContext.getBean(TokenParser.class);
        System.out.println(tokenParserBean);
    }
    @Test
    public void testHeaderParser(){
        HeaderParser headerParser = applicationContext.getBean(HeaderParser.class);
        System.out.println(headerParser);
    }


}

测试类

思考**

思考:如果基于以上方式完成自动配置,当要引入一个第三方依赖时,是不是还要知道第三方依赖中有哪些配置类和哪些Bean对象?

  • 答案:是的。 (对程序员来讲,很不友好,而且比较繁琐)

思考:当我们要使用第三方依赖,依赖中到底有哪些bean和配置类,谁最清楚?

  • 答案:第三方依赖自身最清楚。

 结论:我们不用自己指定要导入哪些bean对象和配置类了,让第三方依赖它自己来指定。

怎么让第三方依赖自己指定bean对象和配置类?

  • 比较常见的方案就是第三方依赖给我们提供一个注解,这个注解一般都以@EnableXxxx开头的注解,注解中封装的就是@Import注解


package com.example;

import org.springframework.context.annotation.Import;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)  //指定要导入哪些bean对象或配置类
public @interface EnableHeaderConfig {
}

第三方提供的注解@EnableXxx.:这里封装了要导入那些依赖。

 测试,一切正常。

 -------看讲义理解-------

springboot原理-自动配置-原理分析-源码跟踪

看讲义,复现吧,讲义中很全。自己B站中,有视频,加强巩固

 

自动配置源码小结

自动配置原理源码入口就是@SpringBootApplication注解,在这个注解中封装了3个注解,分别是:

  • @SpringBootConfiguration

    • 声明当前类是一个配置类

  • @ComponentScan

    • 进行组件扫描(SpringBoot中默认扫描的是启动类所在的当前包及其子包)

  • @EnableAutoConfiguration

    • 封装了@Import注解(Import注解中指定了一个ImportSelector接口的实现类)

      • 在实现类重写的selectImports()方法,读取当前项目下所有依赖jar包中META-INF/spring.factories、META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports两个文件里面定义的配置类(配置类中定义了@Bean注解标识的方法)。

当SpringBoot程序启动时,就会加载配置文件当中所定义的配置类,并将这些配置类信息(类的全限定名)封装到String类型的数组中,最终通过@Import注解将这些配置类全部加载到Spring的IOC容器中,交给IOC容器管理。

springboot原理-自动配置-原理分析-@Conditional

 

@ConditoinalOnProperty注解,使用场景,当需要第三方框架时,需判断配置文件中是否配置,配置了,才会生成bean。

@Conditional注解:

  • 作用:按照一定的条件进行判断,在满足给定条件后才会注册对应的bean对象到Spring的IOC容器中。

  • 位置:方法、类

  • @Conditional本身是一个父注解,派生出大量的子注解:

    • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

    • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

    • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。

 

前置操作

 测试HeaderParser的bean是否存在。

 

主类使用 @EnableHeaderConfig 自定义配置注解

使用的是接口实现类。

 接口实现类中,指定的是  配置类的类名。

配置类创建了2个bean,HeaderParser  和 HeaderGenerator

 

所以测试类HeaderParser 肯定存在。

 正式操作-测试三个注解

  • @ConditionalOnClass:判断环境中有对应字节码文件,才注册bean到IOC容器。

  • @ConditionalOnMissingBean:判断环境中没有对应的bean(类型或名称),才注册bean到IOC容器。

  • @ConditionalOnProperty:判断配置文件中有对应属性和值,才注册bean到IOC容器。


1.演示@ConditionalOnClass   拿Jwts这个类做演示。

修改配置类中的代码,当Jwts字节码文件存在,才能注册bean到IOC容器中。

成功拿到。

 是因为我们已经在pom中引入了对应的依赖,所以存在

将其注释掉,刷新。重新测试。

因为依赖已经注释掉,现在环境中没有Jwt这个类,所以就不会注册到IOC容器中。


2.@ConditionalOnMissingBean//不存在该类型的bean,才会将该bean加入IOC容器,指定类型(value属性) 或 名称(name属性)

场景:别人自己声明了bean,就用他自己的,如果别人没有声明创建,就会用当前的为默认。

 测试成功。

3.@ConditionalOnProperty (这个注解和配置文件当中配置的属性有关系)

场景:springboot整合第三方技术时,配置文件中配置了配置项,才会注册bena。

先在application.yml配置文件中添加如下的键值对:

配置文件中,配置键值对

 配置类。

 

测试。

测试成功。

如果修改配置文件和注解@ConditionalOnProperty(name = "name", havingValue = "gzy") name havingValue  对应不上,就会失败。

 springboot原理-自动配置-案例-自定义starter*

 看讲义,详情

视频:Day14-11. SpringBoot原理-自动配置-案例(自定义starter实现)_哔哩哔哩_bilibili

 在SpringBoot项目中,一般都会将这些公共组件封装为SpringBoot当中的starter,也就是我们所说的起步依赖。

需要将autoconfigure引入到starter中。 

以Mybatis为例 

Mybatis提供了配置类,并且也提供了springboot会自动读取的配置文件。当SpringBoot项目启动时,会读取到spring.factories配置文件中的配置类并加载配置类,生成相关bean对象注册到IOC容器中。

结果:我们可以直接在SpringBoot程序中使用Mybatis自动配置的bean对象。

 需求

 

<groupId>com.example</groupId> 组id是包的路径

过程出现的问题***

 测试运行启动时,就显示 包不存在,找不到。

autocinfig的模块,可以编译成功。但是starter编译不成功,test也编译不成功。

解决办法:运行maven生命周期  clean  + install。将autocinfig模块重新清理install。

点击clean会清理构建输出,而点击install会编译、测试、打包项目,并将结果安装到本地仓库。这两个操作通常用于确保构建环境的清洁(使用clean)和将项目作为依赖在其他项目中使用(使用install)。


注意

封装阿里云utils时,老师讲解的是 传递4个参数的。

 而现在的版本,是id和密码都存在配置变量中了。


 -------看讲义理解-------

 web后端开发总结(*重要*)

web后端开发现在基本上都是基于标准的三层架构进行开发的,在三层架构当中,Controller控制器层负责接收请求响应数据,Service业务层负责具体的业务逻辑处理,而Dao数据访问层也叫持久层,就是用来处理数据访问操作的,来完成数据库当中数据的增删改查操作。

在三层架构当中,前端发起请求首先会到达Controller(不进行逻辑处理),然后Controller会直接调用Service 进行逻辑处理, Service再调用Dao完成数据访问操作。

如果我们在执行具体的业务处理之前,需要去做一些通用的业务处理,比如:我们要进行统一的登录校验,我们要进行统一的字符编码等这些操作时,我们就可以借助于Javaweb当中三大组件之一的过滤器Filter或者是Spring当中提供的拦截器Interceptor来实现。

而为了实现三层架构层与层之间的解耦,我们学习了Spring框架当中的第一大核心:IOC控制反转与DI依赖注入。

所谓控制反转,指的是将对象创建的控制权由应用程序自身交给外部容器,这个容器就是我们常说的IOC容器或Spring容器。

而DI依赖注入指的是容器为程序提供运行时所需要的资源。

除了IOC与DI我们还讲到了AOP面向切面编程,还有Spring中的事务管理、全局异常处理器,以及传递会话技术Cookie、Session以及新的会话跟踪解决方案JWT令牌,阿里云OSS对象存储服务,以及通过Mybatis持久层架构操作数据库等技术。

 我们在学习这些web后端开发技术的时候,我们都是基于主流的SpringBoot进行整合使用的。而SpringBoot又是用来简化开发,提高开发效率的。像过滤器、拦截器、IOC、DI、AOP、事务管理等这些技术到底是哪个框架提供的核心功能?

Filter过滤器、Cookie、 Session这些都是传统的JavaWeb提供的技术。

JWT令牌、阿里云OSS对象存储服务,是现在企业项目中常见的一些解决方案。

IOC控制反转、DI依赖注入、AOP面向切面编程、事务管理、全局异常处理、拦截器等,这些技术都是 Spring Framework框架当中提供的核心功能。

Mybatis就是一个持久层的框架,是用来操作数据库的。

在Spring框架的生态中,对web程序开发提供了很好的支持,如:全局异常处理器、拦截器这些都是Spring框架中web开发模块所提供的功能,而Spring框架的web开发模块,我们也称为:SpringMVC

SpringMVC不是一个单独的框架,它是Spring框架的一部分,是Spring框架中的web开发模块,是用来简化原始的Servlet程序开发的。

外界俗称的SSM,就是由:SpringMVC、Spring Framework、Mybatis三块组成。

基于传统的SSM框架进行整合开发项目会比较繁琐,而且效率也比较低,所以在现在的企业项目开发当中,基本上都是直接基于SpringBoot整合SSM进行项目开发的。

maven高级-分模块设计与开发

git有代码。也可以参考讲义,说明的很清楚。

主工程是基于springboot骨架的工程,我们需要创建的是maven工程。只需要在主工程里引入模块坐标就行。

在管理模块,直接引入实体 与 工具 两个模块就可以了。

分模块开发,拆分pojo与utils

操作

引入实体的坐标

controller中将不再报错。

 

拆分utils时,pom文件中就需要引入所有工具所依赖的坐标,可能比较多。 

maven高级-继承与聚合-继承关系

就是把共用的依赖放入到父工程的pom文件中。其他子工程都继承父工程,实现传递。

我们可以再创建一个父工程 tlias-parent ,然后让上述的三个模块 tlias-pojo、tlias-utils、tlias-web-management 都来继承这个父工程 。 然后再将各个模块中都共有的依赖,都提取到父工程 tlias-parent中进行配置,只要子工程继承了父工程,依赖它也会继承下来,这样就无需在各个子工程中进行配置了。

 

创建父工程,打包方式要设置为pom,代表该模块不写代码,仅进行依赖管理。

因为原来management模块的父工程,已经继承了spring-boot-starter-parent。所以我们需要把创建的父工程来继承spring-boot-starter-parent。management再继承创建的父工程。传递下去

在子工程中pom中设置,继承tlias-parent工程。

操作步骤

 父工程pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.neuedu</groupId>
    <artifactId>tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
    </dependencies>

</project>

 

父工程引入共有的依赖,并设置打包方式为pom。

子工程的pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.neuedu</groupId>
        <artifactId>tlias-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../tlias-parent/pom.xml</relativePath>
    </parent>


    <artifactId>tlias-pojo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>


</project>

 

子工程导入父工程的坐标并指定父的pom文件的相对位置,并且groupId可以继承下来省略(一样的话)

其他子工程操作类似一样。


因为学习的缘故,正常情况下父工程是在最外层。两种都行,第二种很清晰而已。

maven高级-继承与聚合-版本锁定

就是父工程统一管理版本

父工程只负责管理版本,不会引入真正的依赖。子工程引入的时候,就不需要引入版本号了。

演示 

 父工程的pom文件

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.neuedu</groupId>
    <artifactId>tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
    </dependencies>
<!--    父工程统一管理版本(不是共用的依赖).管理了jwt、oss、web-->
    <dependencyManagement>
        <dependencies>
            <!--        springboot web起步依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>2.7.5</version>
            </dependency>

            <!--        oss-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>3.15.1</version>
            </dependency>
            <!--        oss JAXB相关依赖-->
            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
                <version>2.3.1</version>
            </dependency>
            <dependency>
                <groupId>javax.activation</groupId>
                <artifactId>activation</artifactId>
                <version>1.1.1</version>
            </dependency>
            <!-- no more than 2.3.3-->
            <dependency>
                <groupId>org.glassfish.jaxb</groupId>
                <artifactId>jaxb-runtime</artifactId>
                <version>2.3.3</version>
            </dependency>
            <!--        jwt-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>0.9.1</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

 dependencyManagement标签来管理,并不会真正的引入。

子工程pom

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>com.neuedu</groupId>
        <artifactId>tlias-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
        <relativePath>../tlias-parent/pom.xml</relativePath>
    </parent>

    <artifactId>tlias-util</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <dependencies>
<!--        springboot web起步依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!--        oss-->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
        </dependency>
        <!--        oss JAXB相关依赖-->
        <dependency>
            <groupId>javax.xml.bind</groupId>
            <artifactId>jaxb-api</artifactId>
        </dependency>
        <dependency>
            <groupId>javax.activation</groupId>
            <artifactId>activation</artifactId>
        </dependency>
        <!-- no more than 2.3.3-->
        <dependency>
            <groupId>org.glassfish.jaxb</groupId>
            <artifactId>jaxb-runtime</artifactId>
        </dependency>
<!--        jwt-->
        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
        </dependency>
    </dependencies>

</project>

不再需要指定版本号了,idea也没有提示版本号标签version。

思考 

当父工程的pom文件中,依赖很多,不方便查找时,不方便维护,该怎么办?

该用到自定义属性,来维护依赖 与 版本的关系 ,统一维护。 

自定义属性

父工程代码

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.5</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.neuedu</groupId>
    <artifactId>tlias-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>

<!--        自定义-->
        <lombok.version>1.18.30</lombok.version>
        <springweb.version>2.7.5</springweb.version>
        <oss.version>3.15.1</oss.version>
        <jaxb.runtime.version>2.3.3</jaxb.runtime.version>
        <jjwt.version>0.9.1</jjwt.version>
        <activation.version>1.1.1</activation.version>
        <jaxb.api.version>2.3.1</jaxb.api.version>
    </properties>
    <dependencies>
        <!--        lombok-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>
<!--    父工程统一管理版本(不是共用的依赖).管理了jwt、oss、web-->
    <dependencyManagement>
        <dependencies>
            <!--        springboot web起步依赖-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <version>${springweb.version}</version>
            </dependency>

            <!--        oss-->
            <dependency>
                <groupId>com.aliyun.oss</groupId>
                <artifactId>aliyun-sdk-oss</artifactId>
                <version>${oss.version}</version>
            </dependency>
            <!--        oss JAXB相关依赖-->
            <dependency>
                <groupId>javax.xml.bind</groupId>
                <artifactId>jaxb-api</artifactId>
                <version>${jaxb.api.version}</version>
            </dependency>
            <dependency>
                <groupId>javax.activation</groupId>
                <artifactId>activation</artifactId>
                <version>${activation.version}</version>
            </dependency>
            <!-- no more than 2.3.3-->
            <dependency>
                <groupId>org.glassfish.jaxb</groupId>
                <artifactId>jaxb-runtime</artifactId>
                <version>${jaxb.runtime.version}</version>
            </dependency>
            <!--        jwt-->
            <dependency>
                <groupId>io.jsonwebtoken</groupId>
                <artifactId>jjwt</artifactId>
                <version>${jjwt.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

</project>

web的起步依赖,其实不用维护,因为 springboot的起步依赖中已经配置了。

点进去查看 

 继续点击

已经管理了版本。此处就是springboot的版本。 

总结

 dependencyManagement和dependencies的区别**。

 maven高级-继承与聚合-聚合实现

直接打包management模块,就会失败,因为管理模块依赖pojo和util和parent。而本地仓库中没有并没有pojo和util和parent。此时就需要对这三个模块执行maven生命周期install(所依赖的模块,必须先安装到本地仓库,才能对所需模块进行打包),进行打包注册。再对管理模块进行打包。

父工程pom

<!--聚合其他模块-->
    <modules>
        <module>../tlias-pojo</module>
        <module>../tlias-util</module>
        <module>../tlias-web-management</module>
    </modules>

接下来直接操作父工程的生命周期,就可以了。 

总结

maven高级-私服

如果有需要,看详细资料文档md。资料不全,查看百度网盘。

介绍

中央仓库:全球只有一个,不是能随便上传的。

首先会查找自己本地仓库是否存在utils这个jar包,然后去中央仓库查找。发现都没有。没办法去访问别人本地仓库的utilsjar包的。

 如果需要mybaits最新版本依赖,这时候,私服是没有的,就会去中央仓库去查找。

查找顺序:本地->私服->中央仓库

资源上传与下载

 central就是从中央仓库下载下来的。

  

与我们idea的pom文件相对应。只要不加SNAPSHOT,都会上传到RELEASE中。

步骤

 配置私服仓库的用户名密码

配置上传到私服的路径地址

配置私服下载地址   仓库组地址(说白了就是三个仓库的上层文件夹 )

profiles配置

SNAPSHOT不稳定,一般不允许下载的。

注意

 上传的id要对应仓库。

 执行生命周期 deploy就是上传到私服仓库。

模拟私服

  • 12
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值