spring学习笔记

框架怎么学习?

  1. 知道框架能做什么:Mybatis --> 访问数据库,对表中的数据进行增删改查
  2. 框架的语法:框架要完成一个功能,需要一定的步骤
  3. 框架的内部实现:框架的内部怎么做的,原理是什么
  4. 通过学习,可以实现一个框架

代码:https://gitee.com/qkmango/IdeaProjects/tree/master/Web/09_Spring

IoC控制反转

IoC (Inversion of Control) : 控制反转,是一个理论,概念,思想。

IoC介绍

ioc描述的是

把对象的创建,赋值,管理工作都交给代码之外的容器来实现,也就是对象的创建是由其他的外部资源来完成的

  • 控制:创建对象,对象的属性赋值,对象之间的关系管理

  • 反转:把原来开发人员管理、创建对象的工作转移给代码之外的容器实现,由容器代替开发人员创建管理对象

  • 正转:由开发人员在代码中,使用 new 来创建对象,开发人员主动过管理对象new Student()

容器:可以是一个服务器软件(Tomcat),一个框架(Spring)

为什么使用ioc

目的就是减少对代码的改动,异能实现不同的功能,实现解耦合

Java中创建对象有哪些方式?

  1. 构造方法
  2. 反射
  3. 序列化
  4. 克隆
  5. 动态代理
  6. ioc:容器创建对象

ioc的体现

servlet

  1. 继承HttpServlet
  2. 在web.xml中注册servlet
  3. 没有手动创建过servlet对象,却可是使用

servlet对象是由Tomcat服务器帮你创建的

IoC的技术实现

DI是ioc的技术实现

DI(Dependency Injection):依赖注入,只需要在程序中提供需要使用的对象名称就可以了,至于对象如何在容器中创建、赋值,查找 都由容器内部实现

Spring 是使用的 DI 实现了 ioc 的功能,Spring 底层创建对象最根本是使用的反射机制

Spring第一个例子

  • 在创建spring容器时,会创建配置文件中的所有对象
  • beans.xml配置文件中,一个bean标签就是一个对象
  • spring创建对象默认调用的是无参构造方法
