Spring-Boot学习笔记

这个笔记是在自己学习的过程中根据实际用到的和学到的整理出来的,可能会有缺失,错误等,主要是给激励自己学习,遇到写不下去的情况给自己一个参考,请各位大佬发现问题提出问题时能嘴下留情,也希望多提建议,谢谢。
本笔记长期更新(更新日期2024年9月21日)

目录

  • 第1章.固定格式参考
    • 1.1application.yml
    • 1.2mapper.xml(详细操作见另一个文件[XML数据库操作笔记]())
    • 1.3application.propertes(和yml二选一)
  • 第2章.功能性封装类和工具类参考
    • 2.1.Result类
    • 2.2.PageBean类
    • 2.3.AliyunOssUtils类(详细的阿里云操作见[第九章阿里云OSS的使用]())
    • 2.4.AliyunOSSProperties类(详细的阿里云操作见[第九章阿里云OSS的使用]())
    • 2.5.JWTUtils类
  • 第3章.SpringBoot工程
    • 3.1.创建一个SpringBoot工程
    • 3.2.SpringBoot项目配置文件优先级
    • 3.3.SpringBoot层级结构
      • 3.3.1.控制层Controller
      • 3.3.2.服务层Service
      • 3.3.3.数据访问层Repository
        • 3.3.3.1.Mapper
        • 3.3.3.2.Dao
      • 3.3.4.实体层pojo/entity
      • 3.3.5.工具类Utils
      • 3.3.6.其他可能存在的软件包
  • 第4章.常用注解
    • 4.1.配置和管理Bean的常用注解
    • 4.2.Controller内常用注解
    • 4.3.Service内常用注解
    • 4.4.Mapper内常用注解
    • 4.5.Dao内常用注解
    • 4.6.封装类中常用注解
    • 4.7.测试和方法执行
    • 4.8.异常(详细用法见[第八章异常]())
    • 4.9.过滤器和拦截器
    • 4.10.AOP
    • 4.11.自定义注解(使用方法见AOP)
  • 第5章.分页查询
    • 5.1.接受前端发送的数据
      • 5.1.1.通过封装类获取
      • 5.1.2.直接获取
    • 5.2.不使用插件的分页查询
    • 5.3.使用插件PageHelper的分页查询
  • 第6章一对多的多表操作
  • 第7章事务(更多请见单独文档[事务](https://blog.csdn.net/2403_86693263/article/details/142312629?spm=1001.2014.3001.5501))
    • 7.1.事务的特性
    • 7.2.Spring事务管理
      • 7.2.1.Transactional注解
  • 第8章.异常
  • 第9章.阿里云OSS的使用
    • 9.1.配置环境变量
    • 9.2.引入依赖
    • 9.3.配置application.yml
    • 9.4.引入工具类和封装类
      • 9.4.1.工具类AliyunOssUtils
      • 9.4.2.封装类AliyunOSSProperties
    • 9.5.MultipartFile介绍
    • 9.6.实现参考
  • 第10章.会话技术
    • 10.1.Cookie
    • 10.2.Session
    • 10.3.JWT令牌
      • 10.3.1.JWT组成介绍
      • 10.3.2.引入依赖
      • 10.3.3.配置环境变量(可以不配置,直接写在服务器里)
      • 10.3.4.配置JWTUtils工具类
      • 10.3.5.使用示例
  • 第11章.过滤器的使用(用于登录判断等)
    • 11.1.定义过滤器
  • 第12章.拦截器的使用(用于登录判断等)
    • 12.1.拦截器配置
  • 第13章.AOP
    • 13.1.引入依赖
    • 13.2.注解介绍
    • 13.3.切入点
      • 13.3.1.execution
      • 13.3.2.@annotation
      • 13.3.3.连接点
    • 13.4.ProceedingJoinPoint的方法
  • 第14章.Bean的获取、引入和使用
    • 14.1.Bean
    • 14.2.获取自己封装的Bean(在包下并且能被启动器扫描到的)
    • 14.3.第三方依赖的Bean
      • 14.3.1.启动器下导入(不推荐)
      • 14.3.2.设置一个配置类导入
    • 14.4.第三方项目的Bean
      • 14.4.1.@ComponentScan组件扫描(不推荐)
      • 14.4.2.@Import导入(不推荐,使用@Import导入的类会被Spring加载到IOC容器中)
      • 14.4.3.@Enable注解方式(推荐)
  • 第15章.高级Maven使用之模块化
    • 15.1依赖继承
    • 15.2.依赖聚合
    • 15.3.私服

第1章.固定格式参考

1.1application.yml

使用注意事项:

  • 需要删除application.properties,SpringBoot项目会优先按照那个执行,如果不想删除重命名也是可以的
  • 创建一个SpringBoot项目一般是不带的,需要手动去创建一个,在模块或者项目的src/resources目录下

书写规范:

  1. 严格的缩进关系,代表同级,下一级,上一级
  2. 冒号后面跟一个空格再跟值
  3. 如果不知道yml怎么写,知道properties怎么写,这个就是把properties的每个分隔点.换成冒号:加换行,然后合并
spring:
  application:
    name: 模块或者项目名称
#数据库配置
  datasource:
    url: jdbc:mysql://localhost:3306/数据库名称
    #这个是8.0的配置,如果是5.0需要改成com.mysql.jdbc.Driver
    driver-class-name: com.mysql.cj.jdbc.Driver
    username: 数据库用户名
    password: 数据库密码
#文件上传大小限制
  servlet:
    multipart:
      max-file-size: 单个文件的最大大小
      max-request-size: 整个请求所有文件的最大大小

mybatis:
  configuration:
  	#让mybatis每次运行sql代码的时候再控制台打印输出
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
    #mybatis大小写驼峰转换
    map-underscore-to-camel-case: true

#spring日志打印
logging:
  level:
    org.springframework.jdbc.support.JdbcTransactionManager: debug

#阿里云OSS地址
aliyun:
  oss:
    endpoint: 阿里云端点
    bucketName: 存储空间名称
    #这两个一般不用写,一般配置在环境变量里面了。这两个是配置Access Key ID和Access Key Secret
    accessKeyId: 从阿里云获取Access Key Id
    accessKeySecret: 从阿里云获取Access Key secret
  
server:
	port: 端口号
	address: 地址

1.2mapper.xml(详细操作见另一个文件XML数据库操作笔记)

书写规范

  1. 开始不能有任何空格,换行,必须保证顶格
  2. namespace里面写映射到的对应Mapper接口,从包名开始写,例如com.example.pojo.UserMapper

DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="">

mapper>

1.3application.propertes(和yml二选一)

尽量使用yml文件,这个也可以用,并且SpringBoot会优先找这个

spring.application.name=模块或项目名称
#数据库连接
spring.datasource.url=jdbc:mysql://localhost:3306/数据库名称
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.username=数据库用户名
spring.datasource.password=数据库密码

#让mybatis每次运行sql代码的时候再控制台打印输出
mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
#让mybatis自动让两个规范的命名转换,小驼峰转下划线
mybatis.configuration.map-underscore-to-camel-case=true

#spring打印日志
logging.level.org.springframework.jdbc.support.JdbcTransactionManager = debug

#阿里云OSS配置
aliyun.oss.endpoint=阿里云端点
aliyun.oss.bucketName=存储空间名称

#设置启动端口
server.port=端口号

第2章.功能性封装类和工具类参考

2.1.Result类

一般用于封装,然后将数据发送给前端

注意事项:

前端接收的code也不一定是code,也有可能是flag,接收的也有概率是布尔值等,一定要随机应变,不要粘过去就是用

前端接收的数据不一定是data,也有接收list的,此时就需要把里面的data数据名改成前端接收的

@Data
public class Result {

    private Integer code; //编码:1成功,0为失败
    private String msg; //错误信息
    private Object data; //数据

    public static Result success() {
        Result result = new Result();
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result success(Object object) {
        Result result = new Result();
        result.data = object;
        result.code = 1;
        result.msg = "success";
        return result;
    }

    public static Result error(String msg) {
        Result result = new Result();
        result.msg = msg;
        result.code = 0;
        return result;
    }

}

2.2.PageBean类

一般用于分页功能

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
	private Long total; //总记录数
	private List rows; //当前页数据列表
}

2.3.AliyunOssUtils类(详细的阿里云操作见第九章阿里云OSS的使用)

当要使用阿里云OSS存储的时候使用这个工具类来快速完成向云端存储

注意事项:

  1. 需要本机环境变量中已经配置好了OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET
@Slf4j
public class AliyunOSSUtils {

    /**
     * 上传文件
     * @param endpoint endpoint域名
     * @param bucketName 存储空间的名字
     * @param content 内容字节数组
     */
    public static String upload(String endpoint, String bucketName, byte[] content, String extName) throws Exception {
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = UUID.randomUUID() + extName;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content));
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            log.error("Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.");
            log.error("Error Message:" + oe.getErrorMessage());
            log.error("Error Code:" + oe.getErrorCode());
            log.error("Request ID:" + oe.getRequestId());
            log.error("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            log.error("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.");
            log.error("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName;
    }

}

2.4.AliyunOSSProperties类(详细的阿里云操作见第九章阿里云OSS的使用)

也是用来简化阿里云配置的,这个会通过注解@ConfigurationProperties(prefix = “aliyun.oss”)从配置文件application.properties或者yml中找到并赋值给对应的值

注意事项:

  1. 需要在配置文件中配置好名称完全一致的值
  2. 这个是简化配置的,如果说配置了二十多个属性,有好几个用到阿里云OSS操作的类,总不能在里面一个个写,所以也需要在用到该类的里面使用@Autowried填装
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
}

2.5.JWTUtils类

用于生成和解析TOKEN的,但是必须要用JWT令牌并且引入了正确的依赖

public class JwtUtils {
    
    private static Long expire = 43200000L;//这个是TOKEN的有效期毫秒数,这个是12小时
    private static String JWT_KEY = "JWT_SECRET";//如果配置了环境变量就这么写
    //如果没配置环境变量就这么写
    //private static String JWT_SECRET = "密钥"

    //生成JWT令牌TOKEN
    public static String generateJwt(Map<String, Object> claims) {
        String signKey = System.getenv(JWT_KEY);//如果没配置环境变量就不用写了
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    //解析JWT令牌TOKEN
    public static Claims parseJWT(String jwt) {
        String signKey = System.getenv(JWT_KEY);//如果没配置环境变量就不用写了
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

第3章.SpringBoot工程

3.1.创建一个SpringBoot工程

创建时选择Spring Web依赖自动配置一些基本的WEB环境,比如Tomcat服务器的集成,Spring MVC核心组件等

创建时选择Lombok 可以对封装类进行简化注解

如果需要用到MySql数据库操作和MyBatis勾选MySql DriverMyBatis Framework

**注意事项:**创建好的SpringBoot工程会有一个对应的启动器,这个要和软件包同级,不然Spring会找不到

3.2.SpringBoot项目配置文件优先级

如果项目中同时存在application.properties,application.yml,application.yaml三个文件,三个的优先级顺序是

application.properties>application.yml>application.yaml

但是项目打包以后,使用cmd命令行运行;或者在启动时添加了虚拟机选项和程序实参,又会引入两个新的属性配置,对应的就是java属性配置和命令行参数,这两个的优先级都比配置文件高,并且命令行参数的优先级会更高一些。

java属性配置格式(虚拟机选项):(-Dkey=value),这个大写D是必须写的,然后就是键值对,例如配置端口号就是

-Dserver.port=9000

命令行参数(程序实参):(–key=value)

--server.port=10000

当然,如果打成jar包以后,可以通过这种方式来设置数据库的各项属性来启动项目

启动jar包的方式:

java (这里可以写java属性配置) -jar XXX(jar包名).jar (之类可以写命令行参数) 

启动jar包的时候配置数据库属性:

java -jar XXX.jar --spring.datascoure.url=jdbc://localhost:3306/数据库名称 --spring.datasource.username=名 --spring.datasource.password=密码

所以最终的优先级顺序是:

命令行参数>java属性配置>application.properties>application.yml>application.yaml

3.3.SpringBoot层级结构

3.3.1.控制层Controller

主要负责接收用户请求、处理请求参数、调用适当的服务层(Service)方法,并将结果返回给用户。是用户界面与后端服务器的桥梁。

3.3.2.服务层Service

负责业务逻辑的实现,封装了与业务相关的处理和操作,处理业务规则、数据验证、计算、转换等操作。也会去数据访问层拿到需要的数据。

3.3.3.数据访问层Repository

主要是隐藏数据访问的底层细节,提供给上层(服务层)的一个简洁的接口。

3.3.3.1.Mapper

通常与MyBatis这样的框架结合使用。通过定义SQL映射来处理数据访问。

3.3.3.2.Dao

通常与ORM框架结合使用。将数据访问操作封装到DAO对象中,这些对象通常对应于数据库中的表或者集合。

3.3.4.实体层pojo/entity

不依赖于任何框架或特定API,仅仅是实现了JAVA语言特定的对象。

3.3.5.工具类Utils

一般是一些静态方法,用于提供各种通用功能或者简化某些常见的开发任务。这些工具类在整个应用程序中被广泛使用,提高代码的复用性和简洁性。

3.3.6.其他可能存在的软件包

因为每个项目的不同,还有可能存在异常包exception等。

第4章.常用注解

注解名称 注解位置 注解作用
@Component 类上 Spring能识别这个类并将其作为一个bean对象管理
@Controller,@Service,@Repository都集成了这个注解,一般用这几个
@Autowried 类、方法、字段、构造函数或局部变量上 当一个类需要另一个类的实例作为依赖时,可以使用此注解来告诉 Spring 容器自动注入正确的实例
@Resource(name=“指定注入的bean名称”) 类、方法、字段、构造函数或局部变量上 可以通过名称查找 Bean,如果未指定名称,则默认按照字段名进行装配
不能和Autowried一起使用处理多实现可以用
@Qualifier(value=“制定注入的bean名称”) 类、方法、字段、构造函数或局部变量上 用于解决自动装配时的歧义问题,即当存在多个候选 Bean 时,可以指定具体的 Bean 名称来进行装配
需要和Autowried一起使用处理多实现可以用
@Primary 类上 用于解决在同一个类型下存在多个候选 Bean 时,应该优先使用哪一个 Bean 的问题。
如果一个 Bean 被标记为 @Primary,那么当存在多个候选者时,Spring 会优先选择带有 @Primary 标记的 Bean 进行注入
@Slf4j 类上 来自 Lombok 库,用于自动生成一个 Logger实例
@Log 类上 可以由不同的日志库提供,例如 Log4j2,用于自动生成一个 Logger实例
@Value(“${要引入的值的位置}”) 字段、方法或方法参数级别上 用于直接将配置值注入到 Java 类的字段中。它可以用于注入各种类型的值,如字符串、数字等。
@ConfigurationProperties 类上 可以批量注入属性、验证等,并且可以更容易地与配置文件中的复杂属性结构相匹配。

@Value和@ConfigurationProperties使用举例:

public class Test {//这里的${property.name}是从Spring的PropertySources中获取的属性值,PropertySources多种来源,如application.properties文件,环境变量,命令行参数等。
    @Value("${property.name}")
    private String propertyName;
    
    public void test(@Value("${property.name}") String name)
}


@ConfigurationProperties(prefix = "property")
public class Test{//从配置文件中读取property.name和property.age
    private String name;
    private int age;
}

4.1.配置和管理Bean的常用注解

注解名称 注解位置 注解作用
@Component 类上 Spring能识别这个类并将其作为一个bean对象管理
@Controller,@Service,@Repository都集成了这个注解,一般用这几个
@Autowried 类、方法、字段、构造函数或局部变量上 当一个类需要另一个类的实例作为依赖时,可以使用此注解来告诉 Spring 容器自动注入正确的实例
@Scope 类上 用于指定一个 Bean 的作用域,如单例(singleton)、原型(prototype)、请求(request)、会话(session)等
@Bean 类上 注解用于在 @Configuration 类中声明一个方法,该方法返回的对象将被注册为 Spring 容器中的一个 Bean
@Lazy 类上 用于延迟初始化 Bean。默认情况下,Spring 容器会在启动时立即初始化所有的单例 Bean,而使用 @Lazy 注解可以推迟到第一次使用时才进行初始化。
@ComponentScan 启动器类上 用于指定 Spring 应该扫描哪些包来查找组件
@Import 类上 用于导入其他配置类,从而使它们定义的 Bean 也被纳入当前配置类的管理范围。
@Conditional 类上或具体的@Bean方法上 注解用于根据某些条件决定是否创建某个 Bean。
@ConditionalOnClass 类上或具体的@Bean方法上 用于根据类路径上是否存在某个类来决定是否创建 Bean。
@ConditionalOnMissingBean 类上或具体的@Bean方法上 用于根据容器中是否已经存在某个类型的 Bean 来决定是否创建新的 Bean。如果指定类型的 Bean 已经存在,则不会创建新的 Bean。
@ConditionalOnProperty 类上或具体的@Bean方法上 用于根据属性值来决定是否创建 Bean。它可以检查配置文件中的属性是否存在以及其值是否满足指定条件。

4.2.Controller内常用注解

注解名称 注解位置 注解作用
@RestController 类上 复合注解,集成了@Controller和@ResponseBody的功能
@Controller 类上 用来接收HTTP请求,可以根据地址来判断执行哪个功能
@ResponseBody 类上或者方法上 和@RequestBody相反,这个是在JAVA向前端发送时将数据转换为JSON数据
@RequestMapping(“/路径地址”,method=RequestMethod.请求方式) 类或者方法上 基础的注解,用来指定控制器方法处理哪些HTTP请求,定义一个或者多个URL映射
@GetMapping 类或者方法上 专门用于处理Get请求
@PostMapping 类或者方法上 专门用于处理POST请求
@PutMapping 类或者方法上 专门用于处理Put请求
@DeleteMapping 类或者方法上 专门用于处理Delete请求
@RequestParam(value=“获取的值的键名”,defaultValue=“如果没有参数的默认值”,required=true/false) 参数上 从HTTP请求的查询字符串中获取参数,可以指定一个请求参数的名字,并且可以指定默认值,是否必须存在等属性。获得的方式类似于map,根据键获得值
@RequestBody 参数上 当客户端发送JSON或者其他格式的数据作为请求体时,Spring会尝试将主体的内容转换为相应的JAVA对象
@PathVariable() 参数上 用于将URL路径的模板变量绑定到方法参数上。

4.3.Service内常用注解

注解名称 注解位置 注解作用
@Service 类上 表明类是一个服务提供者
@Transactional 类上或者方法上 表名这个方法是事务或者整个类下所有方法都是事务

4.4.Mapper内常用注解

注解名称 注解位置 注解作用
@Mapper 类上 表明类是一个映射接口方法
@MapKey(“键名”) 方法上 声明Map的键名,不写也行
@Param(“需要传递的参数名”) 参数上 SpringBoot一般不用。在MyBatis框架中,这个注解用于在SQL映射文件中的预编译语句中传递多个参数
@Options(useGeneratedKeys=true/false,keyProperty=“键名”) 方法上 在完成数据库操作后会获取数据库自动成成的键值并将这个值设置到对应的对象属性上
@Result 方法上 描述单个列如何映射到 Java 对象的属性上。
@Results 方法上 用于包装一个或多个 @Result 注解,可以用来描述一个完整的映射规则集合。
@Select 方法上 用来编写 SELECT 查询语句。可以直接在 Mapper 接口的方法上使用该注解,并提供 SQL 语句。
@Update 方法上 用来编写 UPDATE 语句。可以直接在 Mapper 接口的方法上使用该注解,并提供 SQL 语句。
@Insert 方法上 用来编写 INSERT 语句。可以直接在 Mapper 接口的方法上使用该注解,并提供 SQL 语句。
@Delete 方法上 用来编写 DELETE 语句。可以直接在 Mapper 接口的方法上使用该注解,并提供 SQL 语句。

Result和Results的用法示例:

就是在JAVA中使用注解完成应该在XML的resultMap标签的功能

@Results({
    @Result(column="user_id",property="userId",id=true),
    @Result(column="user_name",property="userName"),
    @Result(column="phone",property="phone")
})
public List<User> select();

4.5.Dao内常用注解

注解名称 注解位置 注解租用
@Respository 类上 表名类是一个数据访问层或持久化层的组件

4.6.封装类中常用注解

注解名称 注解位置 注解作用
@Data 类上 生成get set hashcode equals toString方法
@AllArgsConstructor 类上 生成全参构造
@NoArgsConstructor 类上 生成无参构造
@Getter 类上 生成Getter方法
@Setter 类上 生成Setter方法
@DataTimeFormat(pattern = “”) 成员变量上 按照pattern写的规则解析String文件变成LocalDateTime(LocalDate/LocalTime)

4.7.测试和方法执行

注解名称 注解位置 注解作用
@Test 方法上 用于标记一个方法作为测试方法。
@BeforeAll 方法上 在所有测试方法之前只执行一次的方法。
@AfterAll 方法上 在所有测试方法之后只执行一次的方法。
@BeforeEach 方法上 在每个测试方法之前执行一次的方法。
@AfterEach 方法上 在每个测试方法之后执行一次的方法。
@ParameterizedTest 方法上 JUnit5的注解,测试方法可以使用多个参数集来执行多次,每组参数对应一次执行
@ValueSource 方法上 提供一个或多个固定值作为参数化测试的参数源。
@CsvSource 方法上 提供 CSV 格式的字符串作为参数化测试的参数源。

@ParameterizedTest、@ValueSource和@CsvSource示例:

@ParameterizedTest
@ValueSource(ints = {1, 2, 3})
void testNumbers(int number) {
    System.out.println(number);//会执行三次,分别打印1,2,3
}

@ParameterizedTest
@CsvSource({"1, one", "2, two", "3, three"})
void testNumbers(String number, String word) {
    System.out.println(number + ": " + word);//会执行三次,分别打印1:one,2:two,3:three
}

4.8.异常(详细用法见第八章异常)

注解名称 注解位置 注解作用
@RestControllerAdvice 类上 该类中的方法将用于处理特定的异常
@ExceptionHandler(value=“异常类名”) 方法上 用于标记处理特定异常的方法。当应用程序抛出某种异常时,被注解的方法会被调用,并处理该异常。

4.9.过滤器和拦截器

注解名称 注解位置 注解作用
@WebFilter(“写拦截路径,如果是/*则代表全部拦截”) 类上 声明一个类作为 Web 应用程序中的过滤器
@ServletComponentScan 类上 用于指示容器扫描并自动部署在类路径中的 Servlet、过滤器(Filter)、监听器(Listener)和其他 Servlet 组件。
@Configuration 类上 用于声明一个类作为配置类,可以以编程的方式定义和配置 Spring 应用程序中的依赖关系,这种方式称为基于 Java 的配置。

4.10.AOP

注解名称 注解位置 注解作用
@Aspect 切面类上 告诉Spring这是一个包含切面逻辑的类。
@Before 切面类的方法上 定义一个前置通知,在目标方法调用之前执行。
@After 切面类的方法上 定义一个后置通知,在目标方法执行完毕之后执行,无论方法是否成功执行。
@AfterReturning 切面类的方法上 定义一个返回后通知,在目标方法成功执行并返回结果之后执行。
@AfterThrowing 切面类的方法上 定义一个异常抛出后通知,在目标方法抛出异常后执行。
@Around 切面类的方法上 定义一个环绕通知,在目标方法调用前后都执行自定义的行为。
@annotation 上面五个的括号里面 使用自定义注解来标记需要进行切面。
@pointcut 方法上 定义一个可重用的切入点表达式。

4.11.自定义注解(使用方法见AOP)

注解名称 注解位置 注解作用
@interface 元注解,在代替类的样式(class/interface) 是 Java 语言中的一个保留关键字,用于定义注解类型。
@Retention 自定义注解类上 指定注解的保留策略,可以是 SOURCE(源码阶段)、CLASS(字节码阶段)或 RUNTIME(运行时阶段)。
@Target 自定义注解类上 指定注解的应用目标,如 TYPE(类、接口)、METHOD(方法)、FIELD(字段)等。
@Documented 自定义注解类上 指定注解是否应该被包含在 JavaDoc 文档中。
@Inherited 自定义注解类上 指定子类是否继承父类上的注解。
@Repeatable 自定义注解类上 指定注解是否可以重复使用在同一元素上。

第5章.分页查询

分页查询一般是同时包含分页和查询了

有两种接收数据的方法,一种是通过封装类获得,一种是通过一个个接收

5.1.接受前端发送的数据

5.1.1.通过封装类获取

建议是通过封装类的方式,这样再传入数据多的时候,不用一个个写接收,让Spring自己对应封装好,但是这样的话需要注意,名字和前端返回的必须完全一致

样例参考:
假如我们可以通过姓名,手机号查询,并且有分页

已知前端发送回来的数据如下

{
    "name":"张",
    "phone":1234,
    "page":1,
    "pageSize":5
}

所以后端封装类(UserQueryParam)参考

@Data
public class UserQueryParam {
    private Integer page = 1;//前端未传回时默认为第一页
    private Integer pageSize = 10;//前端未传回时默认为查找十条数据
    private String name;
    private String phone;
}

定义返回对象PageBean类

这个类也是要取决于前端写的接收数据的名称来定义,这个total也有写在别的地方的,视情况而定。有的是取res.data.list,也有res.data.data,也有res.data.rows。

下面这个例子是前端接收res.data.total和res.data.rows的例子。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
	private Long total; //总记录数
	private List rows; //当前页数据列表,如果是list或者data成员变量的名要跟着变
}

Controller类

@RestController
@RequestMapping("/users")
public class Controller {
    
    @Autowried
    private Service service;
    
    public Result select(UserQueryParam param){
        //调用Service层进行数据处理并返回
        PageBean pageBean = service.select(param);
        return Result.success(pageBean);
    }
}

5.1.2.直接获取

因为同时是做了分页和查询,所以接受的参数写起来也会很麻烦,不建议这么写

查询一般是GET请求,请求参数一般就是跟在路径后面的字符串

例如:/users?page=1&pageSize=10

@RestController
@RequestMapping("/users")
public class Controller{
    
    @Autowried
    private Service service;
    
    @GetMapping
    public Result select(@RequestParam(defaultValue="1") Integer page,
                        @RequsetParam(defaultValue="10") Integer Pagesize,//这两个是做分页功能用到的
                        @RequestParam(required = false) String name,
                        @RequestParam(required = false) String phone){//这两个是做查询功能用到的
        //调用Service层进行数据处理并返回
        PageBean pageBean = service.select(page,pageSize,name,phone);
        return Resule.success(pageBean);
    }
}

定义返回对象PageBean类

这个类也是要取决于前端写的接收数据的名称来定义,这个total也有写在别的地方的,视情况而定。有的是取res.data.list,也有res.data.data,也有res.data.rows。

下面这个例子是前端接收res.data.total和res.data.rows的例子。

@Data
@NoArgsConstructor
@AllArgsConstructor
public class PageBean {
	private Long total; //总记录数
	private List rows; //当前页数据列表,如果是list或者data成员变量的名要跟着变
}

5.2.不使用插件的分页查询

5.1已经完成了从前端获取数据并发送到Service层了,接下来就是根据条件分页和查询了。

不使用插件的分页查询就是使用SQL语句提供的limit功能,Service层的实现类里需要提前计算limit里面的两个值。

Service的实现

@Service
public class Service {
    
	@Autowried
	private Mapper mapper;
	
	public PageBean select(Integer page,Integer pageSize,String name,String phone){
        //获取总记录数
        Long total = mapper.count();
        
        //获取结果列表
        Integer start = (page - 1) * pageSize;//这个计算是计算起始值,加上MySql的limit索引是从0开始的
        List<User> list = mapper.select(start,pageSize,name,phone);
        
        //封装结果返回
        return new PageBean(total,list);
    }
}

Mapper的实现

@Mapper
public class Mapper {
    
    public Long count();
    
    public List<User> select(Integer start,Integer pageSize,String name,String phone)
    
}

Mapper.xml的实现

主要就是倒数第三行那里的limit


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.Mapper">
	<select id="count">
    	select count(*) from user
    select>
    
    <select id="select" resultType="com.example.pojo.User">
    	select * from user
        <where>
        	<if test="name != null and name != ''">and name like concat('%','#{name}','%')if>
            <if test="phone != null and phone != ''">and phone like concat('%','#{phone}','%')if>
        where>
        limit #{start},#{pageSize}
    select>
mapper>

5.3.使用插件PageHelper的分页查询

5.1已经完成了从前端获取数据并发送到Service层了,接下来就是根据条件分页和查询了。

这里使用了PageHelper工具,需要在POM加入如下依赖

<dependency>
    <groupId>com.github.pagehelpergroupId>
    <artifactId>pagehelper-spring-boot-starterartifactId>
    <version>2.1.0version>
dependency>

Service的实现

@Service
public class Service {
    PageHelper.startPage(param.getPage(),param.getPageSize());//这个就是PageHelper提供的分页方法,并且下面就必须写查询方法,仅针对下面生效,务必注意书写位置
    List<User> userList = mapper.select(param);
    Page<User> list = (Page<User>) userList;//将List类型强制转换为Page类型
    return new PageBean(list.getTotal(),list.geResult());//调用Page的获取总页数和数据的方法
}

Mapper的实现

@Mapper
public class Mapper {
    List<User> select(UserQueryParam param);
}

Mapper.xml的实现,好处就是PageHelper会自动分页,不需要我们写limit


DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.mapper.Mapper">  
    <select id="select" resultType="com.example.pojo.User">
    	select * from user
        <where>
        	<if test="name != null and name != ''">and name like concat('%','#{param.name}','%')if>
            <if test="phone != null and phone != ''">and phone like concat('%','#{param.phone}','%')if>
        where>
    select>
mapper>

第6章一对多的多表操作

一对多的操作非常常见,例如员工和工作经历,一个员工可能会有0个,也可能一个,也可能多个工作经历。

这种情况一般前端传来的JSON里面还会有一个数组,这个数组里面又是一个JSON

关键点就是我们要插入的工作经历需要员工的ID,但是这个员工是新增,ID是自动增长的主键,导致我们在做添加操作时没有ID,就需要我们添加到数据库以后再获得这个员工的ID。

如果插入后再查询效率难免太低,所以Spring提供了一个注解@Options,这个注解在完成数据库操作后会获取数据库自动成成的键值并将这个值设置到对应的对象属性上。

**使用前提:**这个必须是主键,自增主键

1.第一种方法:在Mapper接口上使用注解

@Options(useGeneratedKeys=true,keyProperty="自增主键")

2.第二种方法:在XML映射文件中使用属性useGeneratedKeys和keyProperty

<insert id="Mapper的方法名" useGeneratedKeys="true" keyProperty="自增主键">
	
insert>

示例:在这个示例中,当前端发送请求后,传入一个没有id的param,但是在mapper完成add方法后,所有的param都会给ID返回数据库对应的值。

@RestController
@RequestMapper("/users")
public class Controller {
    
    @Autowried
    private Service service;
    
    @PostMapping
    public Result add(Param param){
        service.add(param);
        return Result.success();
    }
}


@Service
public class Service {
    
    @Autowried
    private Mapper mapper;
    
    public void add(Param param){
        mapper.add(prarm);
    }
}

@Mapper
public class Mapper {
    
    @Option(useGeneratedKeys=true,keyProperty="id")//这个和XML内写两种方法二选一
    @Insert("insert into user(name,phone) values(#{name},#{phone})")//这个和XML内写两种方法二选一
    void add(Param param);
    
}

第7章事务(更多请见单独文档事务)

7.1.事务的特性

概念: 事务是一组操作的集合,它是一个不可分割的工作单位。事务会把所有的操作作为一个整体一起向系统提交或撤销操作请求,即这些操作 要么同时成功,要么同时失败。

事务的四大特性(ACID)

  • 原子性(Atomicity) :事务是不可分割的最小单元,要么全部成功,要么全部失败
  • 一致性(Consistency):事务完成时,必须所有的数据都保持一致状态
  • 隔离性(Isolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行
  • 持久性(Durability):事务一单提交或回滚,它对数据库中的数据改变就是永久的

7.2.Spring事务管理

7.2.1.Transactional注解

使用位置

位置 作用
方法上 将当前方法交给Spring进行事务管理
类上 当前类中所有方法都交给Spring进行事务管理
接口上 接口上所有的实现类当中的所有方法都交给Spring进行事务管理

属性

属性 作用
rollbackFor 默认情况下,只有出现RuntimeException(运行时异常)才会回滚事务,这个属性可以定义异常的class,指定出现何种异常才会回滚事务
propagation 用来指定传播行为,具体值见下表

propagation值

假设现在有两个事务方法,一个A方法,一个B方法,两个方法都被@Transactional注解了,A方法中又调用了B方法,此时就会出现一个问题,B方法运行时,是加入A方法的事务还是新建一个事务?

属性值 含义
REQUIRED 【默认值】需要事务,有则加入,无则创建新事务
REQUIRES_NEW 需要新事务,无论有无,总是创建新事务
SUPPORTS 支持事务,有则加入,无则在无事务状态中运行
NOT_SUPPORTED 不支持事务,在无事务状态下运行,如果当前存在已有事务,则挂起当前事务
MANDATORY 必须有事务,否则抛异常
NEVER 必须没事务,否则抛异常

假如说B方法也是操作数据表,比如转账,A是扣钱,B是加钱,A扣完了调用B,B这时候就得加入A这个事务中,出问题全都回滚,此时用默认值就可以;如果说B是日志记录,不管成功与否,都要记录本次操作,那么B就需要单独开启一个事务,A后续出问题不会回滚B,此时B的值就是REQUIRES_NEW。

唯一一个要注意的点就是在不改变rollbackFor的值的情况下只有在出现RuntimeExcrption也就是运行时异常才会回滚,如果出现编译时异常并且异常被触发是不会回滚的

第8章.异常

一个项目中功能会有很多,不免就会有很多异常。有编译时异常,也有运行时异常,也有自定义异常。但是每一个异常如果都要try catch处理,难免会导致代码量过大,可读性变差;如果我们只是一味地往上抛异常,又会导致一件事,异常只是抛出,没有做任何处理。因此需要一个对异常做集中处理的方式。

Spring提供了全局异常处理器,只需要我们定义一个类,在这个类上增加一个注解**@RestControllerAdvice**就是定义好了。

最好是新建一个Exception包来存储处理器和自定义的异常。

在全局异常处理器当中,需要定义方法来捕获异常,在方法上需要加上注解**@ExceptionHandler**,可以通过控制该注解的value属性来制定我们要捕获的是哪一类异常,如果什么value都不加,它会默认处理我们所有没有处理的异常,就是没有被try/catch处理的或者没有被本注解处理的异常。

示例:一个自定义异常 CustomerException.java和全局处理异常 GlobalExceptionHandler.java

public class CustomerException extends RuntimeException {
    String msg;

    public CustomerException(String msg) {
        this.msg = msg;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }
}


public class GlobalExceptionHandler {

    @ExceptionHandler
    public Result exception(Exception e) {//如果项目内没有任何try/catch那么这个方法将会处理所有非CustomerException的异常
        e.printStackTrace();
        return Result.error("对不起,操作失败,请联系管理员");
    }

    @ExceptionHandler(CustomerException.class)
    public Result exception(CustomerException e) {//这个方法将会处理所有的CustomerException异常
        log.info(e.getMessage());
        return Result.error(e.getMsg());
    }

}

例如我们遇到的那个问题,删除班级的时候如果班级里面有人抛出异常并且返回前端该班级有人,不能删除就可以这么写:

throw new CustomerException("对不起,该班级下有学生,不能直接删除");

第9章.阿里云OSS的使用

首先,注册,开通服务等在此都不介绍。

阿里云OSS帮助文档SDK参考JAVA:Java_对象存储(OSS)-阿里云帮助中心 (aliyun.com)截止于2024年9月15日,本网站可以正常访问,如果不能正常访问请尝试在阿里云官网自行查找

我们需要在拿到AccessKey后再进行下面的步骤

9.1.配置环境变量

管理员身份运行CMD命令行,执行如下命令,配置系统的环境变量

set OSS_ACCESS_KEY_ID=从阿里云拿到的AccessKeyId
set OSS_ACCESS_KEY_SECRET=从阿里云拿到的AccessKeySecret

执行如下命令,让更改生效

setx OSS_ACCESS_KEY_ID "%OSS_ACCESS_KEY_ID%"
setx OSS_ACCESS_KEY_SECRET "%OSS_ACCESS_KEY_SECRET%"

验证是否生效,这个的好处是都存在了计算机上,不怕服务器被攻击看到源码

echo %OSS_ACCESS_KEY_ID%
echo %OSS_ACCESS_KEY_SECRET%

9.2.引入依赖

<dependency>
    <groupId>com.aliyun.ossgroupId>
    <artifactId>aliyun-sdk-ossartifactId>
    <version>3.17.4version>
dependency>
<dependency>
    <groupId>javax.xml.bindgroupId>
    <artifactId>jaxb-apiartifactId>
    <version>2.3.1version>
dependency>
<dependency>
    <groupId>javax.activationgroupId>
    <artifactId>activationartifactId>
    <version>1.1.1version>
dependency>

<dependency>
    <groupId>org.glassfish.jaxbgroupId>
    <artifactId>jaxb-runtimeartifactId>
    <version>2.3.3version>
dependency>

9.3.配置application.yml

不是所有的都需要添加,按需添加

#阿里云OSS地址
aliyun:
  oss:
    endpoint: 阿里云端点
    bucketName: 存储空间名称
    #这两个一般不用写,一般配置在环境变量里面了。这两个是配置Access Key ID和Access Key Secret
    accessKeyId: 从阿里云获取Access Key Id
    accessKeySecret: 从阿里云获取Access Key secret

9.4.引入工具类和封装类

9.4.1.工具类AliyunOssUtils

@Slf4j
public class AliyunOSSUtils {

    /**
     * 上传文件
     * @param endpoint endpoint域名
     * @param bucketName 存储空间的名字
     * @param content 内容字节数组
     */
    public static String upload(String endpoint, String bucketName, byte[] content, String extName) throws Exception {
        // 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。
        EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();
        // 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。
        String objectName = UUID.randomUUID() + extName;

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
        try {
            // 创建PutObjectRequest对象。
            PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content));
            // 创建PutObject请求。
            PutObjectResult result = ossClient.putObject(putObjectRequest);
        } catch (OSSException oe) {
            log.error("Caught an OSSException, which means your request made it to OSS, but was rejected with an error response for some reason.");
            log.error("Error Message:" + oe.getErrorMessage());
            log.error("Error Code:" + oe.getErrorCode());
            log.error("Request ID:" + oe.getRequestId());
            log.error("Host ID:" + oe.getHostId());
        } catch (ClientException ce) {
            log.error("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.");
            log.error("Error Message:" + ce.getMessage());
        } finally {
            if (ossClient != null) {
                ossClient.shutdown();
            }
        }

        return endpoint.split("//")[0] + "//" + bucketName + "." + endpoint.split("//")[1] + "/" + objectName;
    }

}

9.4.2.封装类AliyunOSSProperties

这个类想不写的话也可以不写,只要给每个都要用OSS服务的实现类都配置一下里面的属性就可以了。

参考:

public class Test{
    
    @Value("${aliyun.oss.endpoint}")
    private String endpoint;
    @Value("${aliyun.oss.bucketName}")
    private String bucketName;
    
    //各种实现方法
}

注意事项:

  1. 需要在配置文件中配置好名称完全一致的值
  2. 这个是简化配置的,如果说配置了二十多个属性,有好几个用到阿里云OSS操作的类,总不能在里面一个个写,所以也需要在用到该类的里面使用@Autowried填装
@Data
@Component
@ConfigurationProperties(prefix = "aliyun.oss")//该注解从配置文件application.properties或者yml中找到并赋值给对应的值
public class AliyunOSSProperties {
    private String endpoint;
    private String bucketName;
}

9.5.MultipartFile介绍

MultipartFile是Spring框架中的一个接口,用于处理上传的文件。

MultipartFile定义了一下方法,可以轻松读取,验证,和保存上传的文件。

方法名 作用
getOriginalFilename() 返回上传文件的原始文件名
getSize() 返回上传文件的大小(一字节为单位)
getContentType() 返回上传文件的MIME类型(如image/jpg)
getBytes() 将上传文件的内容读取为字节数组
transferTo(File dest) 将上传文件的内容保存到指定的目标文件中

9.6.实现参考

假如现在要在Controller中实现上传方法

@RestController
public class Controller {
	
	@Autowried
    private AliyunOSSProperties aliyunOSSProperties;
    
	@PostMapping("/upload")
    public Result upload(MultipartFile file) throws Exception {
        String extName = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf("."));//获得文件的后缀,例如.jpg
        String endpoint = aliyunOSSProperties.getEndpoint();//获得阿里云端点号
        String bucketName = aliyunOSSProperties.getBucketName();//获得阿里云存储空间名称
        String url = AliyunOSSUtils.upload(endpoint, bucketName, file.getBytes(), extName);//上传文件,这个方法是自动生成一个UUID名字,所以只需要后缀名就可以了
        return Result.success(url);//返回存储的url地址
    }
}

第10章.会话技术

JavaWeb中会话技术是用来处理HTTP协议无状态特性的解决方案。HTTP协议是一种无状态协议,这意味着每次客户端向服务器发起请求时,服务器将此请求视为独立的事件,不会记住前一次请求的信息。然而,在构建Web应用程序时,通常需要跨多个请求来维护用户的状态信息,比如用户的登录状态、购物车内容等。这就需要用到会话技术来跟踪用户会话并保持状态信息。

10.1.Cookie

Cookie是在客户端上存储少量信息(最大4KB)的一种方法。

当客户端首次访问服务器时,服务器可能会设置一个Cookie,其中可能包含一个唯一标识符或其他信息。每当客户端向服务器发送请求时,都会自动包含任何已设置的Cookies。服务器可以读取这些Cookies,并根据它们来识别客户端或提供特定于该客户端的服务(如免登录,访问权限等)。

实现示例:

@RestController
public class Controller {
    //设置Cookie
    @GetMapping("/set")
    public Result setCookie(HttpServletResponse response){
        response.addCookie(new Cookie("key1","value1"));//只能传两个String类型
        response.addCookie(new Cookie("key2","value2"));//可以设置多个cookie
        return Result.success();
    }
    
    //获得Cookie
    @GetMapping("/get")
    public Result getCookie(HttpServletRequest request){
        Cookie[] cookies = request.getCookies();
        for(Cookie cookie : cookies){
            System.out.println(cookie.getName()+":"+cookie.getValue());//打印出来的就是key1:value1  key2:value2
            //可以进行别的操作,判断等
        }
        return Result.success();
    }
}

优点:

  • 方便:Cookies 允许服务器保存用户的状态信息,如登录状态、偏好设置等,使得网站能够为用户提供个性化的体验。
  • 无状态协议的支持:HTTP 协议本身是无状态的,Cookies 提供了这一种机制。
  • 轻量级:Cookies 存储的数据量较小(4KB)。
  • 支持:几乎所有的现代浏览器都支持Cookies(包括手机)

缺点:

  • 安全问题:Cookies存储的是明文,在使用非加密的传输时候,可能会被截获
  • 存储容量有限
  • 隐私问题:因为Cookies会追踪用户行为,会导致一些用户禁用Cookies或者使用隐私模式浏览。
  • 跨域限制:默认只能由设置它的站点访问,这限制了跨域数据共享的能力。当协议不同(例如HTTP和HTTPS),IP不同,端口不同都算跨域。

10.2.Session

Session是服务器端会话跟踪技术,它是存在服务端的。Session就是基于Cookie实现的。

实现示例:

@RestController
public class Controller {
    //设置session
    @GetMapping("/set")
    public Result setSession(HttpSession session){
        session.setAttribute("key1","value1");
        session.setAttribute("key2","value2");
        return Result.success();
    }
    //获取session
    @GetMapping("/get")
    public Result getSession(HttpServletRequest request){
        HttpSession session = request.getSession();
        Object value1 = session.getAttribute("key1");//通过key1拿到了value1
        Object value2 = session.getAttribute("key2");//通过key2拿到了value2
        //可以进行别的操作
        return Result.success();
    }
}

优点:

  • 安全性较高:因为存在了服务器,相比客户端更安全
  • 存储容量更大:虽然存储容量取决于服务器配置,但是比Cookies大的多
  • 不受客户端限制:即使用户禁用了Cookies,只要服务器能通过其他方式识别用户,Session可以正常工作

缺点:

  • 服务器负载增加
  • 跨域问题
  • 维护复杂:需要考虑分布式环境中共享Session数据
  • 被攻击风险:如果Session被攻击者猜出或者通过其他方式获得,就会发生Session攻击

10.3.JWT令牌

JWT是一种用于在网络上安全传输信息的令牌,通过‌数字签名的方式,以JSON对象为载体,在不同的服务终端之间安全地传输信息。

优势:

  • JWT数据量小,传输时还可以压缩
  • 可以被缓存,从而减少服务器请求次数,提高性能
  • 支持多种签名算法
  • 可以跨域认证
  • 包含所有必要的认证信息,并且被签名保护,这意味着服务器不需要查询数据库来验证用户身份,减轻服务器存储压力

缺点:

  • JWT的安全性高度依赖于密钥的安全管理。需要保管好密钥,不能丢失也不能泄露。

10.3.1.JWT组成介绍

JWT:(官网:https://jwt.io)

  • 第一部分:Header(头),记录令牌类型、签名算法等。
  • 第二部分:Payload(有效载荷),携带一些自定义信息、默认信息等。
  • 第三部分:Signature(签名),防止Token被篡改、确保安全性。将header、payload加入指定密钥,通过指定签名算法计算而来。

10.3.2.引入依赖

<dependency>
    <groupId>io.jsonwebtokengroupId>
    <artifactId>jjwtartifactId>
    <version>0.9.1version>
dependency>

似乎新版的jwt更换了新的写法,下面都是按照这个版本写的

10.3.3.配置环境变量(可以不配置,直接写在服务器里)

分别在管理员身份运行下面三条,前两条是加入环境变量,第三条是验证这个数据是否加入了环境变量 ,这个的好处是都存在了计算机上,不怕服务器被攻击看到源码。

 set JWT_SECRET=你想要的密钥
 
 setx JWT_SECRET %JWT_SECRET%
 
 echo %JWT_SECRET%

10.3.4.配置JWTUtils工具类

public class JwtUtils {


    private static Long expire = 43200000L;//这个是TOKEN的有效期毫秒数,这个是12小时
    private static String JWT_KEY = "JWT_SECRET";//如果配置了环境变量就这么写
    //如果没配置环境变量就这么写
    //private static String JWT_SECRET = "密钥"

    //生成JWT令牌TOKEN
    public static String generateJwt(Map<String, Object> claims) {
        String signKey = System.getenv(JWT_KEY);//如果没配置环境变量就不用写了
        String jwt = Jwts.builder()
                .addClaims(claims)
                .signWith(SignatureAlgorithm.HS256, signKey)
                .setExpiration(new Date(System.currentTimeMillis() + expire))
                .compact();
        return jwt;
    }

    //解析JWT令牌TOKEN
    public static Claims parseJWT(String jwt) {
        String signKey = System.getenv(JWT_KEY);//如果没配置环境变量就不用写了
        Claims claims = Jwts.parser()
                .setSigningKey(signKey)
                .parseClaimsJws(jwt)
                .getBody();
        return claims;
    }
}

10.3.5.使用示例

使用上面的工具类生成TOKEN

public String getToken(){
    Map<String,Object> data = new HashMap<>();
    data.put(key1,value1);
    data.put(key2,value2);
    String jwt = JwtUtils.generateJwt(data);
    return jwt;
}

解析TOKEN

public Result parstJwt(String jwt){
    try{
        JwtUtils.parstJwt(jwt);
    }catch(Exception e){
        //如果走到这一步就说明解析出错了
        return Result.orrer("解析失败");
    }
    return Result.success();
}

第11章.过滤器的使用(用于登录判断等)

过滤器Filter是JavaWeb提供的三大组件之一(Servlet程序、Listener监听器、Filter过滤器)

作用:拦截请求,过滤响应,一般用于登录校验,权限检查,日志操作,事务管理,敏感字符处理等。

过滤器和拦截器有一个不同的是过滤器在拦截器前面,相当于在TOMCAT里面,客户端发送的数据需要先经过过滤器,才能再经过拦截器(拦截器见下一章)

优点:

  • 可以在多个 Servlet 中使用相同的 Filter 实现相同的功能。
  • Filter 是独立的组件,可以方便地进行修改和替换。

11.1.定义过滤器

最好是新建一个软件包,名为filter,专门用来存储过滤器。然后在里面定义类,实现Filter接口,并重写其中的方法。

需要在实现Filter接口的类上添加@WebFilter注解,声明这个类作为 Web 应用程序中的过滤器,这个注解后面可以添加拦截的路径,如果是"/*"则是全部拦截。然后在启动器类上添加注解@ServletComponentScan ,这个注解用于指示容器扫描并自动部署在类路径中的 Servlet、过滤器(Filter)、监听器(Listener)和其他 Servlet 组件。

示例:这个示例是拦截了所有的非/login路径请求,如果有合法TOKEN就通过

@WebFilter("/*")
public class TestFilter implements Filter {//应该导入jakarta.servlet里面的Filter
    //初始化方法,随着web服务器启动执行一次,因为接口里面写成了默认方法,可以不重写,也可以重写
    public void init(FilterConfig filterConfig) throws ServletException {
        Filter.super.init(filterConfig);
    }

    //这个必须重写,每次有请求来的时候都要先执行这一步
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException {
        //转换成HttpServlet
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        //获取请求的路径
        String url = request.getRequestURL().toString();
        System.out.println("url:"+url);
        //如果请求是login直接放行
        if (url.contains("/login")){
            filterChain.doFilter(request, response);
            return;
        }
        //获取请求头里面的token信息
        String token = request.getHeader("token");
        if (!StringUtils.hasLength(token)){
            //设置响应码
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            return;
        }

        //解析token
        try {
            JwtUtils.parseJWT(token);
        } catch (Exception e) {
            //说明token错误,或者过期,解析失败
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            return;
        }
        //放行
        filterChain.doFilter(request, response);
    }

    //销毁方法, web服务器关闭时执行一次, 因为接口里面写成了默认方法,可以不重写,也可以重写
    public void destroy() {
        Filter.super.destroy();
    }
}

第12章.拦截器的使用(用于登录判断等)

拦截器Interceptor用于在请求到达目标控制器之前或之后执行某些操作。虽然 JavaWeb 中通常使用的是过滤器,而非拦截器,但它们的作用有些相似。Filter 可以在请求到达 Servlet 之前或响应离开 Servlet 之后进行干预,常用于执行一些预处理或后处理任务。

优点:

  • 拦截器可以注册在特定的控制器或全局范围,具有更精准的控制。
  • 提供了更多的回调方法,分别在请求处理的不同阶段执行。

12.1.拦截器配置

示例步骤:

1.创建一个拦截器Interceptor的软件包,里面存放拦截器类,类实现HandlerInterceptor接口,注意,需要加入容器注解@Component,里面三个接口可以都不重写,用啥写啥。三个方法的执行时机见下表。

@Component
public class TokenInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        //获取url
        String url = request.getRequestURL().toString();
        
        //判断url中是否包含login,如果包含,就放行,但是下面在配置文件里面也写了,所以这里都注释掉了
        //if(url.contains("login")){ //登录请求
        //    return true; //ture就是放行,false就是不放行
        //}
        
        //获得TOKEN
        String jwt = request.getHeader("token");
        
        //这个StringUtils.hasLength()是获得里面的String字符串是否非空并且长度大于0,如果没有,就说明token不存在,返回错误结果
        if(!StringUtils.hasLength(jwt)){
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);//这个返回的是401
            return false;
        }
        
        //解析token
        try {
            JwtUtils.parseJWT(jwt);
        } catch (Exception e) {
            e.printStackTrace();
            response.setStatus(HttpStatus.SC_UNAUTHORIZED);
            return false;
        }
        
        //走到这一步都没被return说明传过来的请求token也是正确的,放行
        return ture;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
    }
}
方法名 执行时机
preHandle() 方法在请求处理之前被调用,即在控制器方法执行之前。实现处理器的预处理(如登录检查)
postHandle() 方法在控制器方法执行之后,但在视图渲染之前调用。只要preHandle()返回true,这个一定执行。
afterCompletion() 方法在视图渲染完成后调用,即整个请求处理流程的最后阶段。不管preHandle()返回true还是false,这个一定执行。

