第一章 Spring概述

1.1 概述

Spring:出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017出现新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:IoC、AOP,能使模块之间、类之间解耦合。
依赖:class A使用class B的属性或方法,称之为class A依赖class B。
官网:spring.io

1.2 优点

(1)轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率高,不依赖其他jar。
(2) 针对接口编程,解耦合
(3) AOP 编程的支持
(4) 方便集成各种优秀框架
2,3,4的优点在接下来的学习中体会。

1.3 Spring 体系结构

Spring 由 20 多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、 Web、面向切面编程(AOP, Aspects)、提供JVM的代理 (Instrumentation)、消息发送(Messaging)、 核心容器(Core Container)和测试(Test)。

第二章 IoC控制反转

  • 控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代 码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对 象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。

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

  • 反转: 把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。 由容器代替开发人员管理对象。创建对象,给属性赋值。

  • 正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。

public static void main(String args[]){Student student = new Student(); // 在代码中, 创建对象。--正转。
}
  • 容器:是一个服务器软件, 一个框架(spring)

java中创建对象有哪些方式:

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

为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。

Ioc 的实现:
➢ 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件。
➢ 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行 完成。

Spring 框架使用依赖注入(DI)实现 IoC。
spring底层创建对象,使用的是反射机制。spring是一个容器,管理对象,给属性赋值, 底层是反射创建对象。
之前学习到的应用控制反转的实例:Servlet对象的创建管理,这一工作完全交给了Web容器。

2.1 Spring的第一个程序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y5Z7kU8U-1649172819311)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220401085420945.png)]

2.1.1 创建Maven项目,导入Spring依赖

(Maven项目采用quickstart模板,建立好resources目录)

<dependencies><!--Spring的依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version></dependency></dependencies>

2.1.2 创建业务接口与实现类

接口:

public interface SomeService {void doSome();
}

实现类:

public class SomeServiceImpl implements SomeService {public SomeServiceImpl() {System.out.println("SomeServiceImpl的无参数构造方法");}@Overridepublic void doSome() {System.out.println("执行了SomeServiceImpl的doSome()方法");}
}

2.1.3 创建spring配置文件

在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名 称为 applicationContext.xml。 IDEA已经为我们设计好了Spring配置文件的模板:
右击resources–>new–>XML configuration file–>Spring Config
模板如下:

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"></beans>

注意,Spring 配置文件中使用的约束文件为 xsd 文件。作用与Mybatis的sql映射文件的dtd约束文件类似,但xsd约束作用更强:

<beans/>是配置文件的根标签。在Spring中,java对象称之为bean。在这个标签下进行java对象的注册:

<?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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><!--告诉spring创建对象声明bean , 就是告诉spring要创建某个类的对象id:对象的自定义名称,唯一值。 spring通过这个名称找到对象class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)spring就完成 SomeService someService = new SomeServiceImpl();spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。springMap.put(id的值, 对象);例如 springMap.put("someService", new SomeServiceImpl());一个bean标签声明一个对象。--><bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl" /><bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl" scope="prototype"/><!--spring能创建一个非自定义类的对象吗, 创建一个存在的某个类的对象。--><bean id="mydate" class="java.util.Date" /></beans>
<!--spring的配置文件1.beans : 是根标签,spring把java对象成为bean。2.spring-beans.xsd 是约束文件,和mybatis指定  dtd是一样的。
-->
  1. 声明java对象交给Spring创建和管理,这个步骤称之为声明bean。

    < bean>等同于: SomeService someService = new com.bjpowernode.service.SomeServiceImpl();

    然后将创建的对象是放入到Spring的容器(Map<id,对象>):

    private final Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap(16);
    factoryBeanObjectCache.put("service",someService);
    
  2. < bean/>标签的属性:
    class:类的全限定名称,不能是接口(Spring使用反射创建对象);class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象。
    id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象。
    scope:指定bean对象的作用域(对象的存在范围和可见性)。可取值:

    1. 单例 :singleton , 默认值,表示叫这个名称的对象在spring容器中只有一个。
    2. 原型 :prototype , 表示每次使用getBean()都创建一个新的对象。

类路径的根目录:类路径就是在编译生成后的target目标目录下的classes作为根目录。

2.1.4 创建测试类

 /*** spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中的所有的对象。* spring创建对象:默认调用的是无参数构造方法*/
@Testpublic void test02(){//使用spring容器创建的对象//1.指定spring配置文件的名称,配置文件是在类路径的根目录之下String config="beans.xml";//2.创建表示spring容器的对象, ApplicationContext// ApplicationContext就是表示Spring容器,通过容器获取对象了// ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取某个对象, 你要调用对象的方法//getBean("配置文件中的bean的id值")SomeService service = (SomeService) ac.getBean("someService");//使用spring创建好的对象service.doSome();}

创建Spring的容器对象.根据Spring配置文件的位置,使用接口的不同实现类
1.如果Spring的配置文件是在类路径(classpath),使用ClassPathXmlApplicationContext
2.如果Spring的配置文件是放在项目的根之下(与src、target同级目录),使用FileSystemXmlApplicationContext

2.1.5 Spring创建对象的时机

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-z0Ca3OCH-1649172819312)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220401122302686.png)]

进入Application ac = new ClassPathXmlApplicationContext(config);

在这个类的构造方法执行时会去读取类路径下的这个config配置文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ijXhc7G-1649172819312)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220401122552222.png)]

然后就来到了config这个配置文件,从上到下读取,当读取到bean标签时,spring就会完成对象的创建工作,通过反射机制来调用这个Impl这个类的构造方法创建对象,对象的名字叫someService并且把创建好的对象放到map之中,那么这样这个标签的功能就实现了,对象就创建好了。当创建ac对象时,通过构造方法读取配置文件时,就已经创建了java对象。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-M0d89CTt-1649172819313)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220401123744315.png)]

当到ac.getBean()的时候,我们是从容器中拿到someService这个对象,也就是从容器的map中去获取,把它赋值给我们的程序代码中。然后就可以调用方法了。

  • spring默认创建对象的时间:在创建spring的容器时,会创建配置文件中的所有的对象。
  • spring创建对象:默认调用的是无参数构造方法

2.2 Bean的装配

2.2.1 默认装配方式

当我们创建ApplicationContext对象时(2.1.4中),Spring会读取配置文件的< bean/>并执行对应类的无参构造器。
在这些类的无参构造器中加一条输出语句可以验证以上结论。

2.2.2 容器中Bean的作用域

当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持多种作用域。
(1)singleton:单例模式。即在整个 Spring 容器中,使用 singleton 定义的 Bean 将是单例 的,叫这个名称的对象只有一个实例。默认为单例的。
(2)prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个 新的实例。
(3)request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。 (4)session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。

注意:

  • 对于 scope 的值 request、session 只有在 Web 应用中使用 Spring 时,该作用域才有效。
  • 对于 scope 为 singleton 的单例模式,该 Bean 是在容器被创建时即被装配好了;对于 scope 为 prototype的原型模式,Bean 实例是在代码中使用该 Bean 实例时才进行 装配的。

现在我们只是自动创建了对象,没有给对象的属性赋值。2.3和2.4介绍了两种给属性赋值的方法。

2.3 基于XML的DI

2.3.1 注入分类

1.set注入

set 注入也叫设值注入,是指通过 setter 方法传入被调用者的实例属性进行赋值。这种注入方式简单、 直观,因而在 Spring 的依赖注入中大量使用。

A.简单类型(基本类型和String类型)

每一个property标签,完成一个属性的赋值。
注意:Spring执行property标签的原理,是执行对象属性值对应的set方法。而并不关心set方法的具体实现属性是否真的存在。也就是就算属性不存在,但是提供了其setter方法,就可以执行调用其setter方法进行操作,不会报错。

配置文件:

    <!--声明student对象注入:就是赋值的意思简单类型: spring中规定java的基本数据类型和String都是简单类型。di:给属性赋值1. set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值1)简单类型的set注入<bean id="xx" class="yyy"><property name="属性名字" value="此属性的值"/>一个property只能给一个属性赋值<property....></bean>--><bean id="myStudent" class="com.bjpowernode.ba01.Student" ><property name="name" value="李四lisi" /><!--setName("李四")--><property name="age" value="22" /><!--setAge(21)--><property name="email" value="lisi@qq.com" /><!--setEmail("lisi@qq.com")--></bean><bean id="mydate" class="java.util.Date"><property name="time" value="8364297429" /><!--setTime(8364297429)--></bean>

测试类:

@Testpublic void test01(){System.out.println("=====test01========");String config="ba01/applicationContext.xml";ApplicationContext ac = new ClassPathXmlApplicationContext(config);//从容器中获取Student对象Student myStudent =  (Student) ac.getBean("myStudent");System.out.println("student对象="+myStudent);Date myDate = (Date) ac.getBean("mydate");System.out.println("myDate="+myDate);
}
B.引用类型