初步使用
  1. resources目录下创建beans.xml(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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <!--
     告诉Spring创建对象
     	声明bean,就是告诉spring要创建某个类的对象
    	id:对象的自定义名称唯一值。spring通过这个名称找到这个对象
    	class:类的全限定名称
    
    	spring就完成 SomeService someService = new SomeServiceImpl()
    	spring是把创建好的对象放入到map中,spring框架有一个map存放对象的
        	springMap.put(id值, 对象)
    	一个bean标签声明一个对象
    -->
        <bean id="someService" class="cn.qkmango.service.impl.SomeServiceImpl" />
    
    </beans>
    
    <!--
    Spring的配置文件
        beans:spring把Java对象称为bean
    -->
    
  2. 使用Spring容器创建对象并测试

    1. 指定spring容器配置文件创建对象
    2. 创建表示spring容器的对象,ApplicationContext
    3. 从容器中获对象
    4. 使用Spring创建好的对象
    @Test
    public void test02() {
    
        //1. 指定spring容器配置文件创建对象
        String config = "beans.xml";
    
        //2. 创建表示spring容器的对象,ApplicationContext
        // ApplicationContext就表示spring容器,通过这个容器,就能获取对象了
        // ClassPathXmlApplicationContext:表示从类路径中加载Spring配置文件
        ApplicationContext app = new ClassPathXmlApplicationContext(config);
    
        //3. 从容器中获对象
        // getBean("beans.xml配置文件中bean的id")
        SomeService someService = (SomeService) app.getBean("someService");
    
        //使用Spring创建好的对象
        someService.doSome();
    }
    
spring默认创建对象的时间

spring默认创建对象的时间:在创建spring容器时,会创建配置文件中的所有对象

如何理解在创建spring容器时,会创建配置文件中的所有对象?

beans.xml配置文件中,一个bean标签就是一个对象,即使bean标签中存在相同的class

获取Spring容器中Java对象的信息

ac.getBeanDefinitionCount()获取Spring容器中对象的数量

ac.getBeanDefinitionNames()获取Spring容器总对象的名称(id)

@Test
public void test03() {

    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");

    //使用spring提供的方法,获取容器中定义的对象数量
    int nums = ac.getBeanDefinitionCount();
    System.out.println("容器中定义的对象数量:"+nums);

    //获取容器中定义的对象名称
    String[] names = ac.getBeanDefinitionNames();
    for (String name :names) {
        System.out.println(name);
    }

    /**
     * SomeServiceImpl:执行了构造方法
     * SomeServiceImpl:执行了构造方法
     * 容器中定义的对象数量:2
     * someService
     * someService1
     */
}

基于XML的DI

DI依赖注入,表示创建对象,给属性赋值

DI的实现语法有两种

  • 在Spring的配置文件中,使用标签和属性完成,叫做基于XMl的DI实现
  • 使用spring中的注解完成属性的赋值,叫做基于注解的DI实现

DI的语法分类

  • set注入:spring调用类的set(),实现赋值
  • 构造注入:spring调用类的有参构造方法,在构造方法中完成赋值xml

⚠️注意:通过set注入,Spring只是负责调用相应的setter方法而已,至于这个方法是否进行了赋值,这个方法是否有对应的属性,Spring是不管的。即Spring只是调用相关的方法,无关方法内的逻辑。

简单类型:spring中规定,Java的基本数据类型和String都是简单类型
1. set注入(set赋值):spring调用类的set()
- 简单类型的set注入(属性是简单类型的)

XML-set注入

使用在XML配置文件中,使用set方式注入属性

set注入依赖于类中的相关set(),<property name="age" value="20"/>,就会根据命名规范调用setName()进行赋值,所以要通过set注入属性值,一定要有对于的setter方法

简单类型set注入

给任意类的简单类型属性进行赋值,使用<property name="age" value="20"/>

📜applicationContext.xml配置文件(部分)

<bean id="Student" class="cn.qkmango.ba01.Student">
    <property name="name" value="芒果小洛" />
    <property name="age" value="20"/>
</bean>

🚀test

Student student = (Student) ac.getBean("Student");
System.out.println(student);
//Student{name='芒果小洛', age=20}
引用类型set注入

对象中属性有引用类型的时候时,使用<property name="school" ref="mySchool"/>进行赋值,这个标签的ref属性就是这个引用类型的bean标签的id,相对于嵌套关系

📜applicationContext.xml配置文件(部分)

<bean id="Student" class="cn.qkmango.ba02.Student">
    <property name="name" value="芒果小洛" />
    <property name="age" value="20"/>
    <property name="school" ref="mySchool"/>
</bean>

<bean id="mySchool" class="cn.qkmango.ba02.School" >
    <property name="name" value="axhu" />
    <property name="address" value="hefei"/>
</bean>

🚀test

cn.qkmango.ba02.Student student = (cn.qkmango.ba02.Student) ac.getBean("Student");
System.out.println(student);
//Student{name='芒果小洛', age=20, school=School{name='axhu', address='hefei'}}

XML-构造注入

spring调用类的有参构造方法,在创建对象的同时,在构造方法中给属性赋值

使用 <constructor-arg>标签,一个标签表示一个构造函数的参数

  • name属性:构造函数中形参的名
  • index:形参的位置,0,1,2,3…
  • value:简单类型使用
  • ref:引用类型使用,值为引用对象的bean标签的id

了解:大部分都使用name属性指定构造方法的形参名,也可以使用index来指定形参的位置,当<constructor-arg>标签的顺序符合构造方法形参的顺序时,时可以省略index的

📜applicationContext.xml配置文件(部分)

<!--  使用name指定构造方法形参名  -->
<bean id="Student1" class="cn.qkmango.ba03.Student">
    <constructor-arg name="name" value="芒果小洛"/>
    <constructor-arg name="age" value="20"/>
    <constructor-arg name="school" ref="mySchool1" />
</bean>

<!--  使用index指定构造方法形参位置  -->
<bean id="Student2" class="cn.qkmango.ba03.Student">
    <constructor-arg index="0" value="芒果味的"/>
    <constructor-arg index="1" value="19"/>
    <constructor-arg index="2" ref="mySchool1" />
</bean>

<!--  当 <constructor-arg> 顺序与构造方法形参循序一样时,可以省略index  -->
<bean id="Student3" class="cn.qkmango.ba03.Student">
    <constructor-arg value="柠檬味的"/>
    <constructor-arg value="18"/>
    <constructor-arg ref="mySchool1" />
</bean>


<bean id="mySchool1" class="cn.qkmango.ba03.School" >
    <constructor-arg name="name" value="axhu"/>
    <constructor-arg name="address" value="hefei"/>
</bean>

🚀test

@Test
public void test03() {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ba03/applicationContext.xml");

    Student student = null;
    student = (Student) ac.getBean("Student1");
    System.out.println(student);
    // Student{name='芒果小洛', age=20, school=School{name='axhu', address='hefei'}}

    student = (Student) ac.getBean("Student2");
    System.out.println(student);
    // Student{name='芒果味的', age=19, school=School{name='axhu', address='hefei'}}

    student = (Student) ac.getBean("Student3");
    System.out.println(student);
    // Student{name='柠檬味的', age=18, school=School{name='axhu', address='hefei'}}
}

XMl-自动注入

引用类型自动注入:spring框架根据某些规则可以给引用类型赋值注入,不用你再给引用类型赋值了,使用的规则常用的是 byName、byType

byName(按名称注入)

类中引用类型的属性名和spring容器中(配置文件)<bean>的id一样且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型

<bean id="xx" class="yyy" autowire="byName"></bean>

📜ApplicationContext配置文件(部分)

<!--指定 autowire="byName" ,会将类中的属性根据属性名,注入id与此属性名相同的bean对象-->
<bean id="student" class="cn.qkmango.ba04.Student" autowire="byName">
    <property name="name" value="芒果小洛"/>
    <property name="age" value="20" />
</bean>

<bean id="school" class="cn.qkmango.ba04.School" >
    <constructor-arg name="name" value="新华学院"/>
    <constructor-arg name="address" value="安徽合肥"/>
</bean>

🚀test

@Test
public void testByName(){
    ApplicationContext context = new ClassPathXmlApplicationContext("ba04/applicationContext.xml");

    Student student = (Student) context.getBean("student");
    System.out.println(student);
    //Student{name='芒果小洛', age=20, school=School{name='新华学院', address='安徽合肥'}}
}
byType(按类型注入)

类中引用类型的数据类型和spring容器中(配置文件)<bean>class属性是同源关系的,这样的bean能够赋值给引用类型

<bean id="xx" class="yyy" autowire="byType"></bean>

同源就是同一类的意思:

  • 同一个类(将同一个类型的bean赋值给属性)
  • 父子类关系(将子类赋值给父类属性引用:父类引用指向子类对象)
  • 接口和实现类关系(将实现类赋值给接口类属性)

⚠️通过byType自动注入,注入的bean必须是唯一的(不能是多个同源),否则会报错:如多个子类(多个同源)、多个实现类(多个同源)

同源第1种情况:同一个类

📜ApplicationContext配置文件(部分)

<bean id="student" class="cn.qkmango.ba04.testByType01.Student" autowire="byType">
    <property name="name" value="芒果小洛"/>
    <property name="age" value="20" />
</bean>

<bean id="school" class="cn.qkmango.ba04.testByType01.School" >
    <constructor-arg name="name" value="新华学院"/>
    <constructor-arg name="address" value="安徽合肥"/>
</bean>

🚀test

@Test
//byType:同源第2种情况:父子类关系
public void testByType01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType02/applicationContext.xml");

    cn.qkmango.ba04.testByType02.Student student2 = (cn.qkmango.ba04.testByType02.Student) context.getBean("student2");
    System.out.println(student2);
    //Student{name='芒果味的', age=19, school=School{name='小学', address='合肥'}}
}
同源第2种情况:父子类关系

📜ApplicationContext配置文件(部分)

<bean id="student2" class="cn.qkmango.ba04.testByType02.Student" autowire="byType">
    <property name="name" value="芒果味的"/>
    <property name="age" value="19" />
</bean>

<!-- 
class School
class PrimarySchool extends School 
-->
<bean id="primarySchool" class="cn.qkmango.ba04.testByType02.PrimarySchool" >
    <property name="name" value="小学"/>
    <property name="address" value="合肥"/>
</bean>

🚀test

 @Test
//byType:同源第2种情况:父子类关系
public void testByType02() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType02/applicationContext.xml");

    cn.qkmango.ba04.testByType02.Student student2 = (cn.qkmango.ba04.testByType02.Student) context.getBean("student2");
    System.out.println(student2);
    //Student{name='芒果味的', age=19, school=School{name='小学', address='合肥'}}
}
同源第3种情况:接口和实现类关系

📜ApplicationContext配置文件(部分)

<bean id="student3" class="cn.qkmango.ba04.testByType03.Student" autowire="byType">
    <property name="name" value="芒果味的"/>
    <property name="age" value="19" />
</bean>

<!--
	interface School
	class PrimarySchool implements School
-->
<bean id="primarySchool" class="cn.qkmango.ba04.testByType03.PrimarySchool" >
    <property name="name" value="小学(接口)"/>
    <property name="address" value="合肥"/>
