Spring框架学习笔记

0 阅读22分钟

笔记基于教程:

尚硅谷Spring零基础入门到进阶,一套搞定spring6全套视频教程(源码级讲解)

一、基本介绍

官网:spring.io/

1.架构图

2.核心知识点

  1. IOC
  2. AOP
  3. 事务管理

二、核心概念 IOC

1.基本介绍

  1. 基本概念IOC(控制反转):即由原本手动 new 对象变为容器自动创建对象。Spring 框架提供了一个 IOC 容器,会将框架自动创建的对象放到里面。需要使用什么对象从容器里拿即可,不需要自己手动 new 了。
  2. 基本概念 DI:对于有依赖关系的对象,IOC 容器会自动对他们进行关系的绑定
  3. 最终效果:使用的对象的时候可以直接从容器中获取,并且获取到的 bean 对象已经绑定了依赖关系

2.基本案例

  1. 在 Maven 项目中导入 Spring 的 IOC 依赖
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.1</version>
  </dependency>
</dependencies>
  1. 编写实体类
public class Student {
    private String name;
    private Integer age;
    private String address;

    public Student() {
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Student{" +
        "name='" + name + '\'' +
        ", age=" + age +
        ", address='" + address + '\'' +
        '}';
    }
}
  1. 创建配置文件,将实体类配置进去

id 属性:对象的唯一表示

class 属性:类的全路径

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="student" class="dto.Student"></bean>
</beans>
  1. 初始化容器,然后通过容器获取对象
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Student student = (Student) context.getBean("student");
    System.out.println(student);
}

3.基本案例分析

3.1 无参构造器是否会执行

无参构造会执行

3.2 怎么创建对象的

加载 xml 文件后,解析到里面的 bean 标签。然后读取 bean 的 class 属性作为全路径,通过反射创建对象。然后 bean 的 id 属性作为该对象的唯一表示。

3.3 创建的对象放到哪里了

会把创建的对象放到 IOC 容器中(一个 Map 集合,key 是 bean 标签的 id 属性,value 是类的定义信息)

4.IOC 基于 XML

操作案例可以参考 IOC 的基本案例部分,这部分主要进行详细知识点介绍

4.1 获取 Bean 的方式

有三种方式获取 Bean:

  1. 根据 id 属性
  2. 根据 class 属性(同一个类在多例情况下会报错)(同一个接口有多个实现类的情况下,无法通过接口类型的 class 获取对应的 bean)
  3. 根据 id 属性和 class 属性
4.1.1 根据 id 属性
  1. XML 中配置 Bean 信息,然后给 bean 加上 id 属性
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="dto.Student"></bean>
</beans>
  1. 然后 java 代码中根据 id 值获取对象
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Student student = (Student) context.getBean("student");
    System.out.println(student);
}
4.1.2 根据 class 属性
  1. XML 中配置 Bean 信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="dto.Student"></bean>
</beans>
  1. 然后 java 代码中根据 class 值获取对象
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Student student = (Student) context.getBean(Student.class);
    System.out.println(student);
}
4.1.3 根据 id 和 class 属性
  1. XML 中配置 Bean 信息
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="student" class="dto.Student"></bean>
</beans>
  1. 然后 java 代码中根据 id 和 class 值获取对象
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
    Student student = (Student) context.getBean("student",Student.class);
    System.out.println(student);
}

4.2 属性注入的方式

有两种方式注入依赖:

  1. 根据 setter 方法注入
  2. 根据构造器注入
4.2.1 基于 setter 方法注入
  1. 创建类,定义属性和set方法
  2. 在xml文件中配置对象
  3. 在xml中使用property标签注入属性
<!--创建Person对象(Bean)-->
<bean id="person1" class="com.junqing.test.Person">
  <!--propert 使用set注入属性
      name:属性名称
      value:注入的值
  -->
  <property name="name" value="jack"></property>
  <property name="age" value="20"></property>
</bean>
4.2.2 基于构造器注入
  1. 创建类,定义属性和set方法,声明有参构造器
  2. 在xml文件中配置对象
  3. 在xml中使用constructor-arg标签注入属性
