JavaWeb

JavaWeb学习记录(黑马程序员2023版)

一、Maven

        管理和构建java项目的工具,它基于项目对象模型pom(project object model)的概念,通过一小段描述信息来管理项目的构建。

作用:

1.依赖管理

        无需手动导入jar包,只需要在pom.xml中,配置一块描述信息即可(artifactId中写依赖名,version中写版本),会自动联网下载对应的依赖,修改版本也是直接修改即可

        

        先在本地仓库进行jar包的查找,若没有会到中央仓库(https://repo1.maven.org/maven2/)下载到本地之后进行导入。若有远程仓库(私服)会比中央仓库先进行,速度较快,目前阶段,我们通常会使用阿里云的私服来提高速度。

        安装我就不记录了,因为我电脑上面已经安装过了,直接看视频安装maven08:00。

        终端运行mvn -v查看是否配置完毕

2.统一项目结构

         我们知道Java项目的开发工具非常多,如

         ​​​​

        其目录结构有差异,无法互相导入。通过Maven会统一他们的项目结构,完美解决此问题。

        

3.项目构建

        标准跨平台的自动化项目构建方式 。即通过maven提供的一系列指令快速的进行以下一系列操作。

                            

test:会生成target目标保存了编译之后的文件

1.依赖配置

        在pom.xml中添加如下信息,点击浮动的小m即成功配置,在Maven面板中可以看到依赖项中含有这个依赖

2.依赖传递

依赖具有传递性

直接依赖:在当前项目中通过依赖配置建立的依赖关系

间接依赖:被依赖的资源如果依赖其他资源,当前项目间接依赖其他资源

A项目要直接依赖B项目,直接配置B项目的坐标即可,然后B项目中直接配置C项目的坐标

如果A项目想要B项目的依赖,但是不想要C项目的依赖,使用排除依赖(主动断开依赖的资源)

<exclusions><exclusion>

3.依赖范围

依赖的jar包,默认情况下,可以在任何地方使用。可通过<scope>设置其作用范围

作用范围:

        主程序范围有效(main文件夹范围内)

        测试程序范围有效(test文件夹范围内)

        是否参与打包运行(package指令范围内)

junit单元测试

4.生命周期

为了对所有的maven项目构建过程进行抽象和统一

主要

二、SpringBootWeb

Spring 官网spring.io形成一种开发生态圈,提供了若干个子项目,每个项目用于完成特定的功能。

入门需求:

基于SpringBoot开发一个web应用,浏览器发起请求后,给浏览器返回字符串hello world

1.创建springboot程序,并勾选web开发相关依赖

我用的是jdk22

pom.xml 中记录的这个父工程是所有SpringBoot需要继承的

2.定义HelloController类,添加方法hello,并添加注解

加上注解@RestController才能标识这是请求处理类

RequestMapping浏览器调用/hello就会调用下面的方法

3.运行测试

启动则是在自动自称类中main方法处启动

访问端口号8080/hello

打印了内容说明没有问题

三、http协议

超文本传输协议,规定了浏览器和服务器之间数据传输的规则

1.特点

基于tcp协议:面向连接,安全。每一次请求者之前要先进行三次握手,确定双方都有收发能力了,再传输数据。

基于请求-相应模型:一次请求对应一次响应

HTTP协议是无状态的协议:对于事务处理没有记忆能力。每次请求-响应都是独立的。=》会话技术解决

        缺点:多次请求间不能共享数据。

        优点:速度快

2.请求协议

3.响应协议

四、Tomcat

一个轻量级的web服务器,支持servlet、jsp等少量javaEE规范

也被称为web容器、servlet容器

(学过了)

springbootWeb开发中已经把tomcat集成进来了(内嵌)

这就是为什么在下面这个启动类就可以启动tomcat的原因

五、postman请求响应

用于接口测试需求 是一款功能强大的网页调试与发送网页HTTP请求的Chorme插件

controller 接受请求 设置响应

1.测试环境的创建

创建自己的workspace

选择请求方式 填入请求路径 Send即可看到响应回来的数据

ctrl+s保存路径 创建一个集合保存在里面

2.简单参数

原始方式

在Controller方法形参中声明HttpServletRequest对象

调用对象的getParameter(参数)获取请求参数

SpringBoot方式

简单参数:参数名与形参变量名相同,定义形参即可接收参数

调用一次返回了tom:18,成功接收到了参数

如果是post请求呢?上面我们学过post请求要在请求体中携带 通过body设置请求体的内容

如果方法形参名称与请求参数名称不匹配,可以使用@requestParam这个注解完成映射

一旦给name加上注解,就表示这个参数必须传递,不传递就会报错(400 客户端发生错误)

3.实体参数

如果前端传递的参数非常多,有十个二十怎么处理?故可以将所有的请求参数封装到一个实体类当中,同样请求参数名与形参对象属性名保持一致。

简单实体对象:请求参数名与形参对象属性名相同,定义POJO接收即可

创建实体User类

实体参数

测试

如果是复杂的实体对象呢?按照对象层次结构关系即可接收嵌套POJO属性参数

toString一定要写,要不然adress这边就返回的是内部地址

测试

4.数组集合参数

很多form表单中的checkbox里面有多个值需要提交到服务端,同样是逐个提交

数组参数:请求参数名与形参数组名称相同且请求参数为多个,定义数组类型形参即可接收参数

集合参数:请求参数名与形参集合名称相同且请求参数为多个,@requestParam 绑定参数关系

5.日期参数

日期参数:使用@DateTimeFormat注解完成日期参数格式转换

由于日期的格式有多样,所以需要在注解之后指定格式

我一开始测试的参数是

报错

2024-06-26T20:49:31.376+08:00  WARN 25092 --- [sprintboot-web] [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.method.annotation.MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.time.LocalDateTime'; Failed to convert from type [java.lang.String] to type [@org.springframework.format.annotation.DateTimeFormat java.time.LocalDateTime] for value [2024-6-26 20:50:00]]

是因为月份只有一个数字的时候忘记填0了

改正即可正确输出了

6.Json参数

Json格式的参数在前后端异步交互的时候使用是非常多的

Json参数:Json数据键名与形参对象属性名相同,定义pojo类型形参即可接收参数,需要使用@RequestBody标识

(1)postman发送请求的时候如何传递Json格式的请求参数?

需要将请求方式设置为post:因为Json格式的请求数据是需要放在请求体当中携带到服务端的

(2)在服务端controller当中怎么接收Json格式的请求参数?

        往往通过实体对象来接收,用@RequestBody注解将Json格式的数据封装到实体类当中

成功响应!

7.路径参数

路径参数:通过请求URL直接传递参数,使用{...}来标识该路径参数,需要使用@PathVariable获取路径参数

多个路径参数,就定义多个形参  都用@PathVariable获取路径参数后绑定给形参就可以了

8.响应数据

通过注解@ResponseBody响应

作用:将方法返回值直接响应,如果返回值类型是实体对象/集合,将会转换为JSON格式响应

为什么我们以上例子没有看到这个注解?

因为@RestController = @Controller+@ResponseBody

统一所有接口的响应结果  用一个类封装

https://www.bilibili.com/video/BV1m84y1w7Tb/?p=73&spm_id_from=pageDriver&vd_source=865f32e12aef524afb83863069b036aa

前端只会拿到一种格式的数据,这对项目的管理和维护都有大大的帮助

/**
 * 统一响应结果封装类
 */
public class Result {
    private Integer code ;//1 成功 , 0 失败
    private String msg; //提示信息
    private Object data; //数据 date

    public Result() {
    }
    public Result(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
    public Integer getCode() {
        return code;
    }
    public void setCode(Integer code) {
        this.code = code;
    }
    public String getMsg() {
        return msg;
    }
    public void setMsg(String msg) {
        this.msg = msg;
    }
    public Object getData() {
        return data;
    }
    public void setData(Object data) {
        this.data = data;
    }

    public static Result success(Object data){
        return new Result(1, "success", data);
    }
    public static Result success(){
        return new Result(1, "success", null);
    }
    public static Result error(String msg){
        return new Result(0, msg, null);
    }

    @Override
    public String toString() {
        return "Result{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", data=" + data +
                '}';
    }
}

案例:

在pom.xml文件中引入dom4j的依赖,用于解析XML文件

引入资料中提供的解析XML的工具类XMLParserUtils、对应的实体类Emp、XML文件emp.xml

引入资料中提供的静态页面文件,放在resources下的static目录

SpringBoot项目的静态资源默认的存放目录为:classpath:/static、classpath:/public、classpath:/resources (classpath:resources)

编写Controller程序,处理请求,响应数据

https://www.bilibili.com/video/BV1m84y1w7Tb/?p=73&spm_id_from=pageDriver&vd_source=865f32e12aef524afb83863069b036aa

六、分层解耦

1.三层架构

实际开发过程中需要尽量让每一个类的职责单一(单一职责原则),使复杂性更低,可读性更强

 三层架构包括:

controller:控制层,接收前端发送的请求,对请求进行处理,并响应数据。

service:业务逻辑层,处理具体的业务逻辑。

dao:数据访问层(Data Access Object)(持久层),负责数据访问操作,包括数据的增、删、查、改

2.思想!!

内聚:软件中各个功能模块内部的功能联系

耦合:衡量软件中各个层/模块之间的依赖、关联程度

比如说上面的代码中

Controller类要使用数据,需要先new一个Service层的实现类(Controller和Service类之间耦合)

就是一个代码改动,另一边也需要跟着改动

原则:高内聚低耦合

我们需要解耦!=》容器 这样即使是改动了 Controller层也不需要动

控制反转:IOC。对象的创建控制权由程序自身转移到外部(容器)

        反转之前是程序自身来控制对象的创建(new),反转之后是由容器来控制。容器叫(IOC容器 or Spring容器)

依赖注入:DI。容器为应用程序提供运行时,所依赖的资源

Bean对象:IOC容器中创建、管理的对象

3.IOC & DI 入门

(1)将service层和dao层的实现类,交给IOC容器管理

        =》加上注解@Component

(2)为Controller及Service注入运行时,依赖的对象

        =》运行时,IOC为其提供Bean对象,并赋值给该变量,加上注解@Autowired

从实现类A变成B 直接把Component注解注释掉 给B添上

4.IOC控制反转详解

若某个类不归属于下面三个层,那么就用@Component进行

注意:@RestController中包含了@Controller

每一个Bean都有标识(就是Bean的名字)默认是类名首字母小写

若你需要自己设置名字 通过给value赋值即可

一般不用指定

Bean组件扫描

声明这四大注解想要生效还需要被组件扫描注解@ComponentScan扫描

默认包含在了启动类声明注解中(范围在启动类所在包及其子包 意思就是不要随便改变位置)

或者手动设置

5.DI 依赖注入详解

通过以下几种方案解决:

@Primary\

@Qualifier\ 按照类型注入

@Resource 按照名称注入

总结

七、SQL查缺补漏

1.分页查询limit 

(mysql方言 limit)

起始索引=(页码-1)* 每页展示的记录数

2.流程控制函数

3.外键添加

foreign key属于物理外键

缺点:影响增删改的效率(检查外键关系)

        仅用于单节点数据库,不适用于分布式、集群场景

        容易引发数据库的死锁问题,消耗性能

4.一对一&多对多

        一对一关系,用于单表拆分,基础字段和其他字段分开存储,以提升工作效率

        实现:在任意一方加入外键,关联另外一方的主键,并且设置外键为唯一的(UNIQUE)

        多对多如下

可视化关系表

5.多表查询

(1)内连接

        

(2)外连接

左外 以前一个为标准 如果后一个是null就查不出来 右外反之

(3)行子查询

(4)表子查询

6.事务

回滚:恢复到操作之前

7.索引

往往我们考虑查询的优化比增删改查更多

一般默认B+树数据结构(多路平衡搜索树)

所有的数据都是在叶子结点保存的 所有的key也会在叶子结点出现

排序从小到大 双向链表

查询性能稳定

语法

注意事项

八、Mybatis

优秀的持久层框架,用于简化JDBC的开发

几乎免除了几乎所有的JDBC代码以及设置参数和获取结果集的工作。mybatis可以通过简单的XML或者注解来配置和映射原始类型、接口与Java POJO 为数据库中的记录。

 1.简单步骤

1.返回的每一个数据都会封装成一个User对象,表中的字段会自动的封装成对象的属性

字段名和类中的属性名保持一致,框架会完成自动封装

定义实体类时,如果需要用到整型,不要用int,要用Integer  Why?

如果用int类型 默认值为0,会影响数据的判断,用Integer 默认值null 不会给数据的判断带来干扰

2.引入mybatis框架的依赖和mysql驱动的依赖 

配置mybatis

更改数据库的名字和密码

3.需要定义持久层的接口叫UserMapper

即数据访问层的接口

在MyBatis开发当中,实现接口即可,不需要完成实现类

因为程序在运行时,框架底层会自动生成接口的实现类对象,并且将该对象交给IOC容器管理

单元测试

UserMapper是一个接口 不可以直接new一个接口

但是上面提到自动生成接口的代理对象并且将对象交给了IOC容器管理

已经成为IOC容器中的Bean

所以此时用依赖注入 用@Autowired将Bean注入

我在这里卡了好久!!!!一直报错:

无法自动装配。找不到“UserMapper”类型的Bean

解决方法:在测试类上即@SpringBootTest上加上一个新的注解@MapperScan("com.test.mapper")

2.配置sql提示

默认在mybatis中编写sql语句是不识别的。做以下配置  再连接database(右边栏)

3.JDBC

 sun公司官方定义的一套操作所有关系型数据库的规范,即接口

 仅仅是一套规范或是接口

其实现由各个厂商提供  e.g: Mysql Oracle SqlServer

此实现称作数据库驱动(数据库驱动jar包)

我们使用这套接口编程,真正执行的代码是驱动jar包中的实现类

我在java课设中用到servlet实现了一个图书管理系统

每次我在dao层的实现类中与数据库建立连接都会执行以下步骤:

1.注册驱动

2.获取连接

3.执行sql语句

4.获取查询的结果集

5.封装结果数据

6.释放资源

以下是黑马的例子 

弊端:

(1)出现硬编码 一旦此内容发生变化,需要重新编译打包之后才能运行

(2)解析结果封装结果需要一个字段一个字段的解析(一旦字段很多将会非常繁琐)

(3)频繁地获取连接和释放连接,会造成资源浪费,从而导致性能降低

所以我们用mybatis解决(完胜)

(1)数据库连接的四要素配置在了.properties文件中 无需操作java代码

(2)用@Mapper自动将查询的结果封装到集合中,返回的每一条记录封装成对象,所有的User对象封装到一个List集合当中

(3)用spring.datasource连接信息,springboot会自动采用数据库连接池技术统一管理分配连接

4.数据库连接池

是一个容器,负责分配管理数据库连接

允许重新使用一个现有的数据库连接,而不是重新建立一个

释放空闲空间超过最大空闲时间的连接,来避免因为没有释放连接而引起的数据库连接遗漏

连接复用而不是每次创建新的连接

上面我的图书管理系统也是通过c3p0连接池连接的

现在更多用的是后两个

springboot自带连接池hikari  此连接池会提供datasource接口 

如果我们不想用默认的连接池,换成druid连接池呢?

引入druid起步依赖和修改配置文件即可

 

我的springboot是3.x版本的,所以在引入依赖的时候

<artifactId>后接druid-spring-boot-3-starter

<version>后接1.2.20

5.lombok工具包

Java类库,通过注解的形式自动生成构造器、getter/setter、equals、hashcode、toString等方法,并可以自动化生成日志变量,简化Java开发,提高效率

比如,解决臃肿的实体类中getter和setter以及构造方法和toString()

只需在实体类上面加上注解@Data

要想使用lombok还需要引入依赖 无需版本号

6.环境准备

curd就是增删改查的简写

1.准备数据库表

员工表emp

部门表dept

2.创建一个新的springboot工程,引入起步依赖

3.application.properties引入数据库连接信息

4.创建对应的实体类Emp(实体类采用驼峰命名)

5.准备Mapper接口EmpMapper

7.删除操作

根据id删除数据也需要一个接口方法

如果mapper接口方法只有一个普通类型的参数。#{...}里面的属性名可以随便写,如#{id},#{value}

还是建议两者保持一致

单元测试

可以看到第17条记录已经删除

返回影响的记录数

再次执行刚才的操作返回的记录数为0

借助mybatis日志来看到删除的具体信息

直接输入mybatislog会有提示信息选择第一个

mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl

显示 上面两行就叫预编译sql   性能更高更安全  传入值之后会先检查缓存当中有无

如果在登录界面检查有无此用户的信息

就用select count(*) from emp where username = ' ' and password = ' '

返回结果为 0 说明没有找到 如果返回结果为 1 说明找到了

有一个信息安全问题 sql注入

所以不能直接编译 而是使用预编译 用问号进行

参数占位符

#{...}执行sql时,会把#{...}替换为?,生成预编译SQL

¥{...}是拼接sql。直接将参数拼接在sql语句中,存在sql注入问题

8.新增

新增操作中如果属性过多就用emp封装一下再传值  记住values后面一定要跟实体类中的属性名一样

新增成功

主键返回 

在@insert注解之前加上这样的注解

@Options(keyProperty="id",useGeneratedKeys=true)

表示会自动生成的主键值,赋值给emp对象的id属性

9.更新

更新成功

10.查询(根据ID查询)

一般用于页面回显展示,查询到的封装起来

problem:未封装进来 显示null

mybatis封装中 如果实体类属性名和数据库表查询返回的字段名一致才会自动封装

怎样解决上面的问题呢?

solution1

给字段起别名,让别名与实体类属性一致

sql语句不能再写* 需要把所有字段罗列出来

solution2:

通过@Results,@Result注释手动映射封装

对不一致的进行封装

solution3:

开启mybatis的驼峰命名自动映射开关

在.properties中配置

输入camel加上以下这句话

11.查询(条件查询)

员工姓名->模糊匹配

性别->精准查询

入职时间->范围查询

支持分页查询  并对查询结果根据最后的修改时间进行倒序排序

!!如果需要再sql中的字符串中写入传入的值 需要把 # 改成 $(拼接符号 不会生成预编译)

用拼接函数

12.XML映射文件

注解开发简单的sql,xml开发动态sql

配置文件在resource下

创建Directory要用 / 分隔

同时xml文件需要约束 可以在mybatis中文网copy过来

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

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

mybatisX

基于idea的快速开发mybatis的插件  会跳转定位

13.动态sql

ctrl+alt+l格式化

随着用户的输入或者外部条件的变化而变化的sql语句

(1)if

如果name是null就会出现问题=》

那么我们需要引入where标签 自动去除and和空

update中去掉多余的逗号<set>标签 字段不存在则不更新

(2)foreach
collection遍历的集合
item遍历出来的元素
separator分隔符
open遍历开始前拼接的SQL片段
close遍历结束后拼接的SQL片段

e.g批量删除员工

(3)sql&include

为了提高复用性

九、案例细节

1.记录日志

项目开发中尽量不用sout记录日志

用日志记录框架记录日志

声明日志记录对象

private static Logger log = LoggerFactory.getLogger(DeptController.class);
log.info("根据id删除部门:{}",id);

调用info方法输出日志

可以不添加第一行代码 直接在class前面添加@Slf4j

2.Get/Post等请求方式

3.参数占位符

4.注解

@RequestBody 将数据封装到实体类当中

@PathVariable 获取路径参数绑定给形参

/** + 回车 文档注释

5.简化

抽取

6.HTTP请求方式

7.设置默认值

@RequestParam 设置默认值

8.分页查询

分页插件PageHelper  需要添加依赖

        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

代码 分页条件查询

PageBean类

package com.itheima.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;

/**
 * 分页查询结果封装类
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {

    private Long total;//总记录数
    private List rows;//数据列表

}

EmpController

package com.itheima.controller;

import com.itheima.pojo.PageBean;
import com.itheima.pojo.Result;
import com.itheima.service.EmpService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.web.bind.annotation.*;

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

/**
 * 员工管理Controller
 */
@Slf4j
@RestController
@RequestMapping("/emps")
public class EmpController {

    @Autowired
    private EmpService empService;

    @GetMapping
    public Result page(@RequestParam(defaultValue = "1") Integer page,
                       @RequestParam(defaultValue = "10") Integer pageSize,
                       String name, Short gender,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate begin,
                       @DateTimeFormat(pattern = "yyyy-MM-dd") LocalDate end){
        log.info("分页查询, 参数: {},{},{},{},{},{}",page,pageSize,name,gender,begin,end);
        //调用service分页查询
        PageBean pageBean = empService.page(page,pageSize,name,gender,begin,end);
        return Result.success(pageBean);
    }
}

EmpService


package com.itheima.service;

import com.itheima.pojo.PageBean;
import org.springframework.format.annotation.DateTimeFormat;

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

/**
 * 员工管理
 */
public interface EmpService {
    /**
     * 分页查询
     * @param page
     * @param pageSize
     * @return
     */
    PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end);
}

EmpServiceImpl

package com.itheima.service.impl;

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

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

@Service
public class EmpServiceImpl implements EmpService {

    @Autowired
    private EmpMapper empMapper;


    @Override
    public PageBean page(Integer page, Integer pageSize,String name, Short gender,LocalDate begin,LocalDate end) {
        //1. 设置分页参数
        PageHelper.startPage(page,pageSize);

        //2. 执行查询
        List<Emp> empList = empMapper.list(name, gender, begin, end);
        Page<Emp> p = (Page<Emp>) empList;

        //3. 封装PageBean对象
        return new PageBean(p.getTotal(), p.getResult());
    }
}

EmpMapper

package com.itheima.mapper;

import com.itheima.pojo.Emp;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

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

/**
 * 员工管理
 */
@Mapper
public interface EmpMapper {
    /**
     * 员工信息查询
     * @return
     */
    public List<Emp> list(String name, Short gender,LocalDate begin,LocalDate end);
}

EmpMapper.xml

<?xml version="1.0" encoding="UTF8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itheima.mapper.EmpMapper">

    <!--条件查询-->
    <select id="list" resultType="com.itheima.pojo.Emp">
        select *
        from emp
        <where>
            <if test="name != null and name != ''">
                name like concat('%',#{name},'%')
            </if>
            <if test="gender != null">
                and gender = #{gender}
            </if>
            <if test="begin != null and end != null">
                and entrydate between #{begin} and #{end}
            </if>
        </where>
        order by update_time desc
    </select>

</mapper>

9.查询回显

在原有的基础上进行修改(就是根据id查找信息)

10.配置文件

(1)参数配置化

硬编码 如果发生变化 需要重新进行代码的编译  =》 引入到配置文件当中

@Value注解 通常用于外部配置的属性注入,其具体用法为: @Value("${配置文件中的key}")

(2)yml配置文件(更推荐)

后缀可以是yml(更推荐)也可以是yaml

层级结构更清晰

替换

第一次测试没法获取到 发现给密码加上双引号就成功了

(3)@ConfigurationProperties

更加简化啦!!!

不需要单个给属性绑定

属性名得保持一致

引入以下依赖 在配置文件中会有提示(可选)

十、文件上传

将本地图片、视频、音频等文件上传到服务器,供其他用户浏览或下载的过程

比如发朋友圈、微博

常见的两种存储形式:

1.本地存储

2.云存储(阿里云OSS)

前端上传需要在form标签后 用post请求方式

加上编码格式enctype=“multipart/form-data” 支持大型的二进制数据

如果用默认编码 会发现提交的仅仅是文件名并无文件内容

服务端接收文件:提交上来的文件在Controller中怎么接收 MultipartFile image 

如果与绑定属性名不一致用注解 @RequestParam("image")MultipartFile file

1.本地存储

在服务器,接收到上传上来的文件之后,将文件存储在本地服务器磁盘当中

@Slf4j
@RestController
public class UploadController {

    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image) throws Exception {
        log.info("文件上传: {}, {}, {}",username,age,image);
        //获取原始的文件名称
        String originalFilename = image.getOriginalFilename();
        //将文件存储在服务器的磁盘目录中D:\aTestImages
        image.transferTo(new File("D:\\aTestImages\\"+originalFilename));

        return Result.success();
    }
}

此处将文件的原始名称作为存储会有问题:两个文件名有可能一样 =》 构造唯一文件名 uuid

(1)测试类中生成uuid
@Test
    public void testUuid(){
        for (int i = 0; i < 1000; i++) {
            String uuid = UUID.randomUUID().toString();
            System.out.println(uuid);
        }
    }

生成的uuid  长度固定(36)且唯一

同时需要截取后缀 

@Slf4j
@RestController
public class UploadController {

    @PostMapping("/upload")
    public Result upload(String username, Integer age, MultipartFile image) throws Exception {
        log.info("文件上传: {}, {}, {}",username,age,image);
        //获取原始的文件名称
        String originalFilename = image.getOriginalFilename();
        //构造唯一的文件名
        int index = originalFilename.lastIndexOf(".");
        String extname = originalFilename.substring(index);
        String newFileName = UUID.randomUUID().toString()+extname;
        log.info("新的文件名: {}",newFileName);
        //将文件存储在服务器的磁盘目录中D:\aTestImages
        image.transferTo(new File("D:\\aTestImages\\"+newFileName));

        return Result.success();
    }
}

(2)注意

在springboot中,文件上传,默认单个文件允许最大大小为1M。如果需要上传大文件,可以在.properties中进行如下配置

#配置单个文件最大上传大小
logging.logback.rollingpolicy.max-file-size=10MB
#配置单个请求最大上传大小(一次请求可以上传多个文件)
spring.servlet.multipart.max-request-size=100MB

2.阿里云OSS

阿里云对象存储OSS(云存储服务) 可以通过网络随时存储和调用包括文本、图片、音频呢和视频等在内的各种文件

术语:

SDK 软件开发工具包 包括辅助软件开发的依赖(jar包)、代码示例等

Bucket 存储空间是用户用于存储对象的容器,所有的对象都必须隶属于某个存储空间

创建bucket

参考官方sdk 查看文档信息

(1)上传文件流
import com.aliyun.oss.ClientException;
import com.aliyun.oss.OSS;
import com.aliyun.oss.common.auth.*;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.OSSException;
import com.aliyun.oss.model.PutObjectRequest;
import com.aliyun.oss.model.PutObjectResult;
import java.io.FileInputStream;
import java.io.InputStream;

public class Demo {

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

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

        try {
            InputStream inputStream = new FileInputStream(filePath);
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, inputStream);
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            System.out.println("Caught an OSSException, which means your request made it to OSS, "
                    + "but was rejected with an error response for some reason.");
            System.out.println("Error Message:" + oe.getErrorMessage());
            System.out.println("Error Code:" + oe.getErrorCode());
            System.out.println("Request ID:" + oe.getRequestId());
            System.out.println("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            System.out.println("Caught an ClientException, which means the client encountered "
                    + "a serious internal problem while trying to communicate with OSS, "
                    + "such as not being able to access the network.");
            System.out.println("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }
    }
} 
(2)集成

        1.引入阿里云OSS上传文件工具类

/**
 * 阿里云 OSS 工具类
 */
@Component
public class AliOSSUtils {

    private String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";
    private String accessKeyId = "LTAI5tLNNdkWD6p8yaKNCoxw";
    private String accessKeySecret = "rMHRFree0oqbomc3HpI52Inp1xlwB9";
    private String bucketName = "my-java-web-tlias";

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

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

        //上传文件到 OSS
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
        ossClient.putObject(bucketName, fileName, inputStream);

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

}

        2.上传图片接口开发

@Slf4j
@RestController
public class UploadController {

    @Autowired
    private AliOSSUtils aliOSSUtils;

    @PostMapping("/upload")
    public Result upload(MultipartFile image) throws IOException {
        log.info("文件上传,文件名:{}",image.getOriginalFilename());
        //调用阿里云工具类进行文件上传
        String url = aliOSSUtils.upload(image);
        log.info("文件上传成功,文件访问的url:{}",url);
        return Result.success(url);
    }
}

十一、登录

1.登录功能

在postman中进行测试

code为1 表示登陆成功

若失败

2.登录校验

看员工是否登录 再进行其他操作

HTTP是无状态的 每一次请求都是独立的 下一次请求不会携带上一次请求的数据

故要存储标记 在业务操作前判断是否标记 

(1)会话技术

会话: 浏览器和服务器之间的一次连接,我们就叫做会话

            用户打开浏览器访问web服务器的资源,会话建立,直到有一方断开连接,会话结束。一次会话中可以包含多次请求和响应。

会话跟踪:一种维护浏览器状态的方法,服务器需要识别多次请求是否来自于同一浏览器,以便                在同一次会话的多次请求间共享数据

             方案:

                1.客户端会话跟踪技术: Cookie

                HTTP协议中支持的技术 but移动端无法使用,不安全因为用户可以禁用,不能跨域(比如前后端分离的开发程序,需要独立部署)

                2.服务端会话跟踪技术: Session

                存值取值,存储在服务端,安全 but服务器集群环境下无法直接使用以及Cookie的缺点

                3.令牌技术(主流)

                支持PC端、移动端, 解决集群环境下的认证问题,减轻服务器端存储压力  but需要自己实现(还需要前端人员配合)

(2)JWT令牌

        JSON WEB TOKEN

        用于在通信双方以json数据格式安全的传输信息 进行了安全的封装。由于数字签名的存在,这些信息是可靠的。

        由三部分组成:

        Header(头) 记录类型以及签名算法等 Base64编码

        Payload(有效载荷) 携带一些自定义信息、默认信息等

        Signature(签名) 防止Token被篡改,保证安全性

生成:

/**
     * 生成jwt令牌
     */
    @Test
    public void testGenJwt(){
        Map<String, Object> claims = new HashMap<>();
        claims.put("id",1);
        claims.put("name","tom");

        String jwt = Jwts.builder()
                .signWith(SignatureAlgorithm.HS256, "itheima")
                .setClaims(claims)
                .setExpiration(new Date(System.currentTimeMillis() + 3600 * 1000)) //设置有效期为1h
                .compact();
        System.out.println(jwt);
    }

生成的jwt可以在jwt官网解码

解析令牌

    /**
     * 解析jwt令牌
     */
    @Test
    public void testParseJwt(){
        Claims claims = Jwts.parser()
                .setSigningKey("itheima")
                .parseClaimsJws("eyJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoidG9tIiwiaWQiOjEsImV4cCI6MTcyMDg3NTMyOX0.2KbFdYQN6TiJKIkp3ZpFWewO7FCU8TlNsTNW-cqtImA")
                .getBody();
        System.out.println(claims);
    }

}

注意:秘钥需配套

用户登录成功后,系统会自动下发jwt令牌,然后再后续的每次请求中,都需要在请求头header中携带到服务器,请求头的名称为token,值为 登录时下发的jwt令牌

若检测到用户未登录,则会返回如下固定的错误信息

测试成功!

 

校验令牌的有效性  下面两个

(3)过滤器Filter

是JavaWeb三大组件之一(Servlet、Filter、Listener)

可以把对资源的请求拦截下来,从而实现一些特殊的功能

过滤器一般完成一些通用的操作,such as 登录校验、统一编码处理、敏感字符处理等

初始化和销毁的方法是有默认实现的 可以不写出来

FilterChain.doFilter之前可以执行放行前的逻辑  同时放行后还可以执行放行后的逻辑

Filter拦截路径  @WebFilter(urlPatterns = " ")

过滤器链

在一个web应用中,可以配置多个过滤器,这多个过滤器就形成了一个过滤器链

优先级按类名的自然排序

登录校验

fastJSON 阿里巴巴提供的一个手动转换成json的依赖包

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>2.0.0</version>
        </dependency>

try-catch 快捷键 ctrl+alt+t

关注其业务流程

@Slf4j
@WebFilter(urlPatterns = "/*")
public class LoginCheckFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        //强转
        HttpServletRequest req = (HttpServletRequest) servletRequest;
        HttpServletResponse resp = (HttpServletResponse) servletResponse;
        //获取请求的url
        String url = req.getRequestURL().toString();
        log.info("请求的url:{}",url);
        //判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("login")){
            log.info("登录操作,放行...");
            filterChain.doFilter(servletRequest,servletResponse);
            return;
        }
        //获取请求头的令牌(token)
        String jwt = req.getHeader("token");
        //判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }
        //解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            log.info("解析令牌失败,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return;
        }
        //放行
        log.info("令牌合法,放行");
        filterChain.doFilter(servletRequest,servletResponse);
    }
}
(4)拦截器Interceptor

