首页 > 其他分享 >Spring 学习笔记

Spring 学习笔记

时间:2023-01-14 17:23:52浏览次数:37  
标签:Spring 笔记 学习 bean 通知 spring println ioc public

Spring 笔记

1. Spring 基本概念

这里介绍一下,spring的基本概念,简单的说一下ioc是什么,ioc容器是什么,ioc怎么实现的。

spring默认是单例的

1.1 IOC

ioc是一种思想叫控制反转,以前需要自己创建new对象,然后将对象赋值给某个对象。现在控制反转,可以不需要new对象,不关心对象是如何创建的,只需要使用即可。

1.2 IOC 容器

ioc创建的对象会全部放在,IOC容器中,要使用只需要去IOC容器中去取。

1.3 IOC容器在spring中的实现

这个可以看了源码补充,目前现在看到视频太简单了,只是说了用了什么类

2. 安装与HelloWorld

2.1 使用maven安装

只需要在maven中引入依赖即可

<dependencies>

    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

</dependencies>

2.2 创建spring配置文件和测试需要的类

第一步:先创建测试的类

package com.lin.spring.helloworld;

public class HelloWorld {

    public void test1(){
        System.out.println("你好,Spring");
    }

}

第二步:创建spring配置文件

在resources创建spring配置文件,名字可以随便起,推荐叫applicationContext.xml

<?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标签:将一个bean对象,将对象给IOS容器管理
            id:bean的唯一标识(名称),不能重复
            class:需要创建对象的类全类名
     -->
    <bean id="helloworld" class="com.lin.spring.helloworld.HelloWorld"></bean>

</beans>

2.3 创建测试类

package com.lin.spring.helloworld;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class HelloWorldTest {
    
    @Test
    public void test(){
        //ioc 容器
        ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        //根据name获取
        HelloWorld baneName = (HelloWorld) ioc.getBean("helloworld");
        System.out.println(baneName);
    }

}

3. 使用XML管理Bean

3.1 获取bean的三种方式

  1. 第一种:根据bean标签的id获取
  2. 第二种:根据bean的类型获取
    1. 注意:根据类型获取bean,IOC容器中必须只有一个类型匹配bean,否则会报错
  3. 第三种:根据bean标签的id获取和类型获取(常用,这里也不能叫常用,在后面都是通过注解自动装置)

实例:

@Test
public void testIocGetBean(){
    //获取ioc容器
    ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");
    //根据bean标签的id获取
    Student studentOne = (Student) ioc.getBean("studentOne");
    //根据类型获取,ioc容器中的类不能重复
    Student studentTow = ioc.getBean(Student.class);
    //根据bean标签的id获取和类型获取
    Student student = ioc.getBean("studentOne",Student.class);
}

问题1:获取类型是否可以根据接口获取?

答:可以,但是类在spring ioc容器中必须是唯一的,并且类实现了该接口。

问题2:如果接口被多个类实现,并且都交给spring ioc管理了,可以根据类型获取吗?

答:不可以,多个spring也不知道使用那个会抛出异常

3.2 依赖注入之set注入

property标签:通过bean对象的属性注入(必须要创建set方法)

name:设置属性名称
value:设置属性赋值

<bean id="studentTow" class="com.lin.spring.ioc.xml.pojo.Student">
    <property name="sid" value="1001"></property>
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
    <property name="gender" value="男"></property>
</bean>

3.3 依赖注入之构造器注入

  • constructor-arg标签:根据构造器注入
    • name属性:设置构造器参数名称
    • value属性:设置值

注意:如果constructor-arg标签不写name属性,会按照构造器顺序填充

<bean id="studentThree" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- 不设置name属性,会根据构造器顺序填充属性 -->
    <!--<constructor-arg value="1002"></constructor-arg>
        <constructor-arg value="李四"></constructor-arg>
        <constructor-arg value="女"></constructor-arg>
        <constructor-arg value="24"></constructor-arg>-->

    <!-- 设置name,name属性不会管顺序,会按需名称添加(name的值就是构造器参数名称) -->
    <constructor-arg name="sid" value="1002"></constructor-arg>
    <constructor-arg name="name" value="李四"></constructor-arg>
    <constructor-arg name="gender" value="女"></constructor-arg>
    <constructor-arg name="age" value="24"></constructor-arg>