<!--创建Person对象(Bean)-->
<bean id="person1" class="com.junqing.test.Person">
  <!--constructor-arg 使用构造器注入属性
      name:属性名称
      value:注入的值
  -->
  <constructor-arg name="name" value="zyj"></constructor-arg>
  <constructor-arg name="age" value="18"></constructor-arg>
</bean>

4.3 特殊字段注入

直接给字段设置值,既不使用 set 方法,也不使用构造器

4.3.1 字面量注入

同 setter 注入方式一样

4.3.2 null 值注入

使用 null

<property name="address">
    <null/>
</property>
4.3.3 特殊符号注入

当需要注入的特殊值包含特殊符号的时候,写入 xml 文件中会报错,因此可以用 <![CDATA[属性值]]>这种方式注入

<property name="address">
    <value><![CDATA[1>=5]]></value>
</property>

4.4 对象属性注入

一个类调用另一个类的时候需要创建对象,这个时候怎么创建。例如:员工里面有个属性是部门对象。

4.4.1 外部 bean 注入

被引用的对象在外部声明

在xml文件中创建两个类,调用对象添加property标签调用ref

<!--调用类Service-->
<bean id="userService" class="com.junqing.test.UserService">
  <!--ref 是调用类的id -->
  <property name="userDao" ref="userDaoImpl"></property>
</bean>

<!--被调用类Dao-->
<bean id="userDaoImpl" class="com.junqing.test.UserDaoImpl"></bean>
4.4.2 内部 bean 注入

被引用的对象在内部声明

<bean id="emp" class="com.junqing.bean.Emp">
  <property name="name" value="zyj"></property>
  <property name="sex" value="girl"></property>
  <!--内部bean-->
  <property name="dept">
    <bean id="dept" class="com.junqing.bean.Dept">
      <!--内部bean属性注入-->
      <property name="dname" value="英语部"></property>
    </bean>
  </property>
</bean>
4.4.3 级联属性赋值

用的不多,了解即可

4.5 数组属性注入

property 标签中使用 array 标签注入

<bean id="test" class="com.junqing.bean.Test01">
  <!--数组数据的注入-->
  <property name="course">
    <array>
      <value>AJAX课程</value>
      <value>java30天入门</value>
    </array>
  </property>
</bean>

4.6 集合属性注入

4.6.1 基本方式

property 标签中使用 List``Set``Map 标签注入

<bean id="test" class="com.junqing.bean.Test01">
  <!--List数据的注入-->
  <property name="list">
    <list>
      <value>韩顺平</value>
      <value>王振国</value>
    </list>
  </property>

  <!--set数据的注入-->
  <property name="set">
    <set>
      <value>MySQL</value>
      <value>Redis</value>
    </set>
  </property>

  <!--map数据的注入-->
  <property name="map">
    <map>
      <entry key="01" value="韩顺平"></entry>
      <entry key="02" value="王振国"></entry>
    </map>
  </property>
</bean>

4.6.2 引用集合方式

将注入的集合抽取到外面,然后创建对象的时候将抽取的集合注入

  1. 在spring配置文件中的xsi:schemaLocation属性引入名称空间spring-util
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:p="http://www.springframework.org/schema/p"
  xmlns:util="http://www.springframework.org/schema/util"
  xsi:schemaLocation="http://www.springframework.org/schema/beans 
  http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/util 
  http://www.springframework.org/schema/util/spring-util.xsd">

  1. 使用util创建集合对象,然后外部注入
<!--先抽取出list-->
<util:list id="BookList">
  <value>豆奶粉</value>
  <value>豆沙包</value>
</util:list>

<!--外部注入-->
<bean id="test1" class="com.junqing.bean.Test01">
  <property name="list" ref="BookList"></property>
</bean>

4.7 自动装配

在一个 Bean 中注入另一个 Bean, 不再需要在 XML配置文件中写property标签,而是让 spring根据属性名称或者类型,自动进行属性注入。在 bean标签中设置属性autowire,即可开启自动装配。

autowire的两个常用值:

  • byName:根据属性名注入(被注入bean的id值要和类内部set方法的参数名一致)
  • byType:根据属性类型注入(没有找到对应的对象会为空,找到多个会报错)