是一种动态拦截方法调用的机制,类似于过滤器,spring框架中提供的,用来动态拦截控制器方法的执行

作用即拦截请求,在指定的方法调用前后,根据业务需要执行预先设定的代码

1.定义拦截器

ctrl+o 重写方法 

@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //目标资源方法运行前运行,返回true放行,返回false不放行
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("preHandle ...");
        return true;
    }

    @Override //目标资源方法运行后运行
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override //视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

2.配置拦截器

@Configuration //配置类
public class WebConfig implements WebMvcConfigurer {
    @Autowired
    private LoginCheckInterceptor loginCheckInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginCheckInterceptor).addPathPatterns("/**");
    }
}

 

拦截路径

执行流程

若过滤器和拦截器同时存在

登录校验

此处无需强转 因为传入的值中就是HTTP的

@Slf4j
@Component
public class LoginCheckInterceptor implements HandlerInterceptor {
    @Override //目标资源方法运行前运行,返回true放行,返回false不放行
    public boolean preHandle(HttpServletRequest req, HttpServletResponse resp, Object handler) throws Exception {
        System.out.println("preHandle ...");
        //获取请求的url
        String url = req.getRequestURL().toString();
        log.info("请求的url:{}",url);
        //判断请求url中是否包含login,如果包含,说明是登录操作,放行
        if(url.contains("login")){
            log.info("登录操作,放行...");
            return true;
        }
        //获取请求头的令牌(token)
        String jwt = req.getHeader("token");
        //判断令牌是否存在,如果不存在,返回错误结果(未登录)
        if (!StringUtils.hasLength(jwt)){
            log.info("请求头token为空,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        //解析token,如果解析失败,返回错误结果(未登录)
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            log.info("解析令牌失败,返回未登录的信息");
            Result error = Result.error("NOT_LOGIN");
            String notLogin = JSONObject.toJSONString(error);
            resp.getWriter().write(notLogin);
            return false;
        }
        //放行
        log.info("令牌合法,放行");
        return true;
    }

    @Override //目标资源方法运行后运行
    public void postHandle(HttpServletRequest req, HttpServletResponse resp, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle...");
    }

    @Override //视图渲染完毕后运行,最后运行
    public void afterCompletion(HttpServletRequest req, HttpServletResponse resp, Object handler, Exception ex) throws Exception {
        System.out.println("afterCompletion...");
    }
}

3.异常处理

/**
 * 全局异常处理器
 */
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class) //捕获所有异常
    public Result ex(Exception ex){
        ex.printStackTrace();
        return Result.error("对不起,操作失败,请联系管理员");
    }
}