</bean>

3.4 特殊字符处理

记录一下null还有像<>等特殊字符字符如何处理

3.4.1 null

正确写法:

<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- 给一个属性赋值为null -->
    <property name="gender">
        <null></null>
    </property>
</bean>

错误写法:

<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- 如果这样写,会将null作为字符串赋值给gender变量 -->
    <property name="gender" value="null">></property>
</bean>

3.4.2 特殊字符

因为xml是标签标记语言,像<>等特殊字符,有两种方式,可以解决

方式一:使用转移字符(这里可以百度XML中常用转义符,查找)

<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- &lt;相当于< &gt;相当于> -->
	<property name="name" value="&lt;王五&gt;"></property>
</bean>

方式二:使用xml语法定义的纯文本标签:

不管写什么都会直接赋值,不过不能写在属性里面,必须写在标签里面

<bean id="studentFour" class="com.lin.spring.ioc.xml.pojo.Student">
    <property name="name">
        <!-- <![CDATA[<>]]> 不好记,也不好写,可以使用idea快捷键,输入CD回车即可-->
        <value><![CDATA[<王五>]]></value>
    </property>
</bean>

3.5 依赖注入之Bean注入

bean注入可以定义使用IOC容器中的bean,也可以直接使用匿名类的方式

value标签或者属性都是用来填写普通类型的,比如string、int、long之类的

ref标签或者或者属性都是用来应用ioc容器中的bean,也就是对象

3.5.1 使用IOC容器的bean

只要在其他地方声明过的bean存在ioc容器中,都可以直接拿来使用,ref属性专门引用Ioc容器中的Bean

<bean id="clazz" class="com.lin.spring.ioc.xml.pojo.Clazz">
    <property name="cId" value="1"></property>
    <property name="cname" value="大班"></property>
</bean>

<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- ref属性:引用被spring ioc管理的对象,值对应id -->
	<property name="clazz" ref="clazz"></property>
</bean>

3.5.2 使用级联方式

注意:级联方式必须要保证属性必须提前被初始化,new一个空对象,或者有一个对象,不能是null,否则会报错

<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
	<property name="clazz.cId" value="2"></property>
	<property name="clazz.cname" value="小班"></property>
</bean>

3.5.3 使用内部匿名bean的方式

使用内部匿名bean进行依赖注入,内部匿名bean只能在内部使用,不能通过ioc容器获取

<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
	<property name="clazz">
        <bean id="clazz" class="com.lin.spring.ioc.xml.pojo.Clazz">
        	<property name="cId" value="3"></property>
        	<property name="cname" value="中班"></property>
        </bean>
    </property>
</bean>

3.5.4 数组注入

public class Student{
    
    private String[] hobby;
    
    //隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- 数组注入 -->
    <property name="hobby">
        <array>
            <!-- 使用ref 标签可以引入对象 -->
            <value>唱</value>
            <value>跳</value>
            <value>Rap</value>
            <value>篮球</value>
        </array>
    </property>
</bean>

3.5.5 list注入

public class Student {
    
    private List<String> hobbyList;
    
    //隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
    <!-- List -->
    <property name="hobbyList">
        <list>
            <!-- 使用ref 标签可以引入对象 -->
            <value>唱</value>
            <value>跳</value>
            <value>Rap</value>
            <value>篮球</value>
        </list>
    </property>
</bean>

3.5.6 map对象

public class Student implements Person{
    
    private Map<String,Teacher> teacherMap;
    
    //隐藏get/set方法
}
<bean id="studentFive" class="com.lin.spring.ioc.xml.pojo.Student">
    <property name="teacherMap">
        <map>
            <!-- value-ref属性改成value,就是赋值基本类型了 -->
            <entry key="1" value-ref="teacherOne"></entry>
            <entry key="2" value-ref="teacherTwo"></entry>
        </map>
    </property>
</bean>

3.6 bean的单例和多例

在spring中默认是单例的,如果想要配置多例只需要在bean标签中加上scope属性值写成prototype(多例)。多例的时候在调用的时候才会创建bean,而单例启动就会创建bean

scope属性值有两个值:

  • singleton:单利(默认就是这个)
  • prototype:多利

示例:

<bean id="student" class="com.lin.spring.ioc.xml.pojo.Student" scope="singleton">
    <property name="sid" value="1001"></property>
    <property name="name" value="张三"></property>
</bean>

3.7 bean的生命周期

生命周期步骤:实例化-> 依赖注入-> 初始化-> 销毁

  • 实例化:创建bean的时候执行

  • 依赖注入:给属性赋值的时候

  • 初始化:由bean标签的init-method属性调用

  • 销毁:由bean标签的idestroy-method属性调用

3.7.1 单例的情况

实例:打印一个bean的生命周期

实体类:

package com.lin.spring.ioc.xml.pojo;

public class User {
    private Integer id;
    private String username;
    private String password;
    private Integer age;

    public User() {
        System.out.println("生命周期1:实例化");
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        System.out.println("生命周期2:依赖注入");
        this.id = id;
    }

    public void initMethod(){
        System.out.println("生命周期3:初始化");
    }

    public void destroyMethod(){
        System.out.println("生命周期4:销毁");
    }
}

spring的xml配置文件:

<!--
	init-method属性:用来指定对象初始化的时候调用(里面填写方法名)
	destroy-method属性:用来指定对象销毁的时候调用(里面填写方法名)
-->
<bean id="user" class="com.lin.spring.ioc.xml.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
    <property name="id" value="1"></property>
    <property name="username" value="admin"></property>
    <property name="password" value="123456"></property>
    <property name="age" value="23"></property>
</bean>

测试方法:

@Test
public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("spring-lifecycle.xml");
    User user = ioc.getBean("user", User.class);
    System.out.println(user);

    /**
     * spring ioc容器关闭的时候,才会销毁bean,也就是ioc容器关闭的时候才会调用销毁代码
     * ClassPathXmlApplicationContext实现了ApplicationContext接口扩展了这个接口刷新和关闭的方法
     * ApplicationContext接口里面是没有销毁的方法的,ClassPathXmlApplicationContext类里面有销毁代码
     */
    ioc.close();
}

3.7.2 多例的情况

多例和单例没什么太大的区别,这里的就记录了,就是bean不会提前创建而是使用的时候才进行创建

4.使用注解管理Bean

注解和xml作用一样可以管理Bean,只是把xml的bean标签换成了注解

常用的注解,等同于xml的bean标签

  • @Component 将类标记为普通组件

  • @Controller 将类标记为控制层组件

  • @Service 将类标识为业务组件

  • @Repository 将类标识为持久层组件

这四个组件在spring ioc中,作用是一模一样的,存在的目的只是给开发人员看,提高代码的可读性

4.1 组件的扫描Bean

在spring ioc配置文件中,只需要使用context:component-scan标签配置包名,只要配置了包名,就是扫描这个包下面的所有注解

注:需要导入context命名空间,提示

4.1.1 配置基本扫描

<!-- 扫描注解:扫描是扫描这个包路径下全部类 -->
<context:component-scan base-package="com.lin.spring.ioc.annotation"></context:component-scan>
<!-- 多个可以用,隔开 -->
<context:component-scan base-package="com.lin.spring.ioc.annotation.controller,com.lin.spring.ioc.annotation.dao.impl"></context:component-scan>

4.1.2 配置不扫描谁

使用context:exclude-filter标签,可以将不想扫描的排除出去。它里面有个type属性,常用值的:annotation和assignable

  • type属性:类型

    • annotation:通过注解方式,expression(排除规则)属性填写需要排除的注解全类名

    • assignable:根据类的类型进行排除,expression(排除规则)属性填写需要排除类的全类名

  • expression属性:排除规则

示例一:根据注解排除

<context:component-scan base-package="com.lin.spring.ioc.annotation">
    <!-- 表示com.lin.spring.ioc.annotation包下面的所有类排除使用@Controller注解的 -->
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

示例二:根据类排除

<context:component-scan base-package="com.lin.spring.ioc.annotation">
    <!-- 表示com.lin.spring.ioc.annotation.controller.UserController类进行排除 -->
    <context:exclude-filter type="assignable" expression="com.lin.spring.ioc.annotation.controller.UserController"/>
</context:component-scan>

4.1.3 配置只扫描谁

使用context:include-filter标签,这个没有什么实际的用处,因为用普通扫描的包已经将需要扫描的类全部扫描进去了,没有必要在进行扫描。如果实在需要使用需要在父标签context:component-scan加上use-default-filters="false"属性