<!--要把Dept注入emp中,Dept的id要和emp中的set方法的参数名一样-->
<bean id="dept" class="com.junqing.autowire.Dept"></bean>
<bean id="emp" class="com.junqing.autowire.Emp" autowire="byType"></bean>
public class Test {
    public static void main(String[] args) {
        ApplicationContext context=new ClassPathXmlApplicationContext("/com/junqing/autowire/Auto.xml");
        Emp emp = context.getBean("emp", Emp.class);
        System.out.println(emp);
    }
}

5.IOC 基于 注解

5.1 标注型注解

在开启 Bean 扫描后。会自动将包含该类型注解的类加载进 IOC 容器中

  • Controller:一般用在web层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。
  • Service:一般用在Service层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。
  • Repository:一般用在Dao层,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。
  • Component:通用的注解,注解有 value 属性,如果不赋值默认是类名首字母小写,可以手动修改,会以这个值作为 Bean 的名字添加到容器中。

5.2 属性注入注解

5.1.1 Autowired

根据属性类型进行自动注入

  1. 作用位置:构造器set 方法有参构造的方法形参属性注解
  2. 使用案例
@Service
public class UserService {
    @Autowired
    private UserRepository userRepository; // 根据类型注入
}
5.1.2 Qualifier

根据属性名称进行注入。这个注解需要和上面的@AutoWired一起使用

@Autowired
@Qualifier(value = "user")
5.1.3 Resource

这个注解不是 Spring 自带的,而是 JDK 中提供的(JDK8 中自带,但是当 JDK 高于 11 或低于 8 需要引入依赖)。

  1. 作用位置:属性set方法
  2. 使用案例
// 做法1:明确指定 name,则只根据属性名匹配容器中的bean
@Service
public class UserService {
    @Resource(name = "mysqlUserDao")
    private UserDao userDao;
}

// 做法2:不指定name,则先根据属性名匹配,找不到再根据类型匹配
@Service
public class UserService {
    @Resource
    private UserDao userDao;
}
  1. 使用规则
    1. 可以根据属性名称装配(默认),也可以根据类型装配。
    2. 当使用注解的 name 属性时,只按 name匹配容器中的 bean,匹配不到直接报错
    3. 当没有使用 name 属性时,会先根据属性名匹配容器中的 bean,如果匹配不到会自动根据类型匹配容器中的 bean。
  2. 相关依赖
<dependency>
    <groupId>jakarta.annotation</groupId>
    <artifactId>jakarta.annotation-api</artifactId>
    <version>3.0.0</version>
</dependency>

6 单例/多例切换

在Spring中,可以设置创建bean实例时,是单实例还是多实例。在Spring 中,默认情况下,bean是单实例。

  1. 单实例:Bean 对象在 IOC 容器初始化的时候创建
  2. 多实例:每次获取 Bean 的时候都会创建一个新的

6.1 XML 方式切换

使用 Bean 的scope属性实现切换:

  1. singleton:表示单实例对象(默认)
  2. prototype:表示多实例对象
//多实例对象
<bean id="test1" class="com.junqing.bean.Test01" scope="prototype">
</bean>

6.2 注解方式切换

使用 Scope注解切换

@Component
@Scope("prototype") // 或 "singleton"
public class UserService { ... }

7.Bean 的生命周期

7.1 基本流程

  1. 通过反射调用构造器创建 Bean 对象
  2. 通过 set 方法设置相关属性
  3. 调用 Bean 的后置处理器的前置方法postProcessBeforeInitialization()(这个方法对容器中的所有 bean 都会生效,不是只针对于一个 bean)
  4. Bean 对象初始化(会调用指定初始化方法:给bean标签一个init-method属性,值是自己写的方法)
  5. 调用 Bean 后置处理器的后置方法 postProcessAfterInitialization()(这个方法对容器中的所有 bean 都会生效,不是只针对于一个 bean)
  6. Bean 对象创建完成,供我们使用
  7. Bean 对象销毁(会调用指定销毁方法:给bean标签一个destroy-method属性,值是自己写的方法)

7.2 代码示例

  1. 创建实体类(编写属性、构造器、get 方法、set 方法、初始化方法、销毁方法)
public class Dept {
    private String dname;//部门名称

