Spring相关概念
Spring对IOC的实现
IOC控制反转:控制反转是一种思想,将对象的创建和对象之间关系的维护交出去,交由第三方容器负责。可以降低程序耦合度,提高程序扩展力。
声明组件的注解
声明Bean的注解都有一个value属性,属性值用来指定Bean的ID,如果不指定默认是类名首字母变小写后的名字。
@Controller:将一个类标识为请求处理的控制器组件,用于接收用户的请求并处理相关的业务逻辑,最后返回相应的结果。@Service:将一个类标识为业务逻辑层(Service)组件,用于封装复杂的业务操作,并提供事务管理、依赖注入等功能。@Repository:将一个类标识为持久层(Repository)组件,主要用于实现数据访问和持久化操作。@Component:通用的组件注解,并没有特定的业务含义。@Mapper:MyBatis框架提供的一个注解,用于标识一个接口为Mapper接口,提供了数据库持久化操作的映射规则。
负责注入的注解
- @Value:用于将值赋给一个类的属性或方法参数。通过@Value注解,可以在运行时将值注入到被注解的属性或方法参数中。
- 属性注入:可以通过@Value注解将值注入到交由容器管理的类的属性中。
- 方法参数注入:可以通过@Value注解将值注入到方法参数中。
属性注入
@RestController
public class TestController {
@Value("${wenxuan.name}")
private String name;
}
方法参数注入
@Service
public class UserService {
private final String name;
@Autowired
public UserService (@Value("${wenxuan.name}") String name) {
this.name = name;
}
}
- @AutoWired注解可以用来注入非简单类型,被翻译为自动装配,单独使用@AutoWired注解,默认是根据类型自动装配(byType)。
@AutoWired注解有一个属性:required
- 如果该属性值为true,则表示被注入的Bean必须存在,如果不存在则报错。
- 如果该属性值为false,则表示被注入的Bean是否存在都可以,如果存在则注入,如果不存在也不报错。
@RestController
public class UserController {
@Autowired
private UserService userService;
}
- @Quafier注解可以用来注入非简单类型,用来指定注入的Bean的名称,也就是Bean的id(byName)。
@Quafier注解使用场景:当使用@AutoWired注解,来根据属性类型自动装配时,但属性类型有多个类匹配时,此时不知道使用哪个类对象进行装配,可以通过@Quafier注解指定Bean的name,从而实现唯一性。
@RestController
public class UserController {
@Autowired
private UserService userService;
}
- @Resource
@Resource注解也可以用来注入非简单类型,@Resource注解查找Bean的方式:
- 如果@Resource注解有name属性值,根据name属性值去查找对应名称的Bean(byName);
- 如果没有name名称匹配的Bean,根据属性名去查找对应名称的Bean(byName);
- 如果没有属性名匹配的Bean,根据属性类型去查找对应类型的Bean(byType);
- 如果没有类型的Bean,则报错;如果有多个类型匹配的Bean,则报错。
Bean对象生命周期
Spring其实就是一个管理Bean对象的工厂,它负责所有Bean对象的创建、Bean对象的销毁等。Bean的生命周期就是Bean对象从创建到销毁的这个过程。
Bean的生命周期之五步
Bean生命周期分为五步:
- 实例化Bean,调用Bean的无参构造方法
- 给Bean属性赋值,调用Bean的set方法
- 初始化Bean,调用Bean的initBean方法(手动编写且配置)
- 使用Bean对象
- 销毁Bean,调用Bean的destroyBean方法(手动编写且配置)
public class User {
private String name;
public void setName(String name) {
this.name = name;
System.out.println("第2步:给属性赋值");
}
public User() {
System.out.println("第1步:无参数构造方法执行");
}
public void initBean() {
System.out.println("第3步:初始化方法执行");
}
public void destroyBean() {
System.out.println("第5步:销毁方法执行");
}
}
Bean的生命周期之七步
Bean的生命周期之七步:
- 实例化Bean,调用无参构造函数
- 给Bean属性赋值,调用set方法
- Bean前处理器before
- 初始化Bean,调用initBean方法(手动编写且配置)
- Bean后处理器after
- 使用Bean
- 销毁Bean,调用destroyBean方法(手动编写且配置)
public class LogBeanPostProcessor implements BeanPostProcessor {
/**
* @param bean Bean对象
* @param beanName Bean对象的名称
*/
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器before执行");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
System.out.println("Bean后处理器after执行");
return bean;
}
}
new对象放入Spring容器中
public class BeanLifecycleTest {
// 自己new的对象放到spring容器中
@Test
public void testRegisterBean() {
User user = new User();
DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
// 将user对象放到spring容器中
defaultListableBeanFactory.registerSingleton("user", user);
// 从spring容器中取出对象
User userBean = defaultListableBeanFactory.getBean("user", User.class);
}
}
面向切面编程AOP
AOP介绍
AOP:核心的业务是纵向的,将与业务逻辑无关的交叉业务代码单独的提取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程中的过程。
AOP的七大术语
- 连接点:在程序的整个执行过程中,可以织入切面的位置。方法执行前后,异常抛出后等位置。
- 切点:在程序执行流程中,真正植入切面的方法。
- 通知:通知又叫做增强,具体要织入的diamagnetic。包括前置通知、后置通知、环绕通知、异常通知、最终通知。
- 切面:切点 + 通知 等于 切面。
- 织入:把通知应用到目标对象上的过程。
- 代理对象:一个目标对象被织入通知后产生的新对象。
- 目标对象:被织入通知的对象。
AOP的使用
- 添加maven依赖
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.17</version>
</dependency>
- 定义切面类:切面类需要使用@Aspect注解和@Component注解标识
@Aspect
@Component
public class ExceptionAOP {
}
- 在切面类中定义切入点
@Aspect
@Component
public class ExceptionAOP {
// value编写表达式定义切入点,标识某一类方法,在通知中添加切入点,这些标识的方法就会得到对应的通知
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
}
- 在切面类中定义通知:在通知中标识哪些切点得到增强的通知
@Aspect
@Component
public class ExceptionAOP {
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
// 前置通知,在切点执行之前执行的操作
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
// 逻辑代码
System.out.println("前置通知");
}
}
切入点的定义
切入点是一个表达式,可以直接定义在通知中,也可以通过@PointCut注解进行定义。
- 直接定义在通知中:不需要额外的注解或方法来定义切入点。这种方式比较简洁,适用于直接在单个通知方法中使用的切入点。
- 通过@PointCut注解定义:可以使得切入点表达式在多个通知方法中共享,提高代码重用性和可读性。
切点表达式:用来定义通知往哪些目标方法上切入
- 访问控制修饰符:可选项,指定访问修饰符的方法,如果没有写表示四个权限都包括。
- 返回值类型:必填项,表示返回值类型任意。
- 全限定类名:可选项,两个点 “..” 代表当前包以及子包下的所有类,如果没有写表示所有类。
- 方法名:必填项,* 表示所有的方法,set* 表示所有以set开头的方法(set方法)。
- 形式参数列表:必填项,() 表示没有参数的方法,(..) 表示参数类型和个数随意的方法,(* ) 表示只有一个参数的方法,(* , String) 表示第一个参数类型随意第二个参数类型为String类型的方法。
- 异常:可选项,省略时表示任意异常类型。
// 切点表达式的语法格式:
execution([访问控制修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
// 切点表达式举例:
// 表示 service包下以delete开头的并且是public修饰的方法
execution(public * com.powernode.mall.service.*.delete*(..))
// 表示mall包下所有的方法
execution(* com.powernode.mall..*(..))
// 表示所有包下所有类的所有方法
execution(* *(..))
AOP通知的分类
AOP通知在切面类中定义,通知分为前置通知、后置通知、环绕通知、异常通知、最终通知。
- 前置通知通过@Before注解进行定义,切点执行前会执行的语句。
// 前置通知,在切点执行之前执行的操作
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
// 逻辑代码
System.out.println("前置通知");
}
- 后置通知通过@AfterReturningr注解进行定义,当切点正常执行结束后会执行的语句。
@Aspect
@Component
public class ExceptionAOP {
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
/**
* 后置返回通知
* 1. 方法参数
* 如果参数中的第一个参数为JoinPoint,则第二个参数为返回值的信息
* 如果参数中的第一个参数不为JoinPoint,则第一个参数为returning中对应的参数
* 2. @AfterReturning注解
* value:用于指定被拦截的目标方法
* returning:用于指定目标方法的返回值绑定的参数名称,如果参数类型为Object则可以匹配任何目标返回值,否则匹配指定目标返回值
* argNames:用于指定目标方法的参数名称列表,参数名称之间用逗号分隔。
*/
@AfterReturning(value = "pointCut()",returning = "keys")
public void doAfterReturningAdvice1(JoinPoint joinPoint, Object keys){
System.out.println("后置通知的返回值 = " + keys);
}
@AfterReturning(value = "pointCut()",returning = "keys",argNames = "keys")
public void doAfterReturningAdvice2(String keys){
System.out.println("后置通知的返回值 = " + keys);
}
}
- 环绕通知通过@Around注解进行定义,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
@Aspect
@Component
public class ExceptionAOP {
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
/**
* 环绕通知第一个参数必须是org.aspectj.lang.ProceedingJoinPoint类型
*/
@Around(value = "pointCut()")
public Object doAroundAdvice(ProceedingJoinPoint proceedingJoinPoint){
System.out.println("环绕通知开始");
Object result = null;
try {
result = proceedingJoinPoint.proceed(); // 执行目标方法
} catch (Throwable e) {
throw new RuntimeException(e);
}
System.out.println("环绕通知结束");
// 可以在此处修改目标方法的返回值
return result;
}
}
- 异常通知通过@AfterThrowing注解进行定义,当切点抛出异常会执行的代码,类似于catch。
@Aspect
@Component
public class ExceptionAOP {
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
/**
* 异常通知
* @AfterThrowing注解:
* 1. value:用于指定被拦截的目标方法,可以使用表达式语言来指定
* 2. pointcut:与value属性功能相同用于指定被拦的目标方法,可以表达式语言指定。
* 3. throwing:用于指定目标方法抛出的异常绑定的参数名称,可以在后续逻辑中使用该参数获取目标方法抛出的异常。
* 4. argNames:用于指定目标方法的参数名称列表,参数名称之间用逗号分隔。
*/
@AfterThrowing(value = "pointCut()",throwing = "exception")
public void doAfterThrowingAdvice(JoinPoint joinPoint, Throwable exception){
//目标方法名
System.out.println("目标方法发生异常:" + exception);
}
}
- 最终通知通过@After注解进行定义,方法不管是否抛出异常都会执行的通知,类似于final语句块中的语句。
@Aspect
@Component
public class ExceptionAOP {
@Pointcut(value = "execution(* com.wenxuan.service..*(..))")
public void pointCut() {
}
/**
* 后置最终通知(目标方法只要执行完了就会执行后置通知方法)
* @param joinPoint
*/
@After(value = "pointCut()")
public void doAfterAdvice(JoinPoint joinPoint){
System.out.println("最终通知");
}
}
JoinPoint对象
基本上每个通知中都可以在第一个形参中声明JoinPoint对象,JoinPoint兑现中封装了切入点的一系列信息。
//返回目标对象,即被代理的对象
Object getTarget();
//返回切入点的参数
Object[] getArgs();
//返回切入点的Signature
Signature getSignature();
//返回切入的类型,比如method-call,field-get等等
String getKind();
ProceedingJoinPoint是JoinPoint的实现类,环绕通知中第一个形参必须是ProceedingJoinPoint对象,这个对象中定义了proceed方法,表示执行目标方法并得到返回值。
@SpringBootApplication注解
@SpringBootApplication用于标注主程序类,该类有一个主方法
@SpringBootApplication注解的定义如下
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication{}
- @SpringBootConfiguration注解:里面有一个@Configuration注解,标识这是一个配置类。
- @Component注解:指定扫描哪些类,Spring中的注解,默认扫描主程序类所在包下和其所有子包。
- @EnableAutoConfiguration注解:里面有一个@AutoConfigurationPackage注解,将指定目录下的所有组件自动导入到Spring容器中,将扫描路径注册到全局,给其他组件查询。
Object的划分
VO(Value Object):VO 是值对象的缩写,表示值对象。它通常是一种用于封装数据的简单 Java 类,用于在各个层之间传递数据。VO 主要用于表示业务领域中的数据对象,不包含业务逻辑,一般只包含属性和对应的 getter 和 setter 方法。TO(Transfer Object):TO 是传输对象的缩写,表示传输对象。它用于在服务端之间传输数据,主要用于网络传输或跨进程通信。TO 通常包含客户端和服务器端共同需要的数据和操作,且不包含业务逻辑。TO 可以看作是业务逻辑无关的数据传输模型。PO(Persistent Object):PO 是持久化对象的缩写,表示持久化对象。它是与数据库表相映射的 Java 对象,在数据库操作中起到数据持久化的作用。PO 一般与数据库表的字段一一对应,并提供对应的 getter 和 setter 方法。PO 可以使用 ORM(对象关系映射)框架自动生成,简化了与数据库的交互。DO(Domain Object):领域对象的缩写,表示从现实世界中抽象出来的有形或无形的业务实体。BO(Business Object):BO是业务对象的缩写,用于封装业务逻辑和数据操作,通常用于表示业务实体或业务逻辑处理的对象。POJO(Plain Ordinary Java Object):普通的Java对象,是一个简单的Java类,只有属性字段和getter、setter方法。POJO是DO/DTO/BO/VO的统称。DAO(Data Access Object):DAO主要负责对数据库或其他数据存储设备的访问和操作,包括数据的增删改查等操作。夹在业务逻辑和数据库资源中间,配合VO提供数据的CRUD操作。
MVC三层架构
Controller层的作用
- 处理请求,接受和校验数据
- 调用service层提供的方法,进行业务处理
- 接受service层处理完的数据。封装页面指定的vo
SpringBoot的使用
SpringBoot创建Web项目
- 添加maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
- 编写启动类
package com.wenxuan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* @author 文轩
* @create 2023-11-23 21:15
*/
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringBoot请求处理
Rest风格的使用
不同的请求路径对应不同的功能
- get请求:获取资源
- post请求:添加资源
- put请求:修改资源
- delete请求:删除资源
@RestController
public class UserController {
@GetMapping("/user")
public String getUser(){
return "GET-张三";
}
@PostMapping("/user")
public String saveUser(){
return "POST-张三";
}
@PutMapping("/user")
public String putUser(){
return "PUT-张三";
}
@DeleteMapping("/user")
public String deleteUser(){
return "DELETE-张三";
}
}
请求参数处理
- @RequestParam注解获取以键值对形式的参数
@RequestParam注解的required属性,默认为true表示前端必须传递参数,如果没有传递则表示路径不匹配。
单个键值对形式的参数
@GetMapping("/user")
public User getUserByUserId(@RequestParam String id) {
return userService.getById(id);
}
多个键值对形式的参数
// 通过Bean类接收,属性名和属性值进行设置
@PostMapping("/user")
public boolean saveUser(@RequestParam User user) {
return userService.save(user);
}
// 通过Map接收,key和value进行设置
@PostMapping("/user")
public boolean saveUser(@RequestParam Map<String, String> params) {
return userService.save(params);
}
- @PathVariable
@PathVariable获取URL路径上的参数
@PathVariable注解的required属性,默认为true表示前端必须传递参数,如果没有传递则表示路径不匹配。
// 比如URL:/user/1
@GetMapping("/user/{id}")
public User getUserByUserId(@PathVariable String id) {
return userService.getById(id);
}
- @RequestBody
@RequestBody注解接收JSON形式的参数
@RequestBody注解的required属性,默认为true表示前端必须传递参数,如果没有传递则表示路径不匹配。
@PostMapping("/user")
public boolean saveUser(@RequestBody User user) {
return userService.save(user);
}
- @RequestHeader获取请求头部分或者全部信息
获取请求头指定参数
@GetMapping("/header")
public String getHeader(@RequestHeader("User-Agent") String userAgent) {
return userAgent;
}
获取请求头所有参数
@GetMapping("/headerList")
public Map<String, String> getHeaderList(@RequestHeader Map<String, String> headerList) {
System.out.println(headerList);
return headerList;
}
- @CookieValue获取Cookie值
@GetMapping("/cookie")
public String cookie(@CookieValue("name") String name) {
return name;
}
- @RequestAttribute获取请求域中的值
@GetMapping("/attribute")
public String attribute(@RequestAttribute("name") String name) {
return name;
}
响应数据的处理
通过@RequestBody注解标识类或者方法,可以使响应数据以JSON形式返回
@RestController注解的作用等价于@Controller + @RequestBody
@ResponseBody
@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
return userService.select(id);
}
请求转发
方式1:使用 "forward" 关键字
// 类的注解不能使用@RestController 要用@Controller
@RequestMapping("/forward")
public String forward() {
return "forward:/index.html";
}
方式2:使用servlet 提供的API
// 类的注解可以使用@RestController,也可以使用@Controller
@RequestMapping("/forward")
public void forward(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getRequestDispatcher("/index").forward(request, response);
}
请求重定向
方式1:使用 "redirect" 关键字
// 类的注解不能使用@RestController,要用@Controller
@RequestMapping("/redirect")
public String redirect() {
return "redirect:/index.html";
}
方式2:使用servlet 提供的API
@RequestMapping("/redirect”)
public void redirect(HttpServletResponse response) throws IOException {
response.sendRedirect("/index.html");
}
SpringBoot实现事务
事务:在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。可以通过事务保证业务中多条DML语句同时成功或者同时失败。
SpringBoot实现事务的步骤
- 添加maven依赖
<!-- 引入事务支持 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.7.RELEASE</version>
</dependency>
- 在启动类中添加@EnableTransactionManagement注解
Spring推荐的方式,是将@EnableTransactionManagement加到被@Configuration注解的类上,而@SpringBootApplication被@SpringBootConfiguration注解,@SpringBootConfiguration又被@Configuration,所以可以将@EnableTransactionManagement注解加到被@SpringBootApplication注解的类上。
@SpringBootApplication
@EnableTransactionManagement
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 使用事务
@RestController
public class UserController {
@Resource
private UserService userService;
/**
* 在需要使用事务的方法上添加@Transactional注解即可,
* 这个方法中执行的SQL语句就会要么全部执行成功,要么全部执行失败
*/
@Transactional
@GetMapping("/insert")
public User saveUser() {
User user = new User();
user.setName("小李");
user.setId("3");
userService.insert(user);
return user;
}
}
@Transaction注解介绍
- @Transaction注解的定义
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 设置事务管理器的名称,可以通过名称指定使用哪个事务管理器进行事务管理。
@AliasFor("transactionManager")
String value() default "";
// 设置事务的传播行为。在使用嵌套事务的情况下,该属性可以控制事务的传播方式
Propagation propagation() default Propagation.REQUIRED;
// 事务隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 事务超时时间
int timeout() default -1;
// 设置事务只读:设置为true表示该事务中只能执行select语句,不能执行DML语句
boolean readOnly() default false;
// 设置哪些异常回滚事务
Class<? extends Throwable>[] rollbackFor() default {};
// 设置哪些异常不回滚事务
Class<? extends Throwable>[] noRollbackFor() default {};
}
- @Transaction注解事务的传播行为
事务的传播行为:在使用嵌套事务的情况下,嵌套的事务的设置情况通过事务的传播行为进行设置。
// 设置事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
事务的传播行为有如下几种
package org.springframework.transaction.annotation;
public enum Propagation {
REQUIRED(0), // 支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
SUPPORTS(1), // 支持当前事务,如果当前没有事务,就以非事务方式执行【有就加入,没有就不管了】
MANDATORY(2), // 必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常【有就加入,没有就抛异常】
REQUIRES_NEW(3),// 开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】,两个事务之间没有关系
NOT_SUPPORTED(4),// 以非事务方式运行,如果有事务存在,挂起当前事务【不支持事务,存在就挂起】
NEVER(5), // 以非事务方式运行,如果有事务存在,抛出异常【不支持事务,存在就抛异常】
NESTED(6); // 如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】
private final int value;
private Propagation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- @Transaction注解事务的隔离级别
为了保证并发读取数据的正确性,提出事务的隔离级别。事务的隔离级别越好,并发读取数据越正确,但是性能就越差。
// 设置事务的隔离级别
@Transactional(isolation = Isolation.READ_COMITTED)
事务的隔离级别有如下四种:
package org.springframework.transaction.annotation;
public enum Isolation {
DEFAULT(-1), // 默认的隔离级别
READ_UNCOMMITTED(1), // 读未提交
READ_COMMITTED(2), // 读已提交
REPEATABLE_READ(4), // 可重复读
SERIALIZABLE(8); // 序列化
private final int value;
private Isolation(int value) {
this.value = value;
}
public int value() {
return this.value;
}
}
- @Transactional失效的场景
@Transactional注解不生效的场景
- 数据库引擎不支持事务(MySQL的MyIsam引擎不支持事务)
- 注解所在的类没有被加载成Bean,也就是没有被Spring容器管理
- 注解所在的方法不是public修饰的
- 注解所在的方法发生自调用问题
- 所在数据源是否加载事务管理器
- 注解的扩展配置propagation错误
SpringBoot容器添加数据
@Configuration + @Bean
@Configuration:告诉SpringBoot这是一个配置类,有一个proxyBeanMethods属性,true表示配置类中方法返回的组件对象是单例(默认值)的,false表示配置类中方法返回的组件对象不是单例的。
@Bean:表示方法是一个组件,将返回的Bean对象交由SpringBoot容器管理。
@Configuration
public class UserConfig {
@Bean
public User getUser() {
return new User("1", "zs");
}
}
@Configuration + @Import
@Configuration:告诉SpringBoot这是一个配置类,有一个proxyBeanMethods属性,true表示配置类中方法返回的组件对象是单例(默认值)的,false表示配置类中方法返回的组件对象不是单例的。
@Import注解在被@Configuration注解修饰的类中使用,导入特定的Bean类对象。
@Configuration
@Import({User.class})
public class UserConfig {
}
@Conditional注解
@Conditional注解用于条件装配,使用该注解可以使得在满足某些条件下才进行注入组件,@Conditional注解使用在配置方法或者配置类上,@Conditional注解有很多子注解,代表不同的条件:
- @ConditionalOnBean:表示当存在某个组件时,才注入组件
- @ConditionalOnMissingBean:表示不存在某个组件时,才注入组件
- @ConditionalOnClass:表示存在某个类时,才注入组件
- @ConditionalOnMissingClass:表示当不存在某个组件时,才注入组件
- @ConditionalOnProperty:表示当存在配置的存在指定属性和属性值时,才注入组件
- @ConditionalOnJava:表示如果是Java应用时,才会注入组件
@Conditional注解和@Bean注解结合使用
@Configuration
public class UserConfig {
@Bean
@ConditionalOnBean(SexEnum.class)
public User getUser() {
return new User("1", "zs");
}
}
@Conditional注解和@Import注解结合使用
@Configuration
@Import({User.class})
@ConditionalOnBean(SexEnum.class)
public class UserConfig {
}
SpringBoot实现拦截器
- 定义拦截器
public class LoginInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("目标方法执行之前执行该方法");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("目标方法执行完成后执行该方法");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("页面渲染之后执行该方法");
}
}
- 注册拦截器
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new LoginInterceptor())
//所有请求都被拦截包括静态资源
.addPathPatterns("/**")
//放行的请求
.excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
}
}
自定义SpringBootStarter
添加maven依赖
<!-- 指定父依赖为springboot应用依赖,可以快速构建springboot应用 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/>
</parent>
<!-- springboot启动器依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- springboot自动配置模块 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-autoconfigure</artifactId>
</dependency>
编写读取配置文件的属性类
配置属性类:获取配置文件内容
@ConfigurationProperties(prefix = "wenxuan")
public class HelloProperties {
private String name;
private String address;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
编写starter使用服务类
向外部提供一个服务类,starter使用者可以使用服务类对象
public class HelloService {
private String name;
private String address;
public HelloService(String name, String address) {
this.name = name;
this.address = address;
}
public String sayHello(){
return "你好!我的名字叫 " + name + ",我来自 " + address;
}
}
编写自动配置类
自动配置类:使得外部可以通过注解方式获取服务类,将HelloProperties类属性注入到HelloService对象中。
// Configuration:表示这是一个配置类
// EnableConfigurationProperties:启用对@ConfigurationProperties注解标注的类的支持
@Configuration
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
private HelloProperties helloProperties;
public HelloServiceAutoConfiguration(HelloProperties helloProperties) {
this.helloProperties = helloProperties;
}
// ConditionalOnMissingBean:表示当HelloService类没有注入时才会注入
@Bean
@ConditionalOnMissingBean
public HelloService helloService() {
return new HelloService(helloProperties.getName(), helloProperties.getAddress());
}
}
编写SpringBootStarter配置文件
在resources目录下创建META-INF/spring.factories:让spring可以找到自动装配类,让自动配置生效,如果有多个自动配置类,之间使用逗号分割。
# com.wenxuan.config.HelloServiceAutoConfiguration:自动装配类的全类名
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.wenxuan.config.HelloServiceAutoConfiguration
使用自定义的starter
- 将jar包导入maven仓库中,通过
mvn install - 在项目中的
pom.xml配置文件中添加自定义SpringBootStarter的依赖
<!--导入自定义starter-->
<dependency>
<groupId>com.wenxuan</groupId>
<artifactId>hello-spring-boot-starter</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
- 编写配置文件application.yml
wenxuan:
name: xiaoming
address: beijing
- 通过注解获取服务类
@Autowired
private HelloService helloService;
SpringBoot全局异常处理
同一处理控制器中的异常通过@ControllerAdvice注解和@ExceptionHandler注解结合实现:
- @ControllerAdvice: 提供了全局性的控制器Advice,可以实现同一处理控制器中的异常、全局数据绑定、页面跳转。
- @ExceptionHandler:定义全局的异常处理方法,在控制器中使用改注解可以指定处理特定异常的方法,当控制器中抛出对应类型的异常,Spring会调用这个方法进行处理。
// 表示异常处理器作用于使用RestController注解的类抛出的遗产
@ControllerAdvice(annotations = {RestController.class, Controller.class})
@RestController
public class ExceptionConfiguration {
// 表示当程序出现ConstraintViolationException或者BindException异常时会调用该方法
@ExceptionHandler({ConstraintViolationException.class, BindException.class})
public String validatorException(Exception ex, HttpServletRequest request) {
ex.printStackTrace();
String msg = null;
if(ex instanceof ConstraintViolationException){
ConstraintViolationException constraintViolationException = (ConstraintViolationException)ex;
Set<ConstraintViolation<?>> violations = constraintViolationException.getConstraintViolations();
ConstraintViolation<?> next = violations.iterator().next();
msg = next.getMessage();
}else if(ex instanceof BindException){
BindException bindException = (BindException)ex;
msg = bindException.getBindingResult().getFieldError().getDefaultMessage();
}
return msg;
}
}
SpringBoot编写测试类
- 添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
编写测试类:在test目录下面编写测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserServiceTest {
@Resource
private UserService userService;
@Test
public void testUserService() {
userService.test();
}
}
SpringBoot解决跨域问题
// 使用@WebFilter注解标记该类为一个过滤器,并指定filterName为"CorsFilter"
@WebFilter(filterName = "CorsFilter ")
@Configuration
public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse servletResponse = (HttpServletResponse) response;
// 设置响应头,允许所有跨域请求访问
servletResponse.setHeader("Access-Control-Allow-Origin","*");
// 设置响应头,允许发送Cookie等凭证
servletResponse.setHeader("Access-Control-Allow-Credentials", "true");
// 设置响应头允许的请求方法
servletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PATCH, DELETE, PUT");
// 设置响应头,设置预检请求的有效期
servletResponse.setHeader("Access-Control-Max-Age", "3600");
// 设置响应头,允许的请求头
servletResponse.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
// 将请求传递给下一个过器或目标资源
chain.doFilter(request, response);
}
}
SpringBoot实现枚举常量
开发过程中,当出现一组常量时不能在代码中直接用整数或者字符串表示,应该将这组常量抽取出来定义成一个枚举类,这样子可以可以提高代码的可读性和可维护性。
package com.wenxuan.common.constant;
public class UserConstant {
public enum SexStatusEnum{
MAN(0,"男"),
WOMAN(1,"女");
private int code;
private String msg;
PurchaseStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
public enum MemberStatusEnum{
DAY_MEMBER(0,"一天会员"),
WEEK_MEMBER(1,"一周会员"),
MONTH_MEMBER(2,"月度会员")
QUARTER_MEMBER(3,"季度会员"),
YEAR_MEMBER(4, "年度会员");
private int code;
private String msg;
PurchaseDetailStatusEnum(int code,String msg){
this.code = code;
this.msg = msg;
}
public int getCode() {
return code;
}
public String getMsg() {
return msg;
}
}
}
SpringBoot业务状态码规范
这里举出一种业务状态码开发规范,还有很多种业务状态码开发规范,可以自行搜索。
- 错误码定义规则为五位数字,正确码为00000
- 前两位表示业务场景,最后三位表示错误码
- 维护错误码后需要错误提示,所以将其定义为枚举类型
例如:
- 0:表示成功的状态码
- 10开头:表示通用的错误状态码
- 11开头:表示商品业务的错误状态码
- 12开头:表示订单业务的错误状态码
其中比如10开头的状态码
- 10000:系统未知异常
- 10001:参数格式校验失败
public enum BizCodeEnum {
SUCCESS(0, "操作成功"),
UNKNOW_EXCEPTION(10000, "系统未知异常"),
VALID_EXCEPTION(10001, "参数格式校验失败")
;
private int code;
private String message;
BizCodeEnum(int code, String message) {
this.code = code;
this.message = message;
}
public int getCode() {
return code;
}
public String getMessage() {
return message;
}
}
SpringBoot接口统一响应
编写接口统一响应类的前提是需要先添加Spring业务状态码规范类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class ResponseData<T> implements Serializable {
/**
* 状态码
*/
private int code;
/**
* 状态码描述信息
*/
private String message;
/**
* 请求返回信息
*/
private T data;
public ResponseData(int code, String message) {
this.code = code;
this.message = message;
}
/**
* 成功但不携带返回参数
*/
public static <T> ResponseData<T> success() {
return new ResponseData<>(BizCodeEnum.SUCCESS.getCode(), BizCodeEnum.SUCCESS.getMessage());
}
/**
* 成功但不携带返回参数
*/
public static <T> ResponseData<T> success(String message) {
return new ResponseData<>(BizCodeEnum.SUCCESS.getCode(), message);
}
/**
* 成功,且携带返回参数
* @param data 返回参数
*/
public static <T> ResponseData<T> success(T data) {
return new ResponseData<>(BizCodeEnum.SUCCESS.getCode(), BizCodeEnum.SUCCESS.getMessage(), data);
}
/**
* 成功,且携带返回参数
* @param data 返回参数
*/
public static <T> ResponseData<T> success(String message, T data) {
return new ResponseData<>(BizCodeEnum.SUCCESS.getCode(), message, data);
}
/**
* 失败,自定义返回码且不包含返回参数
*
* @param code 返回码
* @param message 返回信息
*/
public static <T> ResponseData<T> error(int code, String message) {
return new ResponseData<>(code, message);
}
/**
* 失败,自定义返回码且包含返回参数
* @param code 返回码
* @param message 返回信息
* @param data 返回参数
*/
public static <T> ResponseData<T> error(int code, String message, T data) {
return new ResponseData<>(code, message, data);
}
/**
* 失败,使用枚举类返回码且不包含返回参数
* @param bizCodeEnum 返回码枚举类
*/
public static <T> ResponseData<T> error(BizCodeEnum bizCodeEnum) {
return new ResponseData<>(bizCodeEnum.getCode(), bizCodeEnum.getMessage());
}
/**
* 失败,使用枚举类返回码且包含返回参数
*
* @param bizCodeEnum 返回码枚举类
* @param data 返回参数
*/
public static <T> ResponseData<T> error(BizCodeEnum bizCodeEnum, T data) {
return new ResponseData<>(bizCodeEnum.getCode(), bizCodeEnum.getMessage(), data);
}
}
SpringBoot使用yaml文件
设置应用上下文路径
SpringBoot默认的上下文路径为空,可以通过下面方式设置项目的上下文路径:
server:
servlet:
context-path: /api
yaml文件中定义变量
如果yaml文件中存在多个地方使用同一个值,可以自定义变量通过${变量名}在多处引用。
# 定义变量
wenxuan:
redis:
ip: localhost
# 使用变量:通过 ${变量名} 的方式使用
spring:
redis:
host: ${wenxuan.redis.ip}
yaml设置Date的日期格式化
spring:
jackson:
date-format: yyyy-MM-dd HH:mm:ss
SpringBoot读取yaml文件
- @Value方式
@Value:用于将值赋给一个类的属性或方法参数。通过@Value注解,可以在运行时将值注入到被注解的属性或方法参数中。
- 属性注入:可以通过@Value注解将值注入到交由容器管理的类的属性中。
- 方法参数注入:可以通过@Value注解将值注入到方法参数中。
属性注入
@RestController
public class TestController {
@Value("${wenxuan.name}")
private String name;
}
方法参数注入
@Service
public class UserService {
private final String name;
@Autowired
public UserService (@Value("${wenxuan.name}") String name) {
this.name = name;
}
}
- @ConfigurationProperties方式
@ConfigurationProperties注解用于将类和某个配置文件进行绑定,可以自动获取配置文件的值(资源绑定),@ConfigurationProperties注解有一个属性prefix,指定获取配置文件中以什么开头的值。
在application.yaml配置文件中编写需要绑定的数据
car:
name: BYD
price: 10000
编写Bean类,定义需要绑定的属性
@ConfigurationProperties(prefix = "car")
@Component
public class Car {
private String name;
private Integer price;
public void setName(String name) {
this.name = name;
}
public void setPrice(Integer price) {
this.price = price;
}
public String getName() {
return name;
}
public Integer getPrice() {
return price;
}
}
SpringBoot整合各种中间件
SpringBoot整合数据源
数据源:给程序员提供Connection对象的,都叫做数据源(datasource)。数据源实际上是一套规范(接口),接口全路径名:javax.sql.DataSource(JDK规范)。所有的数据源都实现了DataSource接口,重写了接口中的方法。
添加maven依赖
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
编写application.yaml
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
驱动类driver-class-name
- mysql-connector-java版本为5:
driver-class-name: com.mysql.jdbc.Driver- mysql-connector-java版本为8:
driver-class-name: com.mysql.cj.jdbc.Driver
连接地址url
- mysql-connector-java版本为5:
jdbc:mysql://localhost:3306/test?characterEncoding=utf-8&useSSL=false- mysql-connector-java版本为8:
jdbc:mysql://localhost:3306/mybatis_plus?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
SpringBoot整合第三方数据源
常见的第三方数据源:druid、c3p0、dbcp。
添加maven依赖
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
在application.yaml配置文件中切换数据源
spring:
datasource:
url: jdbc:mysql://localhost:3306/db_account
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
druid数据源其他配置
spring:
datasource:
url: jdbc:mysql://localhost:3306/test
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
# 初始化时建立物理连接的个数
initialSize: 5
# 最小连接池数量
minIdle: 5
# 最大连接池数量
maxActive: 201
# 获取连接时最大等待时间,单位毫秒
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 连接保持空闲而不被驱逐的最小时间
minEvictableIdleTimeMillis: 300000
# 用来检测连接是否有效的sql,要求是一个查询语句
validationQuery: SELECT 1 FROM DUAL
# 建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testWhileIdle: true
# 申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnBorrow: false
# 归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false
# 是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭。
poolPreparedStatements: true
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙
filters: stat,wall,log4j
# 要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。
maxPoolPreparedStatementPerConnectionSize: 20
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
SpringBoot整合Redis
- 添加Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 注册Redis连接信息
spring:
redis:
host: redis
port: 6379
password: 123456
database: 0 # 指定操作的0号数据库
jedis:
pool:
# Redis连接池配置
max-active: 8 # 最大连接数
max-wait: 1ms # 连接池最大阻塞等待时间
max-idle: 4 # 连接池中的最大空闲连接数
min-idle: 0 # 连接池最小空闲连接
- 添加Redis工具类
/**
* @author 文轩
* @create 2024-03-03 17:15
*
* Redis缓存工具类
*/
@SuppressWarnings(value = { "all"})
@Component
public class RedisCacheUtil {
@Autowired
public RedisTemplate redisTemplate;
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
*/
public <T> void setCacheObject(final String key, final T value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 缓存基本的对象,Integer、String、实体类等
*
* @param key 缓存的键值
* @param value 缓存的值
* @param timeout 时间
* @param timeUnit 时间颗粒度
*/
public <T> void setCacheObject(final String key, final T value, final Integer timeout, final TimeUnit timeUnit) {
redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout) {
return expire(key, timeout, TimeUnit.SECONDS);
}
/**
* 设置有效时间
*
* @param key Redis键
* @param timeout 超时时间
* @param unit 时间单位
* @return true=设置成功;false=设置失败
*/
public boolean expire(final String key, final long timeout, final TimeUnit unit) {
return redisTemplate.expire(key, timeout, unit);
}
/**
* 获得缓存的基本对象。
*
* @param key 缓存键值
* @return 缓存键值对应的数据
*/
public <T> T getCacheObject(final String key) {
ValueOperations<String, T> operation = redisTemplate.opsForValue();
return operation.get(key);
}
/**
* 删除单个对象
*
* @param key
*/
public boolean deleteObject(final String key) {
return redisTemplate.delete(key);
}
/**
* 删除集合对象
*
* @param collection 多个对象
* @return
*/
public long deleteObject(final Collection collection) {
return redisTemplate.delete(collection);
}
/**
* 缓存List数据
*
* @param key 缓存的键值
* @param dataList 待缓存的List数据
* @return 缓存的对象
*/
public <T> long setCacheList(final String key, final List<T> dataList) {
Long count = redisTemplate.opsForList().rightPushAll(key, dataList);
return count == null ? 0 : count;
}
/**
* 获得缓存的list对象
*
* @param key 缓存的键值
* @return 缓存键值对应的数据
*/
public <T> List<T> getCacheList(final String key) {
return redisTemplate.opsForList().range(key, 0, -1);
}
/**
* 缓存Set
*
* @param key 缓存键值
* @param dataSet 缓存的数据
* @return 缓存数据的对象
*/
public <T> BoundSetOperations<String, T> setCacheSet(final String key, final Set<T> dataSet) {
BoundSetOperations<String, T> setOperation = redisTemplate.boundSetOps(key);
Iterator<T> it = dataSet.iterator();
while (it.hasNext()) {
setOperation.add(it.next());
}
return setOperation;
}
/**
* 获得缓存的set
*
* @param key
* @return
*/
public <T> Set<T> getCacheSet(final String key) {
return redisTemplate.opsForSet().members(key);
}
/**
* 缓存Map
*
* @param key
* @param dataMap
*/
public <T> void setCacheMap(final String key, final Map<String, T> dataMap) {
if (dataMap != null) {
redisTemplate.opsForHash().putAll(key, dataMap);
}
}
/**
* 获得缓存的Map
*
* @param key
* @return
*/
public <T> Map<String, T> getCacheMap(final String key) {
return redisTemplate.opsForHash().entries(key);
}
/**
* 往Hash中存入数据
*
* @param key Redis键
* @param hKey Hash键
* @param value 值
*/
public <T> void setCacheMapValue(final String key, final String hKey, final T value) {
redisTemplate.opsForHash().put(key, hKey, value);
}
/**
* 获取Hash中的数据
*
* @param key Redis键
* @param hKey Hash键
* @return Hash中的对象
*/
public <T> T getCacheMapValue(final String key, final String hKey) {
HashOperations<String, String, T> opsForHash = redisTemplate.opsForHash();
return opsForHash.get(key, hKey);
}
/**
* 删除Hash中的数据
*
* @param key
* @param hkey
*/
public void delCacheMapValue(final String key, final String hkey) {
HashOperations hashOperations = redisTemplate.opsForHash();
hashOperations.delete(key, hkey);
}
/**
* 获取多个Hash中的数据
*
* @param key Redis键
* @param hKeys Hash键集合
* @return Hash对象集合
*/
public <T> List<T> getMultiCacheMapValue(final String key, final Collection<Object> hKeys) {
return redisTemplate.opsForHash().multiGet(key, hKeys);
}
/**
* 获得缓存的基本对象列表
*
* @param pattern 字符串前缀
* @return 对象列表
*/
public Collection<String> keys(final String pattern) {
return redisTemplate.keys(pattern);
}
}
SpringBoot整合Redisson
Redisson是一个基于Redis的Java客户端,它提供了一系列的分布式Java对象和服务,以便于在Java应用程序中使用分布式锁、分布式集合、分布式Map、分布式队列等分布式数据结构和分布式服务。
Redisson官方文档:github.com/redisson/re…
- 添加maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
</dependency>
- 编写配置类
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
Config config = new Config();
config.useSingleServer().setAddress("redis://localhost.84:6379");
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
- 使用RedissonClient
@Controller
public class IndexController {
@Autowired
RedissonClient redissonClient;
@ResponseBody
@RequestMapping("/hello")
public String hello() {
// 获取一把锁
RLock lock = redissonClient.getLock("lock");
// 加锁,加锁失败会阻塞式等待
lock.lock();
try {
// 执行业务代码
Thread.sleep(30000);
} catch (Exception e) {
} finally {
// 解锁
lock.unlock();
}
return "hello";
}
}
SpringBoot整合SpringCache
- 添加maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
- 编写配置信息
spring:
redis:
host: localhost
password: 123456
port: 6379
cache:
type: redis
redis:
time-to-live: 3600000 # 有效时间,毫秒为单位
key-prefix: CACHE_ # KEY前缀
use-key-prefix: true # 是否使用前缀
cache-null-values: true # 是否缓存空值,防止缓存穿透
- 编写SpringCache的配置类
@Configuration
@EnableCaching
@EnableConfigurationProperties(CacheProperties.class)
public class MyCacheConfig {
@Bean
RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
// 配置数据序列化
config = config.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()));
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
// 设置配置文件的数据
CacheProperties.Redis redisProperties = cacheProperties.getRedis();
if(redisProperties.getTimeToLive() != null) {
config = config.entryTtl(redisProperties.getTimeToLive());
}
if(redisProperties.getKeyPrefix() != null) {
config = config.prefixKeysWith(redisProperties.getKeyPrefix());
}
if(redisProperties.isCacheNullValues()) {
config = config.disableCachingNullValues();
}
if(!redisProperties.isUseKeyPrefix()) {
config = config.disableKeyPrefix();
}
return config;
}
}
- 使用SpringCache,SpringCache提供注解方式使用缓存
@Cacheable:被注解的方法在执行前会先查询缓存,如果缓存中存在对应的数据,则直接返回缓存数据,不会执行方法体的代码。如果缓存中不存在对应的数据,则将方法返回结果添加到缓存中。@CacheEvict:被注解的方法执行后会清空缓存中的对应数据,可以配置在方法执行前或者执行后清空缓存。@CachePut:被注解的方法会执行方法体的代码,并将返回值更新到缓存中,即每次方法调用都会执行方法体并更新缓存。@Caching:用于组合多个缓存操作的注解,可以在同一个方法上同时使用多个缓存注解。@CacheConfig:用于在类级别上定义缓存的一些公共配置,如缓存的名称、缓存管理器等。
SpringBoot整合日志
- 添加maven依赖
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
</dependencies>
- 添加log4j的配置文件
# log4j的设置
log4j.rootLogger=info,stdout,D,E
# 日志信息默认输出到控制抬
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=[%-5p] %d{yyyy-MM-dd HH:mm:ss,SSS} method:%l%n%m%n
# DEBUG 级别以上的日志输出到 logs/debug.log
log4j.appender.D=org.apache.log4j.DailyRollingFileAppender
log4j.appender.D.File=/logs/debug.log
log4j.appender.D.Append=true
log4j.appender.D.Threshold=DEBUG
log4j.appender.D.layout=org.apache.log4j.PatternLayout
log4j.appender.D.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
# ERROR 级别以上的日志到 logs/error.log
log4j.appender.E=org.apache.log4j.DailyRollingFileAppender
log4j.appender.E.File=/logs/error.log
log4j.appender.E.Append=true
log4j.appender.E.Threshold=ERROR
log4j.appender.E.layout=org.apache.log4j.PatternLayout
log4j.appender.E.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss} [ %t:%r ] - [ %p ] %m%n
- @Slf4j的使用
package com.wenxuan.test;
import lombok.extern.slf4j.Slf4j;
/**
* @author 文轩
* @create 2024-01-17 18:40
*/
@Slf4j
public class Test {
public static void main(String[] args) {
log.error("error");
}
}
SpringBoot整合Swagger
Swagger官网:swagger.io/
- 添加依赖
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>3.0.0</version>
</dependency>
- 解决Spring2.6以上匹配规则错误
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
- 编写Swagger配置类
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket docket() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo()).enable(true)
.select()
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("wenxuan-Swagger") // 文档标题
.description("文轩编程") // 文档描述信息
// 文档作者信息
.contact(new Contact("文轩", "https://xxx","xxx@163.com"))
// 文档版本号
.version("v1.0")
.build();
}
}
- 编写接口
@Api(tags = "测试 Swagger")
@RestController
@RequestMapping("/swagger")
public class SwaggerController {
@ApiOperation("test")
@RequestMapping("/test")
public String testNoParam() {
return "wenxuan";
}
}
- Swagger常用注解
- @Api:用在Controller类上,说明该类的作用,通过tags标签指定某一类接口。
- @ApiOperation:用在请求方法上,说明请求方法的作用,每一个接口的定义。
- @ApiImplicitParam 和 @ApiImplicitParams:用于请求方法上,为单独的请求参数进行说明。
- name:参数名,指定参数的名称
- value:参数描述,用于描述参数的作用。
- required:参数是否必传,默认为 false,设置为 true 表示必传。
- dataTypeClass:参数的数据类型,可以是基本数据类型(如 int、String)也可以是复杂数据类型(如自定义类、集合等)
- paramType:参数类型,包括 path(路径参数)、query(查询参数)、header(请求头参数)等。
- example:参数的示例值,用于展示参数的示例。
- @ApiModel:用在Modal类上,表示对类进行说明,用于实体类中的参数接收或接口响应说明。
- @ApiModelProperty:用于Modal类的字段上,表示对 model 属性的说明。
- value:属性描述,用于描述属性的作用。
- name:属性名称,指定属性的名称。
- dataType:属性的数据类型,可以是基本数据类型(如 int、String)也可以是复杂数据类型(如自定义类、集合等)。
- required:属性是否必须,默认为 false,设置为 true 表示必须。
- example:属性的示例值,用于展示属性的示例。
- hidden:属性是否隐藏,默认为 false,设置为 true 表示在文档中隐藏该属性。
- allowEmptyValue:属性是否允许为空值,默认为 false,设置为 true 表示允许为空值。
- notes:属性的额外说明,用于对属性进行额外描述。
- @ApiIgnore:用于请求方法的参数上,用来忽视自定义类型的参数。
注意:
- query参数存在多个参数,则通过使用@ApiImplicitParam注解实现,而无法通过@ApiModelProperty注解实现。
- query参数如果是单个单个接收,则无需使用@ApiIgnore注解。但如果是将多个参数封装成VO,则需要通过@ApiIgnore注解将请求方法上的参数忽略。
- body参数存在多个参数,则通过@ApiModelProperty注解实现,而无法通过@ApiImplicitParam注解实现。
- 实现分组
实现分组只需要修改Config配置类即可,将哪些Controller设置为同一个分组下面,那就在apis中添加即可。
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket group1() {
return new Docket(DocumentationType.OAS_30)
.groupName("分组1")
.apiInfo(apiInfo()).enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller1")
// 可以添加扫描多个包下的Controller
.or(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller2")))
.build();
}
@Bean
public Docket group2() {
return new Docket(DocumentationType.OAS_30)
.groupName("分组2")
.apiInfo(apiInfo()).enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller3"))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("wenxuan-Swagger") // 文档标题
.description("文轩编程") // 文档描述信息
// 文档作者信息
.contact(new Contact("文轩", "https://xxx","xxx@163.com"))
// 文档版本号
.version("v1.0")
.build();
}
}
SpringBoot整合Knife4
Knife4官网:doc.xiaominfo.com/knife4j/doc…
knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名knife4j是希望它能像一把匕首一样小巧、轻量、并且功能强悍。其底层是对Springfox的封装,使用方式也和Springfox一致,只是对接口文档UI进行了优化。
- 添加Maven依赖
<dependency>
<groupId>com.github.xiaoymin</groupId>
<artifactId>knife4j-spring-boot-starter</artifactId>
<version>3.0.3</version>
</dependency>
- 解决Spring2.6以上匹配规则错误
spring:
mvc:
pathmatch:
matching-strategy: ant_path_matcher
- 编写Swagger配置类
import springfox.documentation.service.ApiInfo;
import springfox.documentation.service.Contact;
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket docket() {
return new Docket(DocumentationType.OAS_30)
.apiInfo(apiInfo()).enable(true)
.select()
.paths(PathSelectors.any())
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("wenxuan-Swagger") // 文档标题
.description("文轩编程") // 文档描述信息
// 文档作者信息
.contact(new Contact("文轩", "https://xxx","xxx@163.com"))
// 文档版本号
.version("v1.0")
.build();
}
}
- 编写接口
@Api(tags = "测试 Swagger")
@RestController
@RequestMapping("/swagger")
public class SwaggerController {
@ApiOperation("test")
@RequestMapping("/test")
public String testNoParam() {
return "wenxuan";
}
}
- Swagger常用注解
- @Api:用在Controller类上,说明该类的作用,通过tags标签指定某一类接口。
- @ApiOperation:用在请求方法上,说明请求方法的作用,每一个接口的定义。
- @ApiImplicitParam 和 @ApiImplicitParams:用于请求方法上,为单独的请求参数进行说明。
- name:参数名,指定参数的名称
- value:参数描述,用于描述参数的作用。
- required:参数是否必传,默认为 false,设置为 true 表示必传。
- dataTypeClass:参数的数据类型,可以是基本数据类型(如 int、String)也可以是复杂数据类型(如自定义类、集合等)
- paramType:参数类型,包括 path(路径参数)、query(查询参数)、header(请求头参数)等。
- example:参数的示例值,用于展示参数的示例。
- @ApiModel:用在Modal类上,表示对类进行说明,用于实体类中的参数接收或接口响应说明。
- @ApiModelProperty:用于Modal类的字段上,表示对 model 属性的说明。
- value:属性描述,用于描述属性的作用。
- name:属性名称,指定属性的名称。
- dataType:属性的数据类型,可以是基本数据类型(如 int、String)也可以是复杂数据类型(如自定义类、集合等)。
- required:属性是否必须,默认为 false,设置为 true 表示必须。
- example:属性的示例值,用于展示属性的示例。
- hidden:属性是否隐藏,默认为 false,设置为 true 表示在文档中隐藏该属性。
- allowEmptyValue:属性是否允许为空值,默认为 false,设置为 true 表示允许为空值。
- notes:属性的额外说明,用于对属性进行额外描述。
- @ApiIgnore:用于请求方法的参数上,用来忽视自定义类型的参数。
注意:
- query参数存在多个参数,则通过使用@ApiImplicitParam注解实现,而无法通过@ApiModelProperty注解实现。
- query参数如果是单个单个接收,则无需使用@ApiIgnore注解。但如果是将多个参数封装成VO,则需要通过@ApiIgnore注解将请求方法上的参数忽略。
- body参数存在多个参数,则通过@ApiModelProperty注解实现,而无法通过@ApiImplicitParam注解实现。
- 实现分组
实现分组只需要修改Config配置类即可,将哪些Controller设置为同一个分组下面,那就在apis中添加即可。
@Configuration
@EnableOpenApi
public class SwaggerConfig {
@Bean
public Docket group1() {
return new Docket(DocumentationType.OAS_30)
.groupName("分组1")
.apiInfo(apiInfo()).enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller1")
// 可以添加扫描多个包下的Controller
.or(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller2")))
.build();
}
@Bean
public Docket group2() {
return new Docket(DocumentationType.OAS_30)
.groupName("分组2")
.apiInfo(apiInfo()).enable(true)
.select()
.apis(RequestHandlerSelectors.basePackage("com.wenxuan.web.controller3"))
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("wenxuan-Swagger") // 文档标题
.description("文轩编程") // 文档描述信息
// 文档作者信息
.contact(new Contact("文轩", "https://xxx","xxx@163.com"))
// 文档版本号
.version("v1.0")
.build();
}
}
- 访问接口文档:http://ip:port/doc.html
SpringBoot整合Validation
Validation:用于验证数据的机制,通常用于确保用户输入或传入的数据符合特定的规则或格式。在Web应用程序中,经常需要对用户提交的表单数据进行验证,以确保数据的完整性、正确性和安全性。
- 添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
- 给TO中添加注解
public class AddUserVO {
@Range(min = 0, message = "年龄不合法")
private Integer age;
@NotEmpty(message = "姓名不能为空")
private String name;
@Email(message = "邮箱不合法")
private String email;
}
- 在Controller中开启校验
@PostMapping("save")
public String saveUser(@Validated @RequestBody AddUserVO addUserVO) {
// 处理逻辑
return "success";
}
- 校验异常统一处理
目的:使得当参数校验出现异常时,给前端的返回值也是符合程序的标准
编写校验统一处理前提是需要先添加
SpringBoot接口统一响应类的
@RestControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseData handle(MethodArgumentNotValidException e) {
List<FieldError> fieldErrors = e.getFieldErrors();
List<ValidMessage> validMessages = new ArrayList<>();
fieldErrors.forEach(item -> {
String field = item.getField();
String defaultMessage = item.getDefaultMessage();
validMessages.add(new ValidMessage(field, defaultMessage));
});
return ResponseData.error(BizCodeEnum.VALID_EXCEPTION, validMessages);
}
}
异常消息类如下:
@Data
@AllArgsConstructor
public class ValidMessage {
private String field;
private String message;
}
- Validation常用注解
作用在Bean类上的注解
| 注解 | 说明 |
|---|---|
| @AssertTrue | 用于boolean字段,该字段只能为true |
| @AssertFalse | 用于boolean字段,该字段只能为false |
| @CreditCardNumber | 对信用卡号进行一个大致的验证 |
| @DecimalMax | 只能小于或等于该值 |
| @DecimalMin | 只能大于或等于该值 |
| 检查是否是一个有效的email地址 | |
| @Future | 检查该字段的日期是否是属于将来的日期 |
| @Length(min=,max=) | 检查所属的字段的长度是否在min和max之间,只能用于字符串 |
| @Max | 该字段的值只能小于或等于该值 |
| @Min | 该字段的值只能大于或等于该值 |
| @NotNull | 不能为null,用在任意类型的属性上 |
| @NotBlank | 不能为NULL,但可以为空字符串,用在字符串属性上 |
| @NotEmpty | 不能为NULL也不能为空字符串,只能用在字符串属性上 |
| @Pattern(regexp=) | 被注释的元素必须符合指定的正则表达式 |
| @URL(protocol=,host,port) | 检查是否是一个有效的URL,如果提供了protocol,host等,则该URL还需满足提供的条件 |
- 自定义校验注解
当Validation中提供的注解中不满足要求时,可以自定义校验注解实现校验功能。
比如实现一个校验需要满足指定值的注解:
编写一个自定义的校验注解
@Documented
// 关联自定义的校验器和自定义的校验注解,自定义校验器在下面编写的,这里可以填写多个
@Constraint(validatedBy = { ListValueConstraintValidator.class })
@Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE })
@Retention(RUNTIME)
public @interface ListValue {
// 设置校验失败默认的提示信息
String message() default "不满足值";
// 表示可以分组
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
int[] vals() default {};
}
编写一个自定义校验器:校验的逻辑在这里实现
public class ListValueConstraintValidator implements ConstraintValidator<ListValue, Integer> {
private Set<Integer> set = new HashSet<>();
// 初始化方法
@Override
public void initialize(ListValue constraintAnnotation) {
// 获取注解使用时填写的值
int[] vals = constraintAnnotation.vals();
for (int val : vals) {
set.add(val);
}
}
// 判断是否校验成功
@Override
public boolean isValid(Integer value, ConstraintValidatorContext context) {
return set.contains(value);
}
}
使用自定义校验注解
@ListValue(vals = {0, 1}, message = "显示状态不满足指定值")
private Integer showStatus;
SpringBoot整合Quartz
Quartz 是一个功能强大的开源任务调度框架,广泛应用于 Java 应用程序中。它提供了一套丰富的 API,用于创建、管理和调度定时任务。
- 添加Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
- 添加yml配置
spring:
quartz:
job-store-type: jdbc # memory:表示使用内存存储任务,jdbc:表示使用数据库存储任务
wait-for-jobs-to-complete-on-shutdown: true # 关闭时等待任务完成
overwrite-existing-jobs: true # 可以覆盖已有的任务
jdbc:
# 控制Quartz是否在启动时初始化数据库表结构
# embedded:表示Quartz将自动检查数据库中的相关表结构是否存在,如果不存在,则尝试创建它们。如果存在相关表结构,则不会进行任何修改。这是一种适用于嵌入式数据库的策略。
# always:表示每次应用启动时,Quartz都会删除并重新创建相关表结构,即使数据库中已经存在这些表结构。这是一种适用于开发和测试环境的策略。
# never:表示Quartz不会进行任何数据库表结构的初始化操作。这要求你确保数据库中已存在Quartz所需的表结构。这种策略适用于生产环境,其中通常会使用专门的数据库脚本或其他手动方式来管理Quartz表结构。
initialize-schema: never
properties:
org:
quartz:
scheduler:
instanceName: scheduler # 调度器实例名称
instanceId: AUTO # 调度器实例ID自动生成
jobStore:
driverDelegateClass: org.quartz.impl.jdbcjobstore.StdJDBCDelegate # 使用完全兼容JDBC的驱动
tablePrefix: QRTZ_ # QuartZ 表前缀
useProperties: false # 取消将JobDataMap中的属性转为字符串存储
# 线程池相关配置
threadPool:
threadCount: 25 # 线程池大小。默认为 10 。
threadPriority: 5 # 线程优先级
class: org.quartz.simpl.SimpleThreadPool
- 为任务类编写配置类
@Configuration
public class QuartzDeleteExpireLinkJobConfig {
@Bean
public JobDetail quartzTestJobDetail() {
/**
* 通过JobBuilder来创建一个Job实例
* withIdentity(QuartzTestJob.class.getSimpleName()):为Job指定一个唯一的标识
* storeDurably:设置Job为持久化的,即使没有被任何触发器关联也不会被删除
* usingJobData("key", "value"):
* 可以传值到Job中通过jobExecutionContext.getJobDetail().getJobDataMap().get("key")获取value值
*/
return JobBuilder.newJob(QuartzDeleteExpireLinkJob.class)
.withIdentity(QuartzDeleteExpireLinkJob.class.getSimpleName())
.storeDurably()
.build();
}
@Bean
public Trigger quartzTestJobTrigger() {
/**
* 通过TriggerBuilder来创建一个Trigger实例
* forJob(QuartzTestJob.class.getSimpleName()):指定要联接的Job的唯一标识
* withIdentity(QuartzTestJob.class.getSimpleName()):为Trigger指定一个唯一的标识
* withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever()):
* 配置触发器的调度规则。这里使用了简单调度规则,即每隔1秒触发一次,并且无限重复执行
* withSchedule(CronScheduleBuilder.cronSchedule("0 0 0 * * ?")):
* 配置触发器的调度规则。这里使用了Cron表达式,表示每天的午夜(00:0000)触发一次。
*/
return TriggerBuilder.newTrigger()
.forJob(QuartzDeleteExpireLinkJob.class.getSimpleName())
.withIdentity(QuartzDeleteExpireLinkJob.class.getSimpleName())
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever())
.build();
}
}
- 编写Service类
/**
* @author 文轩
* @create 2023-09-04 22:30
*/
@Service
public class QuartzService {
public int deleteExpireLinkMappingDataTask() {
System.out.println("执行...");
return 1;
}
}
- 编写任务类
@Component
public class QuartzDeleteExpireLinkJob extends QuartzJobBean {
@Resource
private QuartzService quartzService;
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
quartzService.deleteExpireLinkMappingDataTask();
}
}
-
将SQL文件执行
-
Cron 表达式
Cron的语法:Seconds Minutes Hours DayofMonth Month DayofWeek
Cron举例
| 示例 | 说明 |
|---|---|
| 0 0 0 * * * | 每天上午00:00执行任务 |
| 0 0 0,12,18 * * * | 每天0点、12 点、18点执行任务 |
| 0 0 12 * * 1 | 每个星期1中午 12 点执行任务 |
| 0 15 10 5 * * | 每月 5 日上午 10 点 15 执行任务 |
Cron中的特殊字符
| 特殊字符 | 含义 | 解释 |
|---|---|---|
| * | 所有可能的值 | 比如每个月、每个月的每天 |
| , | 列举多个值 | 0,12,18表示0、12、18这些值 |
| - | 范围值 | DayofMonth取值1-3,表示每月的1日到3日 |
| / | 间隔值 | Minutes取值0、15,表示每隔15分钟执行一次 |
| L | 最后值 | DayofMonth和DayofWeek可以使用,表示每月的最后一天或每周的最后一天 |
SpringBoot整合SpringTask
SpringBoot中已经集成了SpringTask,所以无需添加任何的依赖。
- 启用SpringTask,在启动类上添加
@EnableScheduling
- 编写定时任务
@Component
public class CronTask {
// 这边可以编写corn表达式,每天凌晨十二点
@Scheduled(cron = "0 0 0 * * *")
public void cron() {
log.info("执行定时任务...");
}
}
SpringBoot整合hutool实现文件上传下载
- 添加相关依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/>
</parent>
<dependencies>
<!--spring-boot-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--hutool-core依赖-->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-core</artifactId>
<version>5.8.32</version>
</dependency>
<!--lombok依赖-->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.20</version>
</dependency>
</dependencies>
- 编写配置文件
server:
port: 8080
params:
# 文件上传路径,可根据需要修改
fileDir: C:/temp/images
- 定义相关的Bean
- 定义统一的响应状态码:参考上面《SpringBoot业务状态码规范》
- 定义统一的返回信息:参考上面《SpringBoot接口统一响应》
- 实现文件上传和下载功能的接口
@RestController
@RequestMapping("/file")
public class FileLoadController {
@Value("${params.fileDir}")
private String fileDir;
@Value("${params.fileDownloadUrl}")
private String fileDownloadUrl;
/**
* 获取文件下载的路径
* @param fileName 文件名称
*/
private String getFileDownloadUrl(String fileName, HttpServletRequest request) {
// 构建完整的 URL: http://localhost:8080/admin/
String fullUrl = request.getScheme() + "://" +
request.getServerName() + ":" +
request.getServerPort() +
request.getContextPath();
fullUrl = fullUrl + fileDownloadUrl + "/" + fileName;
return fullUrl;
}
/**
* 根据文件类型获取文件保存路径
* @param fileName 文件名称
*/
private String getFilePath(String fileName) {
// 获取文件原始名称
if (fileName == null || !fileName.contains(".")) {
return "others";
}
// 获取文件扩展名
String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
String fileType = null;
switch (fileExtension) {
case "jpg":
case "jpeg":
case "png":
case "gif":
case "svg":
fileType = "images";
break;
case "txt":
case "doc":
case "docx":
case "pdf":
fileType = "documents";
break;
case "mp4":
case "avi":
case "mkv":
fileType = "videos";
break;
default:
fileType = "others";
break;
}
return fileDir + "/" + fileType + "/" + fileName;
}
/**
* 上传单个文件接口
* @param file 文件名称
*/
@PostMapping("/upload")
public ResponseData<Object> uploadFile(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file.isEmpty()) {
throw new RuntimeException("上传的文件参数有误");
}
try {
// 保存文件到指定路径
String fileName = file.getOriginalFilename(); // 获取原始文件名
// 生成新的文件名
String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
fileName = System.currentTimeMillis() + "." + fileExtension;
File savedFile = FileUtil.writeFromStream(file.getInputStream(), getFilePath(fileName));
// 拼接文件下载的路径
String url = getFileDownloadUrl(fileName, request);
Map<String, String> map = new HashMap<>();
map.put("url", url);
return ResponseData.success(map);
} catch (Exception e) {
throw new RuntimeException("文件写入失败");
}
}
/**
* 上传多个文件接口
* @param files 文件名称
*/
@PostMapping("/uploadMultiple")
public ResponseData<List<String>> uploadFiles(@RequestParam("files") MultipartFile[] files, HttpServletRequest request) {
if (files.length == 0) {
// No files provided
throw new RuntimeException("上传的文件参数有误");
}
List<String> urls = new ArrayList<>();
try {
for (MultipartFile file : files) {
if (!file.isEmpty()) {
// 保存文件到指定路径
String fileName = file.getOriginalFilename(); // 获取原始文件名
// 生成新的文件名
String fileExtension = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
fileName = System.currentTimeMillis() + "." + fileExtension;
File savedFile = FileUtil.writeFromStream(file.getInputStream(), getFilePath(fileName));
// 拼接文件下载的路径
String url = getFileDownloadUrl(fileName, request);
urls.add(url);
} else {
// 某个文件写入失败
}
}
return ResponseData.success(urls);
} catch (Exception e) {
throw new RuntimeException("文件上传失败");
}
}
/**
* 文件下载接口
* @param fileName 文件名称
*/
@GetMapping("/download/{fileName}")
public void downloadFile(@PathVariable("fileName") String fileName, HttpServletResponse response) {
File file = new File(getFilePath(fileName));
if (!file.exists()) {
throw new RuntimeException(fileName + "文件不存在");
}
try {
// 设置响应头
response.setContentType("application/octet-stream");
response.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8"));
// 将文件内容写入响应输出流
FileUtil.writeToStream(file, response.getOutputStream());
} catch (Exception e) {
throw new RuntimeException("文件下载失败");
}
}
/**
* 文件输出到浏览器
* @param fileName 文件名称
*/
@GetMapping("/view/{fileName}")
public void viewFile(@PathVariable("fileName") String fileName, HttpServletResponse response) {
File file = new File(getFilePath(fileName));
if (!file.exists()) {
throw new RuntimeException(fileName + "文件不存在");
}
try {
// 设置响应的Content-Type,根据文件类型动态设置
String contentType = Files.probeContentType(file.toPath()); // 自动检测文件类型
if (contentType == null) {
contentType = "application/octet-stream"; // 默认类型
}
response.setContentType(contentType);
// 将文件流写入响应
FileUtil.writeToStream(file, response.getOutputStream());
} catch (Exception e) {
throw new RuntimeException("文件下载失败");
}
}
}
SpringBoot整合Mybatis
- 添加maven依赖
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.17</version>
<relativePath/>
</parent>
<dependencies>
<!--添加spring-boot-starter-web依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
</dependencies>
- 编写yml配置
server:
port: 8900
mybatis:
# 配置mapper映射文件
mapper-locations: classpath:mybatis/mapper/*.xml
# 配置mybatis配置文件
config-location: classpath:mybatis/mybatis-config.xml
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3308/test?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
# 日志级别可以是:info、debug、error
logging:
level:
com.wenxuan.mapper: debug
- mybatis配置文件:/resource/mybatis/mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
</configuration>
- Controller的编写
@RestController
public class UserController {
@Autowired private UserService userService;
@PostMapping("/save")
public User save(@RequestBody User user) {
return userService.save(user);
}
}
- Service编写
@Service
public class UserService {
@Resource
private UserMapper userMapper;
public void save(User user) {
userMapper.save(user);
}
}
- Mapper的编写
@Mapper
public interface UserMapper {
void save(User user);
}
- Mapper对应配置文件
Mapper的路径:mybatis/mapper/UserMapper.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wenxuan.mapper.UserMapper">
<!--Bean属性名和表字段名的对应关系-->
<resultMap id="userMapper" type="com.wenxuan.bean.User">
<result property="id" column="id"/>
<result property="name" column="name"/>
</resultMap>
<insert id="save" parameterType="com.wenxuan.bean.User">
insert into user(id, name)
values (#{id}, #{name})
</insert>
</mapper>
- 编写启动类
@SpringBootApplication
@MapperScan("com.wenxuan.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
SpringBoot整合Mybatis实现多数据源
- 添加Maven依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.3</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>
<version>8.2.2.jre8</version>
</dependency>
- 编写数据源信息
spring:
datasource:
mysql: # MySQL数据源信息
driver-class-name: com.mysql.cj.jdbc.Driver
jdbc-url: jdbc:mysql://localhost:3306/test?serverTimezone=GMT%2B8&characterEncoding=utf-8&useSSL=false
username: root
password: 123456
sqlserver: # SQLServer数据源信息
jdbc-url: jdbc:sqlserver://localhost:1433;databaseName=db-test
username: sa
password: wenxuan@2001
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver
- 编写启动类
@SpringBootApplication
@MapperScan("com.wenxuan.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
- 编写多数据源的配置类
MySQL对应的数据源配置类
@Configuration
// 指定数据源对应的mapper接口位置
@MapperScan(basePackages = "com.wenxuan.mapper.mysql", sqlSessionFactoryRef = "mysqlSqlSessionFactory")
public class MySQLDataSourceConfig {
@Bean(name = "mysqlDataSource")
// 指定数据源配置前缀
@ConfigurationProperties(prefix = "spring.datasource.mysql")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name = "mysqlSqlSessionFactory")
public SqlSessionFactory mysqlSqlSessionFactory(@Qualifier("mysqlDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 指定数据源对应的mapper xml文件位置
sessionFactory.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/mysql/*.xml"));
return sessionFactory.getObject();
}
@Bean(name = "mysqlSqlSessionTemplate")
public SqlSessionTemplate mysqlSqlSessionTemplate(@Qualifier("mysqlSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
SQLServer数据源配置类
@Configuration
// 指定数据源对应的mapper接口位置
@MapperScan(basePackages = "com.wenxuan.mapper.sqlserver", sqlSessionFactoryRef = "sqlServerSqlSessionFactory")
public class SQLServerDataSourceConfig {
@Bean(name = "sqlServerDataSource")
@ConfigurationProperties(prefix = "spring.datasource.sqlserver")
public DataSource sqlServerDataSource() {
return DataSourceBuilder.create().build();
}
// 指定数据源配置前缀
@Bean(name = "sqlServerSqlSessionFactory")
public SqlSessionFactory sqlServerSqlSessionFactory(@Qualifier("sqlServerDataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
// 指定数据源对应的mapper xml文件位置
sessionFactory.setMapperLocations(
new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/sqlserver/*.xml"));
return sessionFactory.getObject();
}
@Bean(name = "sqlServerSqlSessionTemplate")
public SqlSessionTemplate sqlServerSqlSessionTemplate(@Qualifier("sqlServerSqlSessionFactory") SqlSessionFactory sqlSessionFactory) {
return new SqlSessionTemplate(sqlSessionFactory);
}
}