</bean>

🚀test

@Test
//byType:同源第3种情况:接口
public void testByType03() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba04/testByType03/applicationContext.xml");

    cn.qkmango.ba04.testByType03.Student student3 = (cn.qkmango.ba04.testByType03.Student) context.getBean("student3");
    System.out.println(student3);
    //Student{name='芒果味的', age=19, school=School{name='小学(接口)', address='合肥'}}
}

Spring的注解

如何使用

  1. 加入Maven依赖 spring-context,在你加入spring-context的同时,间接的加入spring-aop的依赖了,使用注解必须使用spring-aop依赖

  2. 在类中加入spring的注解(多个不同功能的注解)

    package cn.qkmango.ba01;
    import org.springframework.stereotype.Component;
    
    @Component(value="myStudent")
    public class Student {
    	//
    }
    
  3. 在spring的配置文件中,加入一个组件扫描器的标签,说明注解在项目中的位置

    <beans>
        <context:component-scan base-package="cn.qkmango.ba01" />
    </beans>
    

组件扫描器 component-scan

  • 组件就是Java对象
  • base-package:指定包含注解的类所在的包
  • component-scan工作方式:spring会扫描遍历 base-package 指定的包,把包中和子包中所有的类,找到类中的注解,按照注解的功能创建对象,给属性赋值

学习的注解

以下注解都能创建对象,不过创建的对象代表着不同的功能,给不同的类,赋予不同的角色

注解功能
@Component创建对象
@Repository持久层:放在dao实现类上,表示创建dao对象,dao对象就是能访问数据库的,持久层的
@Service业务层:放在service实现类上,表示创建service对象,service对象是做业务处理的,可以有事务等功能的
@Controller控制层:放在控制器类的上面,创建控制器对象的,能够接受用户提交的参数,响应请求的处理结果
@Value
@Autowired
@Resource

@Controller、@Service、@Repository是给项目的对象分层的:控制层、业务层、数据访问层

通过注解创建Java对象

通过注解来创建普通Java对象(无特殊功能的,不是控制层、业务层、持久层 的Java对象)

@Component注解只有一个属性value,所以可以有一下写法

@Component(value="myStudent")//不省略value属性名
@Component("myStudent")	//省略value属性名(常用)
@Component //spring默认指定value属性(默认为类名小写)

带有@Componen注解的类

@Component
public class Student {
	//
}

📜applicationContext配置文件

<beans>
    <context:component-scan base-package="cn.qkmango.ba01" />
</beans>

🚀test

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    Student myStudent = (Student) context.getBean("myStudent");
    System.out.println(myStudent);
    //Student{name='null', age=0}
}

基于注解的DI

简单类型注入 @Value

使用 @Value 注解对简单类型的属性进行赋值

使用位置

  • 在属性字段上使用:在定义的属性上使用 @Value 注解来对简单类型的属性进行赋值,无需set方法(推荐)
  • 在set方法上使用:在属性对应的set方法上使用 @Value 注解来对简单类型的属性进行赋值
@Component("student")
public class Student {
    @Value("芒果小洛")
    private String name;

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

    @Value("18")
    public void setAge(int age) {
        this.age = age;
    }
}

byType引用类型自动注入 @Autowired

使用@Autowired注解对引用类性的赋值(byType)

使用位置

  • 在属性的定义上面使用,无需set方法(推荐使用)
  • 在set方法上面使用

使用案例

@Component("student03")
public class Student {
    //自动注入,byType方式
    @Autowired
    private School school;
}

byName引用类型自动注入 @Autowired @Qualifier

自动注入,通过@Qualifier注解指定将哪个bean注入到属性,此时使用的是自动注入的byName模式

属性

@Autowired有required属性,boolean值,默认为true

属性值介绍
@Autowired(required = true)当使用@autowired指定的bean的id没找到时,则报错,并终止执行(推荐)
@Autowired(required = false)当使用@autowired指定的bean的id没找到时,程序正常执行,没有找到则不赋值(即为null)

使用案例

@Component("student03")
public class Student {
    //自动注入,byName方式,指定bean的id
    //默认 @Autowired(required = true):当使用 @autowired 指定的bean的id没找到时,则报错,并终止执行(推荐)
    @Autowired
    @Qualifier("school03")
    private School school;
}

JDK引用类型自动注入 @Resource

来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,使用的也是自动注入的原理

使用位置

  • 在属性的定义上面使用,无需set方法(推荐使用)
  • 在set方法上面使用

属性

属性值介绍
@Resource先使用byName自动注入,如果byName赋值失败,再使用byType
@Resource(name=“bean_id”)指定 name 属性,则只使用byName

使用案例

import javax.annotation.Resource;

@Component("student04")
public class Student {

    @Resource(name = "school04")
    private School school;
    //...
}

注解使用总结

简单类型赋值注解

@Value(“value”)

引用类型赋值注解
注解与属性注入方式介绍
@AutowiredbyType
@Autowired
@Qualifier("bean_id")
byName没找到则报错
@Autowired(required = false)
@Qualifier("bean_id")
byName没找到则不赋值
@ResourcebyName > byTypeJDK注解
@Resource(name="bean_id")byNameJDK注解

多配置文件

spring 主配置文件,包含其他配置文件,主配置文件一般不定义对象

如何包含其他配置文件?

<import resource="classpath:spring-school.xml"/>

多个配置优势

  • 每个文件的大小比一个文件要小很多,效率高
  • 避免多人同时编辑带来的冲突

多文件的分配方式

  • 按功能模块,一个模块一个配置文件
  • 按类的功能,数据库相关的配置一个文件配置文件,做事务的功能一个配置文件,做service功能的一个配置文件等

多配置文件案例

resources
applicationContext.xml(主配置文件)
spring-school.xml(student相关类的配置文件)
spring-student.xml(school相关类的配置文件)

📜applicationcContext(部分)

<beans>
	<!--每个文件分别包含-->
	<import resource="classpath:spring-school.xml"/>
 	<import resource="classpath:spring-student.xml"/>
    
    <!-- 使用通配符包含
			注意:通配符包含的配置文件不能包含主配置文件(套娃)-->
    <import resource="spring-*.xml"/>
</beans>

📜student相关类的配置文件(部分)

<!-- Spring student相关类的配置文件-->
<bean id="student" class="cn.qkmango.Student" >
    <property name="name" value="芒果小洛"/>
    <property name="age" value="20"/>
    <property name="school" ref="school"/>
</bean>

📜school相关类的配置文件(部分)

<!-- Spring school相关类的配置文件-->
<bean id="school" class="cn.qkmango.School">
    <property name="name" value="新华学院"/>
    <property name="address" value="合肥"/>
</bean>

配置文件路径问题