    public Dept() {
        System.out.println("1.无参构造器被调用了");
    }

    public void setDname(String dname) {
        this.dname = dname;
        System.out.println("2.set方法被调用了");
    }
    
    public void initMethod(){
        System.out.println("3.调用了init-method");
    }
    public void destoryMethod(){
        System.out.println("4.调用了destoryMethod方法");
    }
    
    @Override
    public String toString() {
        return "Dept{" +
                "dname='" + dname + '\'' +
                '}';
    }
}

  1. 创建 Bean 后置处理器,实现BeanPostProcessor 接口,重写postProcessBeforeInitializationpostProcessAfterInitialization方法
public class BeanPostProcess implements BeanPostProcessor {
    @Override
    public @Nullable Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean初始化时后置处理器的前置方法被调用");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }

    @Override
    public @Nullable Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean初始化时后置处理器的后置方法被调用");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}
  1. 在 xml 文件中配置 Bean、后置处理器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="dept" class="dto.Dept" init-method="initMethod" destroy-method="destoryMethod">
        <property name="dname" value="再保部"></property>
    </bean>

    <bean id="beanPostProcess" class="BeanPostProcess"></bean>
</beans>
  1. 调用方法
public class IOCTest {

    public static void main(String[] args) {
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        System.out.println("bean对象可以被使用了");
        Dept dept = (Dept) context.getBean("dept");
        System.out.println("调用容器的摧毁方法");
        context.close();
    }
}

三、核心概念 AOP

1.基本介绍

  1. AOP:面向切面编程
  2. AOP的作用:利用AOP可以对业务逻辑的各个部分进行隔离,从而降低耦合度,提高程序的可重用性
  3. 通俗描述:不修改原来的方法,给程序增加新的功能

2.底层原理

AOP 底层通过动态代理实现:即不直接调用目标方法,而是调用代理类,代理类在内部调用目标方法,然后返回。可以理解为明星的经济人。

2.1 JDK 动态代理

对于有接口的情况,使用 JDK 的动态代理实现 AOP

2.2 CGLIB 动态代理

对于没有接口的情况,使用 CGLIB 动态代理

3.相关概念

  • 连接点:类里面可以被增强的方法(不一定去增强)
  • 切入点:实际被增强的方法
  • 通知(增强):实际增强的逻辑部分(实现增强功能的方法)
    • 前置通知:在被增强的方法之前执行
    • 后置通知:在被增强的方法之后执行(仅当方法正常返回,未抛出异常)
    • 环绕通知:在被增强的方法前后都执行
    • 异常通知:捕获到目标方法的异常后才会执行
    • 最终通知:在目标方法执行结束后运行(无论是否抛出异常,类似于 finally 块)
  • 切面:把通知应用到切入点的过程(可以简单理解为封装通知方法的类)

4.基于注解的 AOP

4.1 技术说明

AspectJAspectJ不是Spring组成部分,是一个独立的AOP框架,里面提供了特殊的注解,Spring 觉得好用就引用了他的注解,完成AOP操作

4.2 基本案例

通过 AOP,给登录功能加一个日志输出功能

  1. 引入依赖:Spring 的容器、 Spring 的 AOP 依赖、AspectJ 的依赖
<!-- spring容器 -->
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.1</version>
  </dependency>
</dependencies>
<!-- spring-AOP -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
  <version>7.0.2</version>
</dependency>
<!-- spring-AspectJ -->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>7.0.2</version>
</dependency>
  1. 编辑 Spring 的配置文件(开启组件扫描,开启 AspectJ 功能)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--组件扫描-->
    <context:component-scan base-package="*"></context:component-scan>
  