十二、事务管理

事务是一组操作的集合,它是一个不可分割的工作单位,这些操作要么同时成功,要么同时失败

1.spring事务管理

my

开启事务管理日志

logging:
  level:
    org.springframework.jdbc.support.jdbcTransactionManager: debug

2.rollbackFor属性

如果代码这样抛出异常,会发现还是会提交

是因为!!
默认情况下,只有出现RuntimeException才回滚异常。rollbackFor属性用于控制出现何种异常类型,回滚事务

以下,所有异常都会进行事务的回滚

3.propagation属性

事务传播行为:指的就是一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行事务控制

 

因为delete和insert公用同一个事物 所以发生错误都会回滚 传播行为默认required 改成requires_new会把上面的事务挂起 然后开启一个新的事务 当此事物完成之后 又回到刚刚挂起的事务

此插件会高亮事务信息(日志的过滤筛选)

十三、AOP

1.基础

面向切面编程、面向方面编程(就是面向特定方法编程)

案例部分功能运行较慢,定位执行耗时较长的业务方法,此时需要统计每一个业务方法的执行耗时

只需定义模版方法 把记录方法执行耗时这一部分公共的逻辑代码定义

实现:动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程

(1)导入依赖

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

(2)编写aop程序 

核心概念

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

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

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

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

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