classpath:前缀,代表一个路径,相当于/WIN-INF/classes/

  • 加上此前缀,则resource表示的是一个绝对路径
  • 不加此前缀,则resource表示的是一个相对路径

⚠️当使用通配符引入文件时,且使用 classpath: 前缀,则被引入的文件必须在文件夹下(不能直接在根目录下
⚠️当使用通配符引入文件时,且被包含的配置文件在根目录下,则不能加上 classpath: ,只能使用相对路径

AOP面向切面编程

Aspect Orient Programming 面向切面编程

面向切面编程,基于动态代理,可以使用jdk、cglib两种动态代理方式;AOP就是动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式使用动态代理

动态代理

动态代理实现方式

  1. jdk动态代理

    使用jdk中的Proxy,Method,InvocationHandler创建代理对象。jdk动态代理要求目标类必须实现接口

  2. cglib动态代理

    第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是final的, 方法也不能是final的

动态代理的作用

  1. 在目标类源代码不改变的情况下,增加功能
  2. 减少代码的重复
  3. 专注业务逻辑代码
  4. 解耦合,让你的业务功能和日志,事务非业务功能分离

AOP术语

Aspect 切面

表示增强的功能,就是一堆代码,完成某一个非业务功能。常见的切面功能有日志、事务、统计信息、参数检查、权限验证

JoinPoint 连接点

连接业务方法和切面的位置,也就是指的是业务方法,业务方法被动态代理增强了功能,业务方法和增强的功能连接点就是业务方法

Pointcut 切入点

切面的执行位置,指多个连接点方法的集合,多个方法

目标对象

给哪个类的方法增加功能,这个类就是目标对象

Advice 通知

通知表示切面功能执行的时机

AOP的实现

aop是一个规范,是动态的一个规范化,一个标准

aop的技术实现框架

  1. spring:spring在内部实现了aop规范,能做aop的工作;spring主要在事务处理时使用aop。我们在项目开发中很少用spring的aop实现,因为spring的aop比较笨重

  2. aspectJ:来自于eclipse开源项目,一个专门做aop的框架。spring框架中已经集成了aspectj框架,通过spring就能使用aspectj的功能。

AspectJ框架

切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强);
在aspectj框架中使用注解表示的,也可以使用xml配置文件中的标签;

切面的执行时刻

@Before
@AfterReturning
@Around
@AfterThrowing
@After

AspectJ切入点表达式

execution([访问权限] 返回值类型 方法名(参数) [异常类型])

符号意义
*通配符,任意
..在方法参数中,表示任意多个参数
用在包名后,表示当前包及其子包(或包中的所有类)
+用在类名(接口)后,表示当前类(接口)及其子类(实现类)
示例
通过方法名定义切点
execution(public * *(..))

匹配所有目标类的public方法,第一个*代表方法返回值类型,第二个*代表方法名,而..代表任意入参的方法;

execution(* *To(..))

匹配目标类所有以To为后缀的方法。第一个*代表任意方法返回类型,而*To代表任意以To结尾的方法名。

通过类定义切点
execution(* com.taotao.Waiter.*(..))

匹配Waiter接口的所有方法,第一个*代表任意返回类型,com.taotao.Waiter.*代表Waiter接口中的所有方法。

execution(* com.taotao.Waiter+.*(..))

匹配Waiter接口及其所有实现类的方法

通过包名定义切点

注意:在包名模式串中,.*表示包下的所有类,而..*表示包、子孙包下的所有类。

execution(* com.taotao.*(..))

匹配com.taotao包下所有类的所有方法

execution(* com.taotao..*(..))

匹配com.taotao包及其子孙包下所有类的所有方法,如com.taotao.user.dao,com.taotao.user.service等包下的所有类的所有方法。

execution(* com..*.*Dao.find*(..))

匹配以com开头的任何包名下后缀为Dao的类,并且方法名以find为前缀,如com.taotao.UserDao#findByUserId()、com.taotao.dao.ForumDao#findById()的方法都是匹配切点。

通过方法入参定义切点

切点表达式中方法入参部分比较复杂,可以使用*..通配符,其中*表示任意类型的参数,而..表示任意类型参数且参数个数不限。

* joke(String, *)

匹配目标类中joke()方法,该方法第一个入参为String类型,第二个入参可以是任意类型

execution(* joke(String, int))

匹配类中的joke()方法,且第一个入参为String类型,第二个入参 为int类型

execution(* joke(String, ..))

匹配目标类中joke()方法,该方法第一个入参为String,后面可以有任意个且类型不限的参数

实现步骤

  1. 加入依赖

    • spring依赖
    • aspectj依赖
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. 创建目标类

  3. 创建切面类(普通的类)

    • 在类的上面加入@Aspect注解

    • 在类中定义方法,方法就是切面要执行的功能代码

    • 在方法上加入aspectJ中的通知注解,入@Before

    • 在方法上指定切入点表达式execution()

  4. 声明对象,将对象交给spring统一管理(注解@Component、xml<bean/>

    • 声明目标对象
    • 声明切面类对象
  5. 声明aspectj框架中的自动代理生成器标签

    完成代理对象自动创建功能的<aop:aspectj-autoproxy/>

  6. 测试:

    • 从spring容器中获取目标对象(实际上就是代理对象)
    • 通过实行方法,实现aop的功能增强

创建切面类方法定义的要求

  • 公共方法 public
  • 方法没有返回值
  • 方法可以有参数,也可以没有参数
  • 如果有参数,参数不是自定义的,有几个参数类型可以使用

@Before前置通知

前置通知注解,在目标方法之前执行

注解属性
  • value:切入点表达式,@Before(“execution(* *…ba01…doSome(…))”)
方法参数
  • 可选参数 JoinPoint
示例
<!--applicationContext.xml配置文件-->
<beans>
	<!-- 目标对象-->
    <bean id="someService" class="cn.qkmango.ba01.SomeServiceImpl"/>
    <!-- 切面类对象-->
    <bean id="myAspect" class="cn.qkmango.ba01.MyAspect"/>

    <!-- 自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
    创建代理对象是在内存中实现的,修改目标对象的内存中的结构,所以目标对象就是被修改后的代理对象 -->
    <aop:aspectj-autoproxy/>
</beans>
//接口
public interface SomeService {
    void doBefore(String name,Integer age);
}
//实现类,目标方法
public class SomeServiceImpl implements SomeService{
    @Override
    public void doBefore(String name,Integer age) {
        //给doBefore增加一个方法:在方法执行前,输出方法的执行时间
        System.out.println("目标方法doBefore()");
    }
}
//通知
@Before("execution(* *..ba01..doBefore(..))")
public void myBefore(JoinPoint jp) {
    System.out.println(jp.getSignature());
    for (Object arg : jp.getArgs()) {
        System.out.println("param:"+arg);
    }
    //切面要指定的功能代码
    System.out.println("前置通知,切面增强功能,目标方法执行前输出时间:" + new Date());
}

Test

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);

    someService.doBefore("芒果小洛",18);
    // void cn.qkmango.ba01.SomeService.doBefore(String,Integer)
    // param:芒果小洛
    // param:18
    // 前置通知,切面增强功能,目标方法执行前输出时间:Mon Apr 12 22:16:04 CST 2021
    // 目标方法doBefore()
}