    <!--开启AspectJ的自动代理功能(这样才能识别AspecJ注解,生成代理对象)-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
  1. 写一个接口
public interface LoginService {
    void login(String username, String password);
}
  1. 写一个接口的实现类,实现基本功能
@Service
public class LoginServiceImpl implements LoginService {
    @Override
    public void login(String username, String password) {
        if (username.equals("admin") && password.equals("admin")) {
            System.out.println("登录成功");
        }
    }
}
  1. 创建切面类(实现增强功能的类),确认切入点和通知类型,编写增强方法
@Aspect     //表示这是一个切面类
@Component //将切面类加入容器
public class LoginAOP {
    @Around(value = "execution( * com.altman.service.LoginService.login(..) )") //环绕通知,里面是切入点表达式
    public void AfterMethod(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("登录方法开始执行:"+System.currentTimeMillis());
        joinPoint.proceed(); //环绕通知中执行目标方法的方法
        System.out.println("登录方法执行完毕:"+System.currentTimeMillis());

    }
}

6.启动容器测试

public class IOCTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        LoginService loginService = (LoginService) context.getBean("loginServiceImpl");
        loginService.login("admin", "admin");
    }
}

4.3 切入点表达式

4.3.1 语法结构

基本结构:execution(权限修饰符 返回值 目标类全路径.目标方法(方法参数类型))

写法案例:execution(public int com.altguigu.aop.Cal.add(int,int))

简化写法:execution(* com.altguigu.aop.Cal.add(..))

写法解析:

  1. execution:必须写的固定值
  2. 权限修饰符返回值:可以写具体值(*表示任意)。如果权限修饰符和返回值都默认可以合并为一个*
  3. 全类名:方法所在类的全路径,可以用*表示任意。也可以用*开始,表示任意开始,指定结尾;也可以用*结束,表示指定开始,任意结束。
  4. 方法名:方法的名称,可以用*表示任意。也可以用*开始,表示任意开始,指定结尾;也可以用*结束,表示指定开始,任意结束。
  5. 参数列表:参数类型,可以用 ..表示任意
4.3.2 使用案例
  1. 基本写法
@Before(value = "execution(public int com.altguigu.aop.Cal.add(int,int))")
  1. 简化写法
execution(* com.altguigu.aop.Cal.add(..))
  1. 类名任意写法
execution(* com.altguigu.aop.*.add(..))
  1. 方法名前面匹配后面任意写法
execution(* com.altguigu.aop.*.add*(..))
  1. 方法名前面任意后面匹配写法
execution(* com.altguigu.aop.*.*add(..))

4.4 5种通知

环绕通知需要显示的在通知中使用proceedingJoinPoint.proceed();调用目标方法,其余方法不需要显示调用

  1. 前置通知:@Before(value = "切入点表达式")
@Before(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )") 
public void AfterMethod() throws Throwable {
    System.out.println("登录方法开始执行:"+System.currentTimeMillis());
}
  1. 后置通知:@AfterReturning(value = "切入点表达式")。后置通知可以获取到目标方法的返回值。returning属性是后置通知注解的特有属性,用来给目标方法的返回值起别名,别名是随意的,但是需要和通知的形参列表上的返回值参数名一致(如果不用也可以不写这个参数)
@AfterReturning(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )" ,
                returning = "result") //returning属性如果不需要可以省略
public void AfterReturningMethod(boolean result) {//方法返回值的参数名需要和returning属性一致,不需要的话可以省略
    if (result) {
        System.out.println("登录成功:"+System.currentTimeMillis());
    }else {
        System.out.println("登录失败:"+System.currentTimeMillis());
    }
}
  1. 环绕通知:@Around(value = "切入点表达式"),必须要写ProceedingJoinPoint 形参,必须要在通知里面显示通过proceedingJoinPoint.proceed()方法调用目标方法,不然目标方法不会执行
@Around(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )")
public void AfterMethod(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    System.out.println("登录方法开始执行:"+System.currentTimeMillis());
    proceedingJoinPoint.proceed();//执行目标方法
    System.out.println("登录方法执行完毕:"+System.currentTimeMillis());
}
  1. 异常通知:@AfterThrowing(value = "切入点表达式")捕获到目标方法的异常才会执行。异常通知可以获取到目标方法异常信息。throwing属性是后置通知注解的特有属性,用来给目标抛出的异常起别名,别名是随意的,但是需要和通知的形参列表上的返回值参数名一致(如果不用也可以不写这个参数)
@AfterThrowing(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )",
               throwing = "ex") //给异常起个名,不用的话可以不用写
public void AfterThrowingMethod(Throwable ex) throws Exception {
    System.out.println(ex.getMessage());
    System.out.println("报错啦");
}
  1. 最终通知:@After(value = "切入点表达式")