当指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的 值必须为某 bean 的 id 值。 例如:创建一个学校类;

注意:这里的 name=“school” 和 ref="school"的名字没有关系,可以一样,他两不冲突。name是对象的属性,ref是另一个对象的变量名。

并且他们在配置文件的bean中没有位置定义顺序的要求(声明对象不用关心顺序)。因为spring的处理方式很智能,配置文件会加载两次,第一次会把遇到的所以bean标签的对象都创建出,如果在执行过程中发现属性里面的对象找不到,他会第二次扫描这个配置文件,找到创建好的对象再赋值。

public class School {private String name;private String address;public void setName(String name) {this.name = name;}public void setAddress(String address) {this.address = address;}@Overridepublic String toString() {return "School{" +"name='" + name + '\'' +", address='" + address + '\'' +'}';}
}

然后再在学生类中添加School类型的属性:

public class Student {private String name;private int age;//声明一个引用类型private School school;public Student() {System.out.println("spring会调用类的无参数构造方法创建对象");}// 包名.类名.方法名称// com.bjpowernode.ba02.Student.setName()public void setName(String name) {System.out.println("setName:"+name);this.name = name;}public void setAge(int age) {System.out.println("setAge:"+age);this.age = age;}public void setSchool(School school) {System.out.println("setSchool:"+school);this.school = school;}public String getName() {return name;}public int getAge() {return age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +", school=" + school +'}';}
}

配置文件:

<?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">
<!--声明student对象注入:就是赋值的意思简单类型: spring中规定java的基本数据类型和String都是简单类型。di:给属性赋值1. set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值1)简单类型的set注入<bean id="xx" class="yyy"><property name="属性名字" value="此属性的值"/>一个property只能给一个属性赋值<property....></bean>2) 引用类型的set注入 : spring调用类的set方法<bean id="xxx" class="yyy"><property name="属性名称" ref="bean的id(对象的名称)" /></bean>
-->
<bean id="myStudent" class="com.bjpowernode.ba02.Student" ><property name="name" value="李四" /><property name="age" value="26" /><!--引用类型--><property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
</bean>
<!--声明School对象-->
<bean id="mySchool" class="com.bjpowernode.ba02.School"><property name="name" value="北京大学"/><property name="address" value="北京的海淀区" />
</bean>
</beans>

2.构造注入

执行类的有参构造,在构造对象的同时给属性赋值。

< constructor-arg />标签中用于指定参数的属性有:
➢ name:指定参数名称,指的是构造方法中的形参。
➢ index:指明该参数对应着构造器的第几个参数,从 0 开始。不过,该性不要也行,但要注意,若参数类型相同,或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致。

注意:这里使用name属性时,顺序可以打乱,不必按构造方法的顺序给对象的属性赋值。但是在使用index且缺省index属性定义时,不能打乱顺序给属性赋值,要严格按照构造方法的参数顺序给属性赋值。

1.给student添加有参构造

/*** 创建有参数构造方法*/
public Student(String myname,int myage, School mySchool){System.out.println("=====Student有参数构造方法======");//属性赋值this.name  = myname;this.age  = myage;this.school = mySchool;}

2.配置文件

<!--2.构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。构造注入使用 <constructor-arg> 标签。<constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。<constructor-arg> 标签属性:name:表示构造方法的形参名index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序value:构造方法的形参类型是简单类型的,使用valueref:构造方法的形参类型是引用类型的,使用ref
-->
<!--使用name属性实现构造注入-->
<bean id="myStudent" class="com.bjpowernode.ba03.Student" ><constructor-arg name="myage" value="20" /><constructor-arg name="mySchool" ref="myXueXiao" /><constructor-arg name="myname" value="周良"/>
</bean>
<!--声明School对象-->
<bean id="myXueXiao" class="com.bjpowernode.ba03.School"><property name="name" value="清华大学"/><property name="address" value="北京的海淀区" />
</bean>
<!--使用index属性-->
<bean id="myStudent2" class="com.bjpowernode.ba03.Student"><constructor-arg index="1" value="22" /><constructor-arg index="0" value="李四" /><constructor-arg index="2" ref="myXueXiao" />
</bean><!--省略index-->
<bean id="myStudent3" class="com.bjpowernode.ba03.Student"><constructor-arg  value="张强强" /><constructor-arg  value="22" /><constructor-arg  ref="myXueXiao" />
</bean>
<!--声明School对象-->
<bean id="myXueXiao" class="com.bjpowernode.ba03.School"><property name="name" value="清华大学"/><property name="address" value="北京的海淀区" />
</bean>

2.3.2引用类型自动注入

对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为< bean/>标签 设置 autowire 属性值,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属 性)。根据自动注入判断标准的不同,可以分为两种:

  • byName:根据名称自动注入
  • byType: 根据类型自动注入

1.byname方式自动注入

当配置文件中被调用者 bean 的 id 值与代码中调用者 bean 类的(set方法后缀的)属性名相同时,可使用 byName 方式,让容器自动将被调用者 bean 注入给调用者 bean。容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的 id 进行比较而实现自动注入的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U4ShD7JR-1649172819314)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402111649393.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sVB4WXiN-1649172819317)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402213905935.png)]

<!--引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。不用你在给引用类型赋值了使用的规则常用的是byName, byType.1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。语法:<bean id="xx" class="yyy" autowire="byName">简单类型属性赋值</bean>
--><!--byName--><bean id="myStudent" class="com.bjpowernode.ba04.Student"  autowire="byName"><property name="name" value="李四" /><property name="age" value="26" /><!--引用类型--><!--<property name="school" ref="mySchool" />--></bean><!--声明School对象--><bean id="school" class="com.bjpowernode.ba04.School"><property name="name" value="清华大学"/><property name="address" value="北京的海淀区" /></bean>

2.bytype方式自动注入

使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类, 要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类,或是实现类)。但这样的同源的被调用 bean 只能有一个。多于一个,容器就不知该匹配 哪一个了。就会报错。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GCTVghQu-1649172819318)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402214355388.png)]

同源就是一类的意思:
1.java类中引用类型的数据类型和bean的class的值是一样的。
2.java类中引用类型的数据类型和bean的class的值父子类关系的。
3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的

<!--2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性是同源关系的,这样的bean能够赋值给引用类型同源就是一类的意思:1.java类中引用类型的数据类型和bean的class的值是一样的。2.java类中引用类型的数据类型和bean的class的值父子类关系的。3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的语法:<bean id="xx" class="yyy" autowire="byType">简单类型属性赋值</bean>注意:在byType中, 在xml配置文件中声明bean只能有一个符合条件的,多余一个是错误的
-->
<!--byType-->
<bean id="myStudent" class="com.bjpowernode.ba05.Student"  autowire="byType"><property name="name" value="张飒" /><property name="age" value="26" /><!--引用类型--><!--<property name="school" ref="mySchool" />-->
</bean><!--声明School对象-->
<bean id="mySchool" class="com.bjpowernode.ba05.School"><property name="name" value="人民大学"/><property name="address" value="北京的海淀区" />
</bean><!--声明School的子类-->
<!--<bean id="primarySchool" class="com.bjpowernode.ba05.PrimarySchool"><property name="name" value="北京小学" /><property name="address" value="北京的大兴区" />
</bean>-->

2.3.3为应用指定多个Spring配置文件

在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变 得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
包含关系的配置文件: 多个配置文件中有一个总文件,总配置文件将各其它子文件通过**< import/>**引入。在 Java 代码中只需要使用总配置文件对容器进行初始化即可。例如:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fwYWsRK3-1649172819319)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402172411225.png)]

<?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-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。语法:<import resource="其他配置文件的路径" />关键字:"classpath:" 表示类路径(class文件所在的目录),在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。--><!--加载的是文件列表--><!--<import resource="classpath:ba06/spring-school.xml" /><import resource="classpath:ba06/spring-student.xml" />--><!--在包含关系的配置文件中,可以通配符(*:表示任意字符)注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)--><import resource="classpath:ba06/spring-*.xml" />
</beans>

也可使用通配符 *。但,此时要求父配置文件名不能满足*所能匹配的格式,否则将出现 循环递归包含。就本例而言,父配置文件不能匹配 spring-*.xml 的格式,即不能起名为 spring-total.xml。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wfGRgSid-1649172819321)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402124958356.png)]

如果直接在类路径下是包含不到其他包的

2.4基于注解的DI

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zZRqakQ3-1649172819324)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402173114352.png)]

通过以下四个步骤实现DI:
1. 创建Maven项目,在pom.xml 加入 AOP 依赖

<!--spring依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version>
</dependency>

如果你在pom.xml中添加了spring-context,那此依赖自动下载包含AOP,无需再次添加。

2.在类中添加注解