功能增强

一旦执行了aop程序的开发 最终运行的就不再是原始的目标对象 而是基于目标对象所生成的代理对象

2.进阶

(1)通知类型

后俩通知类型互斥

e.g:

有大量一样的代码就抽取

@PointCut 将公共的切点表达式抽取出来,需要用到时引用该切点表达式即可。

若想在其它的切入类中也使用 需要将private改为public

(2)通知顺序

原始方法运行之前的类名排名越靠前越先执行

                之后类名排名越靠后越先执行

(3)切入点表达式

描述切入点方法的一种表达式

主要用来决定项目中哪些方法需要加入通知

1.execution 

e.g:

包名.类名还是不建议省略 因为匹配的范围过大 影响匹配的效率

还可以代替一部分例如*Service

描述两个不相干的方法

2.annotation 

匹配标识有特定注解的方法

注解目前仅起到标识的作用 无需属性

给方法加上注解(想匹配什么方法只需要加上注解即可)

切面类

(4)连接点

获取方法运行时的相关信息

Around才有返回值

3.记录操作日志

(1)导入依赖 上面有

(2)准备数据库表结构,引入对应实体类

(3)自定义注解@Log

(4)切面类

@Slf4j
@Component
@Aspect
public class LogAspect {
    @Autowired
    private OperateLogMapper operateLogMapper;

