likes
comments
collection
share

Spring + SpringMVC + SpringBoot

作者站长头像
站长
· 阅读数 4

Spring相关概念

Spring对IOC的实现

IOC控制反转:控制反转是一种思想,将对象的创建和对象之间关系的维护交出去,交由第三方容器负责。可以降低程序耦合度,提高程序扩展力。

声明组件的注解

声明Bean的注解都有一个value属性,属性值用来指定Bean的ID,如果不指定默认是类名首字母变小写后的名字。

  • @Controller:将一个类标识为请求处理的控制器组件,用于接收用户的请求并处理相关的业务逻辑,最后返回相应的结果。
  • @Service:将一个类标识为业务逻辑层(Service)组件,用于封装复杂的业务操作,并提供事务管理、依赖注入等功能。
  • @Repository:将一个类标识为持久层(Repository)组件,主要用于实现数据访问和持久化操作。
  • @Component:通用的组件注解,并没有特定的业务含义。
  • @Mapper:MyBatis框架提供的一个注解,用于标识一个接口为Mapper接口,提供了数据库持久化操作的映射规则。

负责注入的注解

@Value

@Value:用于将值赋给一个类的属性或方法参数。通过@Value注解,可以在运行时将值注入到被注解的属性或方法参数中。

  • 属性注入:可以通过@Value注解将值注入到交由容器管理的类的属性中。
  • 方法参数注入:可以通过@Value注解将值注入到方法参数中。

属性注入

/**  
* @author 文轩  
* @create 2023-11-25 9:50  
*/  
@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注解可以用来注入非简单类型,被翻译为自动装配,单独使用@AutoWired注解,默认是根据类型自动装配(byType)。

@AutoWired注解有一个属性:required

  • 如果该属性值为true,则表示被注入的Bean必须存在,如果不存在则报错。
  • 如果该属性值为false,则表示被注入的Bean是否存在都可以,如果存在则注入,如果不存在也不报错。
/**  
* @author 文轩  
* @create 2023-11-23 21:23  
*/  
@RestController  
public class UserController {  
    @Autowired  
    private UserService userService;
}

@Quafier

@Quafier注解可以用来注入非简单类型,用来指定注入的Bean的名称,也就是Bean的id(byName)。

@Quafier注解使用场景:当使用@AutoWired注解,来根据属性类型自动装配时,但属性类型有多个类匹配时,此时不知道使用哪个类对象进行装配,可以通过@Quafier注解指定Bean的name,从而实现唯一性。

/**  
* @author 文轩  
* @create 2023-11-23 22:23  
*/  
@RestController  
public class UserController {  
    @Autowired  
    private UserService userService;
}

@Resource

@Resource注解也可以用来注入非简单类型,@Resource注解查找Bean的方式:

  1. 如果@Resource注解有name属性值,根据name属性值去查找对应名称的Bean(byName);
  2. 如果没有name名称匹配的Bean,根据属性名去查找对应名称的Bean(byName);
  3. 如果没有属性名匹配的Bean,根据属性类型去查找对应类型的Bean(byType);
  4. 如果没有类型的Bean,则报错;如果有多个类型匹配的Bean,则报错。

Bean对象生命周期

Spring其实就是一个管理Bean对象的工厂,它负责所有Bean对象的创建、Bean对象的销毁等。Bean的生命周期就是Bean对象从创建到销毁的这个过程。

Bean的生命周期之五步

Spring + SpringMVC + SpringBoot

Bean生命周期分为五步:

  1. 实例化Bean,调用Bean的无参构造方法
  2. 给Bean属性赋值,调用Bean的set方法
  3. 初始化Bean,调用Bean的initBean方法(手动编写且配置)
  4. 使用Bean对象
  5. 销毁Bean,调用Bean的destroyBean方法(手动编写且配置)

编写Bean

package com.wenxuan.spring.bean;

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的生命周期之七步

Spring + SpringMVC + SpringBoot