通知方法JoinPoint参数

获取被增强的业务方法的相关信息

通知方法中的参数:JoinPoint
作用是可以 在通知方法中获取方法的执行信息,如方法名称、方法参数
如果你的 切面功能中需要用到方法的信息,就加入JoinPoint参数
这个JoinPoint参数的值是框架赋予的, 必须是第一个位置的参数

JoinPoint参数可以在任何通知(增强)方法中使用,注意:环绕通知@Around,方法可选参数ProceedingJoinPoint就是JoinPoint的子类

如下通知,使用了JoinPoint参数,获取业务方法的相关信息

@Before("execution(* *..ba01..doSome(..))")
public void myBefore(JoinPoint jp) {

    //获取被增强的方法的定义
    System.out.println(jp.getSignature());
    //获取被增强的方法的参数
    for (Object arg : jp.getArgs()) {
        System.out.println("param:"+arg);
    }

    System.out.println("前置通知,切面增强功能,目标方法执行前输出时间:" + new Date());
}

测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);

    someService.doSome("芒果小洛",18);
    // void cn.qkmango.ba01.SomeService.doSome(String,Integer)
    // param:芒果小洛
    // param:18
    // 前置通知,切面增强功能,目标方法执行前输出时间:Sun Apr 11 21:30:53 CST 2021
    // 目标方法doSome()
}

@AfterReturning后置通知

后置通知,目标方法执行后再执行

能够获取到目标方法的返回值

注解属性
  • value:切入点表达式

  • returning:自定义的变量,指明通知方法的形参中,哪个参数代表着目标方法的执行结果;

    如下示例通知方法res形参就是代表目标方法执行后返回值,通过注解returning属性指定

方法参数
  • 可选参数:JoinPoint

  • 必选参数:目标方法的返回值引用 Object res,类型自定义,推荐Object

    表示目标方法执行后,方法的返回值

示例
<!--applicationContext.xml配置文件-->
<bean>
	<!-- 目标对象-->
    <bean id="someService" class="cn.qkmango.ba02.SomeServiceImpl"/>
    <!-- 切面类对象-->
    <bean id="myAspect" class="cn.qkmango.ba02.MyAspect"/>
    <!-- 自动代理生成器 -->
    <aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
    @Override
    public String doAfterReturning(String name, Integer age) {
        //给doAfterReturning增加一个方法:在方法执行后,模拟提交事务
        System.out.println("目标方法doAfterReturning()执行");
        return name;
    }
}
//通知方法
@AfterReturning(value = "execution(* *..SomeServiceImpl.doAfterReturning(..))",
                    returning = "res")
public void myAfterReturning(JoinPoint jp,Object res) {
    System.out.println("@AfterReturning后置通知,目标方法返回值是:"+res);
    System.out.println("方法的定义: "+jp.getSignature());
}

测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba02/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);
    someService.doAfterReturning("芒果小洛",18);

    // 目标方法doAfterReturning()执行
    // @AfterReturning后置通知,目标方法返回值是:芒果小洛
    // 方法的定义: String cn.qkmango.ba02.SomeService.doAfterReturning(String,Integer)
}

@Around环绕通知

通知内部控制目标方法的执行

  1. 她是功能最强的通知
  2. 可以在目标方法前和后都能增强功能
  3. 控制目标方法是否被调用执行(通过方法的参数 ProceedingJoinPoint 控制调用)
  4. 通知方法的返回值就是目标方法调用处的执行结果返回值
  5. 环绕通知经常做的是事务:目标方法执行前开启事务 > 执行目标方法 > 之后提交事务
注解属性
  • value:切入点表达式
方法定义
  • 通知方法必须有一个返回值,这个返回值就是目标方法的执行结果的返回值
方法参数
  • ProceedingJoinPoint:可选参数, 控制目标方法的执行,继承JoinPoint,就相当于jdk动态代理的 Method
示例
<!--applicationContext.xml配置文件-->
<bean>
	<!-- 目标对象-->
    <bean id="someService" class="cn.qkmango.ba03.SomeServiceImpl"/>
    <!-- 切面类对象-->
    <bean id="myAspect" class="cn.qkmango.ba03.MyAspect"/>
    <!-- 自动代理生成器 -->
    <aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
    @Override
    public String doAround(String name, Integer age) {
        //给doOther增加一个方法:在方法执行后,模拟提交事务
        System.out.println("目标方法doAround()执行");
        return name;
    }
}
//通知方法
@Around("execution(* *..*Impl.doAround(..))")
public Object myAfterReturning(ProceedingJoinPoint pjp) throws Throwable {
    System.out.println("环绕通知:目标方法调用前");

    //根据参数,控制调用目标方法的执行
    Object result = null;
    int age = (int)pjp.getArgs()[1];
    if (age>=18) {
        result = pjp.proceed();//相当于method.invoke()
    }

    System.out.println("环绕通知:目标方法调用后");

    //修改返回值结果
    return "Hello AspectJ: "+result;
}

测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba03/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);

    String result = null;
    result = someService.doAround("芒果小洛", 17);
    System.out.println(result);

    result = someService.doAround("草莓味的", 18);
    System.out.println(result);

    // 环绕通知:目标方法调用前
    // 环绕通知:目标方法调用后
    // Hello AspectJ: null
    // 环绕通知:目标方法调用前
    // 目标方法doAround()执行
    // 环绕通知:目标方法调用后
    // Hello AspectJ: 草莓味的
}

@AfterThrowing异常通知

  1. 在目标方法抛出异常时执行的
  2. 可以做异常的监控程序,监控目标方法执行时是否由异常,如果有异常,可以进行发邮件、发短信进行通知等
注解属性
  • value:切入点表达式
  • throwing:指定方法形参中,哪个形参是目标方法执行时抛出的异常对象(目标方法执行抛出的异常对象)
方法定义
  • 无需返回值
方法参数
  • 可选参数:JoinPoint
  • 必选参数:Exception:目标方法执行时抛出的异常对象