    @Autowired
    private HttpServletRequest request;

    @Around("@annotation(com.itheima.anno.Log)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {

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

        //操作时间
        LocalDateTime operateTime = LocalDateTime.now();

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

        //操作方法名
        String methodName = joinPoint.getSignature().getName();

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

        //记录原始目标方法运行之前的毫秒值
        long begin = System.currentTimeMillis();
        //调用原始目标方法运行
        Object result = joinPoint.proceed();
        //记录原始目标方法运行之后的毫秒值
        long end = System.currentTimeMillis();
        //方法返回值
        String returnValue = JSONObject.toJSONString(result);

        //操作耗时
        Long costTime = end - begin;

        //记录操作日志
        OperateLog OperateLog = new OperateLog(null,operateUser,operateTime,className,methodName,methodParams,returnValue,costTime);
        operateLogMapper.insert(OperateLog);

        log.info("AOP记录操作日志:{}",OperateLog);

        return result;
    }
}

(5)最后由于我们对于增删改进行操作,所以直接给对应的操作Controller上加上注释@Log即可

测试成功

十四、SpringBoot原理

1.配置优先级&&打包

测试 生效的是.properties的文件  其次是.yml 再是.yaml

SpringBoot 除了支持配置文件属性配置,还支持java系统属性命令行参数的方式进行属性配置