@Component("myStudent")
public class Student {private String name;private Integer age;public Student() {System.out.println("==student无参数构造方法===");}public void setName(String name) {this.name = name;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

3.创建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/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!--声明组件扫描器(component-scan),组件就是java对象base-package:指定注解在你的项目中的包名。component-scan工作方式: spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。加入了component-scan标签,配置文件的变化:1.加入一个新的约束文件spring-context.xsd2.给这个新的约束文件起个命名空间的名称--><context:component-scan base-package="com.bjpowernode.ba02" />
</beans>

4.使用注解创建对象,创建容器ApplicationContext

public class MyTest01 {@Testpublic void test01(){String config="applicationContext.xml";ApplicationContext ctx = new ClassPathXmlApplicationContext(config);//从容器中获取对象Student student = (Student) ctx.getBean("myStudent");System.out.println("student="+student);}
}

2.4.1 指定包的三种方式

<!--声明组件扫描器(component-scan),组件就是java对象base-package:指定注解在你的项目中的包名。component-scan工作方式: spring会扫描遍历base-package指定的包,把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。加入了component-scan标签,配置文件的变化:1.加入一个新的约束文件spring-context.xsd2.给这个新的约束文件起个命名空间的名称
-->
<context:component-scan base-package="com.bjpowernode.ba01" /><!--指定多个包的三种方式-->
<!--第一种方式:使用多次组件扫描器,指定不同的包-->
<context:component-scan base-package="com.bjpowernode.ba01"/>
<context:component-scan base-package="com.bjpowernode.ba02"/><!--第二种方式:使用分隔符(;或,)分隔多个包名 还可以使用空格,不建议使用空格。-->
<context:component-scan base-package="com.bjpowernode.ba01;com.bjpowernode.ba02" /><!--第三种方式:指定父包-->
<context:component-scan base-package="com.bjpowernode" />

2.4.2 定义Bean的注解@Component

@Component: 创建类的对象,等同于< bean />,默认是单例对象
属性: value 表示对象的名称(的id)
位置: 在类定义的上面,表示创建此类的对象。

例如:@Component(value = “myStudent”)等价于< bean id=“myStudent” class=“com.bjpowernode.ba01.Student”/>

/*** @Component: 创建对象的, 等同于<bean>的功能*     属性:value 就是对象的名称,也就是bean的id值,*          value的值是唯一的,创建的对象在整个spring容器中就一个*     位置:在类的上面**  @Component(value = "myStudent")等同于*   <bean id="myStudent" class="com.bjpowernode.ba01.Student" />**  spring中和@Component功能一致,创建对象的注解还有:*  1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,*               表示创建dao对象,dao对象是能访问数据库的。*  2.@Service(用在业务层类的上面):放在service的实现类上面,*              创建service对象,service对象是做业务处理,可以有事务等功能的。*  3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,*              控制器对象,能够接受用户提交的参数,显示请求的处理结果。*  以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。*  @Repository,@Service,@Controller是给项目的对象分层的。*/
//使用value属性,指定对象名称
//@Component(value = "myStudent")//省略value
@Component("myStudent")//不指定对象名称,由spring提供默认名称: 类名的首字母小写
//@Component
public class Student {private String name;private Integer age;public Student() {System.out.println("==student无参数构造方法===");}public void setName(String name) {this.name = name;}public void setAge(Integer age) {this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age +'}';}
}

另外,Spring 还提供了 3 个创建对象的注解:
➢ @Repository 用于对 DAO 实现类进行注解
➢ @Service 用于对 Service 实现类进行注解
➢ @Controller 用于对 Controller 实现类进行注解

这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
@Repository,@Service,@Controller 是对@Component 注解的细化,标注不同层的对象。即持久层对象,业务层对象,控制层对象。

@Component 不指定 value 属性,bean 的 id 是类名的首字母小写。

2.4.4 简单类型属性注入@Value

需要在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。

@Value: 简单类型的属性赋值
属性: value 是String类型的,表示简单类型的属性值
位置: 1.在属性定义的上面,无需set方法,推荐使用。
2.在set方法的上面

@Component("myStudent")
public class Student {/*** @Value: 简单类型的属性赋值*   属性: value 是String类型的,表示简单类型的属性值*   位置: 1.在属性定义的上面,无需set方法,推荐使用。*         2.在set方法的上面*/@Value("李四" )private String name;private Integer age;public Student() {System.out.println("==student无参数构造方法===");}@Value("30")public void setAge(Integer age) {System.out.println("setAge:"+age);this.age = age;}@Overridepublic String toString() {return "Student{" +"name='" + name + '\'' +", age=" + age;}
}

2.4.5 byType自动注入@Autowired

需要在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配 Bean 的方式。

@Autowired:默认使用的是byType自动注入。优先自动匹配相同数据类型的对象,找不到再找同源类型的对象,而不是像xml一样同时找同源类。)
位置:1)在属性定义的上面,无需set方法, 推荐使用
2)在set方法的上面

@Component("myStudent")
public class Student {@Value("李四" )private String name;@Value("20" )private Integer age;/*** 引用类型* @Autowired: spring框架提供的注解,实现引用类型的赋值。* spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType* @Autowired:默认使用的是byType自动注入。*  位置:1)在属性定义的上面,无需set方法, 推荐使用*       2)在set方法的上面*/@Autowiredprivate School school;
}

2.4.6 byName自动注入@Autowired与@Qualifier

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用 于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到 set 方法上。

spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType,如果要使用byName方式,需要做的是:
1.在属性上面加入@Autowired
2.在属性上面加入@Qualifier(value=“bean的id”) :表示使用指定名称的bean完成赋值。

@Component("myStudent")
public class Student {@Value("李四" )private String name;@Value("30")private Integer age;/*** 引用类型* @Autowired: spring框架提供的注解,实现引用类型的赋值。* spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType*  如果要使用byName方式,需要做的是:*  1.在属性上面加入@Autowired*  2.在属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值。*///byName自动注入@Autowired@Qualifier("mySchool")private School school;
}
@Component("mySchool")
public class School {@Value("人民大学")private String name;@Value("北京的海淀区")private String address;
}

@Autowired 还有一个属性 required默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

@Component("myStudent")
public class Student {@Value("李四" )private String name;@Value("30")private Integer age;//byName自动注入@Autowired(required = false)@Qualifier("mySchool")private School school;
}

2.4.7 JDK注解@Resource自动注入

Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean, 也可以按类型匹配 Bean默认是按名称注入。使用该注解,要求 JDK 必须是 6 及以上版本。 @Resource 可在属性上,也可在 set 方法上。

1.byType注入引用类型属性

@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean, 则会按照类型进行 Bean 的匹配注入。

@Resource
private School school;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9dg6Qlq9-1649172819326)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402223814695.png)]

2.byName注入引用类型属性

@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

@Resource("mySchool")
private School school;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w26tTkkn-1649172819326)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220402223901129.png)]

2.4.8 注解与XML的对比

注解优点是:

  • 方便

  • 直观

  • 高效(代码少,没有配置文件的书写那么复杂)。

其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML 方式优点是:

  • 配置和代码是分离的

  • 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。

xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

2.4.9 属性配置文件注入

<!--加载属性配置文件-->
<context:property-placeholder location="classpath:test.properties" />
@Component("myStudent")
public class Student {@Value("${myname}") //使用属性配置文件中的数据private String name;@Value("${myage}")  //使用属性配置文件中的数据private Integer age;public void setName(String name) {this.name = name;}public void setAge(Integer age) {System.out.println("setAge:"+age);this.age = age;}
}
myname=lisi
myage=20

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dipUQv0O-1649172819327)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403145604031.png)]

第三章 AOP面向切面编程

3.1不使用OP的开发方式

3.1.1动态代理

实现方式:

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

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

3.1.2.动态代理的作用

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

3.2AOP概述

3.2.1AOP简介

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程 序运行过程。 AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。Aop就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方式,使用动态代理。

AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 Spring 框架中的一个重要内容。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程 序的可重用性,同时提高了开发的效率。

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑—转账。

3.2.2面向切面编程对有什么好处?

1.减少重复;

2.专注业务;

注意:面向切面编程只是面向对象编程的一种补充。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2s7GKHGf-1649172819329)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403164344762.png)]

3.2.3AOP相关术语

Aspect: 切面,给你的目标类增加的功能,就是切面。 像上面用的日志,事务都是切面。切面的特点: 一般都是非业务方法,独立使用的。
Orient:面向, 对着。
Programming:编程。

iop面向接口编程、oop: 面向对象编程

怎么理解面向切面编程 ?
1)需要在分析项目功能时,找出切面。
2)合理的安排切面的执行时间(在目标方法前, 还是目标方法后)
3)合理的安全切面执行的位置,在哪个类,哪个方法增加增强功能

说一个切面有三个关键的要素:
1)切面的功能代码,切面干什么。(what)
2)切面的执行位置,使用Pointcut表示切面执行的位置。(where)
3)切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。(when)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1Db7vA8P-1649172819332)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403143207646.png)]

1.切面(Aspect)

切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面 是通知(Advice)。实际就是对主业务逻辑的一种增强

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

2.连接点(JoinPoint)