Bean的生命周期之七步:

  1. 实例化Bean,调用无参构造函数
  2. 给Bean属性赋值,调用set方法
  3. Bean前处理器before
  4. 初始化Bean,调用initBean方法(手动编写且配置)
  5. Bean后处理器after
  6. 使用Bean
  7. 销毁Bean,调用destroyBean方法(手动编写且配置)
package com.wenxuan.spring.bean;

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;

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;
    }
}

Bean的生命周期之十步

Spring + SpringMVC + SpringBoot

Aware的相关接口有:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware

  • 当Bean实现BeanNameAware接口时,Spring会将Bean的名字(Bean标签中的id属性值)传递给该接口的setBeanName方法,并且在第三步时Spring自动调用setBeanName方法。
  • 当Bean实现BeanClassLoader接口时,Spring会将该Bean的类加载器传递给该接口的setBeanClassLoader方法,并且在第三步时Spring会自动调用setBeanClassLoader方法。
  • 当Bean实现BeanFactroy接口时,Spring会将该Bean的工厂类对象传递给该接口的setBeanFactroy方法,并且在第三步时Spring会自动调用setBeanFactroy方法。

new对象放入Spring容器中

package com.wenxuan.spring.test;

import com.powernode.spring.bean.User;
import org.junit.Test;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

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:核心的业务是纵向的,将与业务逻辑无关的交叉业务代码单独的提取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程中的过程。

Spring + SpringMVC + SpringBoot

AOP的七大术语

  • 连接点:在程序的整个执行过程中,可以织入切面的位置。方法执行前后,异常抛出后等位置。
  • 切点:在程序执行流程中,真正植入切面的方法。
  • 通知:通知又叫做增强,具体要织入的diamagnetic。包括前置通知、后置通知、环绕通知、异常通知、最终通知。
  • 切面:切点 + 通知 等于 切面。
  • 织入:把通知应用到目标对象上的过程。
  • 代理对象:一个目标对象被织入通知后产生的新对象。
  • 目标对象:被织入通知的对象。

Spring + SpringMVC + SpringBoot

AOP的使用

添加maven依赖

<!-- 引入aop支持 -->  
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-starter-aop</artifactId>  
    <version>2.7.17</version>  
</dependency>

定义切面类:切面类需要使用@Aspect注解和@Component注解标识

package com.wenxuan.aop;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@Aspect
@Component
public class ExceptionAOP {
}

在切面类中定义切入点

package com.wenxuan.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@Aspect
@Component
public class ExceptionAOP {
    // value编写表达式定义切入点,标识某一类方法,在通知中添加切入点,这些标识的方法就会得到对应的通知
    @Pointcut(value = "execution(* com.wenxuan.service..*(..))")
    public void pointCut() {
    }
}

在切面类中定义通知:在通知中标识哪些切点得到增强的通知

package com.wenxuan.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@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注解进行定义,当切点正常执行结束后会执行的语句。

package com.wenxuan.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@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注解进行定义,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。

package com.wenxuan.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@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。

package com.wenxuan.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@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语句块中的语句。

package com.wenxuan.aop;

import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.annotation.After;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.Pointcut;  
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 12:27
 */
@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{}    
  1. @SpringBootConfiguration注解:里面有一个@Configuration注解,标识这是一个配置类。
  2. @Component注解:指定扫描哪些类,Spring中的注解,默认扫描主程序类所在包下和其所有子包。
  3. @EnableAutoConfiguration注解:里面有一个@AutoConfigurationPackage注解,将指定目录下的所有组件自动导入到Spring容器中,将扫描路径注册到全局,给其他组件查询。

VO、TO、PO的含义

  1. VO(Value Object):VO 是值对象的缩写,表示值对象。它通常是一种用于封装数据的简单 Java 类,用于在各个层之间传递数据。VO 主要用于表示业务领域中的数据对象,不包含业务逻辑,一般只包含属性和对应的 getter 和 setter 方法。
  2. TO(Transfer Object):TO 是传输对象的缩写,表示传输对象。它用于在客户端和服务器端之间传输数据,主要用于网络传输或跨进程通信。TO 通常包含客户端和服务器端共同需要的数据和操作,且不包含业务逻辑。TO 可以看作是业务逻辑无关的数据传输模型。
  3. PO(Persistent Object):PO 是持久化对象的缩写,表示持久化对象。它是与数据库表相映射的 Java 对象,在数据库操作中起到数据持久化的作用。PO 一般与数据库表的字段一一对应,并提供对应的 getter 和 setter 方法。PO 可以使用 ORM(对象关系映射)框架自动生成,简化了与数据库的交互。

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请求:删除资源
package com.wenxuan.controller;