命令行参数的优先级比java系统属性高  java系统属性的优先级比三种配置文件优先级高

执行maven打包指令package可以把项目打包成jar包 然后执行java指令可以运行jar包

插件

双击即可

在target目录可以找到jar包  直接在cmd中运行java指令 就可以运行程序了 (默认端口号8080)

总结

2.Bean管理

(1)获取bean

spring项目启动时,会把bean都创建好放在IOC容器中(需要什么bean对象就依赖注入即可),如果想要主动获取这些bean,可以通过一下几种方式:

1.根据name获取bean

2.根据类型获取bean

3.根据name获取bean(带类型转换)

名称注意是首字母要小写  默认的是方法名!!!

三个都是一样 说明默认单例模式

(2)bean作用域

bean对象是单例还是多例是取决于作用域的

@Lazy注解 会延迟Controller实例化 延迟到第一次使用的时候(用构造方法进行测试)

本来默认容器启动的时候就实例化了

会发现每一次调用getBean拿到的都是一个全新的bean对象

(3)第三方bean

自己定义的cean声明非常简单,仅需在类前加上注解@Component以及三个衍生注解@Controller、@Service、@Repository

还有种情况,类不是自己定义的,而是引入的第三方依赖中提供的

比如 学习解析XML时引入的此依赖