连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

连接业务方法和切面的位置。 就某类中的业务方法

3.切入点(Pointcut)

切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。

被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

4.目标对象(Target)

目标对象指将要被增强的对象 。即包含主业务逻辑的类的对象。上 例 中 的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。

5.通知(Advice)

通知表示切面的执行时间,Advice 也叫增强。上例中的 MyInvocationHandler 就可以理 解为是一种通知。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

切入点定义切入的位置,通知定义切入的时间。

3.4 AOP实现

AOP是一个规范,是动态的一个规范化,一个标准
AOP的技术实现框架:

  1. Spring:Spring的AOP实现较为笨重,一般用在事务处理。

  2. aspectJ:一个开源,专门做AOP的框架,隶属于Eclipse基金会。spring框架中集成了aspectj框架。

aspectJ框架实现AOP有两种方式:
1)使用xml配置文件,一般用于配置全局事务。
2)使用注解。一般在项目开发中使用这种方式。

3.5 AspectJ对AOP的实现

3.5.1 AspectJ 的通知类型

切面的执行时间, 这个执行时间在规范中叫做Advice(通知,增强), 在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签:
(1)前置通知
(2)后置通知
(3)环绕通知
(4)异常通知
(5)最终通知

3.5.2 AspectJ 的切入点表达式

AspectJ 定义了专门的表达式用于指定切入点。表达式的原型是:

execution(modifiers-pattern? ret-type-pattern  declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)

解释:

modifiers-pattern] 访问权限类型

ret-type-pattern 返回值类型

declaring-type-pattern 包名类名

name-pattern(param-pattern) 方法名(参数类型和参数个数)

throws-pattern 抛出异常类型

?表示可选的部分

以上表达式共 4 个部分。

execution(访问权限 方法返回值 方法声明(参数) 异常类型)

切入点表达式要匹配的对象就是目标方法的方法名。所以,execution 表达式中明显就 是方法的签名。注意,表达式中黑色文字表示可省略部分,各部分间用空格分开。在其中可 以使用以下符号:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lXW1KGhW-1649172819334)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403172047895.png)]

3.5.3 AspectJ的开发环境

1.maven 依赖
<dependencies>
<!--单元测试:通过单元测试可以测试每一个方法-->
<dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope>
</dependency>
<!--Spring的依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>4.3.16.RELEASE</version>
</dependency>
<!--aspectj的依赖-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>4.3.16.RELEASE</version>
</dependency>
2.引入AOP约束

在 AspectJ 实现 AOP 时,要引入 AOP 的约束。配置文件中使用的 AOP 约束中的标签, 均是 AspectJ 框架使用的,而非 Spring 框架本身在实现 AOP 时使用的。 AspectJ 对于 AOP 的实现有注解和配置文件两种方式,常用是注解方式。(第4、7、8行)

<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"></beans>

在IDEA中开发,可以省略这一步。在添加aop标签时会自动引入约束文件。

3.5.4 AspectJ 基于注解的 AOP 实现

(1)实现步骤

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LEI5Erok-1649172819336)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403173203793.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mjnNOGwN-1649172819336)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403173222668.png)]

1.创建目标类

业务接口与实现类

public interface SomeService {void doSome(String name,Integer age);
}
//目标类
public class SomeServiceImpl implements SomeService {@Overridepublic void doSome(String name,Integer age) {//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间System.out.println("====目标方法doSome()====");}
}
2.创建切面类

类中定义了若干普通方法,将作为不同的通知方法,用来增强功能。

/***  @Aspect : 是aspectj框架中的注解。*     作用:表示当前类是切面类。*     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码*     位置:在类定义的上面*/
@Aspect
public class MyAspect {/*** 定义方法,方法是实现切面功能的。* 方法的定义要求:* 1.公共方法 public* 2.方法没有返回值* 3.方法名称自定义* 4.方法可以有参数,也可以没有参数。*   如果有参数,参数不是自定义的,有几个参数类型可以使用。*//*** @Before: 前置通知注解*   属性:value ,是切入点表达式,表示切面的功能执行的位置。*   位置:在方法的上面* 特点:*  1.在目标方法之前先执行的*  2.不会改变目标方法的执行结果*  3.不会影响目标方法的执行。*/@Before(value = "execution(public void com.bjpowernode.ba01.SomeServiceImpl.doSome(String,Integer))")public void myBefore(){//就是你切面要执行的功能代码System.out.println("前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());}
}
3.创建spring配置文件

在定义好切面 Aspect 后,需要通知 Spring 容器,让容器生成“目标类+ 切面”的代理对象。这个代理是由容器自动生成的。只需要在 Spring 配置文件中注册一个基于 aspectj 的 自动代理生成器,其就会自动扫描到@Aspect 注解,并按通知类型切入点,将其织入,并生成代理。

<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><!--把对象交给spring容器,由spring容器统一创建,管理对象--><!--声明目标对象--><bean id="someService" class="com.bjpowernode.ba08.SomeServiceImpl" /><!--声明切面类对象--><bean id="myAspect" class="com.bjpowernode.ba08.MyAspect" /><!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象所以目标对象就是被修改后的代理对象.aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。--><aop:aspectj-autoproxy /></beans>

其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点

4.创建测试类
public class MyTest01 {@Testpublic void test01(){String config="applicationContext.xml";ApplicationContext ctx = new ClassPathXmlApplicationContext(config);//从容器中获取目标对象SomeService proxy = (SomeService) ctx.getBean("someService");//com.sun.proxy.$Proxy8 :jdk动态代理System.out.println("proxy:"+proxy.getClass().getName());//通过代理的对象执行方法,实现目标方法执行时,增强了功能proxy.doSome("lisi",20);}
}

(2)AspectJ 通知注解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1gR9CytY-1649172819337)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403202051260.png)]

当连接点匹配成功时,创建的是一个代理对象

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0VnLtJj3-1649172819339)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403202217864.png)]

当匹配不成功时,创建的是一个实现类

1.@Before前置通知-方法有JoinPoint参数

​ 在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个 JoinPoint 类型参数。该类型的对象本身就是切入点表达式。通过该参数,可获取切入点表达式方法签名目标对象等。不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。JoinPoint必须是形式参数的第一个。

JoinPoint 代表的是业务方法,这个方法中要加入切面功能,在下面这个例子中,JoinPoint 就是具体的doSome方法。

/***  @Aspect : 是aspectj框架中的注解。*     作用:表示当前类是切面类。*     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码*     位置:在类定义的上面*/
@Aspect
public class MyAspect {/*** 定义方法,方法是实现切面功能的。* 方法的定义要求:* 1.公共方法 public* 2.方法没有返回值* 3.方法名称自定义* 4.方法可以有参数,也可以没有参数。*   如果有参数,参数不是自定义的,有几个参数类型可以使用。*//*** @Before: 前置通知注解*   属性:value ,是切入点表达式,表示切面的功能执行的位置。*   位置:在方法的上面* 特点:*  1.在目标方法之前先执行的*  2.不会改变目标方法的执行结果*  3.不会影响目标方法的执行。** 指定通知方法中的参数 : JoinPoint* JoinPoint:业务方法,要加入切面功能的业务方法。(这里实际上代表的就是doSome这个业务方法)*    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。*    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.*    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数*/@Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")public void myBefore(JoinPoint jp){//获取方法的完整定义System.out.println("方法的签名(定义)="+jp.getSignature());System.out.println("方法的名称="+jp.getSignature().getName());//获取方法的实参Object args [] = jp.getArgs();for (Object arg:args){System.out.println("参数="+arg);}//就是你切面要执行的功能代码System.out.println("2=====前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());}
2.@AfterReturning后置通知-注解有returning属性

在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变量名的。所以,被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

注意:returning 属性自定义变量名必须和通知方法的形参名一样。
例如,在业务接口中定义一个有返回值的抽象方法:

public interface SomeService {void doSome(String name, Integer age);String doOther(String name,Integer age);
}

实现类:

