# 框架介绍

springboot:spring 项目快速部署,起步依赖(maven 坐标) 和 自动配置(ioc)
IOC:控制反转,对象的创建控制权由程序自身转移到外部(容器)
DI:依赖注入

  • controller:控制层,接收前端请求,进行处理,响应数据;封装结果
  • service:业务逻辑层,处理具体的业务逻辑;复用性
  • dao:数据访问层(持久层),负责数据访问操作;mapper 映射和 pojo 实体类

IDEA 服务器 url: https://start.aliyun.com/ (官方 springboot 构建时不能使用低版本 jdk)
pom.xml:maven 依赖

l
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <!-- boot 父工程,用于管理起步依赖的版本 -->
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.14</version>
    </parent>
    <groupId>com.mof</groupId>
    <artifactId>tlias</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>tlias</name>
    <description>tlias</description>
    <dependencies>
        <!-- web 起步依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
</project>

resource/application.yml:配置文件

l
server:
  port: 1145
  servlet:
    context-path: /demo

DemoApplication:启动类

a
@SpringBootApplication
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

# 自动配置

SpringBootApplication 注解组合 EnableAutoConfiguration 注解,再组合 Import 注解导入 AutoConfigurationImportSelector 类,实现 selectImports 方法,读取 META-INF 目录下 .imports 文件(boot2.7 以前是 spring.factories ),读取全类名,解析注册条件,注入满足条件的 Bean 对象
全路径: META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
自动配置

自己实现自动配置,要组合 EnableAutoConfiguration 注解,在 .imports 文件写入自动配置类全名,在自动配置类中进行 bean 配置
starter 模块包含 autoconfigure 模块,别的项目只引用 starter 模块,就可以实现起步依赖和自动配置

# 项目部署

pom.xml
添加 maven 插件

l
<build>
    <plugins>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>3.4.4</version>
        </plugin>
    </plugins>
</build>

打包:Maven -> Lifecycle -> package,打包项目,在 target 目录生成 jar 包
启动: java -jar demo.jar --server.port=3000
属性更改(优先级从上往下提升):

  • resource 下 application.yml
  • jar 包同目录下 application.yml
  • 环境变量
  • 启动时参数,-- 键 = 值

# 多环境配置

# 单文件配置

--- 作为分隔符, on-profile 设置环境名, active 激活环境
特定环境属性优先级高于通用环境
application.yml

l
# 通用信息,指定生效环境
spring:
  profiles:
    active: dev
---
# 开发环境
spring:
  config:
    activate:
      on-profile: dev
server:
  port: 3000
---
# 测试环境
spring:
  config:
    activate:
      on-profile: test
server:
  port: 3001

# 多文件配置

文件名: application-dev.yml ,后面接环境名
application.yml
环境为单文件

l
spring:
  profiles:
    active: dev

环境由多个配置组成,用 group 设置

l
spring:
  profiles:
    active: dev
    group:
      "dev": devServer,devDB,devSelf

# ApplicationContextInitializer

生命周期:Springboot 启动 -> SpringbootApplication 启动 -> ApplicationContextInitializer -> ApplicationContext 初始化
作用:在上下文创建后、刷新前,对上下文进行一些自定义的初始化操作,如,配置环境变量
实现 ApplicationContextInitializer 接口,重写 initialize 方法

a
public class MyApplicationContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        Map<String, Object> map = new HashMap<>();
        map.put("myName", "mof");
        // 获取属性资源管理对象
        MutablePropertySources propertySources = applicationContext.getEnvironment().getPropertySources();
        propertySources.addLast(new MapPropertySource("map", map));
    }
}

resource/spring.factories 写入自定义类,格式为接口全路径 = 自定义类全路径
org.springframework.context.ApplicationContextInitializer=com.mof.springbootdemo.initializer.MyApplicationContextInitializer

在启动类获取环境变量

a
ConfigurableApplicationContext context = SpringApplication.run(SpringbootDemoApplication.class, args);
String value = context.getEnvironment().getProperty("myName");
System.out.println(value);

# ApplicationListener

监听容器发布的事件,可以使用监听器加载资源,开启定时任务等
实现 ApplicationListener 接口,重写 onApplicationEvent 方法

a
public class MyApplicationListener implements ApplicationListener {
    @Override
    public void onApplicationEvent(ApplicationEvent event) {
        if (event instanceof ApplicationReadyEvent){
            System.out.println("容器初始化成功");
        }
        if (event instanceof ApplicationFailedEvent){
            System.out.println("容器初始化失败");
        }
    }
}

