SpringBoot对IOC的实现
IOC控制反转:控制反转是一种思想,将对象的创建和对象之间关系的维护交出去,交由第三方容器负责。可以降低程序耦合度,提高程序扩展力。
声明组件的注解
声明Bean的注解都有一个value属性,属性值用来指定Bean的ID,如果不指定默认是类名首字母变小写后的名字。
@Controller
@Controller:将一个类标识为请求处理的控制器组件,用于接收用户的请求并处理相关的业务逻辑,最后返回相应的结果。
java
/**
* @author 文轩
* @create 2023-11-23 21:23
*/
@Controller
public class UserController {
}
@Service
@Service:将一个类标识为业务逻辑层(Service)组件,用于封装复杂的业务操作,并提供事务管理、依赖注入等功能。
java
/**
* @author 文轩
* @create 2023-11-23 21:54
*/
@Service
public class UserService {
}
@Repository
@Repository:将一个类标识为持久层(Repository)组件,主要用于实现数据访问和持久化操作。
java
/**
* @author 文轩
* @create 2023-11-23 21:24
*/
@Repository
public class UserRepository {
}
@Component
@Component注解只是一个通用的组件注解,并没有特定的业务含义。
java
@Component
public class MyComponent {
}
@Mapper
@Mapper注解是MyBatis框架提供的一个注解,用于标识一个接口为Mapper接口,提供了数据库持久化操作的映射规则。
java
/**
* @author 文轩
* @create 2023-11-23 21:24
*/
@Mapper
public interface UserMapper {
}
负责注入的注解
@Value
@Value:用于将值赋给一个类的属性或方法参数。通过@Value注解,可以在运行时将值注入到被注解的属性或方法参数中。
- 属性注入:可以通过@Value注解将值注入到交由容器管理的类的属性中。
- 方法参数注入:可以通过@Value注解将值注入到方法参数中。
属性注入
java
/**
* @author 文轩
* @create 2023-11-25 9:50
*/
@RestController
public class TestController {
@Value("${wenxuan.name}")
private String name;
}
方法参数注入
java
@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是否存在都可以,如果存在则注入,如果不存在也不报错。
java
/**
* @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,从而实现唯一性。
java
/**
* @author 文轩
* @create 2023-11-23 22:23
*/
@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方法(手动编写且配置)
编写Bean
java
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的生命周期之七步
Bean的生命周期之七步:
- 实例化Bean,调用无参构造函数
- 给Bean属性赋值,调用set方法
- Bean前处理器before
- 初始化Bean,调用initBean方法(手动编写且配置)
- Bean后处理器after
- 使用Bean
- 销毁Bean,调用destroyBean方法(手动编写且配置)
java
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的生命周期之十步
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容器中
java
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:核心的业务是纵向的,将与业务逻辑无关的交叉业务代码单独的提取出来,形成一个独立的组件,然后以横向交叉的方式应用到业务流程中的过程。
AOP的七大术语
- 连接点:在程序的整个执行过程中,可以织入切面的位置。方法执行前后,异常抛出后等位置。
- 切点:在程序执行流程中,真正植入切面的方法。
- 通知:通知又叫做增强,具体要织入的diamagnetic。包括前置通知、后置通知、环绕通知、异常通知、最终通知。
- 切面:切点 + 通知 等于 切面。
- 织入:把通知应用到目标对象上的过程。
- 代理对象:一个目标对象被织入通知后产生的新对象。
- 目标对象:被织入通知的对象。
AOP的使用
添加maven依赖
xml
<!-- 引入aop支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.7.17</version>
</dependency>
定义切面类
切面类需要使用@Aspect注解和@Component注解标识
java
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 {
}
在切面类中定义切入点
java
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() {
}
}
在切面类中定义通知
在通知中标识哪些切点得到增强的通知
java
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类型的方法。
- 异常:可选项,省略时表示任意异常类型。
java
// 切点表达式的语法格式:
execution([访问控制修饰符] 返回值类型 [全限定类名]方法名(形式参数列表) [异常])
// 切点表达式举例:
// 表示 service包下以delete开头的并且是public修饰的方法
execution(public * com.powernode.mall.service.*.delete*(..))
// 表示mall包下所有的方法
execution(* com.powernode.mall..*(..))
// 表示所有包下所有类的所有方法
execution(* *(..))
AOP通知的分类
AOP通知在切面类中定义,通知分为前置通知、后置通知、环绕通知、异常通知、最终通知
前置通知
前置通知通过@Before注解进行定义,切点执行前会执行的语句。
java
// 前置通知,在切点执行之前执行的操作
@Before("pointCut()")
public void before(JoinPoint joinPoint) {
// 逻辑代码
System.out.println("前置通知");
}
后置通知
前置通知通过@AfterReturningr注解进行定义,当切点正常执行结束后会执行的语句。
java
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注解进行定义,环绕通知非常强大,可以决定目标方法是否执行,什么时候执行,执行时是否需要替换方法参数,执行完毕是否需要替换返回值。
java
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。
java
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语句块中的语句。
java
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兑现中封装了切入点的一系列信息。
java
//返回目标对象,即被代理的对象
Object getTarget();
//返回切入点的参数
Object[] getArgs();
//返回切入点的Signature
Signature getSignature();
//返回切入的类型,比如method-call,field-get等等
String getKind();
ProceedingJoinPoint是JoinPoint的实现类,环绕通知中第一个形参必须是ProceedingJoinPoint对象,这个对象中定义了proceed方法,表示执行目标方法并得到返回值。
SpringBoot整合事务
事务:在一个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。可以通过事务保证业务中多条DML语句同时成功或者同时失败。
SpringBoot实现事务
添加maven依赖
xml
<!-- 引入事务支持 -->
<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注解的类上。
java
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);
}
}
使用事务
java
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注解的定义
java
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注解事务的传播行为
事务的传播行为:在使用嵌套事务的情况下,嵌套的事务的设置情况通过事务的传播行为进行设置。
java
// 设置事务的传播行为
@Transactional(propagation = Propagation.REQUIRED)
事务的传播行为有如下几种
java
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注解事务的隔离级别
为了保证并发读取数据的正确性,提出事务的隔离级别。事务的隔离级别越好,并发读取数据越正确,但是性能就越差。
java
// 设置事务的隔离级别
@Transactional(isolation = Isolation.READ_COMITTED)
事务的隔离级别有如下四种:
java
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创建Web项目
添加maven依赖
xml
<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>
编写启动类
java
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请求:删除资源
java
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表示前端必须传递参数,如果没有传递则表示路径不匹配。
单个键值对形式的参数
java
@GetMapping("/user")
public User getUserByUserId(@RequestParam String id) {
return userService.getById(id);
}
多个键值对形式的参数
java
// 通过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表示前端必须传递参数,如果没有传递则表示路径不匹配。
java
// 比如URL:/user/1
@GetMapping("/user/{id}")
public User getUserByUserId(@PathVariable String id) {
return userService.getById(id);
}
@RequestBody
@RequestBody注解接收JSON形式的参数
@RequestBody注解的required属性,默认为true表示前端必须传递参数,如果没有传递则表示路径不匹配。
java
@PostMapping("/user")
public boolean saveUser(@RequestBody User user) {
return userService.save(user);
}
@RequestHeader
@RequestHeader获取请求头部分或者全部信息
获取请求头指定参数
java
@GetMapping("/header")
public String getHeader(@RequestHeader("User-Agent") String userAgent) {
return userAgent;
}
获取请求头所有参数
java
@GetMapping("/headerList")
public Map<String, String> getHeaderList(@RequestHeader Map<String, String> headerList) {
System.out.println(headerList);
return headerList;
}
@CookieValue
@CookieValue获取Cookie值
java
@GetMapping("/cookie")
public String cookie(@CookieValue("name") String name) {
return name;
}
@RequestAttribute
@RequestAttribute获取请求域中的值
java
@GetMapping("/attribute")
public String attribute(@RequestAttribute("name") String name) {
return name;
}
请求数据相应
响应JSON数据
通过@RequestBody注解标识类或者方法
@RestController注解的作用等价于@Controller + @RequestBody
java
@ResponseBody
@GetMapping("/user/{id}")
public User getUser(@PathVariable String id) {
return userService.select(id);
}
请求转发
方式1:使用 "forward" 关键字
java
// 类的注解不能使用@RestController 要用@Controller
@RequestMapping("/forward")
public String forward() {
return "forward:/index.html";
}
方式2:使用servlet 提供的API
java
// 类的注解可以使用@RestController,也可以使用@Controller
@RequestMapping("/forward")
public void forward(HttpServletRequest request, HttpServletResponse response) throws Exception {
request.getRequestDispatcher("/index").forward(request, response);
}
重定向
方式1:使用 "redirect" 关键字
java
// 类的注解不能使用@RestController,要用@Controller
@RequestMapping("/redirect")
public String redirect() {
return "redirect:/index.html";
}
方式2:使用servlet 提供的API
java
@RequestMapping("/redirect")
public void redirect(HttpServletResponse response) throws IOException {
response.sendRedirect("/index.html");
}
SpringBoot容器
@Configuration + @Bean
@Configuration:告诉SpringBoot这是一个配置类,有一个proxyBeanMethods属性,true表示配置类中方法返回的组件对象是单例(默认值)的,false表示配置类中方法返回的组件对象不是单例的。
@Bean:表示方法是一个组件,将返回的Bean对象交由SpringBoot容器管理。
java
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类对象。
java
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注解结合使用
java
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注解结合使用
java
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 {
}
读取yaml配置文件属性
@Value
@Value:用于将值赋给一个类的属性或方法参数。通过@Value注解,可以在运行时将值注入到被注解的属性或方法参数中。
- 属性注入:可以通过@Value注解将值注入到交由容器管理的类的属性中。
- 方法参数注入:可以通过@Value注解将值注入到方法参数中。
属性注入
java
/**
* @author 文轩
* @create 2023-11-25 9:50
*/
@RestController
public class TestController {
@Value("${wenxuan.name}")
private String name;
}
方法参数注入
java
@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配置文件中编写需要绑定的数据
yaml
car:
name: BYD
price: 10000
编写Bean类,定义需要绑定的属性
java
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;
}
}
开启Car配置绑定功能,把这个Car这个组件自动注册到容器中
java
package com.wenxuan.config;
import com.wenxuan.bean.Car;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
/**
* @author 文轩
* @create 2023-11-25 23:38
*/
// 开启Car配置绑定功能,把这个Car这个组件自动注册到容器中
@EnableConfigurationProperties(Car.class)
public class CarConfig {
}
@SpringBootApplication注解
@SpringBootApplication用于标注主程序类,该类有一个主方法
@SpringBootApplication注解的定义如下
java
@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容器中,将扫描路径注册到全局,给其他组件查询。
静态资源的访问
静态资源的默认路径
如果静态资源放在以下类路径下,可以通过当前项目根路径/资源名
进行访问
- /static
- /public
- /resources
- /META-INF/resources
修改静态资源的路径
修改静态资源访问的前缀
yaml
# 修改静态资源访问的前缀后,访问静态资源需要URL后面需要加上前缀才能访问到
spring:
mvc:
# 当前项目根路径/** 修改为 当前项目根路径/res/**
static-path-pattern: /res/**
修改默认的静态资源路径
yaml
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),给予展示。
自定义跳转到指定错误页面
注册错误页面
java
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进行拦截
java
@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实现拦截器
SpringBoot实现拦截器
定义拦截器
java
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("页面渲染之后执行该方法");
}
}
注册拦截器
java
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实现文件上传
java
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实现文件下载
java
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实现将文件出到浏览器
java
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";
}
}
SpringBoot实现数据源
数据源:给程序员提供Connection对象的,都叫做数据源(datasource)。数据源实际上是一套规范(接口),接口全路径名:javax.sql.DataSource(JDK规范)。所有的数据源都实现了DataSource接口,重写了接口中的方法。
SpringBoot配置数据源
添加maven依赖
xml
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
编写application.yaml
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
连接地址urlmysql-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
配置第三方数据源
常见的第三方数据源:druid、c3p0、dbcp。
添加maven依赖
xml
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.17</version>
</dependency>
在application.yaml配置文件中切换数据源
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数据源其他配置
yaml
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