//目标类
public class SomeServiceImpl implements SomeService {@Overridepublic void doSome(String name,Integer age) {//给doSome方法增加一个功能,在doSome()执行之前, 输出方法的执行时间System.out.println("====目标方法doSome()====");}@Overridepublic String doOther(String name, Integer age) {System.out.println("====目标方法doOther()====");return "abcd";}
}

切面类:

@Aspect
public class MyAspect {/*** @AfterReturning:后置通知*    属性:1.value 切入点表达式*         2.returning 自定义的变量,表示目标方法的返回值的。*          自定义变量名必须和通知方法的形参名一样。*    位置:在方法定义的上面* 特点:*  1。在目标方法之后执行的。*  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能*      Object res = doOther();*  3. 可以修改这个返回值**  后置通知的执行*    Object res = doOther();*    参数传递: 传值, 传引用*    myAfterReturing(res);*    System.out.println("res="+res)*/@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")public void myAfterReturing(JoinPoint jp,Object res){// Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理System.out.println("后置通知:方法的定义"+ jp.getSignature());System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);if(res.equals("abcd")){//做一些功能} else{//做其它功能}//修改目标方法的返回值, 看一下是否会影响 最后的方法调用结果if( res != null){res = "Hello Aspectj";}}
}
3.@Around 环绕通知-增强方法有 ProceedingJoinPoint 参数

在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值,Object 类型。并 且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。 接口增加方法:

注意:环绕通知的返回值就是目标方法的执行结果,可以被修改。ProceedingJoinPoint继承了JoinPoint。

public interface SomeService {void doSome(String name, Integer age);String doOther(String name, Integer age);String doFirst(String name,Integer age);
}

接口方法实现:

@Override
public String doFirst(String name, Integer age) {System.out.println("====业务方法doFirst()====");return "doFirst";
}

定义切面:

@Aspect
public class MyAspect {/*** 环绕通知方法的定义格式*  1.public*  2.必须有一个返回值,推荐使用Object*  3.方法名称自定义*  4.方法有参数,固定的参数 ProceedingJoinPoint*//*** @Around: 环绕通知*    属性:value 切入点表达式*    位置:在方法的定义什么* 特点:*   1.它是功能最强的通知*   2.在目标方法的前和后都能增强功能。*   3.控制目标方法是否被调用执行*   4.修改原来的目标方法的执行结果。 影响最后的调用结果**  环绕通知,等同于jdk动态代理的,InvocationHandler接口**  参数:  ProceedingJoinPoint 就等同于 Method*         作用:执行目标方法的*  返回值: 就是目标方法的执行结果,可以被修改。*  环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务*/@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")public Object myAround(ProceedingJoinPoint pjp) throws Throwable {String name = "";//获取第一个参数值Object args [] = pjp.getArgs();if( args!= null && args.length > 1){Object arg=  args[0];name =(String)arg;}//实现环绕通知Object result = null;System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());//1.目标方法调用if( "zhangsan".equals(name)){//符合条件,调用目标方法result = pjp.proceed(); //method.invoke(); Object result = doFirst();}System.out.println("环绕通知:在目标方法之后,提交事务");//2.在目标方法的前或者后加入功能//修改目标方法的执行结果, 影响方法最后的调用结果if( result != null){result = "Hello AspectJ AOP";}//返回目标方法的执行结果return result;}
}
4.@AfterThrowing 异常通知-注解中有 throwing 属性

在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。 当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。

接口实现类:

@Override
public void doSecond() {System.out.println("执行业务方法doSecond()" + (10/0));
}

切面类:

@Aspect
public class MyAspect {/*** 异常通知方法的定义格式*  1.public*  2.没有返回值*  3.方法名称自定义*  4.方法有个一个Exception, 如果还有就是JoinPoint。*//*** @AfterThrowing:异常通知*     属性:1. value 切入点表达式*          2. throwinng 自定义的变量,表示目标方法抛出的异常对象。*             变量名必须和方法的参数名一样* 特点:*   1. 在目标方法抛出异常时执行的*   2. 可以做异常的监控程序, 监控目标方法执行时是不是有异常。*      如果有异常,可以发送邮件,短信进行通知**  执行就是:*   try{*       SomeServiceImpl.doSecond(..)*   }catch(Exception e){*       myAfterThrowing(e);*   }*/@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")public void myAfterThrowing(Exception ex) {System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());//发送邮件,短信,通知开发人员}
}
5.@After 最终通知

无论目标方法是否抛出异常,该增强均会被执行。

切面类:

@Aspect
public class MyAspect {/*** 最终通知方法的定义格式*  1.public*  2.没有返回值*  3.方法名称自定义*  4.方法没有参数,如果有可以是JoinPoint。*//*** @After :最终通知*    属性: value 切入点表达式*    位置: 在方法的上面* 特点:*  1.总是会执行*  2.在目标方法之后执行的**  try{*      SomeServiceImpl.doThird(..)*  }catch(Exception e){*  }finally{*      myAfter()*  }*/@After(value = "execution(* *..SomeServiceImpl.doThird(..))")public  void  myAfter(){System.out.println("执行最终通知,总是会被执行的代码");//一般做资源清除工作的。}
}
6.@Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。 AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。 其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均 可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解 的方法一般使用 private 的标识方法,即没有实际作用的方法。

@Aspect
public class MyAspect {@After(value = "mypt()")public  void  myAfter(){System.out.println("执行最终通知,总是会被执行的代码");//一般做资源清除工作的。}/*** @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。*            可以使用@Pointcut*    属性:value 切入点表达式*    位置:在自定义的方法上面* 特点:*   当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。*   其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了*/@Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )private void mypt(){//无需代码,}
}

(3)Spring对AOP实现方式的管理

1.当有接口时,默认使用的是JDK动态代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3VbLRppo-1649172819340)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403231213748.png)]

切面类:

@Before(value = "execution(void *..*.do*(..))")
public void myBefore(JoinPoint jp) {System.out.println("前置通知,切面功能,在目标方法之前输出执行时间" + new Date());
}

配置文件:

<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="someService" class="com.gz.service.impl.SomeServiceImpl"/><bean id="myAspect" class="com.gz.service.MyAspect"/><aop:aspectj-autoproxy/></beans>

测试类:

@Test
public void shouldAnswerWithTrue()
{String config = "applicationContext.xml";ApplicationContext proxy = new ClassPathXmlApplicationContext(config);SomeService someService = (SomeService) proxy.getBean("someService");System.out.println(someService.getClass().getName());someService.doSome("lisi",20);
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Alnloxy0-1649172819341)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403231309266.png)]

2.当没有接口时,默认使用的是cglib动态代理

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xY0H6rBp-1649172819342)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403231645169.png)]

切面类:

@Before(value = "execution(void *..*.do*(..))")
public void myBefore(JoinPoint jp) {System.out.println("前置通知,切面功能,在目标方法之前输出执行时间" + new Date());
}

配置文件:

<?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: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/aop https://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="someService" class="com.gz.service.impl.SomeServiceImpl"/><bean id="myAspect" class="com.gz.service.MyAspect"/><aop:aspectj-autoproxy/></beans>

测试类:

public class MyTest07 {@Testpublic void test01(){String config="applicationContext.xml";ApplicationContext ctx = new ClassPathXmlApplicationContext(config);//从容器中获取目标对象SomeServiceImpl proxy = (SomeServiceImpl) ctx.getBean("someService");/*** 目标类没有接口,使用cglib动态代理, spring框架会自动应用cglib* com.bjpowernode.ba07.SomeServiceImpl$$EnhancerBySpringCGLIB$$575c8b90*/System.out.println("proxy:"+proxy.getClass().getName());//通过代理的对象执行方法,实现目标方法执行时,增强了功能proxy.doThird();}
}

执行结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-uiLvlQmT-1649172819343)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403231528669.png)]

3.当有接口时,还是想强制使用cglib动态代理

那么就要对配置文件进行声明,需要使用cglib动态代理。这种方式在程序执行时的效率会比较高一些。

<?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:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/aophttps://www.springframework.org/schema/aop/spring-aop.xsd"><bean id="someService" class="com.bjpowernode.ba08.SomeServiceImpl" /><bean id="myAspect" class="com.bjpowernode.ba08.MyAspect" /><!--如果你期望目标类有接口,使用cglib代理proxy-target-class="true":告诉框架,要使用cglib动态代理--><aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ltkeer19-1649172819344)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220403232549742.png)]

3.5.5 AspectJ 基于XML的 AOP 实现

3.6 Spring对AOP的实现

第四章 Spring集成Mybatis

可以把mybatis框架和spring集成在一起,向一个框架一样使用。用的技术是ioc 。将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring 来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。

实现 Spring 与 MyBatis 的整合常用的方式:扫描的 Mapper 动态代理

Spring 像插线板一样,mybatis 框架是插头,可以容易的组合到一起。插线板 spring 插 上 mybatis,两个框架就是一个整体。

回顾mybatis使用步骤
1.定义dao接口 ,StudentDao
2.定义mapper文件 StudentDao.xml
3.定义mybatis的主配置文件 mybatis.xml
4.创建dao的代理对象, StudentDao dao = SqlSession.getMapper(StudentDao.class);

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

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6uZrxxOF-1649172819345)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405152400351.png)]

4.1MySQL新建表 Student

4.2创建Maven项目加入依赖

<dependencies><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!--spring核心ioc--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version></dependency><!--做spring事务用到的--><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.5.RELEASE</version></dependency><!--mybatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.1</version></dependency><!--mybatis和spring集成的依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.1</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.9</version></dependency><!--阿里公司的数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version></dependency>
</dependencies>

4.3创建实体类

public class Student {//属性名和列名一样。private Integer id;private String name;private String email;private Integer age;
}

4.4创建StudentDao

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

4.5创建映射文件mapper