import org.springframework.web.bind.annotation.*;


/**
 * @author 文轩
 * @create 2023-11-23 21:23
 */
@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注解获取以键值对形式的参数

@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

@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

@CookieValue获取Cookie值

@GetMapping("/cookie")  
public String cookie(@CookieValue("name") String name) {  
    return name;  
}

@RequestAttribute

@RequestAttribute获取请求域中的值

@GetMapping("/attribute")  
public String attribute(@RequestAttribute("name") String name) {  
    return name;  
}

响应数据的处理

响应JSON数据

通过@RequestBody注解标识类或者方法

@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注解的类上。

package com.wenxuan;  
  
import org.springframework.boot.SpringApplication;  
import org.springframework.boot.autoconfigure.SpringBootApplication;  
import org.springframework.transaction.annotation.EnableTransactionManagement;  
  
/**  
* @author 文轩  
* @create 2023-11-23 21:15  
*/  
@SpringBootApplication  
@EnableTransactionManagement  
public class Application {  
    public static void main(String[] args) {  
        SpringApplication.run(Application.class, args);  
    }  
}

使用事务

package com.wenxuan.controller;

import com.wenxuan.bean.User;
import com.wenxuan.service.UserService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import javax.annotation.Resource;

/**
 * @author 文轩
 * @create 2023-11-23 21:23
 */
@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注解的定义

package org.springframework.transaction.annotation;

@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注解不生效的场景

  1. 数据库引擎不支持事务(MySQL的MyIsam引擎不支持事务)
  2. 注解所在的类没有被加载成Bean,也就是没有被Spring容器管理
  3. 注解所在的方法不是public修饰的
  4. 注解所在的方法发生自调用问题
  5. 所在数据源是否加载事务管理器
  6. 注解的扩展配置propagation错误

SpringBoot容器添加数据

@Configuration + @Bean

@Configuration:告诉SpringBoot这是一个配置类,有一个proxyBeanMethods属性,true表示配置类中方法返回的组件对象是单例(默认值)的,false表示配置类中方法返回的组件对象不是单例的。

@Bean:表示方法是一个组件,将返回的Bean对象交由SpringBoot容器管理。

package com.wenxuan.config;

import com.wenxuan.bean.User;
import com.wenxuan.enums.SexEnum;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 文轩
 * @create 2023-11-25 23:15
 */
@Configuration
public class UserConfig {

    @Bean
    public User getUser() {
        return new User("1", "zs");
    }
}

@Configuration + @Import

@Configuration:告诉SpringBoot这是一个配置类,有一个proxyBeanMethods属性,true表示配置类中方法返回的组件对象是单例(默认值)的,false表示配置类中方法返回的组件对象不是单例的。

@Import注解在被@Configuration注解修饰的类中使用,导入特定的Bean类对象。

package com.wenxuan.config;

import com.wenxuan.bean.User;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author 文轩
 * @create 2023-11-25 23:15
 */
@Configuration
@Import({User.class})
public class UserConfig {
}

@Conditional注解

@Conditional注解用于条件装配,使用该注解可以使得在满足某些条件下才进行注入组件,@Conditional注解使用在配置方法或者配置类上,@Conditional注解有很多子注解,代表不同的条件:

  • @ConditionalOnBean:表示当存在某个组件时,才注入组件
  • @ConditionalOnMissingBean:表示不存在某个组件时,才注入组件
  • @ConditionalOnClass:表示存在某个类时,才注入组件
  • @ConditionalOnMissingClass:表示当不存在某个组件时,才注入组件
  • @ConditionalOnProperty:表示当存在配置的存在指定属性和属性值时,才注入组件
  • @ConditionalOnJava:表示如果是Java应用时,才会注入组件