spring.factories 添加自定义类

# Bean

# BeanFactory

Bean 容器的根接口,提供 Bean 对象的创建、配置、依赖注入等功能
常见的两个实现类:

  • DefaultListableBeanFactory
  • AnnotationConfigServletWebServerApplicationContext

# BeanDefinition

描述 Bean,包括对象、属性、行为、接口、注解等
在注入 IOC 容器前,先封装为 BeanDefinition,再进一步创建 Bean
两个实现类对应的注解

  • ScannedGenericBeanDefinition
    • Component
    • Controller
    • Service
    • Repository
    • Configuration
  • ConfigurationClassBeanDefinition
    • Bean
a
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
BeanDefinition user = beanFactory.getBeanDefinition("user");
System.out.println(user.getClass());

# BeanFactoryPostProcessor

Bean 工厂后置处理器,当 BeanFactory 准备好后、Bean 初始化前,会调用该接口的 postProcessBeanFactory 方法,经常用于新增 BeanDefinition

实现类名作用
ConfigurationClassPostProcessor扫描启动类所在包下的注解
ServltComponentRegisteringPostProcessor扫描 @WebServlet、.@WebFilter、.@WebListener
CachingMetadataReaderFactoryPostProcessor配置 ConfigurationClassPostProcessor
ConfigurationWarningsPostProcessor配置警告提示
a
@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        // 准备 BeanDefinition
        GenericBeanDefinition dfUser = new GenericBeanDefinition();
        dfUser.setBeanClass(User.class);
        // 注册 BeanDefinition
        DefaultListableBeanFactory dbf = (DefaultListableBeanFactory) configurableListableBeanFactory;
        dbf.registerBeanDefinition("user", dfUser);
    }
}

# Aware

感知接口,感知 Spring 应用程序执行过程中的一些变化,在特定时机调用方法
子接口:

  • BeanNameAware :Bean 名称的感知接口
  • BeanClassLoaderAware :Bean 类加载器的感知接口
  • BeanFactoryAware :Bean 工厂的感知接口

重写对应的 set 方法

a
@Component
public class MyAware implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware {
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("setBeanClassLoader: " + classLoader);
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("setBeanFactory: " + beanFactory);
    }
    @Override
    public void setBeanName(String s) {
        System.out.println("setBeanName: " + s);
    }
}

# InitializingBean/DisposableBean

InitializingBean :初始化接口,重写 afterPropertiesSet 方法,当 Bean 实例化后调用方法,加载资源
DisposableBean :销毁接口,重写 destroy 方法,在 Bean 销毁前调用方法,释放资源
使用注解代替: @PostConstruct@PreDestroy

a
@Component
public class t implements InitializingBean, DisposableBean {
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("t实例化了");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("t被销毁了");
    }
}

# BeanPostProcessor

Bean 后置处理器,在 Bean 对象初始化前后,调用对应方法

  • postProcessBeforeInitialization :Bean 对象初始化前调用
  • postProcessAfterInitialization :Bean 对象初始化后调用
实现类名作用
AutowiredAnnotationBeanPostProcessor用来完成依赖注入
AbstractAutoProxyCreator用来完成代理对象的创建
AbstractAdvising BeanPostProcessor将 Aop 中的通知作用于特定的 Bean 上
a
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor.postProcessBeforeInitialization:" + beanName);
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("BeanPostProcessor.postProcessAfterInitialization:" + beanName);
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

tBean 初始化前
t 实例化了
tBean 初始化后

# Springboot 启动流程

new SpringApplication()

  1. 确认 web 应用的类型,一般为 Servlet
  2. 加载 ApplicationContextInitializer
  3. 加载 ApplicationListener
  4. 记录主启动类

启动流程-1
run()

  1. 准备环境对象 Environment, 用于加载系统属性等等
  2. 打印 Banner
  3. 实例化容器 Context
  4. 准备容器,为容器设置 Environment、 BeanFactoryPostProcessor , 并加载主类对应的 BeanDefinition
  5. 刷新容器,创建 Bean 实例
  6. 返回容器

启动流程-2

# IOC 容器初始化流程

AbstractApplicationContext.refresh()

  1. BeanFactory (DefaultListableBeanFactory)
  • 设置 ClassLoader
  • 设置 Environment
  1. 扫描要放入容器中的 Bean,得到对应的 BeanDefinition (只扫描,并不创建)
  2. 注册 BeanPostProcessor
  3. 处理国际化
  4. 初始化事件多播器 ApplicationEventMulticaster
  5. 启动 tomcat
  6. 绑定事件监听器和事件多播器
  7. 实例化非懒加载的单例 Bean
  8. 扫尾工作,如,清空实例化时占用缓存