  • type属性:类型

    • annotation:通过注解方式,expression(排除规则)属性填写需要排除的注解全类名

    • assignable:根据类的类型进行排除,expression(排除规则)属性填写需要排除类的全类名

  • expression属性:排除规则

示例一:根据注解配置扫描

<context:component-scan base-package="com.lin.spring.ioc.annotation" use-default-filters="false">
    <!-- 表示com.lin.spring.ioc.annotation包下面只扫描使用@Controller注解的 -->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

示例二:根据类配置扫描

<context:component-scan base-package="com.lin.spring.ioc.annotation" use-default-filters="false">
    <!-- 表示只扫描com.lin.spring.ioc.annotation.controller.UserController类 -->
    <context:include-filter type="assignable" expression="com.lin.spring.ioc.annotation.controller.UserController"/>
</context:component-scan>

4.2 问:使用创建bean注解的id是什么?

默认是使用类名开头字母小写,比如UserController默认的bean id就是userController

4.1.1 自定义bean的id

只需要在注解的value参数中传入值,就是bean的id

例如:

@Controller("controller")
public class UserController {
    
}

4.2 使用自动装配

只需要在属性名加上@Autowired注解,就会自动将对象赋值过去。不需要提供set方法,当然也可以不加在属性上面,可以在set方法或者构造器上面。

默认通过type进行查找,如果type有多个的情况使用type+name的方式进行自动装配,如果都查找不到就抛出异

常:NoSuchBeanDefinitionException

4.2.1 加在属性上面

实例:

@Controller
public class UserController {

    @Autowired
    private UserService userService;

}

4.2.2 加在set方法上面

@Service
public class UserServiceImpl implements UserService {
    
    private UserDao userDao;
    
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

4.2.3 加在构造器上面

如果加在构造器上面,spring会优先使用带@Autowired注解的类进行处理

@Service
public class UserServiceImpl implements UserService {
    
    private UserDao userDao;
    
    @Autowired
    public UserServiceImpl(UserDao userDao) {
        this.userDao = userDao;
    }

}

5. AOP

5.1 AOP 概念

AOP挺重要的,而且概率不是特别好理解,所以这里单独开个板块,记录一下自己理解的AOP概念。

aop目的就是在不改变其他人的代码的情况,前面或者后面加上自己的代码,比如某个源码或者其他同事的代码是不能改变别人代码,这里可以使用AOP在方法执行前或者执行后加上我们希望执行的东西。再比如日志,不可能每个业务都进行日志记录,这样子工作量很大,如果使用AOP只需要配置切入点,就利用前置通知和后置通知,记录用户请求参数与返回结果

简单解释:AOP就是不改变代码的情况执行前或者执行后加上我们想要的代码。

在本人看的教程中把AOP说的过于复杂,并没有什么意义,过于抽象,现在本人还是不知道哪些相关术语代表着什么,以后知道了在补充,这里全部记录但是只补充本人理解的专业术语。

- 横切关注点
  • 通知
    • 通知比较重要:其实就是前置通知、后置通知、返回通知、异常通知、环绕通知。后面会详细补充
  • 切面
    • 封装乱七八糟的前置通知、后置通知、返回通知、异常通知、环绕通知的类
  • 目标
  • 代理
    • 被代理的目标类,不是自己实例化的类,是AOP用接口再次实例的那个类
  • 连接点
  • 切入点
    • spring aop就是根据切入点来定位,哪些类需要进行切面(也可以理解成哪些类需要通知)

5.2 AOP 入门

第一步:添加依赖

除了ioc所需要的依赖还需要单独引入spring-aspects

<dependencies>
    <!-- ioc基础依赖 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>

    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
	