@Conditional注解和@Bean注解结合使用

package com.wenxuan.config;

import com.wenxuan.bean.User;
import com.wenxuan.enums.SexEnum;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author 文轩
 * @create 2023-11-25 23:15
 */
@Configuration
public class UserConfig {

    @Bean
    @ConditionalOnBean(SexEnum.class)
    public User getUser() {
        return new User("1", "zs");
    }
}

@Conditional注解和@Import注解结合使用

package com.wenxuan.config;

import com.wenxuan.bean.User;
import com.wenxuan.enums.SexEnum;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author 文轩
 * @create 2023-11-25 23:15
 */
@Configuration
@Import({User.class})
@ConditionalOnBean(SexEnum.class)
public class UserConfig {
}

SpringBoot实现静态资源

静态资源的默认路径

如果静态资源放在以下类路径下,可以通过当前项目根路径/资源名进行访问

  • /static
  • /public
  • /resources
  • /META-INF/resources

修改静态资源的路径

修改静态资源访问的前缀

# 修改静态资源访问的前缀后,访问静态资源需要URL后面需要加上前缀才能访问到
spring:
  mvc:
    # 当前项目根路径/**  修改为  当前项目根路径/res/**
    static-path-pattern: /res/**   

修改默认的静态资源路径

spring:
  resources:
    # 需要将静态资源存在/resources/res目录下
    static-locations: [classpath:/res/]  

自定义Favicon

将需要设置为Favicon的文件重命名为favicon.ico,将favicon.ico文件放到静态资源存放的路径下即可。

自定义错误页

定义静态错误页:在resources下的static目录下,新建error目录,在其中新建各种静态错误页面,如 404、500,也可以模糊处理,如4xx、5xx 等,当程序运行出错时,会自动根据错误代码(如500)找到相应的错误页面(如/static/error/500.html),给予展示。

注册错误页面

package com.wenxuan.config;

import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.ErrorPageRegistrar;
import org.springframework.boot.web.server.ErrorPageRegistry;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-26 9:58
 */
@Component
public class ErrorPageConfig implements ErrorPageRegistrar {
    @Override
    public void registerErrorPages(ErrorPageRegistry registry) {
        ErrorPage error404Page = new ErrorPage(HttpStatus.NOT_FOUND, "/error/404");
        ErrorPage error500Page = new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500");
        registry.addErrorPages(error404Page, error500Page);
    }
}

controller进行拦截

@RequestMapping("/error/{status}")
public String errorPage(@PathVariable int status){
    // status是对应的状态码
    System.out.println("status = " + status);
    if(status == 404) {
        return "redirect:https://localhost/400.html";
    } else if(status == 500) {
        return "redirect:https://localhost/500.html";
    }
    return "redirect:https://localhost/error.html";
}

SpringBoot实现拦截器

定义拦截器

package com.wenxuan.interceptor;

import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * @author 文轩
 * @create 2023-11-26 10:12
 * 定义拦截器
 */
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("页面渲染之后执行该方法");
    }
}

注册拦截器

package com.wenxuan.config;

import com.wenxuan.interceptor.LoginInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

/**
 * @author 文轩
 * @create 2023-11-26 10:13
 */
@Configuration
public class AdminWebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor())
                //所有请求都被拦截包括静态资源
                .addPathPatterns("/**")
                //放行的请求
                .excludePathPatterns("/","/login","/css/**","/fonts/**","/images/**","/js/**");
    }
}

SpringBoot实现文件上传和下载

SpringBoot实现文件上传

package com.wenxuan.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * @author 文轩
 * @create 2023-11-26 13:26
 */
@RestController
@RequestMapping("file")
public class FileController {

    @Value("${file.upload.path}")
    private String uploadFilePath;