IOC容器初始化流程

# Bean 生命周期

AbstractAutowireCapableBeanFactory.doCreateBean :创建对象和初始化

  • 创建对象
    • 实例化(构造方法)
    • 依赖注入
  • 初始化
    • 执行 Aware 接口回调
    • 执行 BeanPostProcessor.postProcessBeforeInitialization
    • 执行 InitializingBean 回调(先执行 @PostConstruct )
    • 执行 BeanPostProcessor.postProcessAfterInitialization
  • 使用对象
  • 销毁对象
    • 执行 DisposableBean 回调(先执行 @PreDestory )

创建实例
Bean生命周期-1

依赖注入和初始化
Bean生命周期-2

初始化中会依次调用 AwarepostProcessBeforeInitializationInitializingBeanpostProcessAfterInitialization 方法
Bean生命周期-3

a
@Component
public class t implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware,
        InitializingBean, DisposableBean{
    public t(){
        System.out.println("t 构造函数");
    }
    @PostConstruct
    public void postConstruct(){
        System.out.println("t PostConstruct");
    }
    @PreDestroy
    private void preDestory(){
        System.out.println("t PreDestory");
    }
    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("t BeanClassLoaderAware.setBeanClassLoader:" + classLoader);
    }
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("t BeanFactoryAware.setBeanFactory:" + beanFactory);
    }
    @Override
    public void setBeanName(String name) {
        System.out.println("t BeanNameAware.setBeanName:" + name);
    }
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("t InitializingBean.afterPropertiesSet");
    }
    @Override
    public void destroy() throws Exception {
        System.out.println("t DisposableBean.destroy");
    }
}

运行结果:

t 构造函数
t BeanNameAware.setBeanName:t
t BeanClassLoaderAware.setBeanClassLoader:sun.misc.Launcher$AppClassLoader@18b4aac2
t BeanFactoryAware.setBeanFactory:……
BeanPostProcessor.postProcessBeforeInitialization:t
t PostConstruct
t InitializingBean.afterPropertiesSet
BeanPostProcessor.postProcessAfterInitialization:t
t PreDestory
t DisposableBean.destroy

# Bean 循环依赖

依赖闭环问题:A 依赖 B,B 依赖 A

采用三级缓存解决

  • 一级缓存: singletonObjects
  • 二级缓存: earlySingletonObjects
  • 三级缓存: singletonFactories ,为了 AOP 动态代理

开启配置: spring.main.allow-circular-references=true
开启缓存配置后,会将 Bean 添加到 Factory,并存储到三级缓存中
其中 get 方法实际上是会返回 Bean 的代理对象
Bean循环依赖-1

a
protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            // 添加 EarlyBean 到 Factory 中,存储在 singletonFactories 中
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

进行依赖注入时,先从一级缓存和二级缓存尝试获取 Bean,获取失败,再加锁,进行一、二、三级缓存的获取
获取成功后,会将依赖的 Bean 添加到二级缓存,从三级缓存中移除
Bean循环依赖-2

将注入完成的 Bean 添加到一级缓存中,从二、三级缓存移除
Bean循环依赖-3

总结:
调用 A 的依赖注入,A 依赖 B,A 存储在三级缓存,此时 B 尚未注入;
调用 B 的依赖注入,B 存储在三级缓存,从三级缓存获取 A 的(代理)对象,获取成功后将其放入二级缓存中,并从三级缓存移除,此时 B 完成依赖注入,将 B 放入一级缓存,并从二三级缓存移除;
回到 A 的依赖注入,此时 B 会注入到 A 中,A 也完成依赖注入,同样将 A 放入一级缓存,从二三级缓存移除。

# SpringMvc 执行流程

从接收请求到给浏览器响应的过程
DispatcherServlet.doDispatch

  1. 获取 HandlerExecutionChain
  2. 获取 HandlerAdapter
  3. 执行拦截器的 preHandle 方法
  4. 执行 HandlerAdapter (HandlerMethod)
  5. 执行拦截器的 postHandle 方法
  6. 执行异常处理逻辑(全局异常处理器)
  7. 解析视图(使用 response 对象响应数据)
  8. 渲染视图
  9. 执行拦截器的 afterCompletion