在Dao接口的包中创建MyBatis的映射文件 mapper,命名与接口名相同,本例为StudentDao.xml。mapper中的namespace取值也为Dao接口的全限定性名。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.StudentDao"><insert id="insertStudent">insert into student values(#{id},#{name},#{email},#{age})</insert><select id="selectStudents" resultType="Student">select id,name,email,age from student order by id desc</select>
</mapper>

4.6创建Service接口和实现类

public interface StudentService {int addStudent(Student student);List<Student> queryStudents();
}
public class StudentServiceImpl implements StudentService {//引用类型private StudentDao studentDao;//使用set注入,赋值public void setStudentDao(StudentDao studentDao) {this.studentDao = studentDao;}@Overridepublic int addStudent(Student student) {int nums = studentDao.insertStudent(student);return nums;}@Overridepublic List<Student> queryStudents() {List<Student> students = studentDao.selectStudents();return students;}
}

4.7创建mybatis主配置文件

在 src 下定义 MyBatis 的主配置文件,命名为 mybatis.xml。

这里有两点需要注意:
(1)主配置文件中不再需要数据源的配置了。因为数据源要交给 Spring 容器来管理了。 由于使用阿里的数据库连接池,所以不需要<environments/>
(2)这里对 mapper 映射文件的注册,使用标签,即只需给出 mapper 映射文件 所在的包即可。因为 mapper 的名称与 Dao 接口名相同。这种方式的好处是,若有多个映射文件,这里的配置也是不用改变的。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>   <!--设置别名--><typeAliases><!--name:实体类所在的包名表示com.bjpowernode.domain包中的列名就是别名你可以使用Student表示com.bjpowenrode.domain.Student--><package name="com.bjpowernode.domain"/></typeAliases><!-- sql mapper(sql映射文件)的位置--><mappers><!--name:是包名,这个包中的所有mapper.xml一次都能加载--><package name="com.bjpowernode.dao"/></mappers>
</configuration>

4.8创建Spring配置文件

要使用dao对象,需要使用getMapper()方法,

  • 怎么能使用getMapper()方法,需要哪些条件?

    1.获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。

    2.创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象需要SqlSessionFactory对象, 使用Factory能获取SqlSession ,有了SqlSession就能有dao ,目的就是获取dao对象。Factory创建需要读取主配置文件。

我们会使用独立的连接池类替换mybatis默认自己带的, 把连接池类也交给spring创建。

  • 通过以上的说明,我们需要让spring创建以下对象

    1.独立的连接池类的对象, 使用阿里的druid连接池

    2.SqlSessionFactory对象

    3.创建出dao对象

4.8.1从属性文件读取数据库连接信息

Spring 配置文件从属性文件中读取数据时,需要在的 value 属性中使用${ }, 将在属性文件中定义的 key 括起来,以引用指定属性的值。

< context:property-placeholder/>方式声明属性配置文件在哪,标签中有一个属性 location,用于指定属性文件的位置。

<!--把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容spring知道jdbc.properties文件的位置
-->
<context:property-placeholder location="classpath:jdbc.properties" />

4.8.2数据源的配置

Druid 数据源 DruidDataSource Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能够提供强大的监控和扩展功能。

配置DataSource连接池:

<!--声明数据源DataSource, 作用是连接数据库的-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"init-method="init" destroy-method="close"><!--set注入给DruidDataSource提供连接数据库信息 --><!--    使用属性配置文件中的数据,语法 ${key} --><property name="url" value="${jdbc.url}" /><!--setUrl()--><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.passwd}" /><property name="maxActive" value="${jdbc.max}" />
</bean>

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3zJPKA0U-1649172819346)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405162634078.png)]

4.8.3注册SqlSessionFactoryBean

这个类内部创建SqlSessionFactory,因为SqlSessionFactory这个类需要读取到mybatis主配置文件的内容,而之前mybatis主配置文件里面的内容主要包含两个:

  1. 数据库信息(数据源)

  2. mapper文件的位置

    所以我们需要为这个SqlSessionFactoryBean标签设置两个属性。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pLA9LZ6A-1649172819347)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405165339274.png)]

<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的SqlSessionFactory  sqlSessionFactory = new ..
-->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--set注入,把数据库连接池付给了dataSource属性--><property name="dataSource" ref="myDataSource" /><!--mybatis主配置文件的位置configLocation属性是Resource类型,读取配置文件它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置--><property name="configLocation" value="classpath:mybatis.xml" />
</bean>

4.8.4定义Mapper扫描配置器

Mapper 扫描配置器 MapperScannerConfigurer 会自动生成指定的基本包中 mapper 的代理对象。该 Bean 无需设置 id 属性。basePackage 使用分号或逗号设置多个包。

因为MapperScannerConfigurer类内部调用getMapper()生成dao的代理对象。

而在之前的例子中需要sqlSessionFactory对象调用openSession()来获取Session对象,从而调用getMapper(dao.class)来创建dao接口的代理对象。所以我们需要给MapperScannerConfigurer标签传入两个属性:

  1. sqlSessionFactory
  2. 在此处我们用basePackage,他会自动扫描在此包下的所有dao接口。
<!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--指定SqlSessionFactory对象的id--><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /><!--指定包名, 包名是dao接口所在的包名。MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次getMapper()方法,得到每个接口的dao对象。创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写--><property name="basePackage" value="com.bjpowernode.dao"/>
</bean>

4.1.9向 Service 注入接口名

向 Service 注入 Mapper 代理对象时需要注意,由于通过 Mapper 扫描配置器 MapperScannerConfigurer 生成的 Mapper 代理对象没有名称,所以在向 Service 注入 Mapper 代理时,无法通过名称注入。但可通过接口的简单类名注入,因为生成的是这个 Dao 接口 的对象。

创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是接口名首字母小写

<!--声明service-->
<bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl"><property name="studentDao" ref="studentDao" />
</bean>

4.1.10Spring 配置文件全部配置

<?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:property-placeholder location="classpath:jdbc.properties" /><bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"init-method="init" destroy-method="close"><property name="url" value="${jdbc.url}" /><!--setUrl()--><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.passwd}" /><property name="maxActive" value="${jdbc.max}" /></bean><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><property name="dataSource" ref="myDataSource" /><property name="configLocation" value="classpath:mybatis.xml" /></bean><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /><property name="basePackage" value="com.bjpowernode.dao"/></bean><bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl"><property name="studentDao" ref="studentDao" /></bean>
</beans>

第五章 Spring 事务

5.1理论知识

1.什么是事务
讲mysql的时候,提出了事务。 事务是指一组sql语句的集合, 集合中有多条sql语句
可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,
或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。

2.在什么时候想到使用事务
当我的操作,涉及得到多个表,或者是多个sql语句的insert,update,delete。需要保证
这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。

在java代码中写程序,控制事务,此时事务应该放在那里呢?
service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句

3.通常使用JDBC访问数据库, 还是mybatis访问数据库怎么处理事务
jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

4.问题中事务的处理方式,有什么不足
1)不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
2)掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
3)处理事务的多种方法。

总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。

5.怎么解决不足
spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理
使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Fsz4xSOO-1649172819348)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220404230839666.png)]

6.处理事务,需要怎么做,做什么
spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了

  1. 事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
    事务管理器是一个接口和他的众多实现类。
    接口:PlatformTransactionManager ,定义了事务重要方法 commit ,rollback
    实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
    mybatis访问数据库—spring创建好的是DataSourceTransactionManager
    hibernate访问数据库----spring创建的是HibernateTransactionManager

    怎么使用:你需要告诉spring 你用是那种数据库的访问技术,怎么告诉spring呢?
    声明数据库访问技术对于的事务管理器实现类, 在spring的配置文件中使用声明就可以了
    例如,你要使用mybatis访问数据库,你应该在xml配置文件中
    <bean id=“xxx" class=“…DataSourceTransactionManager”>

  2. 你的业务方法需要什么样的事务,说明需要事务的类型。
    说明方法需要的事务:

​ 1)事务的隔离级别:有4个值。
​ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
​ ➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
​ ➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
​ ➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
​ ➢ SERIALIZABLE:串行化。不存在并发问题。

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

​ 3)事务的传播行为 : 控制业务方法是不是有事务的, 是什么样的事务的。
行为,表示你的业务方法调用时,事务在方法之间是如何使用的。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
​ 以上三个需要掌握的

​ PROPAGATION_MANDATORY
​ PROPAGATION_NESTED
​ PROPAGATION_NEVER
​ PROPAGATION_NOT_SUPPORTED

  1. 事务提交事务,回滚事务的时机

​ 1)当你的业务方法,执行成功,没有异常抛出,当方法执行完毕,spring在方法执行后提交事务。事务管理器commit

​ 2)当你的业务方法抛出运行时异常或ERROR, spring执行回滚,调用事务管理器的rollback
​ 运行时异常的定义: RuntimeException 和他的子类都是运行时异常, 例如NullPointException , NumberFormatException

​ 3)当你的业务方法抛出非运行时异常, 主要是受查异常时,提交事务
受查异常:在你写代码中,必须处理的异常。例如IOException, SQLException