模拟执行步骤
try {
    someService.doIt(1,0);
	//目标方法执行
} catch(Exception e) {
	//异常通知方法执行
	myAfterThrowing(e);
}
示例
<!--applicationContext.xml配置文件-->
<bean>
	<!-- 目标对象-->
    <bean id="someService" class="cn.qkmango.ba04.SomeServiceImpl"/>
    <!-- 切面类对象-->
    <bean id="myAspect" class="cn.qkmango.ba04.MyAspect"/>
    
	<aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
    @Override
    public void doAfterThrowing(int x, int y) {
        System.out.println("目标方法doAfterThrowing执行:x / y = "+(x/y));
    }
}
//异常通知方法
@AfterThrowing(value = "execution(* *..ba04.*Impl.doAfterThrowing(..))",
               throwing = "e")
public void myAfterThrowing(JoinPoint jp,Exception e) throws Throwable {
    System.out.println("异常通知:方法发生异常时执行,异常信息为:" + e.getMessage());
}

测试

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba04/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);

    someService.doAfterThrowing(1,1);
    //正常执行:目标方法doAfterThrowing()执行:x / y = 1
    someService.doAfterThrowing(1,0);
    //执行异常通知 1/0:异常通知:方法发生异常时执行,异常信息为:/ by zero
}

@After最终通知

  1. 总是会执行(无论是否有异常)
  2. 在目标方法之后执行
  3. 一般做资源清除工作的
注解属性
  • value:切入点表达式
方法参数
  • 可选参数 JoinPoint
模拟执行步骤
try {
    //目标方法执行
    someService.doAfter();
} catch(Exception e) {
    //
} finally {
    //最终通知方法执行
    myAfter();
}
示例
<!--applicationContext.xml配置文件-->
<bean>
    <!-- 目标对象-->
    <bean id="someService" class="cn.qkmango.ba05.SomeServiceImpl"/>
    <!-- 切面类对象-->
    <bean id="myAspect" class="cn.qkmango.ba05.MyAspect"/>

    <aop:aspectj-autoproxy/>
</bean>
//实现类,目标方法
public class SomeServiceImpl implements SomeService {
    @Override
    public void doAfter() {
        System.out.println("目标方法doAfter()执行");
    }
}
//最终通知方法
@After("execution(* *..ba05.*Impl.doAfter(..))")
public void myAfter(JoinPoint jp){
    System.out.println("最终通知:在方法的最后执行");
}

Test

@Test
public void test01() {
    ApplicationContext context = new ClassPathXmlApplicationContext("ba05/applicationContext.xml");
    SomeService someService = context.getBean("someService", SomeService.class);

    someService.doAfter();
    // 目标方法doAfter()执行
    // 最终通知:在方法的最后执行
}

@Pointcut切入点表达式别名

使用@Pointcut注解对切入点表达式定义别名,已达到复用效果

如何使用
  1. 定义空的方法,在空方法上使用注解,方法名就是切入点表达式的别名
  2. 在需要使用切入点表达式的地方(各种通知注解的value熟悉值)设置为 上面定义的 空方法名()
注解属性
  • value:切入点表达式
示例

以下示例,要在目标方法执行前添加前置通知,在目标方法执行后添加最终通知,

所以两个通知方法的切入点表达式是相同的,为了避免写重复代码,

可以将相同的切入点表达式定义别名,然后在两个通知注解中使用别名

//通知类
@Aspect
public class MyAspect {

    //定义切入点表达式别名为:myPointcut()
    @Pointcut("execution(* *..ba06.*Impl.doPointcut(..))")
    public void myPointcut() {}

    //使用切入点表达式别名
    @Before("myPointcut()")
    public void myBefore(JoinPoint jp){
        System.out.println("前置通知执行");
    }

    //使用切入点表达式别名
    @After("myPointcut()")
    public void myAfter(JoinPoint jp){
        System.out.println("最终通知执行");
    }
}

Spring整合MyBatis

把Mybatis框架和Spring集成在一起,像一个框架一样使用

Mybatis使用Druid连接池,内置连接池性能功能较弱

spring整合Mybatis的想法:使用spring的ioc技术,把Mybatis框架中使用的对象交给spring统一创建和管理

本章主要讲Mybatis配置在spring,主要是applicationContext.xml配置文件

回顾Mybatis

使用步骤

  1. 配置Mybatis的主配置文件 mybatis.xml

  2. 定义Dao接口

  3. 配置mapper.xml文件(映射文件)

  4. 创建dao的代理对象

    Student dao SqlSession.getMapper(Student.class);
    List<Student> students = dao.selectStudents();
    

Dao对象使用条件

要使用dao对象,需要使用getMapper()方法,怎么才能使用getMapper()方法,需要哪些条件?

  1. 获取SqlSession对象,需要使用SqlSessionFactory的openSession()方法
  2. 创建SqlSessionFactory对象,通过读取Mybatis的主配置文件,才能创建SqlSessionFactory对象
  3. 使用Druid数据库连接池来替换Mybatis内置的连接池

需要spring管理哪些对象

还需要**Druid连接池对象**,来管理数据库的连接

需要**SqlSessionFactory**对象,使用SqlSessionFactory对象能获取SqlSession对象

有了**SqlSession**就能有Dao对象

有**Dao**对象,就能操作数据库

所以本章需要学习的就是上面三个对象的创建语法(使用xml的bean标签)

使用Spring集成Mybatis

步骤

  1. 加入依赖

    • spring依赖

    • Mybatis依赖

    • MySQL驱动

    • spring事务依赖

    • Mybatis和spring继承的依赖:Mybatis官方提供的,用来在spring项目中创建Mybatis的SqlSessionFactory、Dao对象的

  2. 创建实体类

  3. 创建dao接口和mapper文件

  4. 创建Mybatis主配置文件

  5. 创建Service接口和实现类

  6. 创建spring的配置文件:声明Mybatis的对象交给spring创建

    • 数据源Datasources
    • SqlSessionFactory
    • Dao对象
    • 声明定义的service
  7. 测试:创建测试类,获取Service对象,通过service调用dao完成数据库的访问

实例

resources资源

【主要】applicationContext.xml配置文件

声明数据库连接池对象

声明Mybatis的factory对象

声明Dao数据访问层对象

声明Service服务层对象

<beans>
    <!--把数据库配置信息写在独立的文件中:属性配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--DataSources 数据源
        Druid连接池对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${druid.maxActive}" />
    </bean>

    <!--Factory
        声明的是Mybatis中提供的SqlSessionFactoryBean类,
		这个类内部创建SqlSessionFactory的-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--设置连接池对象-->
        <property name="dataSource" ref="dataSource" />
        <!--Mybatis主配置文件的位置-->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--Dao对象
        使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个Dao接口的代理对象,
		创建好的dao对象放入spring容器中
        (指定多个包,通过逗号 , 分隔)-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!--指定包名,包名是dao接口所在的包名
            会扫描这个包中的所有接口,会把每个接口都执行一个getMapper()方法,
			得到每个接口的dao对象-->
        <property name="basePackage" value="cn.qkmango.dao" />
    </bean>

    <!--service对象-->
    <bean id="studentService" class="cn.qkmango.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao" />
    </bean>