@After(value = "execution( * com.altman.service.LoginServiceImpl.login(..) )") 
public void AfterMethod() throws Exception {
    System.out.println("报错我也执行");
}

5. 基于 XML 的 AOP

了解一下即可,有需要的时候再学习

6.通知的两个特殊参数

在通知上,我们经常会看见两个参数:JoinPointProceedingJoinPoint。这两个参数可以用来获取目标方法的各种信息。

  1. JoinPointProceedingJoinPoint的父类。
  2. ProceedingJoinPoint只能用在环绕通知中,它可以用与执行目标方法,绝对不能用在其他通知类型上面。
  3. JoinPoint可以用于所用的通知类型,但是不能执行目标方法

7.重用切入点表达式

如果好几个通知上使用的都是同一个切入点表达式,以上面的方法需要写好几遍相同的切入点表达式,很麻烦。重用切入点表达式允许我们将切入点表达式写在一个空方法上,然后其他通知的切入点表达式引用方法就可以了。

  1. 定义一个空方法,给空方法加上@Pointcut注解,里面写上公共的切入点表达式
  2. 在通知方法的切入点表达式上直接写方法的全路径即可(同一个类里直接写方法名也可以)
@Aspect     //表示这是一个切面类
@Component //将切面类加入容器
public class LoginAOP {

    //1.定义重用切入点表达式
    @Pointcut(value = "execution(* com.altman.service.LoginServiceImpl.login(..))")
    public void LoginPointCut() {

    }

    //直接引用上面申明的方法即可(同一个类里面直接写方法名也行,不同类里面需要写全路径)
    @AfterReturning(value = "com.altman.aop.LoginAOP.LoginPointCut()" ,returning = "result")
    public void AfterReturningMethod(boolean result) {
        if (result) {
            System.out.println("登录成功:"+System.currentTimeMillis());
        }else {
            System.out.println("登录失败:"+System.currentTimeMillis());
        }
    }
    
    //直接引用上面申明的方法即可(同一个类里面直接写方法名也行,不同类里面需要写全路径)
    @After(value = "LoginPointCut()")
    public void AfterMethod() {
        System.out.println("登录完毕!!!");
    }
}

四、jdbcTemplate

1.基本概念

JdbcTemplate 是 Spring框架对jdbc进行封装,使用JdbcTemplate方便实现对数据库的操作。在实际开发用的很少,目前仅作了解

2.基本使用

  1. 引入依赖
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.3</version>
  </dependency>

  <!-- Source: https://mvnrepository.com/artifact/com.mysql/mysql-connector-j -->
  <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <version>8.3.0</version>
    <scope>compile</scope>
  </dependency>

  <!-- Source: https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>7.0.3</version>
    <scope>compile</scope>
  </dependency>

  <!-- Source: https://mvnrepository.com/artifact/com.alibaba/druid -->
  <dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid</artifactId>
    <version>1.2.27</version>
    <scope>compile</scope>
  </dependency>
</dependencies>
  1. 创建数据库配置文件jdbc.propoerties
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://192.168.1.1:3306/springstudy?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true
jdbc.username=root
jdbc.password=123456
  1. 将数据源配置在 spring 配置文件中
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="fun.altman"></context:component-scan>

    <!--引入外部jdbc配置文件-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--创建数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--创建jdbctemplate对象-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!--将数据源注入jdbctemplate-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>
  1. 创建数据库,创建表
create database springstudy;
use springstudy;

create table student(
  name varchar(50),
  age int,
  gender char(1)
);
  1. 使用单元测试工具测试
@SpringJUnitConfig(locations = "classpath:application.xml")
public class Test1 {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Test
    public void test(){
        String sql = "insert into student(name,age,gender) values(?,?,?)";
        jdbcTemplate.update(sql,"jack",18,1);
    }
}

3.CRUD 操作

3.1 添加操作

调用 upate()方法即可

public class UserDaoImpl implements UserDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    //添加数据的功能
    public int add(User user){
        String sql="insert into user value(?,?,?)";
        return jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getSsex());
    }
}

3.2 删除操作

调用 upate()方法即可