@Bean注解

定义在了启动类当中,不建议,我们要尽量保证启动类的纯粹性

那为什么启动类可以直接声明Bean对象呢?

因为启动类也是一个配置类

可以单独定义一个配置类 通过@Configuration声明

最后注入对象即可


3.SpringBoot原理(高频面试题!)

(1)起步依赖 maven的依赖传递

(2)自动配置

即当spring容器启动后,一些配置类、bean对象就自动存入到了IOC容器中,无需手动声明,简化开发,省去配置操作。

原理:(如果面试问到springboot的原理,实际上就是在问自动配置的原理)

(1)导入第三方依赖包

@SpringBootApplication具有包扫描的作用 但是扫描范围只有当前包及其子包

那怎么把第三方依赖包导入呢?

solution1:@ComponentScan组件扫描

这种方法一旦包变多 会很繁琐 需要查看很多包以及其很多类

solution2:@Import导入。使用@Import导入的类会被Spring加载到IOC容器中,成为IOC容器中的Bean对象

以下是四种导入形式

1.

2.

3.

返回全类型 将哪些类交给IOC容器管理

4.第三方依赖自己来指定 √

(2)源码跟踪剖析 

https://www.bilibili.com/video/BV1m84y1w7Tb/?p=190&spm_id_from=pageDriver&vd_source=865f32e12aef524afb83863069b036aa