</beans>

Mybatis.xml

<configuration>
    <settings>
        <!--控制台输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    
    <!--设置实体类别名-->
    <typeAliases>
        <package name="cn.qkmango.domain"/>
    </typeAliases>

    <!-- sql mapper(SQL映射文件)的位置 -->
    <mappers>
        <package name="cn.qkmango.dao"/>
    </mappers>
</configuration>

jdbc.properties数据库配置文件:将连接数据库的信息写在独立的文件中

jdbc.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=admin
druid.maxActive=5

Dao

cn.qkmango.dao.StudentDao.java

public interface StudentDao {
    List<Student> selectStudents();
}

cn.qkmango.dao.StudentDao.xml

<mapper namespace="cn.qkmango.dao.StudentDao">
    <select id="selectStudents" resultType="cn.qkmango.domain.Student">
        select id,name,email,age from student order by id desc
    </select>
</mapper>

Service

cn.qkmango.service.impl.StudentServiceImpl.java

public class StudentServiceImpl implements StudentService {
    private StudentDao studentDao;
    public void setStudentDao(StudentDao studentDao) {
        this.studentDao = studentDao;
    }
    @Override
    public List<Student> selectStudents() {
        List<Student> studentList = studentDao.selectStudents();
        return studentList;
    }
}

总结

交给Spring的Mybatis对象

  1. 数据源DataSource:阿里巴巴的Druid
  2. SqlSessionFactoryBean:使用的是SqlSessionFactoryBean在内部创建SqlSessionFactory
  3. MapperScannerConfigurer:内部使用SqlSessionFactory.getMapper()获取创建所有Dao类对象,对象名是类名首字母小写(小驼峰)
  4. service对象
    png

Spring事务

spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

事务管理器对象

内部提交、回滚事务,使用的为事务管理器对象,代替你完成commit,rollback
事务管理器是一个接口和他的众多实现类。

  1. 接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
  2. 实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
    mybatis访问数据库:spring创建好的是 DataSourceTransactionManager
    hibernate访问数据库:spring创建的是 HibernateTransactionManager
  3. 怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
    声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用<bean>声明就可以了;例如,你要使用mybatis访问数据库,你应该在xml配置文件中<bean id="xxx" class="...DataSourceTransactionManager">

事务的隔离级别、超时、传播行为

事务的隔离级别

有4个值
DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。

  • READ_UNCOMMITTED:读未提交。未解决任何并发问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。

事务的超时时间

表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚;单位是秒,整数值,默认是 -1.

事务的传播行为

控制业务方法是不是有事务的, 是什么样的事务的。
7个传播行为,表示你的业务方法调用时,事务在方法之间是如果使用的。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
以上三个需要掌握的
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
public void methodA() {
    methodB();
}
public void methodB() {
    //...
}
传播行为解释
PROPAGATION_REQUIRED表示当前方法 必须运行在事务中,如上methodA()如果有事务,则methodB()会在该事务中运行,如果没有事务,则会启动一个新的事务
PROPAGATION_REQUIRES_NEW总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新的事务执行完毕
PROPAGATION_SUPPORTS是否存在事务都可以,如:select查询语句

提交、回滚事务的时机

commit

  • 当你的业务方法,执行成功,没有异常抛出,spring在方法执行后提交事务
  • 当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
    受查异常:在你写代码中,必须处理的异常,例如IOException, SQLException

rollback

  • 当你的业务方法抛出运行时异常(RuntimeException)或ERROR, spring执行回滚,调用事务管理器的rollback
    例NullPointException , NumberFormatException

总结spring事务

  1. 管理事务的是 事务管理器和它的实现类
  2. spring的事务是一个统一模型
    • 指定要使用的事务管理器实现类:<bean>
    • 指定哪些类,哪些方法需要加入事务的功能
    • 指定方法需要的隔离级别,传播行为,超时

你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。

spring事务

@Transactional配置事务(注解)

适合中小项目使用,注解方案

spring框架自己用aop实现给业务方法增加事务的功能,使用 @Transactional注解增加事务

属性
属性解释
propagation事务传播行为,该属性类型为Propagation枚举类型,默认值为Propagation.REQUIRED
isolation事务隔离级别,属性类型为Isolation枚举类型,默认值为Isolaton.DEFAULT
readOnly对数据库操作是否是只读的。属性为boolean,默认值为false
timeOut连接超时时间:点位秒,int类型,默认值为-1,即没有时限
rollbackFor让受检查异常回滚:即让本来不应该回滚的进行回滚操作,参数Class[]
rollbackForClassName让受检查异常回滚:即让本来不应该回滚的进行回滚操作,参数String[]
noRollbackFor忽略非检查异常:即让本来应该回滚的不进行回滚操作,参数Class[]
noRollbackForClassName忽略非检查异常:即让本来应该回滚的不进行回滚操作,参数String[]
使用

放在方法的上面,表示当前方法具有的事务

  1. 需要声明事务管理器对象(applicationContext.xml)

  2. 开启事务注解驱动,告诉spring框架需要使用注解方式管理事务(applicationContext.xml)

    spring使用AOP机制,创建@Transactional所在的类代理对象,给方法加入事务的功能

  3. 在方法上面加入@Transactional注解(class)

示例
  • applicationContext.xml
<beans>
    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!--DataSources 数据源
        Druid连接池对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" 
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${druid.maxActive}" />
    </bean>

    <!--Factory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--Dao对象 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="cn.qkmango.dao" />
    </bean>

    <!--service对象-->
    <bean id="buyGoodsService" class="cn.qkmango.service.impl.BuyGoodsServiceImpl">
        <property name="saleDao" ref="saleDao" />
        <property name="goodsDao" ref="goodsDao" />
    </bean>


    <!-- ########## 使用spring的事务处理 ##############-->
    
    <!--1. 声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
        <!--指定数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象,
        属性值 transaction-manager 代表事务管理器
    注意,同名的标签有多个,要选择 http://www.springframework.org/schema/tx 这个
    -->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>
  • class BuyGoodsServiceImpl

下面类中的buy()方法上使用@Transactional注解添加事务,当抛出运行时异常时会回滚事务

下面@Transactional注解设置的属性值都是默认值,所以等同于不设置属性的注解

public class BuyGoodsServiceImpl implements BuyGoodsService {

    //商品库存表、销售记录表Dao对象,省略get set方法
    private SaleDao saleDao;
    private GoodsDao goodsDao;