public int delete(String username) {
    String sql="delete from `user` where `username`=?";
    return jdbcTemplate.update(sql,username);
}

3.3 修改操作

调用 upate()方法即可

public int update(User user,String username) {
    String sql="update `user` set `username`=?,`password`=?,`ssex`=? where username=?";

    return jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getSsex(),username);
}

3.4 查询操作

  1. 查询单值:调用 queryForObject() 方法。需要两个参数 sql语句,数据返回类型的Class对象
public int queryToone() {
    String sql="select count(*) from `user`";
    return  jdbcTemplate.queryForObject(sql,Integer.class);
}
  1. 查询单列对象:调用 queryForObject() 方法。需要三个参数 sql语句 ,RowMapper(是一个接口,可以对返回的不同类型数据进行封装),sql语句参数的值
public User queryToObject(String username) {
    String sql="select * from `user` where username=?";
    //泛型是返回的类型,()里面是返回类型的class对象
    
    //一定要给有参和无参构造器
    return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<User>(User.class), username);
}
  1. 查询集合:调用query()方法。需要三个参数 sql语句 ,RowMapper(是一个接口,可以对返回的不同类型数据进行封装),sql语句参数的值
public List<User> queryAll() {
    String sql="select * from `user`";
    List<User> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<User>(User.class));
    return query;
}

3.5 批量添加

调用batchUpdate方法有两个参数:

  • 第一个参数:sql语句
  • 第二个参数:List集合(集合里面存放的是一个Object类型的数组,用来存放操作数据)
//需要的参数是一个list集合
//集合里面方的是一个数组
//如果放入对象,他不知道取出来的顺序
@Override
public void batchAll(List <Object []> list) {
    String sql="insert into `user` values(?,?,?)";
    int[] ints = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(ints);
}

3.6 批量删除、修改

调用 batchUpdate 方法

//批量删除
public void batchDelete(List<Object[]> list) {
    String sql="delete from `user` where username=?";
    int[] ints = jdbcTemplate.batchUpdate(sql, list);
    System.out.println(ints.toString());
}

五、核心概念声明式事务

代码中事务的分类

  • 编程式事务管理(用代码的方式实现)
  • 声明式事务管理(用注解的方式实现)

1.基于注解的声明式事务

1.1 基本使用

  1. 在 spring 的配置文件中引入名称空间 tx
xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx.xsd">
  1. 创建事务管理器,并给事务管理器指定需要管理的数据源
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

  1. 在 spring 的配置文件中开启事务注解
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
  1. 给需要事物的方法加上 @Transactional注解
@Service
public class StudentServiceImpl implements StudentService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Transactional
    @Override
    public void addStudent(Student student) {
        String studentSQL= "insert into student (name, age, gender, class_id) values (?,?,?,?)";
        String classesSQL= "update classes set student_num = student_num+1 where class_id = ?";

        jdbcTemplate.update(studentSQL,student.getName(),student.getAge(),student.getGender(),student.getClassId());
        int i = 1/0;
        jdbcTemplate.update(classesSQL,student.getClassId());
    }
}

1.2 @Transactional 注解

1.2.1 使用位置
  • 类:事务对类中的所有方法生效
  • 具体方法:事务只对具体方法生效
1.2.2 相关属性
  • readOnly:设置是否只读操作。改为 true 之后只能进行查询操作不能进行添加删除修改
  • timeout:设置超时时常,单位秒。超时之后抛出异常回滚(默认-1 表示永不超时)
  • rollbackFor/norollbackFor:设置回滚策略:设置哪些异常回滚/不回滚。如果不写这个属性,默认只有出现RuntimeException才会回滚
@Transactional(noRollbackFor = ArithmeticException.class)
  • isolation:设置隔离级别
// 使用数据库默认的隔离级别
@Transactional(isolation = Isolation.DEFAULT) 

// 读未提交(最低隔离级别,可能脏读)
@Transactional(isolation = Isolation.READ_UNCOMMITTED) 

// 读已提交(避免脏读,但可能不可重复读)
@Transactional(isolation = Isolation.READ_COMMITTED)    

// 可重复读(MySQL 默认级别,避免脏读和不可重复读)
@Transactional(isolation = Isolation.REPEATABLE_READ)   