HandlerExecutionChain :获取对应 HandlerMapping,描述路径和对应方法,获取拦截器列表
HandlerAdapter :获取合适的参数解析器和结果处理器
SpringMvc执行流程-1
根据 Handler 和 Adapter,真正执行对应的方法,处理结果

SpringMvc执行流程-2
如果发生异常,处理异常;解析数据并渲染视图

SpringMvc执行流程-3

# 注解

配置读取:

  • @Value (${email.user}) 获取 application.yml 中的配置信息
  • @ConfigurationProperties (prefix = "email") 统一配置前缀

Bean 注册:

  • @Component
  • @Controller
  • @Service
  • @Mapper
  • @Bean
  • @Import

注册条件:

  • @Conditional…… 系列
  • @ConditionalOnProperty 有声明属性才注入
  • @ConditionalOnMissingBean 不存在指定 Bean 才注入
  • @ConditionalOnClass 存在指定类才注入

# ThreadLocal

提供线程局部变量,set 和 get 存取变量,线程安全

a
public class ThreadLocalUtil {
    private final static ThreadLocal THREAD_LOCAL = new ThreadLocal();
    public static <T>T get(){
        return (T) THREAD_LOCAL.get();
    }
    public static void set(Object value){
        THREAD_LOCAL.set(value);
    }
    public static void remove(){
        THREAD_LOCAL.remove();
    }
}

# 全局异常处理器

a
@ControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler({Exception.class})
    public void handleException(Exception ex){
        ex.printStackTrace();
    }
}

# Validation

参数校验框架
pom.xml

l
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
</dependency>
  1. 在参数前面添加 @Pattern 注解,在类前面添加 @Validated 注解,这样需要设置全局错误处理器来处理错误信息
a
// 方法一
@RequestMapping("byId")
public String byId(@Pattern(regexp = "^\\d$") String id){
    return userService.byId(Integer.decode(id));
}
@RestControllerAdvice
public class GlobalExceptionHandler {
    @ExceptionHandler(Exception.class)
    public String handleExcetion(Exception e){
        //e.printStackTrace();
        return StringUtils.isNullOrEmpty(e.toString())?"操作失败!":e.toString();
    }
}
  1. 直接在实体类元素前面添加 NotNull、Pattern 等注解,再在方法中使用 @Validated,配合 BindingResult 处理错误信息
a
// 方法二
@RequestMapping("byId")
public String byId(@Validated Integer id, BindingResult result){
    if (result.hasErrors()){
        return "参数错误";
    }
    return userService.byId(id);
}

# 文件上传

pom.xml

l
<dependency>
    <groupId>com.aliyun.oss</groupId>
    <artifactId>aliyun-sdk-oss</artifactId>
    <version>3.14.1</version>
</dependency>

配置

l
#OSS 配置
spring:
  servlet:
    multipart:
      max-file-size: 10MB       # 单个文件大小限制
      max-request-size: 100MB   # 请求大小限制
aliyun:
  oss:
    file:
      # 控制台 - oss - 点击对应桶 - 概览 - 地域节点
      endpoint: oss-cn-guangzhou.aliyuncs.com
      keyid: 
      keysecret: 
      bucketname: 
file:
  upload-dir: /static-oss/src/main/resources/

前端

l
<form action="/upload" method="post" enctype="multipart/form-data">
    photo: <input type="file" name="file">
    <input type="submit" value="uploadFile" name = "submit">
</form>

上传到本地

a
public String upload(MultipartFile img) throws IOException {
    // TODO: 条件限制,格式、大小、路径
    // 获取当前路径拼接
    String dir = System.getProperty("user.dir") + uploadDir;
    String filename = "public/imgs/" + System.currentTimeMillis() + '-' + img.getOriginalFilename();
    File file = new File(dir , filename);
    img.transferTo(file);
    log.info("ProductController.upload执行完毕,结果为:{}", "图片地址:" + file.getAbsolutePath());
    return filename;
}

阿里云 oss 工具类

a
/**
 * @Description: 阿里云 oss 工具类
 * @Author: mof
 * @CreateTime: 2025-03-18
 */