总结spring的事务

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

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

5.2Spring的事务管理分类

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:

(1)使用 Spring 的事务注解管理事务

(2)使用 AspectJ 的 AOP 配置管理事务

5.3Spring 事务管理 API

5.3.1事务管理器接口

事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回 滚,及获取事务的状态信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJaCBbN3-1649172819349)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405193841610.png)]

1.常用的两个实现类

PlatformTransactionManager 接口有两个常用的实现类:

➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。

➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。

Spring 的回滚方式

Spring 事务的默认回滚方式是:发生运行时异常error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RyevYU35-1649172819350)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405194450362.png)]

异常的分类:

运行时异常,是 RuntimeException 类或其子类,即只有在运行时才出现的异常。如, NullPointerException、ArrayIndexOutOfBoundsException、IllegalArgumentException 等均属于 运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但,只要代 码编写足够仔细,程序足够健壮,运行时异常是可以避免的。

受查异常,也叫编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理, 则无法通过编译。如 SQLException,ClassNotFoundException,IOException 等都属于受查异常。

5.3.2.事务定义接口

事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、 事务传播行为、事务默认超时时限,及对它们的操作

1.定义了五个事务隔离级别常量

DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
➢ SERIALIZABLE:串行化。不存在并发问题。

2. 定义了七个事务传播行为常量

所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。

PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
​ 以上三个需要掌握的

​ PROPAGATION_MANDATORY
​ PROPAGATION_NESTED
​ PROPAGATION_NEVER
​ PROPAGATION_NOT_SUPPORTED

PROPAGATION_REQUIRED

指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。如该传播行为加在 doOther()方法上。

若 doSome()方法在调用 doOther()方法时就是在事务内运行的,则 doOther()方法的执行也加入到该事务内执行。

若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-l8CDJnB2-1649172819350)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405195549048.png)]

PROPAGATION_SUPPORTS

指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aSLp4sBV-1649172819351)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405195615572.png)]

PROPAGATION_REQUIRES_NEW

总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-J34U5p4X-1649172819352)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405195648224.png)]

3.定义了默认事务超时时限

常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。 注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该 值一般就使用默认值即可。

5.4程序搭建

5.4.1创建数据库表

goods表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xx7cLP85-1649172819352)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405202936247.png)]

sale表:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nwbw84eM-1649172819353)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405203009035.png)]

5.4.2加入maven依赖

<dependencies><!--单元测试--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.11</version><scope>test</scope></dependency><!--spring核心ioc--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.2.5.RELEASE</version></dependency><!--做spring事务用到的--><dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.5.RELEASE</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-jdbc</artifactId><version>5.2.5.RELEASE</version></dependency><!--mybatis依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><version>3.5.1</version></dependency><!--mybatis和spring集成的依赖--><dependency><groupId>org.mybatis</groupId><artifactId>mybatis-spring</artifactId><version>1.3.1</version></dependency><!--mysql驱动--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.20</version></dependency><!--阿里公司的数据库连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.12</version></dependency>
</dependencies>

5.4.3创建实体类

public class Goods {private Integer id;private String name;private Integer amount;private Float price;
}
public class Sale {private Integer id;private Integer gid;private Integer nums;
}

5.4.4创建dao接口和mapper文件

public interface GoodsDao {//更新库存//goods表示本次用户购买的商品信息, id, 购买数量int updateGoods(Goods goods);//查询商品的信息Goods selectGoods(Integer id);
}
public interface SaleDao {//增加销售记录int insertSale(Sale sale);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.GoodsDao"><select id="selectGoods" resultType="com.bjpowernode.domain.Goods">select id,name,amount,price from goods where id=#{gid}</select><update id="updateGoods">update goods set amount = amount - #{amount} where id=#{id}</update>
</mapper>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapperPUBLIC "-//mybatis.org//DTD Mapper 3.0//EN""http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bjpowernode.dao.SaleDao"><insert id="insertSale">insert into sale(gid,nums) values(#{gid},#{nums})</insert>
</mapper>

5.4.5创建mybatis主配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration><!--设置别名--><typeAliases><!--name:实体类所在的包名表示com.bjpowernode.domain包中的列名就是别名你可以使用Student表示com.bjpowenrode.domain.Student--><package name="com.bjpowernode.domain"/></typeAliases><!-- sql mapper(sql映射文件)的位置--><mappers><!--name:是包名, 这个包中的所有mapper.xml一次都能加载--><package name="com.bjpowernode.dao"/></mappers>
</configuration>

5.4.5创建Service接口和实现类

public interface BuyGoodsService {//购买商品的方法, goodsId:购买商品的编号, nums:购买的数量void buy(Integer goodsId,Integer nums);
}
public class BuyGoodsServiceImpl implements BuyGoodsService {private SaleDao saleDao;private GoodsDao goodsDao;@Overridepublic void buy(Integer goodsId, Integer nums) {System.out.println("=====buy方法的开始====");//记录销售信息,向sale表添加记录Sale sale  = new Sale();sale.setGid(goodsId);sale.setNums(nums);saleDao.insertSale(sale);//更新库存Goods goods  = goodsDao.selectGoods(goodsId);if( goods == null){//商品不存在throw  new  NullPointerException("编号是:"+goodsId+",商品不存在");} else if( goods.getAmount() < nums){//商品库存不足throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");}//修改库存了Goods buyGoods = new Goods();buyGoods.setId( goodsId);buyGoods.setAmount(nums);goodsDao.updateGoods(buyGoods);System.out.println("=====buy方法的完成====");}public void setSaleDao(SaleDao saleDao) {this.saleDao = saleDao;}public void setGoodsDao(GoodsDao goodsDao) {this.goodsDao = goodsDao;}
}

5.4.5创建spring配置文件

声明mybatis的对象交给spring创建
1)数据源DataSource
2)SqlSessionFactory
3) Dao对象
4)声明自定义的service
<?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"><!--把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容spring知道jdbc.properties文件的位置--><context:property-placeholder location="classpath:jdbc.properties" /><!--声明数据源DataSource, 作用是连接数据库的--><bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"init-method="init" destroy-method="close"><!--set注入给DruidDataSource提供连接数据库信息 --><!--    使用属性配置文件中的数据,语法 ${key} --><property name="url" value="${jdbc.url}" /><!--setUrl()--><property name="username" value="${jdbc.username}"/><property name="password" value="${jdbc.passwd}" /><property name="maxActive" value="${jdbc.max}" /></bean><!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的SqlSessionFactory  sqlSessionFactory = new ..--><bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"><!--set注入,把数据库连接池付给了dataSource属性--><property name="dataSource" ref="myDataSource" /><!--mybatis主配置文件的位置configLocation属性是Resource类型,读取配置文件它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置--><property name="configLocation" value="classpath:mybatis.xml" /></bean><!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。--><bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"><!--指定SqlSessionFactory对象的id--><property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /><!--指定包名, 包名是dao接口所在的包名。MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次getMapper()方法,得到每个接口的dao对象。创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写--><property name="basePackage" value="com.bjpowernode.dao"/></bean><!--声明service--><bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl"><property name="goodsDao" ref="goodsDao" /><property name="saleDao" ref="saleDao" /></bean></beans>

5.4.6创建测试类

public class MyTest {@Testpublic void test01(){String config="applicationContext.xml";ApplicationContext ctx = new ClassPathXmlApplicationContext(config);//从容器获取serviceBuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");//调用方法service.buy(1001,200);}
}

5.5使用 Spring 的事务注解管理事务

spring框架中提供的事务处理方案
1.适合中小项目使用的, 注解方案。
spring框架自己用aop实现给业务方法增加事务的功能, 使用@Transactional注解增加事务。
@Transactional注解是spring框架自己注解,放在public方法的上面,表示当前方法具有事务。
可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等

使用@Transactional的步骤:

  1. 需要声明事务管理器对象

  2. 开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。
    spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
    spring给业务方法加入事务:
    在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知