    <!-- 在IOC所需依赖基础上再加入spring-aspects依赖 -->
    <!-- spring-aspects会帮我们传递过来aspectjweaver -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.1</version>
    </dependency>

</dependencies>

第二步:配置扫描注解

除了要配置扫描bean,还需要单独加入aop:aspectj-autoproxy标签

<!-- 扫描注解:扫描是扫描这个包路径下全部类 -->
<context:component-scan base-package="com.lin.spring.aop.annotation"></context:component-scan>

<!-- 开启基于注解的AOP -->
<aop:aspectj-autoproxy />

第三步:创建目标类

@Component
public class Calculator {
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部 result = " + result);
        return result;
    }

    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部 result = " + result);
        return result;
    }
}

第四步:创建切面类

前面类需要加上@Aspect注解

package com.lin.spring.aop.annotation;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

/**
 * 切面类
 * 在切面中,需要加入指定的注解,将其标记为通知方法
 */
@Component
@Aspect
public class LoggerAspect {

    /**
     * 这里用代理接口或者目标类方法都可以
     * 切入点:
     *  execution()固定写法
     *  public int 与代理接口的访问修饰符和返回值一致
     *  com.lin.spring.aop.annotation.Calculator 全类名,明确表示是那个
     *  .add(int,int) 方法名和参数,参数只需要传类型即可,在java中参数类型已经可以明确表示是那个方法了
     * .
     */
    @Before("execution(public int com.lin.spring.aop.annotation.Calculator.add(int,int))")
    public void beforeAdviceMethod(){
        System.out.println("LoggerAspect,前置通知");
    }
    
}

第四步:创建测试类

import com.lin.spring.aop.annotation.Calculator;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class AopTest {

    @Test
    public void testAPPByAnnotation(){
        ApplicationContext ioc = new ClassPathXmlApplicationContext("aop-annotation.xml");
        /**
         * 使用AOP不能直接获取目标类的对象,必须使用代理的接口的对象,IOC也不会存在目标类的接口
         * 所以这里获取bean要填写获取代理对象的接口,让它自动创建代理接口和实现类
         */
        Calculator calculator = ioc.getBean(Calculator.class);
        calculator.div(1,1);
    }

}

5.3 AOP 切入点基本配置

5.3.1 切入点表达式

用*可以代表包也可以代表类也可以代表包

/**
 * 使用表达式切入点
 *   execution()固定写法
 *   * 表示代理接口所有的访问修饰符和返回值
 *   com.lin.spring.aop.annotation.Calculator.* 表示类下面的所有方法
 *       - 类也可以用*代替,表示这个包下面的所有类:com.lin.spring.aop.annotation.*.*
 *       - 包也可以用*代替,表示这个包同级的下面的所有包:com.lin.spring.*.annotation.*.*
 *   (..) 表示所有参数,我不关心你是什么参数,都进行标记切入点
 */
@Before("execution(* com.lin.spring.aop.annotation.Calculator.*(..))")
public void beforeAdviceMethod(){
    System.out.println("LoggerAspect,前置通知");
}

5.3.2 公共的切入点

很多时候有个一个切面类的规则是一样的,这样子可以定义一个公共的切人点,其他切入点都引用公共的。

只需要使用@Pointcut注解声明公共的切入点,其他切入点都引用次方法名即可

/**
 * 声明公共的切入点表达式
 */
@Pointcut("execution(* com.lin.spring.aop.annotation.Calculator.*(..))")
public void pointcut(){}

/**
 * 使用公共的切入点表达式
 * @param joinPoint
 */