import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import com.aliyun.oss.model.GetObjectRequest;
import com.aliyun.oss.model.ObjectMetadata;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.util.Date;
@Component
@Data
@ConfigurationProperties(prefix = "aliyun.oss.file")
public class AliyunOssUtils {
    // 基本属性 读取配置文件
    private  String endPoint;
    private  String keyId;
    private  String keySecret;
    private  String bucketName;
    /**
     * byte 数组格式上传文件并返回上传后的 URL 地址
     * @param objectName        完整文件名,例如 abc/efg/123.jpg
     * @param content           文件内容,byte 数组格式
     * @param contentType       文件类型   image/png  image/jpeg
     * @param hours             过期时间   单位小时
     * @Author zhaoweifeng
     */
    public  String uploadImage(String objectName,
                               byte[] content,String contentType,int hours)  throws Exception {
        // 创建 OSSClient 实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);
        // 创建上传文件的元信息,可以通过文件元信息设置 HTTP header (设置了才能通过返回的链接直接访问)。
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(contentType);
        // 文件上传
        ossClient.putObject(bucketName, objectName, new ByteArrayInputStream(content), objectMetadata);
        // 设置 URL 过期时间为 hours 小时。
        Date expiration = new Date(System.currentTimeMillis() + 3600 * 1000 * 300 * hours);
        // 返回 url 地址
        String url = ossClient.generatePresignedUrl(bucketName, objectName, expiration).toString();
        // 关闭 OSSClient。
        ossClient.shutdown();
        return url;
    }
    /**
     * 下载文件到本地
     * @param objectName        完整文件名,例如 abc/efg/123.jpg
     * @param localFile         下载到本地文件目录
     * @Author zhaoweifeng
     */
    public  void downFile(String objectName,
                          String localFile) throws Exception {
        // 创建 OSSClient 实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);
        // 下载 OSS 文件到本地文件。如果指定的本地文件存在会覆盖,不存在则新建。
        ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(localFile));
        // 关闭 OSSClient。
        ossClient.shutdown();
    }
    /**
     * 删除文件
     * @param objectName        完整文件名,例如 abc/efg/123.jpg
     * @Author zhaoweifeng
     */
    public  void deleteFile(String objectName) {
        // 创建 OSSClient 实例。
        OSS ossClient = new OSSClientBuilder().build(endPoint, keyId, keySecret);
        // 删除文件。如需删除文件夹,请将 ObjectName 设置为对应的文件夹名称。如果文件夹非空,则需要将文件夹下的所有 object 删除后才能删除该文件夹。
        ossClient.deleteObject(bucketName, objectName);
        // 关闭 OSSClient。
        ossClient.shutdown();
    }
}

# redis

# 配置

pom.xml

l
<!-- 缓存依赖 -->
<!--spring-cache-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- redis 连接池 -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

application.yml

l
spring:
  cache:
    type: redis
  redis:
    host: 127.0.0.1
    port: 6379
    jedis: # 设置 Redis 连接池
      pool:
        max-wait: 2000ms
        min-idle: 2
        max-idle: 8
        max-active: 10

# 使用

a
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Override
public void sendCode(String phone) {
    // 插入,设置键、值、过期时间、时间单位
    stringRedisTemplate.opsForValue().set(LOGIN_CODE_KEY + phone, code, LOGIN_CODE_TTL, TimeUnit.MINUTES);
}

# 缓存使用

开启注解:启动类添加 @EnableCaching 注解
添加缓存:在方法上添加注解,value 为前缀,key 为键,值为返回值
@Cacheable(value = "list.user", key = "#param.currentPage+'-'+#param.pageSize")
删除缓存:
@CacheEvict(value = "list.user", allEntries = true)
注解参考:

  • @EnableCaching 启动类,开启缓存
  • @Cacheable 获取和添加
  • @CachePut 修改
  • @CacheEvict 删除
  • @Caching 多步操作

# 会话跟踪

  • cookie:存储在客户端浏览器
    • http 协议支持(Set-cookie 和 Cookie), HttpServletResponse#addCookie
    • 移动 app 无法使用,不安全,不能跨域
  • session:存储在服务端,用 cookie 存储 sessionId
    • HttpSession#setAttribute
    • 集群环境下无法直接使用,cookie 缺点
  • 令牌:jwt
    • 支持 pc、移动端,支持集群环境,减少服务器压力
    • 需要手动实现

# JWT

JSON Web Token:以 json 格式安全的传输信息,分三部分:

  1. Header 头,记录令牌类型和签名算法
  2. Payload 有效载荷,携带信息,Base64 编码
  3. Signature 签名,由前两部分 + 密钥加密得到

缺点:时间过期前无法自动销毁;过期无法刷新
解决:jwt+redis 缓存,jwt 不设置过期时间,用 redis 存储过期时间

pom.xml

l
<!--jwt-->
<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

jwt 工具类:生成和验证 jwt

a
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.util.Date;
import java.util.Map;
/**
 * JWT 工具类
 */