  @Around("你要增加的事务功能的业务方法名称")Object myAround(){开启事务,spring给你开启try{buy(1001,10);spring的事务管理器.commit();}catch(Exception e){spring的事务管理器.rollback();}}
  1. 在你的方法的上面加入@Trancational

通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。 @Transactional 的所有可选属性如下所示:

propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。

isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。

readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。

timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。

rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。

rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。

noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。

noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。

当然,若只有一个异常类时,可以不使用数组。 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。

若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

5.5.1声明事务管理器

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

5.5.2开启注解驱动

<!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象transaction-manager:事务管理器对象的id
-->
<tx:annotation-driven transaction-manager="transactionManager" />

5.5.3业务层 public 方法加入事务属性

事务注解可以放在类上,这样所有公共方法都会具有事务功能,但是意义不大,因为有些方法不需要事务。所以我们只需要放到有需要事务处理的方法上面。

rollbackFor:表示发生指定的异常一定回滚.
处理逻辑是:
1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,
如果是一定回滚。

public class BuyGoodsServiceImpl implements BuyGoodsService {private SaleDao saleDao;private GoodsDao goodsDao;/**** rollbackFor:表示发生指定的异常一定回滚.*   处理逻辑是:*     1) spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中*         如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。*     2) 如果你的抛出的异常不在rollbackFor列表中,spring会判断异常是不是RuntimeException,*         如果是一定回滚。**//* @Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT,readOnly = false,rollbackFor = {NullPointerException.class,  NotEnoughException.class})*///使用的是事务控制的默认值, 默认的传播行为是REQUIRED,默认的隔离级别DEFAULT//默认抛出运行时异常,回滚事务。@Transactional@Overridepublic void buy(Integer goodsId, Integer nums) {System.out.println("=====buy方法的开始====");//记录销售信息,向sale表添加记录Sale sale  = new Sale();sale.setGid(goodsId);sale.setNums(nums);saleDao.insertSale(sale);//更新库存Goods goods  = goodsDao.selectGoods(goodsId);if( goods == null){//商品不存在throw  new  NullPointerException("编号是:"+goodsId+",商品不存在");} else if( goods.getAmount() < nums){//商品库存不足throw new NotEnoughException("编号是:"+goodsId+",商品库存不足");}//修改库存了Goods buyGoods = new Goods();buyGoods.setId( goodsId);buyGoods.setAmount(nums);goodsDao.updateGoods(buyGoods);System.out.println("=====buy方法的完成====");}public void setSaleDao(SaleDao saleDao) {this.saleDao = saleDao;}public void setGoodsDao(GoodsDao goodsDao) {this.goodsDao = goodsDao;}
}

5.6使用 AspectJ 的 AOP 配置管理事务

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

实现步骤: 都是在xml配置文件中实现。
1)要使用的是aspectj框架,需要加入依赖

<dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.2.5.RELEASE</version></dependency>

2)声明事务管理器对象

< bean id=“xx” class=“DataSourceTransactionManager”>

3)声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)

4)配置aop:指定哪些哪类要创建代理。

5.6.1加入maven 依赖

<!--做spring事务用到的-->
<dependency><groupId>org.springframework</groupId><artifactId>spring-tx</artifactId><version>5.2.5.RELEASE</version>
</dependency>

5.6.2在容器中添加事务管理器

<!--声明式事务处理:和源代码完全分离的-->
<!--1.声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="myDataSource" />
</bean>

5.6.3配置事务通知

优先级匹配顺序:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DIPR79z7-1649172819354)(C:\Users\Dell\AppData\Roaming\Typora\typora-user-images\image-20220405140209548.png)]

为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。

例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。

<!--2.声明业务方法它的事务属性(隔离级别,传播行为,超时时间)id:自定义名称,表示 <tx:advice> 和 </tx:advice>之间的配置内容的transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager"><!--tx:attributes:配置事务属性--><tx:attributes><!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性name:方法名称,1)完整的方法名称,不带有包和类。2)方法可以使用通配符,* 表示任意字符propagation:传播行为,枚举值isolation:隔离级别rollback-for:你指定的异常类名,全限定类名。 发生异常一定回滚--><tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"rollback-for="java.lang.NullPointerException,com.bjpowernode.excep.NotEnoughException"/><!--使用通配符,指定很多的方法--><tx:method name="add*" propagation="REQUIRES_NEW" /><!--指定修改方法--><tx:method name="modify*" /><!--删除方法--><tx:method name="remove*" /><!--查询方法,query,search,find--><tx:method name="*" propagation="SUPPORTS" read-only="true" /></tx:attributes>
</tx:advice>

5.6.4配置增强器

指定将配置好的事务通知,织入给谁。

<!--配置aop-->
<aop:config><!--配置切入点表达式:指定哪些包中的类,要使用事务id:切入点表达式的名称,唯一值expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象com.bjpowernode.servicecom.crm.servicecom.service--><aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/><!--配置增强器:关联adivce和pointcutadvice-ref:通知,上面tx:advice哪里的配置pointcut-ref:切入点表达式的id--><aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
</aop:config>

5.6.5测试类

@Test
public void test01(){String config="applicationContext.xml";ApplicationContext ctx = new ClassPathXmlApplicationContext(config);//从容器获取serviceBuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");//com.sun.proxy.$Proxy12System.out.println("service是代理:"+service.getClass().getName());//调用方法service.buy(1001,10);
}

学习Spring,这篇就够了相关推荐

  1. [mmu/cache]-ARM MMU的学习笔记-一篇就够了

    ★★★ 个人博客导读首页-点击此处 ★★★ . 说明: 在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 64位 . 相关文章 1.ARM cache的学习笔记-一 ...

  2. [mmu/cache]-ARM cache的学习笔记-一篇就够了

    ★★★ 个人博客导读首页-点击此处 ★★★ . 说明: 在默认情况下,本文讲述的都是ARMV8-aarch64架构,linux kernel 64位 . 相关文章 1.ARM MMU的学习笔记-一篇就 ...

  3. 超详细的springBoot学习教程,springboot学习看这篇就够了

    springBoot学习 https://docs.spring.io/spring-boot/docs/2.2.6.RELEASE/reference/html/index.html (官方文档) ...

  4. 学习Less-看这篇就够了

    前言 CSS的短板 作为前端学习者的我们 或多或少都要学些 CSS ,它作为前端开发的三大基石之一,时刻引领着 Web 的发展潮向. 而 CSS 作为一门标记性语言,可能 给初学者第一印象 就是简单易 ...

  5. 渗透测试工程师零基础学习教程2023年最新版,想入门学习这一篇就够了。

    什么是渗透测试? 渗透测试是指通过模拟黑客攻击的方式,评估一个系统或网络的安全性能,以发现潜在的漏洞或安全弱点.渗透测试通常包括对目标系统或网络进行多种攻击方式的测试,如密码破解.漏洞利用.社会工程学 ...

  6. 电阻学习看这篇就够了

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.电阻的特性 二.电阻的参数与应用 1.电阻的封装 2.电力电子中电阻需要注意耐压值 3.插件电阻感性大的原因 5. ...

  7. SMMU学习这一篇就够了

    引流关键词: SMMU,mmu500,mmu600,mmu700,system mmu,Non-cacheable,Cacheable, non-shareable,inner-shareable,o ...

  8. Git 学习看这篇就够了!

    2019独角兽企业重金招聘Python工程师标准>>> Git是一个开源的分布式版本控制系统,可以有效.高速的处理从很小到非常大的项目版本管理. 可能新手会问"git和gi ...

  9. 鸿蒙系统概述(HarmonyOS)学习这一篇就够了!

    鸿蒙系统概述(HarmonyOS) 我们可以从以下三个主要方面进行概述:系统定义.技术特征.系统安全. 目录 鸿蒙系统概述(HarmonyOS) 系统定义 系统定位 技术架构 内核层 系统服务层 框架 ...

  10. armv8/armv9的简介-学习这一篇就够了

    引流关键词: optee.ATF.TF-A.Trustzone.optee3.14.MMU.VMSA.cache.TLB.arm.armv8.armv9.TEE.安全.内存管理.页表- 快速链接: .

最新文章

  1. 小冰数字孪生主播正式上线 全球首创全流程无人化AI直播
  2. GEO芯片数据探针id转化
  3. 旷视砸20亿进军AIoT,发布国内首个机器人协作大脑河图
  4. 职场5年经验的工程师论述linux真没那么难
  5. 开源项目JacpFX
  6. base64 RFC 4648
  7. java中跳出当前循环怎么做_在java中,如何跳出当前的多重循环?
  8. docker添加jar包_Docker部署jar包
  9. 2009年12月8号漕宝路电信机房真是电信封的吗?
  10. Delphi中CoInitialize之探究
  11. 计算机应用基础试卷结果分析,计算机应用基础试卷分析
  12. 百度云盘不限速下载大文件(2021-11亲测有效)
  13. 快上车!薅腾讯羊毛!
  14. 分布式 | dble元数据更新同步
  15. 雾霾都没走!尾气净化器就别来凑热闹了!
  16. 数据结构总结---1.总概
  17. 自由软件到底值多少钱?
  18. Sublime Text 3--->中文乱码的解决方法
  19. @value值获取不到配置文件值
  20. R语言散点图分类、配色、添加趋势线

热门文章

  1. iOS8 web下载ipa install App via OTA
  2. iOS 应用下载链接获取
  3. ubuntu修改IP后无法正常上网的解决
  4. 如何使用IDEA运行Spring实战(第四版)代码
  5. 视频会议软件 Zoom会议客户端
  6. python简单编程--ATM银行管理系统
  7. 现在主流开源分布式系统架构都有哪些?
  8. 针对初学者的 MQL 5 中的自定义指标
  9. 计算机声音管理器不见了,电脑中找不到Realtek高清晰音频管理器的解决方法
  10. 用Python实现简单的服务器【新手必学】