    @RequestMapping("/upload")
    public String upload(@RequestParam("files") MultipartFile[] files) {
        for(int i = 0; i < files.length; i++){
            // 获取上传文件的文件名
            String fileName = files[i].getOriginalFilename();

            // 获取当前时间,作为目录分割文件
            LocalDate currentDate = LocalDate.now();
            DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
            String formattedDate = currentDate.format(formatter);

            // 设置上传文件的保存路径
            File dest = new File(uploadFilePath + '/' + formattedDate + "/" + fileName);
            if (!dest.getParentFile().exists()) {
                dest.getParentFile().mkdirs();
            }
            try {
                files[i].transferTo(dest);
            } catch (Exception e) {
                System.out.println("上传失败:" + e);
                return "error";
            }
        }
        return "success";
    }
}

SpringBoot实现文件下载

package com.wenxuan.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletResponse;
import java.io.*;

/**
 * @author 文轩
 * @create 2023-11-26 13:26
 */
@RestController
@RequestMapping("file")
public class FileController {

    @Value("${file.download.path}")
    private String downloadFilePath;

    @RequestMapping("/download")
    public String download(HttpServletResponse response, @RequestParam("fileName") String fileName){
        File file = new File(uploadFilePath +'/'+ fileName);
        // 文件存在校验
        if(!file.exists()){
            return "下载文件不存在";
        }

        // 重置response对象,保证response对象为初始状态
        response.reset();

        // 获取文件响应类型
        MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap();
        String contentType = fileTypeMap.getContentType(file);
        // 设置下载文件响应类型
        if (contentType != null) {
            response.setContentType(contentType);
        } else {
            response.setContentType("application/octet-stream");
        }

        response.setCharacterEncoding("utf-8");
        response.setContentLength((int) file.length());
        // 设置Content-Disposition头部字段,表示将文件作附件下载
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName );

        try(BufferedInputStream bis = new BufferedInputStream(new FileInputStream(file));) {
            byte[] buff = new byte[1024];
            OutputStream os  = response.getOutputStream();
            int i = 0;
            while ((i = bis.read(buff)) != -1) {
                os.write(buff, 0, i);
                os.flush();
            }
        } catch (IOException e) {
            return "下载失败";
        }
        return "下载成功";
    }
}

SpringBoot实现文件输出到浏览器

package com.wenxuan.controller;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author 文轩
 * @create 2023-11-26 13:26
 */
@RestController
@RequestMapping("file")
public class FileController {

    @Value("${file.path}")
    private String filePath;

    @RequestMapping("/outputBrowser")
    public String outputBrowser(HttpServletResponse response, @RequestParam("fileName") String fileName) {
        // 获取绝对路径
        File file = new File(filePath +'/'+ fileName);

        if(!file.exists()){
            return "输出文件不存在";
        }

        // 获取文件响应类型
        String contentType = null;
        Path path = Paths.get(fileName);
        try {
            contentType =  Files.probeContentType(path);
        } catch (IOException e) {
            e.printStackTrace();
            return "获取文件响应类型失败";
        }

        try(
                // 获取输入流,读取文件内容
                FileInputStream fileInputStream = new FileInputStream(file);
                // 获取输出流,输出到浏览器
                ServletOutputStream outputStream = response.getOutputStream();) {
            // 设置响应类型
            response.setContentType(contentType + ";character=UTF-8");
            int len = 0;
            byte[] bytes = new byte[1024];
            while((len = fileInputStream.read(bytes)) != -1) {
                outputStream.write(bytes, 0, len);
                outputStream.flush();
            }
        } catch (IOException e) {
            System.out.println("文件输出失败:" + e);
            return "error";
        }
        return "success";
    }
}

自定义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>

编写读取配置文件的属性类

配置属性类:获取配置文件内容

package com.wenxuan.config;

import org.springframework.boot.context.properties.ConfigurationProperties;

/**
 * @author 文轩
 * @create 2023-11-05 11:24
 * 配置属性类:和配置文件的参数一一对应,将配置文件的数据封装到对象中
 */
@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使用者可以使用服务类对象

package com.wenxuan.service;