    @Transactional(
            propagation = Propagation.REQUIRED, //传播行为:默认必须运行在事务中
            isolation = Isolation.DEFAULT,      //隔离级别,默认
            readOnly = false,   	//是否为只读的,默认
            rollbackFor = { 		//让哪些异常类型回滚(默认RuntimeException回滚)
                    NullPointerException.class,
                    NotEnoughException.class
            }
    )
    @Override
    public void buy(Integer goodsId, Integer nums) {
        //1. 记录销售信息,向销售表添加记录
        //省略...

        //2. 更新库存
        //查询商品信息
        Goods goods = goodsDao.selectGoods(goodsId);
        if (goods==null) {
            //商品不存在
            throw new NullPointerException("商品不存在:id= "+goodsId);
        } else if (goods.getAmount() < nums) {
            throw new NotEnoughException("商品库存不足:id= "+goodsId);
        }

        //修改库存
       	//省略...
    }
}
  • test

添加事务注解的类会被动态代理

@Test
public void test() {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");

    BuyGoodsService buyGoodsService = context.getBean("buyGoodsService",BuyGoodsService.class);
    //JDK动态代理:com.sun.proxy.$Proxy16
    System.out.println(buyGoodsService.getClass().getName());
    //会抛出异常,spring事务会进行回滚
    buyGoodsService.buy(1001,200);
}

aspectJ配置文件配置事务(声明式)

适合大型项目,有很多类、方法,需要大量配置事务,使用aspectJ框架功能,在spring配置文件中声明类方法需要的事务;这种方式业务方法和事务配置完全分离

实现步骤

都是在xml配置文件中实现的

  1. 加入aspectJ依赖

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    
  2. 声明事务管理器

    <bean id="transactionManager" class="XX.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
    
  3. 配置事务属性(配置方法的事务属性【隔离级别、传播行为、超时】)

    http://www.springframework.org/schema/tx
    

    <tx:method> :(标签相关参数和 @Transactional注解属性 相同)

    • name:方法名(不包含包、类),可以使用通配符 *

    • propagation:传播行为

    • isolation:隔离级别

    • timeout:超时

    • rollback-for:一定回滚的异常类名(完整类名)

    • read-only:只读

    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--配置事务属性-->
        <tx:attributes>
    		<!--配置不同方法的事务类型(需要结合下面)<aop:config>才能关联起来-->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,
                                     cn.qkmango.exception.NotEnoughException"/>
            <tx:method name="add*" propagation="REQUIRES_NEW"/>
            <tx:method name="del*" propagation="REQUIRED"/>
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    

    spring对方法的事务进行匹配时有优先级,
    先进行全匹配(如name="buy"),
    然后进行通配符匹配(如:name="add*"),
    最后进行仅通配符匹配(如 name="*"

  4. 配置AOP,将事务类型和需要加入事务的方法关联起来

    <aop:pointcut>:切入点表达式

    • id:切入点表达式名称

    • expression:切入点表达式,指定哪些类需要使用事务,aspectJ会创建代理对象

    <aop:advisor>:关联事务类型和切入点表达式

    • advice-ref:通知,上面的 <tx:advice> 配置的id
    • pointcut-ref:切入点表达式id
    <aop:config>
        <!--配置切入点表达式:指定哪些包中类需要使用事务-->
        <aop:pointcut id="servicePt" 
                      expression="execution(* cn.qkmango.service.impl.*.*(..))"/>
        <!--配置增强器:关联advice 和 pointcut
            将 切入点表达式指定的方法 与 事务属性配置<tx:advice>关联起来-->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>
    
实例

applicationContext.xml

<beans>
    <!--把数据库配置信息写在独立的文件中:属性配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--DataSources 数据源 Druid连接池对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
        <property name="maxActive" value="${druid.maxActive}" />
    </bean>

    <!--Factory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--Dao对象 -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <property name="basePackage" value="cn.qkmango.dao" />
    </bean>

    <!--service对象-->
    <bean id="buyGoodsService" class="cn.qkmango.service.impl.BuyGoodsServiceImpl">
        <property name="saleDao" ref="saleDao" />
        <property name="goodsDao" ref="goodsDao" />
    </bean>

    <!-- ##### 声明式事务处理:和源代码完全分离的 #####-->

    <!--1. 声明事务管理器对象-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--2. 配置事务的属性(隔离级别、传播行为、超时时间) -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--配置事务属性-->
        <tx:attributes>
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,
                                     cn.qkmango.exception.NotEnoughException"/>

            <!--添加方法、删除方法 事务配置-->
            <tx:method name="add*" propagation="REQUIRES_NEW"/>
            <tx:method name="del*" propagation="REQUIRED"/>
            <!--查询方法 事务配置-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置AOP:将方法和事务属性关联起来-->
    <aop:config>
        <!--配置切入点表达式:指定哪些包中类需要使用事务 -->
        <aop:pointcut id="servicePt" 
                      expression="execution(* cn.qkmango.service.impl.*.*(..))"/>
        <!--配置增强器:关联advice 和 pointcut
            将 切入点表达式指定的方法 与 事务属性配置<tx:advice>关联起来 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>

</beans>

web项目中使用容器对象

spring在web项目中的使用

步骤

  1. 写项业务相关类、切面类

  2. 加入依赖

    servlet
    Spring核心ioc
    spring事务
    aspect
    Mybatis
    Mybatis和spring集成的依赖
    MySQL驱动
    Druid连接池
    spring-web:为了使用监听器
    
  3. 配置web.xml

    <!--配置监听器,在容器启动时创建好spring容器内的对象-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
  4. 配置Mybatis.xml

  5. 配置applicationContext.xml

    • 配置事务相关
    • 切面相关

spring容器获取

如何在web项目总获取spring容器对象

使用监听器自动在项目启动时初始化spring容器,在每次使用spring容器对象时直接获取就行了,需要有以下步骤

配置监听器

在项目启动时,就进行spring容器的初始化,创建bean,则需要使用监听器

  1. 加入依赖(pom.xml)

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-web</artifactId>
      <version>5.1.6.RELEASE</version>
    </dependency>
    
  2. 配置监听器(web.xml)

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!--spring配置文件路径-->
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    

监听器是如何工作的,做了什么事

在项目启动时,将spring容器对象创建并放到servlet全局作用域中

(这个监听器时spring提供的)

//创建容器对象
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//把容器对象放入到ServletContext
ServletContext.setAttribute(key,ctx)

获取spring容器对象

根据监听器是如何工作的,所以获取spring容器对象,可以通过下面方式获取

ApplicationContext context = (ApplicationContext)getServletContext().getAttribute("key");

其实也可以并推荐使用spring提供的方式获取

WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());

End 2021-04-23 23:36 by 芒果小洛(qkmango)

homepage

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值