2.创建一个config的软件包,里面存放配置类,类实现WebMvcConfigurer接口,并重写addInterceptors方法,值得注意的是,这个需要加入@Configuration注解,声明一个类作为配置类,通过该注解,可以以编程的方式定义和配置 Spring 应用程序中的依赖关系,这种方式称为基于 Java 的配置。

@Configuration
public class WebConfig implements WebMvcConfigurer {
    //需要填充容器
    @Autowired
    private TokenInterceptor tokenInterceptor;

    public void addInterceptors(InterceptorRegistry registry) {
        
        registry.addInterceptor(tokenInterceptor)
            .addPathPatterns("/**")//addPathPatterns是添加拦截路径,写什么具体参考下表
            .excludePathPatterns("/login");//excludePathPatterns是排除路径,添加不需要拦截的路径
    }
}

拦截路径 意义
/** 所有子路径,包括二级,三级路径
/* 只能拦截一集子路径,如果有二级路径则没法拦截

第13章.AOP

AOP,面向切面编程,旨在提高应用内关注点的模块化能力,如日志记录,事务管理,错误处理等。解决了传统面向对象编程中,这些关注点散布在整个项目中,导致代码高度重合和耦合度。

记得在Aspect类上也要加@Component注解。

优点:

  • 减少代码重复
  • 增强代码可读性和和维护性

13.1.引入依赖

<dependency>
    <groupId>org.springframework.bootgroupId>
    <artifactId>spring-boot-starter-aopartifactId>
dependency>

13.2.注解介绍

注解名称 注解位置 注解作用
@Aspect 切面类上 告诉Spring这是一个包含切面逻辑的类。
@Before 切面类的方法上 定义一个前置通知,在目标方法调用之前执行。
@After 切面类的方法上 定义一个后置通知,在目标方法执行完毕之后执行,无论方法是否成功执行。
@AfterReturning 切面类的方法上 定义一个返回后通知,在目标方法成功执行并返回结果之后执行。
@AfterThrowing 切面类的方法上 定义一个异常抛出后通知,在目标方法抛出异常后执行。
@Around 切面类的方法上 定义一个环绕通知,在目标方法调用前后都执行自定义的行为。
@annotation 上面五个的括号里面 使用自定义注解来标记需要进行切面。
@pointcut 方法上 定义一个可重用的切入点表达式。

目标方法没出现异常执行顺序:

@Before->@Around(环绕开始部分)->目标方法执行->@Around(环绕结束部分)->@AfterReturning->@After

目标方法出现异常执行顺序:

@Before->@Around(环绕开始部分)->目标方法执行->@Around(环绕结束部分)->@AfterThrowing

理论上来讲,这些注解也都可以用一个注解来@Around实现,但是可读性会变差,例如:

@Aspect
@Component
public class Test{
	@Around("execution(* com.example.service.*.*(..))")
    public Object test(ProceedingJoinPoint pjp) throws Throwable {
        //这里可以写@Before注解完成的方法
        try {
            //这里写@Around(环绕开始部分)
            //执行目标方法
            Object result = pjp.proceed();
            //这里写@Around(环绕结束部分)注解完成的方法(没出异常的情况)
            //这里写@AfterReturning注解完成的方法
            return result;
        } catch (Throwable e) {
            //这里写@Around(环绕结束部分)注解完成的方法(出异常的情况)
            //这里写@AfterThrowing注解完成的方法
        }
        //这里写@After注解完成的方法
        return null;
    }
}

13.3.切入点

13.3.1.execution

切点位置写在切面类的注解的后面,语法格式:

@注解("execution(访问修饰符 返回值 包名.类或接口.方法名(参数列表) throws 异常")

例如

@Around("execution(public com.example.pojo.User com.example.controller.UserController.findByNameAndPassword(String,String)) throws Exception")

注意:

  • 修饰符(如:public,protected),可以省略
  • 异常一般不写,交给方法处理

通配符:

  • *:可以表示任意返回值,包名,类名,方法名,任意类型的一个参数,也可以统配包名,类名,方法名的一部分
  • ..:可以表示任意层级的包,或任意类型,任意个数的参数

省略方法的修饰符号,省略异常,用*代替返回值类型,用…省略参数,然后查询所有find开头的方法,示例如下:

execution(* com.example.example.controller.UserController.find*(..))

根据业务不同,也可以使用&&、||、!来组成复杂的切入点表达式

13.3.2.@annotation

基于注解的方式来匹配切入点方法,需要自定义一个注解,需要匹配哪个方法,就在对应的方法上加上对应的注解。

例如我们自定义一个注解,这个注解的名字就是@Record:

@Target(ElementType.METHOD)
@Documented
@Retention(RetentionPolicy.RUNTIME )
public @interface Record {
}

我们把这个注解拿去使用,例如:

@Aspect
@Component
public class TestAspect {

    @Around("@annotation(com.example.anno.Record)")
    public Object recordLog(ProceedingJoinPoint joinPoint) throws Throwable {
        //这里可以写@Before注解完成的方法
        try {
            //这里写@Around(环绕开始部分)
            //执行目标方法
            Object result = pjp.proceed();
            //这里写@Around(环绕结束部分)注解完成的方法(没出异常的情况)
            //这里写@AfterReturning注解完成的方法
            return result;
        } catch (Throwable e) {
            //这里写@Around(环绕结束部分)注解完成的方法(出异常的情况)
            //这里写@AfterThrowing注解完成的方法
        }
        //这里写@After注解完成的方法
        return null;
    }
}

13.3.3.连接点

连接点(Join Point)是指程序执行过程中的一个特定点,比如方法的调用或异常的抛出。连接点是AOP框架可以插入额外行为的地方。在切面中,可以通过JoinPoint对象来访问这些连接点的信息。

示例:

@Aspect
@Component
public class LogAspect {

    // 定义切入点
    @Pointcut("execution(* com.example.service.*.*(..))")
    public void test() {}

    // 前置通知
    @Before("test()")
    public void logBefore(JoinPoint joinPoint) {
        //目标方法被调用前执行的方法
    }

    // 后置通知
    @After("test()")
    public void logAfter(JoinPoint joinPoint) {
        //目标方法执行完毕后执行
    }

    // 返回后通知
    @AfterReturning("test()")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        //目标方法成功执行后的方法
    }

    // 异常抛出后通知
    @AfterThrowing("test()")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
        //目标方法出现异常的方法
    }

    // 环绕通知
    @Around("test()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        //写环绕前的方法
        Object result = joinPoint.proceed(); // 继续执行目标方法
        //写环绕后的方法
        return result;
    }
}

13.4.ProceedingJoinPoint的方法

方法名称 方法作用
getSignature() 获得当前连接点的方法签名信息
getArgs() 获得当前连接点的方法参数
getTarget() 获得当前连接点的目标对象
getThis() 获得当前连接点的代理对象
getStaticPart() 获得静态部分的方法签名

常用举例:

//获得类名
String className = joinPoint.getTarget().getClass().getName();
//获得操作方法名
String methodName = joinPoint.getSignature().getName();
//获得参数
Object[] args = joinPoint.getArgs();Object[] args = joinPoint.getArgs();

第14章.Bean的获取、引入和使用

14.1.Bean

Bean对象,就是被@Compontent修饰的类,如果被继承了@Compontent的类修饰,例如@Controller,@Service,@Repository也是Bean对象。

可以通过@Scope(“value”)注解来设置这个Bean的对象模式,也可以通过@Lazy懒加载注解来设置Bean的延迟加载(第一次使用Bean对象时,才会创建Bean对象并交给IOC容器管理)

@Scope注解中value的值和作用

作用域 说明
singleton 容器内同名称的bean只有一个实例(单例)(默认)
prototype 每次使用该bean时都会创建新的实例(非单例)
request 每个请求会创建新的实例(web环境中)
session 每个会话都会创建一个新的实例(web环境中)
application 每个应用会创建新的实例(web环境中)

如果只使用注解@Scope或者不添加@Scope注解都是默认单例模式,就是相当于@Scpoe(“singleton”)。

因为除了单例每次使用时都可能会创建一个新的对象,所以@Scope只要值不是Singleton就可以认为是懒加载模式@Lazy

Bean对象也有一个注解@Conditional,这个注解可以通过判断是否满足条件,满足条件才会将对应的Bean对象装载到Spring的IOC容器中。

注解名称 注解位置 注解作用
@Conditional 类、方法上 判断是否满足条件,满足条件才会将对应的Bean对象装载到Spring的IOC容器中
@ConditionalOnClass 类、方法上 判断环境中有对应字节码文件,才注册bean到IOC容器中
@ConditionalOnMissingBean 类、方法上 判断环境中都没有对应bean(类型或者名称),才注册Bean到OIC容器中
@ConditionalOnProperty 类、方法上 判断配置文件中有对应属性和值,才注册bean到IOC容器中

例如:

@Configuration
public class HeaderConfig{
    @Bean
    @ConditionalOnClass(name="io.jsonwebtoken.Jwts")//如果这个JWT令牌类存在才会加入容器里面
    public Test test(){
        return new Test();
    }
    
    @Bean
    @ConditionalOnMissingClass(name="io.jsonwebtoken.Jwts")//如果这个JWT令牌类不!存!在!才会加入容器里面
    public Test test(){
        return new Test();
    }
    
    @Bean
    @ConditionalOnProperty(name="aaa",havingValue="bbb")//判断配置文件中是否有一个叫aaa的属性的值是bbb,如果有则加入,没有则不加入,配置文件就是properties,yml,yaml这几个
    public Test test(){
        return Test();
    }
}

14.2.获取自己封装的Bean(在包下并且能被启动器扫描到的)

从IOC容器中获取到Bean对象只需要使用注解@Autowried就可以拿到了。

IOC容器对象是ApplicationContext接口,如果要使用这个接口,从org.springframework.context包导入

例如:

@Autowried
private ApplicationContext applicationContext;

这个接口提供了三个获取Bean对象的方法

方法名
Object getBean(String name) 通过name获取bean,但是获取的是Object类型的,这个需要强转,一般不用
T getBean(Class requiredType) 根据类型获取bean,没有多个实现类的时候,用这个好
T getBean(String name,Class requiredType) 根据name获取bean(带类型转换),如果有多个实现类,用这个好

例如:我们现在有这么一个名叫Test类的Bean对象,我们要通过测试使用以上三种方法打印出他的对象

@Componment
public class BeanTest{
}
@SpringBootTest
public class SpringBootTest {
    
    @Autowried
    private ApplicationContext applicationContext;//IOC容器对象
    
    @Test
    public void test(){
        //根据bean的名称获取
        BeanTest bean1 = (BeanTest) applicationContext.getBean("beanTest");
        System.out.println(bean1);
        
        //根据bean的类型获取
        BeanTest bean2 = applicationContext.getBean(BeanTest.class);
        System.out.println(bean2);
        
        //根据bean的名称以及类型获取
        BeanTest bean3 = applicationContext.getBean("beanTest",BeanTest.class);
        System.out.println(bean3);
    }
}

14.3.第三方依赖的Bean

第三方Bean例如我们从maven引入了别的依赖,这个依赖本身可能并没有加@Componment类型的注解,我们无法直接使用@Autowried注解去IOC容器去获取。我们要使用的话不能去修改它的源码,给它加@Componment注解的话就不是原来的依赖了。

例如我们引入了XML的解析jar包,然后我们要把这个jar包下的SAXReader类加入IOC容器中,调用出来。

<dependency>
	<groupId>dom4jgroupId>
    <artifactId>dom4jartifactId>
    <version>1.6.1version>
dependency>

导入方法:

14.3.1.启动器下导入(不推荐)

直接在启动器下面加@Bean注解,引入这个类生成这个类对象。但是启动器的主要作用就是启动程序,加这一堆代码就会影响代码可读性,不美观,启动器功能不再单一,所以不推荐。

@SpringBootApplication
@ComponentScan({"这个包名","另一个包名"})//指定要扫描的包
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
    
    @Bean
    public SAXReader getSAXReader(){
        return new SAXReader();
    }
}

14.3.2.设置一个配置类导入

单独配置一个配置类,需要在config软件包下(没有就创建一个)专们创建一个类来导入第三方Bean,因为这个是配置类,所以记得需要添加@Configuration注解。

配置类例如:

@Configuration
public class CommonConfig{
    @Bean
    public SAXReader getSAXReader(){
        return new SAXReader;
    }
}

@Bean后面是可以加value值的,不加value值获取的值默认是方法名!!,这个方法名就是要用@Qualifier注解调用的名字。加了value后容器里存的就是value的值了,这一般用于多个bean类的情况。

例如刚刚那个,从IOC容器中取出的语句就是:

@Autowried
@Qualifier("getSAXReader")//一般省略不写,这里是演示才写的
private SAXReader saxReader;

假设现在有多个Bean

@Configuration
public class CommonConfig{
    @Bean("saxReader1")
    public SAXReader getSAXReader1(){
        return new SAXReader;
    }
    @Bean("saxReader2")
    public SAXReader getSAXReader2(){
        return new SAXReader;
    }
}

再从IOC容器中取出这两个Bean的语句就是

@Autowried
@Qualifier("saxReader1")
private SAXReader saxReader1;

@Autowried
@Qualifier("saxReader2")
private SAXReader saxReader2;

14.4.第三方项目的Bean

假设两个项目都在本地,先去要导入的三方项目的POM里面,找到最开始的地方,没有被框起来的,这个对应的就是依赖的位置

第三方POM:

<groupId>包名groupId>
<artifactId>项目名artifactID>
<version>版本号version>

本地POM添加如下依赖:

<dependency>
    <groupId>包名groupId>
    <artifactId>项目名artifactId>
    <version>版本号version>
dependency>

然后这个项目就作为一个jar包加入自己的项目依赖了。

但是会出现一个问题,引进来的第三方依赖当中的bean没有生效。即使对应的类上已经加了@Component注解。

  • 原因是即使类被Component注解声明了,但是这个注解不能被Spring组件扫描到。就是SpringBoot项目的启动器有一个@SpringBootApplication注解,扫描只能扫描启动器所在的当前包以及当前包的子包。

解决方法

14.4.1.@ComponentScan组件扫描(不推荐)

在启动器上添加@ComponentScan注解,里面的value是一个String类型的数组,这个数组就是包名,会把包里面的所有Bean都提取到IOC容器中

@SpringBootApplication
@ComponentScan({"这个包名","另一个包名"})//指定要扫描的包
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
}

不推荐原因:使用繁琐,性能低

14.4.2.@Import导入(不推荐,使用@Import导入的类会被Spring加载到IOC容器中)

不推荐原因:

这个和上面那个都是在引入第三方依赖时,还要知道第三方依赖中有哪些配置类和哪些Bean类对象,对程序员来讲,很不友好,并且比较繁琐。

导入普通类:

依旧是将注解加在启动器上

@SpringBootApplication
@Import(要使用的类名.class)
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
}

导入配置类

配置类就是上面介绍的使用@Configuration注解的类,在这个配置类里面封装一个个@Bean注解引入第三方Bean

例如:

@Configuration
public class Config{
    @Bean
    public 类名1 方法名1{
        return new1对象;
    }
    
    @Bean
    public 类名2 方法名2{
        return new2对象;
    }
}
@SpringBootApplication
@Import(配置类的类名.class)
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
}

导入ImportSelector接口实现类

这个是在第三方项目中实现ImportSelector的一个类。例如:

public class TestImportSelector implements ImportSelector{
    public String[] selectImports(AnnotationMetadata importClassMetadata){
        return new String[]{"包名.配置类名"};//把我们需要的bean通过这个字符串类名返回
    }
}

然后再启动器中调用

@SpringBootApplication
@Import(ImportSelector接口实现类.class)
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
}

14.4.3.@Enable注解方式(推荐)

这个是为了解决上面两个方法的问题的一种比较常见的解决方式。第三方依赖提供给我们一个注解,这个注解一般都以@EnableXXXX开头的注解,注解中封装的就是@Import注解。并且SpringBoot一般也是这么实现的。

例如第三方项目中有这么一个类:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MyImportSelector.class)
public @interface EnableHeaderConfig{
}

那么我们在启动器上就可以直接使用:

@SpringBootApplication
@EnableHeaderConfig
public class 启动器{
    public static void main(String[] args){
        SpringApplication.run(启动器.class,args);
    }
}

第15章.高级Maven使用之模块化

Maven 是一个主要用于Java项目的构建工具,它通过使用项目对象模型(Project Object Model, 简称POM)来简化构建过程。POM定义了项目的基本信息、依赖关系、构建配置等,并且这些信息被存储在一个名为pom.xml的文件中。

为什么要分模块开发?

答:按照功能拆成若干个子模块,方便项目的管理维护、扩展,也方便模块间的相互调用,资源共享。

15.1依赖继承

  1. 首先,需要创建一个父项目(也称为聚合项目或超级POM)。这个父项目本身可能不包含任何实际代码,但它定义了所有子模块共用的配置。要注意,需要在父项目描述下加上pom的描述。
  2. 每个子模块需要在其pom.xml中指定对父项目的继承。这样,子模块就会自动使用父项目中定义的配置了。
  3. 主要的标签

例如:我们的子模块都使用了lombok注解,我们就可以这么写

父项目pom.xml:

<project>
    <modelVersion>4.0.0modelVersion>
    <groupId>com.examplegroupId>
    <artifactId>parent-projectartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>pompackaging>
    ...
    
    <dependencies>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            <version>1.18.34version>
        dependency>
    dependencies>

    
    <build>
        <plugins>
            ...
        plugins>
    build>
    ...

    
    <modules>
        <module>child-module-1module>
        <module>child-module-2module>
        ...
    modules>
project>

子模块需要配置如下

<project>
    ...
    <parent>
        <groupId>com.examplegroupId>
        <artifactId>parent-projectartifactId>
        <version>1.0-SNAPSHOTversion>
        <relativePath>../parent-project/pom.xmlrelativePath>//这个是相对路径,写这个路径的前提是这两个项目在同一级下
    parent>

    ...
project>

15.2.依赖聚合

依赖聚合指的是在一个父项目(或称为多模块项目)中集中管理所有子模块的依赖关系。它允许在父项目的POM文件中定义一组依赖项及其版本号,然后让各个子模块根据需要引用这些依赖而无需再次指定版本信息。

可以在中定义版本号,然后在或直接在中引用这些属性。这样做可以进一步简化版本管理,使得更新依赖版本更加方便。如果需要更改某个库的版本,只需在一个地方修改即可。

好处:确保整个项目使用相同版本的库、子模块不需要重复制定依赖的版本号、当需要升级某个依赖库时,只需要修改父POM的一个地方。

例如部分子项目要用jwt令牌,部分要用lombok的注解,我们就可以这么写。

父pom.xml

<project>
    <modelVersion>4.0.0modelVersion>
    <groupId>com.examplegroupId>
    <artifactId>parent-projectartifactId>
    <version>1.0-SNAPSHOTversion>
    <packaging>pompackaging>
    ...
    <properties>
        <lombok.version>1.18.24lombok.version>
        <jwt.version>0.9.1jwt.version>
    properties>
    
    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.projectlombokgroupId>
                <artifactId>lombokartifactId>
                <version>${lombok.version}version>
            dependency>
            <dependency>
                <groupId>io.jsonwebtokengroupId>
                <artifactId>jjwtartifactId>
                <version>${jwt.version}version>
            dependency>
            
        dependencies>
    dependencyManagement>
    ...
    <modules>
        <module>child-module-1module>
        <module>child-module-2module>
        ...
    modules>
project>

使用lombok的子模块:

<project>
    ...
    <parent>
        <groupId>com.examplegroupId>
        <artifactId>parent-projectartifactId>
        <version>1.0-SNAPSHOTversion>
        <relativePath>../parent-project/pom.xmlrelativePath>
    parent>
    ...
    <dependencies>
        <dependency>
            <groupId>org.projectlombokgroupId>
            <artifactId>lombokartifactId>
            
        dependency>
    dependencies>
    ...
project>

使用jwt的子模块:

<project>
    ...
    <parent>
        <groupId>com.examplegroupId>
        <artifactId>parent-projectartifactId>
        <version>1.0-SNAPSHOTversion>
        <relativePath>../parent-project/pom.xmlrelativePath>
    parent>
    ...
    <dependencies>
        <dependency>
            <groupId>io.jsonwebtokengroupId>
            <artifactId>jjwtartifactId>
            
        dependency>
    dependencies>
    ...
project>

15.3.私服

这个情况很少见,大部分公司没有自己的本地仓库。主要是改变Maven的Setting.xml来实现。

打开maven的config/setting.xml文件,修改以下元素:

...
<server>
    <id>maven-releasesid>
    <username>用户名username>
    <password>密码password>
server>
    
<server>
    <id>maven-snapshotsid>
    <username>用户名username>
    <password>密码password>
server>
...
<mirror>
    <id>maven-publicid>
    <mirrorOf>*mirrorOf>
    <url>仓库地址url>
mirror>
...
<profile>
    <id>snapshotsid>
        <activation>
        	<activeByDefault>trueactiveByDefault>
        activation>
    <repositories>
        <repository>
            <id>maven-publicid>
            <url>仓库地址url>
            <releases>
            	<enabled>trueenabled>
            releases>
            <snapshots>
            	<enabled>trueenabled>
            snapshots>
        repository>
    repositories>
profile>
...

然后在pom.xml中配置

...
<distributionManagement>
    
    <repository>
        <id>maven-releasesid>
        <url>稳定版仓库地址url>
    repository>
    
    
    <snapshotRepository>
        <id>maven-snapshotsid>
        <url>快照仓库地址url>
    snapshotRepository>
distributionManagement>
...

你可能感兴趣的:(学习笔记,学习,笔记,spring,boot)