/**
 * @author 文轩
 * @create 2023-11-05 11:25
 * 业务类
 */
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;
    }
}

编写自动配置类

自动配置类:使得外部可以通过注解方式获取服务类

package com.wenxuan.config;

import com.wenxuan.service.HelloService;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * @author 文轩
 * @create 2023-11-05 11:25
 * 自动注入类:将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

  1. 将jar包导入maven仓库中,通过mvn install
  2. 在项目中的pom.xml配置文件中添加自定义SpringBootStarter的依赖
<!--导入自定义starter-->
<dependency>
    <groupId>com.wenxuan</groupId>
    <artifactId>hello-spring-boot-starter</artifactId>
    <version>1.0-SNAPSHOT</version>
</dependency>
  1. 编写配置文件application.yml
wenxuan:
  name: xiaoming
  address: beijing
  1. 通过注解获取服务类
@Autowired
private HelloService helloService;

SpringBoot全局异常处理

同一处理控制器中的异常通过@ControllerAdvice注解和@ExceptionHandler注解结合实现:

  • @ControllerAdvice: 提供了全局性的控制器Advice,可以实现同一处理控制器中的异常、全局数据绑定、页面跳转。
  • @ExceptionHandler:定义全局的异常处理方法,在控制器中使用改注解可以指定处理特定异常的方法,当控制器中抛出对应类型的异常,Spring会调用这个方法进行处理。
package com.wenxuan.config;

import org.springframework.validation.BindException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.util.Set;

/**
 * @author 文轩
 * @create 2023-11-08 19:58
 */
// 表示异常处理器作用于使用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目录下面编写测试类

package com.wenxuan.service;

import com.wenxuan.Application;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

/**
 * @author 文轩
 * @create 2023-11-26 17:20
 */
@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
public class UserServiceTest {
    @Resource
    private UserService userService;

    @Test
    public void testUserService() {
        userService.test();
    }
}

SpringBoot解决跨域问题

package com.wenxuan.filter;

import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * @author 文轩
 * @create 2023-11-26 13:29
 *
 * 解决跨域问题:允许任何请求访问
 */
// 使用@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业务状态码规范

这里举出一种业务状态码开发规范,还有很多种业务状态码开发规范,可以自行搜索。

  1. 错误码定义规则为五位数字,正确码为00000
  2. 前两位表示业务场景,最后三位表示错误码
  3. 维护错误码后需要错误提示,所以将其定义为枚举类型

例如:

  • 10开头:表示通用的错误状态码

  • 11开头:表示商品业务的错误状态码

  • 12开头:表示订单业务的错误状态码

  • 10000:系统未知异常

  • 10001:参数格式校验失败

package com.wenxuan.common.code;

public enum BizCodeEnum {

    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使用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注解将值注入到方法参数中。

属性注入

/**  
* @author 文轩  
* @create 2023-11-25 9:50  
*/  
@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类,定义需要绑定的属性

package com.wenxuan.bean;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;

/**
 * @author 文轩
 * @create 2023-11-25 23:34
 */
@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 2023-12-30 11:51
 */
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTemplateTest {
    
    @Autowired
    RedisTemplate redisTemplate;

    @Test
    public void testRedisTemplate() {
        ValueOperations valueOperations = redisTemplate.opsForValue();
        valueOperations.set("name", "wenxuan");
        System.out.println(valueOperations.get("name"));
    }
}

SpringBoot整合Redisson

Redisson是一个基于Redis的Java客户端,它提供了一系列的分布式Java对象和服务,以便于在Java应用程序中使用分布式锁、分布式集合、分布式Map、分布式队列等分布式数据结构和分布式服务。

Redisson官方文档:github.com/redisson/re…

添加maven依赖

<dependency>  
    <groupId>org.redisson</groupId>  
    <artifactId>redisson</artifactId>  
</dependency>

编写配置类


package com.atguigu.gulimall.product.config;  
  
import org.redisson.Redisson;  
import org.redisson.api.RedissonClient;  
import org.redisson.config.Config;  
import org.springframework.context.annotation.Bean;  
import org.springframework.context.annotation.Configuration;  
  
/**  
* @author 文轩  
* @create 2024-01-23 22:24  
*/  
@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

package com.wenxuan.web;

import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @author 文轩
 * @create 2024-01-19 15:43
 */
@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的配置类

package com.wenxuan.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.cache.CacheProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author 文轩
 * @create 2024-01-25 17:01
 */
@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整合Thymeleaf

Thymeleaf 是一个服务器端 Java 模板引擎,能够处理 HTML、XML、CSS、JAVASCRIPT 等模板文件。Thymeleaf 模板可以直接当作静态原型来使用。

添加thymeleaf的依赖

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

在/resource/templates文件夹下面编写html文件

<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head></head>

<body>
<h1 th:text="Thymeleaf"></h1>
</body>
</html>

配置热部署