是不是自动装备的类全部注册为IOC容器的Bean?

No!! 有个注解是@ConditionalOnMissingBean 意思是达到条件才会注册装配

(3)@Conditional

(4)自定义starter

为什么有了起步依赖还要自定义呢?

因为在实际开发中,有很多的第三方技术,不是所有的第三方技术都提供了与springboot整合的starter起步依赖,但这些技术又非常通用,在很多项目中都在使用,比如阿里云OSS对象存储服务。所以,为了方便起见,经常会定义一些公共组件,提供给各个项目团队使用。而在springboot项目中,一般会将这些公共组件封装为springboot的starter(起步依赖)

怎么区分springboot官方提供的还是其他提供?

如果项目中没有找到.iml文件怎么处理?

找到starter的全路径之后cmd输入命令mvn idea:module即可

1.创建maven模块  starter依赖管理 autoConfigure自动配置

aliyun-oss-spring-boot-starter仅保存pom.xml和.iml文件

aliyun-oss-spring-boot-autoconfigure保存pom.xml和.iml文件和src目录

2.在starter中引入autoconfigure

3.在autoconfigure引入阿里云oss的相关依赖

4.将工具类复制到autoconfigure这边

因为无需扫描将这两个类的@Component注解删掉  同时注入@A..也不能用了,就会有以下的解决方法

5.将阿里云OSSUtils声明成了IOC容器中的bean 要想使用直接注入工具类即可

6.加入getter\setter方法 避免有空指针

7.在autoConfigure中需要用enable注解来声明把aliyunproperties交到容器中管理,才能从配置文件aliyunoss中读取到封装使用

8.这份文件是要定义在内路径下的

在resources目录下 创建一级目录

复制文件名在上面创建的目录下创建文件

4.Web后端总结

 

十五、Maven高级

1.分模块设计与开发

拆分为若干个子模块,方便项目的管理维护,组件复用

无需创建springboot项目因为仅仅存放的是实体类  创建Maven项目

将对应的类移动到这个项目中,再在组件中引入外部依赖

2.继承与聚合

继承

子工程继承父工程中的配置信息,各个子工程共有的依赖可以直接定义在父工程中,比如lombok依赖,为了简化依赖配置、统一管理依赖 通过<parent>标签指定父工程坐标

springboot项目都有一个统一的父工程 spring-boot-starter-parent

在maven中一个工程只能继承一个父工程(单继承)

所以在使所有子工程的都是父工程时,也要把父工程的父工程配置成spring-boot-starter-parent

1.创建maven模块tlias-parent,该工程为父工程,设置打包方式pom(如果不设置默认jar包)

常见的打包方式:

设置打包方式:

2.在子工程的pom.xml文件中配置继承关系

最后的标签指定父工程的pom.xml文件

3.在父工程中配置各个工程共有的依赖(子工程会自动继承父工程的依赖)

版本锁定

在maven中,可以在父工程的pom文件中通过<dependencyManagement>来统一管理依赖版本

并不会直接加入,在子工程中如果需要用到,则加入以下代码(就是无需版本号)修改版本号直接在父工程修改即可

自定义属性/引用属性

依赖零散处理,方便集中管理维护

聚合

将多个模块组织成一个整体,同时进行项目的构建

聚合工程  一个不具有业务功能的“空”工程(有且仅有一个pom文件)=》父工程就是

对聚合工程进行打包,那所有聚合的模块都会被打包

或者是安装模块到本地仓库,直接在聚合工程上install安装操作,那么所有的模块都会安装到本地仓库

作用: 快速的构建项目(无需根据依赖关系手动构建,直接在聚合工程上构建即可)

maven中可以通过<modules>标签设置当前聚合工程所包含的子模块名称

对聚合工程进行指令 会对所有子模块进行

3.私服

同一公司两个项目组之间通过私服进行资源的共享

是一种特殊的远程仓库,它是架设在局域网内的仓库服务,用来代理位于外部的中央仓库,用于解决团队内部的资源共享与资源同步问题

查找顺序:本地仓库-》私服-》中央仓库

学会如何上传到私服和从私服中下载依赖即可

只要不是snapshot都会默认上传到release仓库

1.在maven中配置访问私服的用户名和密码(身份凭证)

手动找到maven的配置文件地址settings.xml

2.指定私服仓库的url地址

3.在maven中配置私服的url地址

仓库组就是将若干个仓库划到一个仓库组当中

4.从私服中下载jar包到本地仓库即可使用

直接在pom.xml导入

发布直接运行deploy命令

  • 15
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值