@Before("pointcut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
    //获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
    Signature signature = joinPoint.getSignature();

    //获取连接点所对应方法参数
    Object[] args = joinPoint.getArgs();
    System.out.println("LoggerAspect,前置通知 方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}

5.4 AOP通知-注解版本

aop核心就是通知,分别有前置通知,后置通知、返回通知、异常通知、环绕通知,它们分别有对应的注解,用于标识各种通知。

它们的执行顺序是:前置通知-->被切面的方法-->-->[异常通知]-->返回通知-->后置通知,如果异常通知被执行,返回通知不会被执行,其他都会执行

  • @Before:前置通知,在目标对象方法执行之前执行
  • @After:后置通知,在目标对象方法的finally中执行
  • @AfterReturning:返回通知,在目标对象方法返回值之后执行
  • @AfterThrowing:异常通知,目标对象方法执行发送异常后执行
  • @Around:环绕通知

5.4.1 前置通知:

@Before("pointcut()")
public void beforeAdviceMethod(JoinPoint joinPoint){
    //获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
    Signature signature = joinPoint.getSignature();
    //获取连接点所对应方法参数
    Object[] args = joinPoint.getArgs();
    System.out.println("LoggerAspect,前置通知 方法:"+signature.getName()+",参数:"+ Arrays.toString(args));
}

5.4.2 后置通知

@After("pointcut()")
public void afterAdviceMethod(JoinPoint joinPoint){
    //获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
    Signature signature = joinPoint.getSignature();
    System.out.println("LoggerAspect,后置通知 方法:"+signature.getName());
}

5.4.3 返回通知

只需要result属性的值和参数的名一致,AOP就会自动将返回值赋值给result属性值与参数名称一致的参数

@AfterReturning(value = "pointcut()",returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint,Object result){
    //获取连接点所对应的签名信息,通过签名信息.getName可以获取方法名
    Signature signature = joinPoint.getSignature();
    System.out.println("LoggerAspect,返回通知 方法:"+signature.getName()+",结果:"+ result);
}

5.4.4 异常通知

只需要throwing属性的值和参数的名一致,AOP就会自动将异常赋值给result属性值与参数名称一致的参数,接收类型必须是Throwable的子类

@AfterThrowing(value = "pointcut()",throwing = "ex")
public void afterThrowingAdviceMethod(JoinPoint joinPoint,Throwable ex){
    Signature signature = joinPoint.getSignature();
    System.out.println("LoggerAspect,异常通知 方法:"+signature.getName()+",异常:"+ ex);
}

5.4.5 环绕通知

环绕通知:相当于前置通知+后置通知+返回通知+异常通知,正常情况使用环绕通知也就不会在使用其他通知了。

@Around("pointcut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint){
    Object result = null;
    //表示目标对象方法的执行
    try {
        System.out.println("环绕通知-->前置通知");
        result = joinPoint.proceed();
        System.out.println("环绕通知-->返回通知");
    } catch (Throwable e) {
        e.printStackTrace();
        System.out.println("环绕通知-->异常通知");
    } finally {
        System.out.println("环绕通知-->后置通知");
    }
    return result;
}

5.5 AOP通知-XML版本

xml版本使用的概率很低,如果要使用可以结合注解的方式看,看一眼就明白了。

<aop:config>
    <!-- 设置一个公共的切入点表达式 -->
    <aop:pointcut id="pointCut" expression="execution(* com.lin.spring.aop.xml.Calculator.*(..))"/>

    <!-- 将IOC容器中的某个bean设置成切面 -->
    <aop:aspect ref="loggerAspect">
        <!-- 前置通知 -->
        <aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before>
        <!-- 后置通知 -->
        <aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after>
        <!-- 返回通知 -->
        <aop:after-returning method="afterReturningAdviceMethod" pointcut-ref="pointCut" returning="result"></aop:after-returning>
        <!-- 异常通知 -->
        <aop:after-throwing method="afterThrowingAdviceMethod" pointcut-ref="pointCut" throwing="ex"></aop:after-throwing>
        <!-- 环绕通知 -->
        <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"></aop:around>
    </aop:aspect>

    <!-- 引入第二个切面 -->
    <aop:aspect ref="validateAspect" order="1">
        <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
    </aop:aspect>
</aop:config>

6. 声明式事务

事务的作用,就是当某段代码发生了异常,操作的数据进行回滚

事务有两种,一种是编译式事务,一种是声明式事务,简单来说编译式事务就是要自己写代码处理,而声明式事务通过配置让框架来实现

6.1 开启事务

第一步:导入需要的maven

<dependencies>

    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- Spring 持久化层支持jar包 -->
    <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个 jar包 -->
    <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-orm</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- Spring 测试相关 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.3.1</version>
    </dependency>

    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.16</version>
    </dependency>

    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.31</version>
    </dependency>

</dependencies>

第二步:先在spring配置的xml里面引入开启事务注解驱动

<!--
	开启事务注解的驱动
		将使用@Transactional注解所标识的方法活类中所有的方法使用事务管理
		如果DataSourceTransactionManager的bean id就是叫transactionManager,可以直接简写为:<tx:annotation-driven />,属性不写,只写标签
    -->
<!-- 这里导入命名空间的时候idea会提示有多个必须要选择tx: xmlns:tx="http://www.springframework.org/schema/tx"-->
<tx:annotation-driven transaction-manager="transactionManager"/>

第三步:在类上面或者方法上面加上@Transactional注解,表示开启事务

@Transactional

6.2 事务属性

事务属性分为只读、超时、休眠、回滚策略,只要发送对应的操作就会进行事务回滚

6.2.1 只读

给上readOnly属性设置成true(默认是false),该事务中只能出现查询,不能出现增删改,否则会抛出异常

Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification

are not allowed

@Transactional(readOnly = true)

6.2.2 超时

超时,默认是-1,表示不超时,如果超时会进行事物回滚,整个事务方法必须在x秒内执行完,单位是秒

@Transactional(timeout = 3)

6.2.3 回滚策略

spring事务有四个回滚策略,常用就一个

  • noRollbackFor属性:无视某个异常,让事务正常提交,多个用逗号隔离(此属性用class对象)

  • noRollbackForClassName属性:无视某个异常,让事务正常提交,多个用逗号隔离(此属性用字符串全类名)

//class对象
@Transactional(noRollbackFor = {ArithmeticException.class})

//字符串全类名
@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")

6.3 事务隔离级别

事务有四个特性:一致性、原子性、隔离性、持久性。

事物的隔离级别,用于就是解决对应的问题

mysql 默认隔离级别是:可重复读脏读、幻读、不可重复读

  • 读未提交:
    • 举例:A事务的隔离级别是:读未提交,B事务的隔离级别是:可重复读
    • A事务可以读到,B事务未提交的数据,如果最终B回滚了,A读取到B的数据也就没任何意义 也叫脏读
  • 读已提交:
    • A事务的隔离级别是:读已提交,B事务的隔离级别是:可重复读
    • A事务只能读到,B事务提交的数据,未提交的数据读取不到
  • 可重复读(可重复读的隔离级别,在操作某行数据的时候,会给这行数据加锁)
    • A事务的隔离级别是:可重复读,B事务的隔离级别是:可重复读
    • 比如A事务在操作id为1的数据,这个时候B也想操作id未1的数据,这个时候b会堵塞,必须等A操作完并且提交了数据,B才能操作
    • 如果A没有提交只操作了id为1的数据,B可以操作其他表的其他数据,加锁只是针对某一行
  • 串行化(串行化会给整个表加锁)
    • A事务的隔离级别是:串行化,B事务的隔离级别是:可重复读
    • 串行化会给整个表加锁,如果A事务,没有进行提交,B事务是整个表都不能操作,会进行堵塞状态,必须等A提交完事务

各个隔离级别存在的问题:

名称 隔离级别 脏读 不可重复读 幻读
读未提交 READ UNCOMMITTED
读已提交 READ COMMITTED
可重复读 REPEATABLE READ
串行化 SERIALIZABLE

种数据库产品对事务隔离级别的支持:

名称 隔离级别 Oracle MySQL
读未提交 READ UNCOMMITTED 不支持 支持
读已提交 READ COMMITTED 支持(默认) 支持
可重复读 REPEATABLE READ 不支持 支持(默认)
串行化 SERIALIZABLE 支持 支持
@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化

6.4 事物的传播行为

事物的传播行为是指,比如我在方法外面有一个事物了,我在方法里面调用了某一个方法也有事物,在默认情况,spring会使用最外层的事物。也就是说默认情况下,只需要发生异常最外面那一层代码是事物都会进行回滚;

举例:使用伪代码的方式演示事物

import org.springframework.transaction.annotation.Transactional;

public class Test {

    @Transactional
    public void purchase(){
        System.out.println("购买");
    }

    @Transactional
    public void addIntegral(){
        System.out.println("增加积分");
        //故意制造异常
        System.out.println(1/0);
    }

    @Transactional
    public void checkout(){
        purchase();
        addIntegral();
        System.out.println("结账");
    }

    public static void main(String[] args) {
        new Test().checkout();
    }

}

这段代码可以看出,增加积分的时候,发生了异常,这个时候整个结账方法都会进行回滚。

@Transactional(propagation = Propagation.REQUIRED)//默认传播行为
@Transactional(propagation = Propagation.REQUIRES_NEW)//加在方法中,表示次方法每次调用都会创建一个新的事物,只要这个方法成功就提交

用上面的例子如果将purchase()方法上面的事物改成@Transactional(propagation = Propagation.REQUIRES_NEW),在运行这段代码,购买是成功的,但是到增加积分那就会抛出异常。

6.5 使用xml操作事物

基于xml操作事物需要导入aspectJ的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.1</version>
</dependency>
<aop:config>
    <!-- 配置事务通知和切入点表达式 -->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(* com.atguigu.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollback- for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>

7. Spring的测试类

Spring有提供专门用于测试使用的注解,这样子就不用每次都去引入xml,然后在调用bean,可以直接指定测试环境,然后在引入xml(xml里面开启注解扫描)就可以直接在测试类写注解了。

<?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="com.lin.spring.transaction"></context:component-scan>

    <!-- 引入properties文件 -->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"></property>
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="username" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

</beans>
package com.lin.spring.transaction.test;


import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

//设置测试类运行环境,这样就可以直接通过注入的方式直接获取ioc容器中的bean
@RunWith(SpringJUnit4ClassRunner.class)
//指定spring测试环境的配置文件  classpath:表示类路径下面
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testInsert(){
        String sql = "INSERT INTO t_user values(null,?,?,?,?,?)";
        jdbcTemplate.update(sql,"root","123",23,"女","[email protected]");
        System.out.println("插入语成功");
    }
}

标签:Spring,笔记,学习,bean,通知,spring,println,ioc,public
From: https://www.cnblogs.com/lin168/p/17052063.html

相关文章

  • spring boot——请求与参数校验——spring-mvc——通过 HttpServletRequest 获取请求
    我们可以在控制器方法中设置一个HttpServletRequest类型的形参, SpringMVC会自动将请求中携带的参数封装到HttpServletRequest形参中, 然后我们就可以通过HttpS......
  • MySql学习笔记--进阶05
          ......
  • SpringSecurity基础
    1.安全框架概述解决系统安全问题的框架。如果没有安全框架,我们需要手动处理每个资源的访问控制,非常麻烦。使用安全框架,我们可以通过配置的方式实现对资源的访问限制。2......
  • 复习第7点-7.SpringMVC 的响应方式
    1.使用ServletAPI实现转发/*使用HttpServletRequest对象实现请求转发*/@RequestMapping("/httpServletRequest")publicvoidmethod1(HttpServle......
  • 【800】机器学习特征重要性可视化
    参考:数据科学|避坑!Python特征重要性分析中存在的问题模型代码(复制前一个博客的内容):查看代码fromsklearn.model_selectionimporttrain_test_splitX_train,X_t......
  • C++学习开发路线
    C++等级:0级:掌握常见C++语法和语言构造,能够顺溜地写清楚各种语言构造(很多小白鼠死在这里)1级:掌握基本的编程范式:面向过程、面向对象、泛型编程、以及C++11/14支持的函数式......
  • 复习第6点-6.SpringMVC作用域传值
    作用域范围对象名称作用范围application整个作用范围session在当前会话中有效request在当前请求中有效page在当前页面有效request/session/app......
  • Android studio学习笔记2
    Androidstudio学习笔记220201303张奕博2023.1.14androidstudio动态调试apk1.配置环境androidstudio需要安装插件:1,Smalidea2,SmaliSupport2.打开APK包注......
  • 环论中中国剩余定理证明笔记
    这一阵在看Maki的《抽象代数I》视频课(https://www.bilibili.com/video/BV1xG411j7Hk),其中第20课讲到环论的中国剩余定理,即:若(R,+,·)是交换环,Ii ◁R,i=1,...,......
  • 读编程与类型系统笔记07_子类型
    1. 子类型1.1. 在期望类型T的实例的任何地方,都可以安全地使用类型S的实例,则类型S是类型T的子类型1.1.1. 里氏替换原则(Liskovsubstitutionprinciple)2. 名义子类型......