public class JwtUtil {
    private final static String secret = "secret_key";
    /**
     * 生成 JWT token
     * @param: claim 载荷
     * @return: java.lang.String
     */
    public static String gen(Map<String, Object> claim){
        String token = JWT.create()
                .withClaim("claim", claim)  // 添加载荷
                .withExpiresAt(new Date(System.currentTimeMillis() + 1000 * 3600))  // 过期时间
                .sign(Algorithm.HMAC256(secret));  // 加密算法和密钥
        return token;
    }
    /**
     * 验证 JWT token,返回内部信息
     * @param: token jwt 令牌
     * @return: java.util.Map<java.lang.String,java.lang.Object>
     */
    public static Map<String, Object> parse(String token){
        try {
            Map<String, Object> claim = JWT.require(Algorithm.HMAC256(secret))
                    .build()  // JWT 验证器
                    .verify(token)  // 生成解析后的 JWT 对象
                    .getClaim("claim")  // 获取信息
                    .asMap();
            return claim;
        } catch (Exception e) {
            return null;
        }
    }
}

# 拦截器

添加拦截器

a
@Component
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("验证失败!");
        return false;  // 不放行
    }
}

注册拦截器

  • /* :一级路径
  • /** :任意路径
a
@Configuration
public class WebMvcConfiguration implements WebMvcConfigurer {
    @Autowired
    private LoginInterceptor loginInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(loginInterceptor).addPathPatterns("/**").excludePathPatterns("/login");
    }
}

# AOP

AOP:Aspect Oriented Programming,面向切面编程
Spring 会基于动态代理生成对应的代理对象,执行通知方法和原始方法

  • 连接点:JoinPoint,可以被 AOP 控制的方法
  • 切入点:PointCut,匹配连接点的条件,实际被通知调用的方法
  • 通知:Advice,重复逻辑,共性功能(最终体现为一个方法)
  • 切面:Aspect,描述通知与切入点的对应关系
  • 目标对象:Target,通知所应用的对象
l
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

通知类型:环绕通知用 ProceedingJoinPoint 获取原方法信息,其他通知用 JoinPoint 获取原方法信息

  • @Around:环绕通知,在目标方法前、后都被执行,手动调用 ProceedingJoinPoint#proceed 执行原方法
  • @Before:前置通知,在目标方法前被执行
  • @Aftr:后置通知,在目标方法后被执行,无论是否有异常都会执行
  • @AfterReturning:返回后通知,在目标方法正常执行后被执行
  • @AfterThrowing:异常后通知,在目标方法发生异常后执行

切入点表达式: @Pointcut ,提取公共的切入点

  • execution 表达式,匹配方法签名, execution([访问修饰符] 返回值 [包名.类名.]方法名(方法参数) [throws 异常])
    • * ,单个任意符号
    • .. ,多个连续的任意符号
    • 可以用 &&||! 组合匹配切入点
  • @annotation ,用于匹配标识有特有注解的方法, @annotation(包名.注解)

通知顺序:

  • 默认按切面类字母顺序,前置方法按字母靠前,后置方法按字母靠后
  • @Order 设置权重,前置方法按数字较小,后置方法按数字较大

切面类

a
@Aspect
@Component
@Slf4j
public class RecordTimeAspect {
    // @Pointcut 注解,提取公共切入点
    //execution 表达式,基于方法签名匹配切入点
    @Pointcut("execution(* com.mof.aopdemo.*.*(..))")
    public void pt1(){}
    //annotation 注解,基于注解类型匹配切入点
    @Pointcut("@annotation(com.mof.aopdemo.LogOperation)")
    public void pt2(){}
    // @Around,环绕通知,调用 ProceedingJoinPoint.proceed 执行原方法
    @Around("pt2()")
    public Object recordTime(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long begin = System.currentTimeMillis();
        Object proceed = proceedingJoinPoint.proceed();
        long end = System.currentTimeMillis();
        log.info("RecordTimeAspect.recordTime执行完毕,{}运行时间为:{}ms", proceedingJoinPoint.getSignature(), end - begin);
        return proceed;
    }
    // @After,后置通知,调用 JoinPoint 获取原方法信息
    @After("pt2()")
    public void afterCall(JoinPoint joinPoint){
        log.info("RecordTimeAspect.afterCall执行完毕,{}执行完毕", joinPoint.getSignature());
    }
}

自定义注解

a
@Target(ElementType.METHOD)  // 加在方法上
@Retention(RetentionPolicy.RUNTIME)  // 在运行时生效
public @interface LogOperation {
}