// 串行化(最高隔离级别,完全避免并发问题,性能最低)
@Transactional(isolation = Isolation.SERIALIZABLE)        
  • propagation:设置事务传播行为,默认合并事务。常用的就两个Propagation.REQUIRED/Propagation.REQUIRES_NEW
 // 如果当前存在事务,则加入该事务;否则新建一个事务(默认值)
@Transactional(propagation = Propagation.REQUIRED) 

// 创建一个新事务,如果当前存在事务,则挂起当前事务
@Transactional(propagation = Propagation.REQUIRES_NEW)      

// 如果当前存在事务,则加入该事务;否则以非事务方式执行
@Transactional(propagation = Propagation.SUPPORTS)   

// 如果当前存在事务,则加入该事务;否则抛出异常
@Transactional(propagation = Propagation.MANDATORY)   

// 以非事务方式执行,如果当前存在事务,则挂起当前事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)     

// 以非事务方式执行,如果当前存在事务,则抛出异常
@Transactional(propagation = Propagation.NEVER)             

// 如果当前存在事务,则在嵌套事务内执行;否则新建一个事务(依赖数据库支持,如 MySQL 的 savepoint)
@Transactional(propagation = Propagation.NESTED)            

1.3 完全注解实现声明式注解

@Configuration//用来说明替代配置文件
@ComponentScan(basePackages = "com.junqing")//用来开启组件扫描(参数是被扫描的类)
@EnableTransactionManagement//开启事务
public class ConfigSpring {
    //创建数据库连接池
    @Bean
    public DruidDataSource getDruidSource(){
        DruidDataSource dataSource=new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.jdbc.Driver");//数据库驱动
        dataSource.setUrl("jdbc:mysql://localhost:3306/test");//数据库地址
        dataSource.setUsername("root");//用户名
        dataSource.setPassword("yfj6688642");//密码
        return dataSource;
    }
    //创建jdbcTemplate对象
    @Bean
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //注入dateSource
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }
    //创建事务管理器对象
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

2.基于 XML 的声明式事务

基于 XML 的声明式注解就是通过使用 AOP 实现事务管理

  1. 配置事务管理器
  2. 配置通知
  3. 配置切入点和切面
<!--开启组件扫描-->
<context:component-scan base-package="com.junqing"></context:component-scan>

<!--创建datesorce对象-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
    <property name="username" value="root"></property>
    <property name="password" value="123456"></property>
</bean>

<!--创建jdbcTemplate对象-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <!--注入数据源-->
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--配置通知-->
<tx:advice id="tongzhi">
    <!--配置事务参数-->
    <tx:attributes>
        <!--指定在些方法上添加事务(*代表所有)-->
        <tx:method name="change" isolation="READ_COMMITTED" propagation="NOT_SUPPORTED"/>
        <tx:method name="change*"/><!--表示所有以change开头的方法都添加事务-->
    </tx:attributes>
</tx:advice>

<!--配置切入点和切面-->
<aop:config>
    <!--配置切入点-->
    <aop:pointcut id="pt" expression="execution(* com.junqing.service.*(..))"/>
    <!--配置切面:把通知应用到切入点的过程-->
    <aop:advisor advice-ref="tongzhi" pointcut-ref="pt"></aop:advisor>
</aop:config>

六、整合 Junit5 单元测试

  1. 引入依赖
<dependencies>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>7.0.3</version>
  </dependency>

  <!-- junit依赖-->
  <dependency>
    <groupId>org.junit.jupiter</groupId>
    <artifactId>junit-jupiter-api</artifactId>
    <version>6.0.2</version>
  </dependency>

  <!--spring-test依赖-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>7.0.3</version>
  </dependency>
</dependencies>
  1. 搭建项目结构
  2. 创建测试类,在测试类上加上@SpringJUnitConfig(locations = "classpath:配置文件路径")
  3. 注入对象,进行测试
@SpringJUnitConfig(locations = "classpath:application.xml")
public class Test1 {
    @Autowired
    UserController userController;

    @Test
    public void test(){
        userController.login("admin","123456");
    }
}

七、资源操作 Resources

八、国际化 i18n

九、数据校验 Validation

十、提前编译 AOT