# 框架介绍
springboot:spring 项目快速部署,起步依赖(maven 坐标) 和 自动配置(ioc)
IOC:控制反转,对象的创建控制权由程序自身转移到外部(容器)
DI:依赖注入
- controller:控制层,接收前端请求,进行处理,响应数据;封装结果
- service:业务逻辑层,处理具体的业务逻辑;复用性
- dao:数据访问层(持久层),负责数据访问操作;mapper 映射和 pojo 实体类
IDEA 服务器 url: https://start.aliyun.com/
(官方 springboot 构建时不能使用低版本 jdk)
pom.xml:maven 依赖
<?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:配置文件
server: | |
port: 1145 | |
servlet: | |
context-path: /demo |
DemoApplication:启动类
@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 插件
<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
# 通用信息,指定生效环境 | |
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
环境为单文件
spring: | |
profiles: | |
active: dev |
环境由多个配置组成,用 group 设置
spring: | |
profiles: | |
active: dev | |
group: | |
"dev": devServer,devDB,devSelf |
# ApplicationContextInitializer
生命周期:Springboot 启动 -> SpringbootApplication 启动 -> ApplicationContextInitializer -> ApplicationContext 初始化
作用:在上下文创建后、刷新前,对上下文进行一些自定义的初始化操作,如,配置环境变量
实现 ApplicationContextInitializer
接口,重写 initialize
方法
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
在启动类获取环境变量
ConfigurableApplicationContext context = SpringApplication.run(SpringbootDemoApplication.class, args); | |
String value = context.getEnvironment().getProperty("myName"); | |
System.out.println(value); |
# ApplicationListener
监听容器发布的事件,可以使用监听器加载资源,开启定时任务等
实现 ApplicationListener
接口,重写 onApplicationEvent
方法
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
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 | 配置警告提示 |
@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 方法
@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
@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 上 |
@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()
- 确认 web 应用的类型,一般为
Servlet
- 加载
ApplicationContextInitializer
- 加载
ApplicationListener
- 记录主启动类
run()
- 准备环境对象 Environment, 用于加载系统属性等等
- 打印 Banner
- 实例化容器 Context
- 准备容器,为容器设置 Environment、
BeanFactoryPostProcessor
, 并加载主类对应的BeanDefinition
- 刷新容器,创建 Bean 实例
- 返回容器
# IOC 容器初始化流程
AbstractApplicationContext.refresh()
- BeanFactory (DefaultListableBeanFactory)
- 设置 ClassLoader
- 设置 Environment
- 扫描要放入容器中的 Bean,得到对应的
BeanDefinition
(只扫描,并不创建) - 注册
BeanPostProcessor
- 处理国际化
- 初始化事件多播器
ApplicationEventMulticaster
- 启动 tomcat
- 绑定事件监听器和事件多播器
- 实例化非懒加载的单例 Bean
- 扫尾工作,如,清空实例化时占用缓存
# Bean 生命周期
AbstractAutowireCapableBeanFactory.doCreateBean
:创建对象和初始化
- 创建对象
- 实例化(构造方法)
- 依赖注入
- 初始化
- 执行
Aware
接口回调 - 执行
BeanPostProcessor.postProcessBeforeInitialization
- 执行
InitializingBean
回调(先执行@PostConstruct
) - 执行
BeanPostProcessor.postProcessAfterInitialization
- 执行
- 使用对象
- 销毁对象
- 执行
DisposableBean
回调(先执行@PreDestory
)
- 执行
创建实例
依赖注入和初始化
初始化中会依次调用 Aware
、 postProcessBeforeInitialization
、 InitializingBean
、 postProcessAfterInitialization
方法
@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 的代理对象
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 添加到一级缓存中,从二、三级缓存移除
总结:
调用 A 的依赖注入,A 依赖 B,A 存储在三级缓存,此时 B 尚未注入;
调用 B 的依赖注入,B 存储在三级缓存,从三级缓存获取 A 的(代理)对象,获取成功后将其放入二级缓存中,并从三级缓存移除,此时 B 完成依赖注入,将 B 放入一级缓存,并从二三级缓存移除;
回到 A 的依赖注入,此时 B 会注入到 A 中,A 也完成依赖注入,同样将 A 放入一级缓存,从二三级缓存移除。
# SpringMvc 执行流程
从接收请求到给浏览器响应的过程DispatcherServlet.doDispatch
- 获取
HandlerExecutionChain
- 获取
HandlerAdapter
- 执行拦截器的
preHandle
方法 - 执行
HandlerAdapter
(HandlerMethod) - 执行拦截器的
postHandle
方法 - 执行异常处理逻辑(全局异常处理器)
- 解析视图(使用 response 对象响应数据)
- 渲染视图
- 执行拦截器的
afterCompletion
HandlerExecutionChain
:获取对应 HandlerMapping,描述路径和对应方法,获取拦截器列表HandlerAdapter
:获取合适的参数解析器和结果处理器
根据 Handler 和 Adapter,真正执行对应的方法,处理结果
如果发生异常,处理异常;解析数据并渲染视图
# 注解
配置读取:
- @Value (${email.user}) 获取 application.yml 中的配置信息
- @ConfigurationProperties (prefix = "email") 统一配置前缀
Bean 注册:
- @Component
- @Controller
- @Service
- @Mapper
- @Bean
- @Import
注册条件:
- @Conditional…… 系列
- @ConditionalOnProperty 有声明属性才注入
- @ConditionalOnMissingBean 不存在指定 Bean 才注入
- @ConditionalOnClass 存在指定类才注入
# ThreadLocal
提供线程局部变量,set 和 get 存取变量,线程安全
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(); | |
} | |
} |
# 全局异常处理器
@ControllerAdvice | |
public class GlobalExceptionHandler { | |
@ExceptionHandler({Exception.class}) | |
public void handleException(Exception ex){ | |
ex.printStackTrace(); | |
} | |
} |
# Validation
参数校验框架
pom.xml
<dependency> | |
<groupId>org.springframework.boot</groupId> | |
<artifactId>spring-boot-starter-validation</artifactId> | |
</dependency> |
- 在参数前面添加 @Pattern 注解,在类前面添加 @Validated 注解,这样需要设置全局错误处理器来处理错误信息
// 方法一 | |
@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(); | |
} | |
} |
- 直接在实体类元素前面添加 NotNull、Pattern 等注解,再在方法中使用 @Validated,配合 BindingResult 处理错误信息
// 方法二 | |
@RequestMapping("byId") | |
public String byId(@Validated Integer id, BindingResult result){ | |
if (result.hasErrors()){ | |
return "参数错误"; | |
} | |
return userService.byId(id); | |
} |
# 文件上传
pom.xml
<dependency> | |
<groupId>com.aliyun.oss</groupId> | |
<artifactId>aliyun-sdk-oss</artifactId> | |
<version>3.14.1</version> | |
</dependency> |
配置
#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/ |
前端
<form action="/upload" method="post" enctype="multipart/form-data"> | |
photo: <input type="file" name="file"> | |
<input type="submit" value="uploadFile" name = "submit"> | |
</form> |
上传到本地
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 工具类
/** | |
* @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
<!-- 缓存依赖 --> | |
<!--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
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 |
# 使用
@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 无法使用,不安全,不能跨域
- http 协议支持(Set-cookie 和 Cookie),
- session:存储在服务端,用 cookie 存储 sessionId
HttpSession#setAttribute
- 集群环境下无法直接使用,cookie 缺点
- 令牌:jwt
- 支持 pc、移动端,支持集群环境,减少服务器压力
- 需要手动实现
# JWT
JSON Web Token:以 json 格式安全的传输信息,分三部分:
- Header 头,记录令牌类型和签名算法
- Payload 有效载荷,携带信息,Base64 编码
- Signature 签名,由前两部分 + 密钥加密得到
缺点:时间过期前无法自动销毁;过期无法刷新
解决:jwt+redis 缓存,jwt 不设置过期时间,用 redis 存储过期时间
pom.xml
<!--jwt--> | |
<dependency> | |
<groupId>com.auth0</groupId> | |
<artifactId>java-jwt</artifactId> | |
<version>4.4.0</version> | |
</dependency> |
jwt 工具类:生成和验证 jwt
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; | |
} | |
} | |
} |
# 拦截器
添加拦截器
@Component | |
public class LoginInterceptor implements HandlerInterceptor { | |
@Override | |
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { | |
System.out.println("验证失败!"); | |
return false; // 不放行 | |
} | |
} |
注册拦截器
/*
:一级路径/**
:任意路径
@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,通知所应用的对象
<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
设置权重,前置方法按数字较小,后置方法按数字较大
切面类
@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()); | |
} | |
} |
自定义注解
@Target(ElementType.METHOD) // 加在方法上 | |
@Retention(RetentionPolicy.RUNTIME) // 在运行时生效 | |
public @interface LogOperation { | |
} |