  1. 添加maven依赖
  2. 关闭thymeleaf缓存
  3. 热部署快捷键:ctrl + F9
<dependency>  
    <groupId>org.springframework.boot</groupId>  
    <artifactId>spring-boot-devtools</artifactId>  
</dependency>
spring.thymeleaf.cache=false

Thymeleaf

Thymeleaf 是一个服务器端 Java 模板引擎,能够处理 HTML、XML、CSS、JAVASCRIPT 等模板文件。Thymeleaf 模板可以直接当作静态原型来使用。

Thymeleaf简单表达式

变量表达式

获取引用类型数据时,该属性需要有get方法,或者是public修饰的属性

"${变量名}"	     // 获取基本数据类型
"${对象名.属性名}"	  // 获取引用类型

链接表达式

链接表达式@{}用于处理web应用中的url地址,可以是相对地址,也可以是绝对地址。

  • 绝对地址:以http协议开始的地址
  • 相对地址:相对于应用的根路径开始的地址,通常以斜杠开头

访问 localhost:8080/link/test路径

  • @{usr}:localhost:8080/link/test/user(直接拼接进去)
  • @{/user}:localhost:8080/user(拼接到根路径下)
  • @{./user}:localhost:8080/link/user(拼接当前访问路径)
  • @{../user}:localhost:8080/user(拼接到上一级访问路径)
  • @{www.baidu.com}:www.baidu.com(绝对路径,以http开头)

Thymeleaf的字面值

所谓字面值,首先它不是一个变量,它是一个具体的确切的值,通常这些值是比较简单的,例如:18'welcome'等,它们没有名称,以至于我们只能用值来称呼它们,因此我们称其为字面值。

文字字面值是用单引号引起来的任何字符内容,如果字符内容里面含有单引号,则需要进行转义:

<!-- Welcome to BeiJing! -->
<p th:text="'Welcome to BeiJing!'"></p>
<!-- 'Welcome to BeiJing!' -->
<p th:text="'\'Welcome to BeiJing!\''"></p>

数字字面值

<!-- 2017 -->
<p th:text="2017"></p>
<!-- 2018 -->
<p th:text="2017 + 1"></p>

布尔字面值

<!-- false -->
<p th:text="1 > 2"></p>
<!-- 否 -->
<p th:text="1 > 2 ? '是' : '否'"></p>

空字面值

<!-- false -->
<p th:text="${user == null}"></p>

Thymeleaf的属性

th:text

在标签体中展示表达式评估结果的文本内容:

<p th:text="${message}"></p>

th:each

遍历(迭代)的语法th:each="自定义的元素变量名称 : ${集合变量名称}"

<div>
    <spn>你所在城市:</spn>
    <select name="mycity">
        <option th:each="city : ${cities}" th:text="${city.name}"></option>
    </select>
</div>

th:if

当表达式的评估结果为真时则显示内容,否则不显示:

<a th:href="@{/user/order(uid=${user.id})}" th:if="${user != null}">我的订单</a>

th:swith

多路选择语句,它需要搭配th:case来使用:

<div th:switch="${user.role}">
    <p th:case="admin">管理员</p>
    <p th:case="user">普通用户</p>
</div>
转载自:https://juejin.cn/post/7305321063668318208
评论
请登录