Spring

本文详细介绍了Spring框架的概述、核心特性、IOC控制反转、DI与构造注入、AOP编程、Spring与MyBatis集成、事务管理和Spring在Web中的应用。通过实例演示,学习者将掌握如何使用Spring创建服务、配置容器、注入依赖并处理事务,适合初学者和进阶开发者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

Spring学习

第一章 Spring概述

1.1 什么是Spring

Spring是2003年兴起的一个轻量级的Java开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring的核心是IOC(控制反转)和AOP(面向切面编程)。Spring是可以在JavaSE/EE中使用的轻量级开源框架。

Spring的主要作用是为代码解耦,降低代码间的耦合度。就是让对象和对象(模块和模块)之间关系不是按代码关联,而是通过配置来说明。即在Spring中说明对象(模块)关系。

总结:Spring是一个用java开发的,轻量级的,开源的框架。可在j2se,j2ee项目中使用。

核心技术:IOC,AOP

Spring又叫做:容器,spring作为容器,装的是java对象。可以让spring创建java对象,给属性赋值。

Spring作用:实现解耦合,降低java对象之间的耦合,降低模块之间的耦合。

tomcat也是容器:管理的是servlet,listener,filter等对象

创建HelloServlet类,写web.xml。

Spring:创建SomeServiceImpl,写spring的配置文件

1.2 Spring的特性

优点:

  1. 轻量:使用jar包小。核心功能所用jar包3M左右;Spring框架运行占用资源少,运行效率高。不依赖其他jar。

  2. 针对接口编程,解耦合:Spring提供了IOC控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式现在由容器完成,对象之间的依赖解耦合

  3. AOP编程的支持:通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。在Spring中开发人员可以从繁杂的事务中管理代码中解脱,通过声明方式灵活的进行事物的管理,提高开发效率和质量

  4. 方便集成各种优秀框架:Spring不排斥各种优秀框架,相反,可以降低各种框架的使用难度,spring提供了对各种优秀框架(如:Stucts,Hibernate,Mybatis)等的直接支持,简化框架使用。

    1.3 Spring框架体系结构

    Spring的体系结构

Spring的体系结构

目前最新的5.x版本中右面的portlet组件已经被废弃掉,同时增加了用于异步响应式处理的WebFlux组件。

第二章 IOC控制反转

2.1 概念

IOC:Inversion Of Control :控制反转,是一个理论,一个指导思想。知道开发人眼如何使用对象,管理对象。将对象的创建,属性赋值,对象的生命周期都交给代码之外的容器管理。

1)IOC分为:控制和反转

控制:对象创建,属性赋值,对象生命周期管理

反转:将开发人员管理对象的权力转移给代码之外的容器实现,由容器完成对象的管理。

正转:开发人员在代码中,使用new创建对象。开发人员掌握了对象的创建,属性赋值,对象从开始到销毁的全部过程。开发人员对 对象 完全控制。

通过容器,可以使用容器中的对象(容器已经创建了对象,对象属性赋值了,对象也组装好了)

Spring就是一个容器,可以管理对象,创建对象,给属性赋值

2)IOC的技术的实现:

DI(Depency Injectio):是IOC的一种技术实现。程序只需提供要使用的对象的名称既可以了,对象如何创建,如何从容器中查找,获取都由容器自己内部实现

依赖:比如说ClassA类使用了ClassB类的属性或方法,叫做ClassA依赖ClassB

public class B{
public void createOrder();
}
public class A{
private B b=new B();
public void buy(){
		b.createOrder();
	}
}
执行Abuy()
    A a=new A();
a.buy();

注入:赋值。A要使用B的createOrder就要给B的对象赋值,从而调用该方法。

3)Spring框架使用的DI实现IOC

通过Spring框架,只需要提供使用的对象名称就可以了。从容器获取名称对应的对象。

Spring底层使用的是反射机制。通过反射创建对象,给属性赋值。

创建对象方式:反射,序列化,new

2.2 创建第一个Spring项目

  1. 实现步骤:

    ch01-first:第一个Spring项目
    使用Spring:Spring作为容器管理对象,开发人员从Spring获取要使用的对象
    
    实现步骤:
    1.新建maven项目
    2.加入依赖,修改pom.xml
        spring-context:spring的季基础依赖
        junit:单元测试
    3.开发人员定义类:接口和实现类
        也可以没有接口。接口和实现类定义:与没有Spring一样
    4.创建Spring配置文件,作用:声明对象。
        将对象交给Spring来创建和管理。
        使用<bean>标签标识对象声明,一个bean标识一个java对象
    5.使用容器中的对象:
        创建一个表示Spring容器的对象 ApplicationContext
        从容其中,根据名称获取对象,使用getBean("对象名称")
    
  2. 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">
     <!--
        声明对象;
        id:自定义对象名称,唯一值(可以没有,Spring会提供默认名称)
        class:类的全限定名称(不能是接口,没有意义),spring通过反射机制创建对象,把对象放入到spring的一个map文件
    
        spring根据id,class创建对象,把对象放入到spring的一个map对象
        map.put(id,对象)
        -->
    <bean id="someService" class="com.henu.service.impl.SomeServiceImpl"/>
    </beans>
    Spring标准的配置文件
    1)根标签是beans
    2)beans后是约束文件说明
    3)beans里面是bean的声明
    4)什么是bean:bean就是java对象,spring容器管理的java对象,叫做bean
    
    

测试代码:

//1.指定Spring配置文件:从类路径(classPath)之下开始的路径
String config="beans.xml";
//2.创建容器对象:从类路径中获取
ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
//3.从容器中获取指定名称的对象
SomeService sm=(SomeService)ctx.getBean("someService");
//4.调用接口方法
sm.doSome();

2.3 Spring容器创建对象的特点

  1. 容器对象:ApplicationContext:接口;

    通过ApplicationContext对象,获取要使用的其他java对象

    执行getBean(“的id”)

  2. Spring默认调用的是类的无参数构造方法。因此类中必须有无参构造方法

  3. Spring读取配置文件,一次性创建好所有的java对象,都放到map中。

  4. 获取容器信息:

    //获取容器中对象的信息
    @Test
    public void test04(){
        String config="beans.xml";
        ApplicationContext ctx=new ClassPathXmlApplicationContext(config);
        int counts=ctx.getBeanDefinitionCount();
        System.out.println(counts);
        //获取容器中定义对象的名称
        String[] names=ctx.getBeanDefinitionNames();
        for(String name:names){
            System.out.println(name);
        }
    }
    

2.4 DI:给属性赋值

Spring调用类的无参构造方法给属性赋值,创建对象。对象创建后给属性赋值。

给属性赋值可以使用

1)xml文件中的标签和属性;

2)使用注解

DI分类:1 set注入,也叫设值注入;2 构造注入。

2.4.1 基于xml的DI

在xml配置文件中使用标签和属性,完成对象创建,属性赋值

  1. set注入,也叫设值注入

    概念:Spring调用类中的方法,在set方法中可以完成属性赋值。推荐使用。

    简单类型与引用类型:

    <!--声明bean对象-->
    <!--
        DI:给属性赋值
        1.set注入:spring调用类的set方法,通过set方法完成属性赋值
            简单类型(java基本数据类型与String)的set注入:
    <bean id="xxx" class="yyy">
        <property name="属性名" value="简单类型属性值"></property>
        属性名:实际为setXXX(yyy);不管类中有没有XXX属性只要有setXXX(yyy)方法就能正常执行
    </bean>
    引用类型set注入:
         <bean id="xxx" class="yyy">
        <property name="属性名" value="简单类型属性值">
        <property id="属性名" ref="bean的id/>
        ....
        </property>
    </bean>
    
    -->
    <bean id="myStudent" class="com.henu.ba02.Student">
        <property name="age" value="18"></property><!--setName("李四")-->
        <property name="name" value="李四"></property>
        <property name="school" ref="mySchool"/>
    </bean>
    <bean id="mySchool" class="com.henu.ba02.School">
        <property name="name" value="河南大学"/>
        <property name="adress" value="河南开封"/>
    </bean>
    
  2. 构造注入:

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

    <!--
        构造注入:spring调用类的有参构造方法,创建对象的同时给属性赋值
         <bean id="myStudent" class="com.henu.ba03.Student">
      <constructor-arg ></constructor-arg>:一个构造方法的形参
      标签有属性:name:构造方法形参名
                index:构造方法的参数位置,从左往右0,1,2.....
                value:简单类型的引用值
                ref:应用类型的形参值
                name与index有一个即可
                index可省略,不过参数传值顺序必须与参数列表顺序一致
                <constructor-arg value="李四"></constructor-arg>
                <constructor-arg value="20"></constructor-arg>
                <constructor-arg ref="mySchool"></constructor-arg>
    </bean>
    -->
    <bean id="myStudent" class="com.henu.ba03.Student">
      <constructor-arg name="name" index="0" value="李四"></constructor-arg>
        <constructor-arg name="age" index="1" value="20"></constructor-arg>
        <constructor-arg name="school" ref="mySchool"></constructor-arg>
    </bean>
    <bean id="mySchool" class="com.henu.ba03.School">
        <property name="name" value="河南大学"/>
        <property name="address" value="河南开封"/>
    </bean>
    <bean id="mydate" class="java.util.Date">
        <property name="time" value="4562676879"></property>
    </bean>
    <!--构造注入创建File对象-->
    <bean id="myFile" class="java.io.File">
        <constructor-arg name="parent" value="D:/IdeaPrj/ch01-first"/>
        <constructor-arg name="child" value="readme.txt"/>
    </bean>
    
  3. 引用类型的自动注入

    概念:Spring可以通过调用类中的有参数方法,在创建对象时,给属性赋值,只对引用类型有效。规则byName,byType

    (1).byName:按名称注入,java类中引用类型属性名称和spring容器中的bean的id一样的,且数据类型是一样的,这些bean能够赋值给引用类型。

    (2)byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean赋值给引用类型。

    示例:

    <!--声明bean对象-->
        <!--
            使用引用类型自动注入:Spring根据byNme,byType规则给引用类型赋值
            1.byName(按名称注入):java类中引用类型的属性名称与Spring容器中bean的id名称一样,且数据类型一样,这样
            的bean能够赋值给引用类型
            语法规则:
            <bean id="xxx" class="yy" autowire="byName">
            简单类型赋值
            </bean>
            2.bytype(按类型注入):java类中引用类型的数据类型和bean的class是同源的,
            这些bean能够赋值给引用类型。
            以下三种情况认为同源:
            1.java中引用类型的数据类型和bean中的class值是一样的
            2.java中引用类型的数据类型和bean的class值是父子关系
            3.java中引用类型的数据类型和bean的class值是接口和实现类关系的
            同一xml配置文件下只能有一个同源bean,否则会报错。
        -->
        <bean id="myStudent" class="com.henu.ba05.Student" autowire="byType">
         <property name="name" value="李四"></property>
            <property name="age" value="20"/>
        </bean>
    <!--    <bean id="mySchool" class="com.henu.ba05.School">-->
    <!--        <property name="name" value="郑州大学"/>-->
    <!--        <property name="adress" value="河南郑州"/>-->
    <!--    </bean>-->
        <bean id="myPrimarySchool" class="com.henu.ba05.PrimarySchool">
            <property name="name" value="北京小学"/>
            <property name="adress" value="北京大兴"/>
        </bean>
        <bean id="mydate" class="java.util.Date">
            <property name="time" value="4562676879"></property>
        </bean>
        <!--构造注入创建File对象-->
        <bean id="myFile" class="java.io.File">
            <constructor-arg name="parent" value="D:/IdeaPrj/ch01-first"/>
            <constructor-arg name="child" value="readme.txt"/>
        </bean>
    
  4. 练习:

    需求:模拟一个用户注册操作

    要求:定义一个Dao接口(UserDao),接口中的方法是insertUser(SysUser user)

    该方法不需要操作数据库,只需输出“使用了dao执行的insert操作”

    定义接口的实现类是MySqlUserDao

    定义一个service接口(UserService),定义接口的实现类为UserSeerviceImpl。在service的实现类里有一个UserDao类型的属性。service类中有一个方法addUser(SysUser user)

    操作是:service类里的addUser(){userDao.insertUser()}完成注册

    定义一个实体类SysUser表示用户数据。

要求实现:

程序中的UserService,mySqlUserDao这些类通过Spring容器创建和管理,同时要给UserServiceImpl类中的UserDao属性赋值。从Spring容器中获取UserServiceImpl类型的对象,调用addUser()方法,输出使用了dao执行的insert操作”;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-srfnzAax-1634019551922)(C:\Users\linhuashang\Desktop\绘图\spring工作.png)]

  1. 项目中使用Spring多个配置文件

    分配多个配置文件的方式:

    1.按功能模块分,一个模块一个配置文件

    2.按类的功能分,数据库操作相关的类在一个配置文件,service类在一个配置文件,配置redis,事务等等的一个配置文件。

    Spring管理多个配置文件:常用的是包含关系的配置文件。项目中有一个总的文件,里面有import标签包含的其他的多个配置文件

    语法:

    总的文件(xml)<import resource="classpath:其他文件的路径1"/><import resource="classpath:其他文件的路径2"/>关键字:"classpath:":表示类路径,也就是类文件所在的目录。Spring到类路径中加载文件		什么时候使用classpath:在一个文件中要使用其他的文件,需要使用classpath
    
     <!--声明bean对象-->    <!--        总的文件包含其他配置文件,一般不声明bean        语法:        <import resource="classpath:其他文件的路径1"/>        <import resource="classpath:其他文件的路径2"/>        classpath:表示类路径。类文件所在的目录。Spring通过类路径加载配置文件    --><!--        <import resource="classpath:ba06/spring-school.xml"/>--><!--         <import resource="classpath:ba06/spring-student.xml"/>-->    <!--包含关系的配置文件,可使用*(通配符)表示任意字符		注意:总的文件名称不能包含在通配符的范围内(appliactionContext.xml不能叫做         spring-applicationContext.xml         )	-->    <import resource="classpath:ba06/spring-*.xml"/>
    

2.4.2 基于注解的DI

基于注解的DI:使用Spring提供的注解,完成java对象创建,赋值。

1.注解使用核心步骤:
  1. 在源代码中加入注解,例如@Component

    @Component(value="myStudent")//value可以省略public class Student 
    

    若没有提供自定义对象名称,则会使用框架提供的默认对象名称:类名首字母小写

  2. 在Spring配置文件加入组件扫描器标签

    <!--声明组件扫描器:使用注解必须加入这个语句		context:是前缀        component-scan:组件扫描器,组件是java对象。        属性:base-package 注解在你项目中的包名        框架会扫描这个包和子包中的所有类,找类中的所有注解        遇到注解后,按照注解表示的功能,去创建对象给属性赋值    --><context:component-scan base-package="com.henu.ba01"/>
    

创建对象的四个注解

@Component("myStudent") 表示创建对象,对象放到容器中。作用与bean相同*  属性:value,表示对象名称,也就是bean的id属性值*  位置:在类的上面,表示创建此类的对象**  @Component("myStudent")等同于<bean id="myStudent class="com.henu.ba01.student"></bean>*@Component功能相同的创建对象的注解:* 1)@Repository:放在dao接口的实现类上面,表示创建的是dao对象,持久层对象,能够访问数据库* 2@Service:放在业务层接口的首先类上,表示创建的业务层对象,业务层对象有事务的功能* 3@Controller:放在控制器类的上面,表示创建控制器对象,属于表示层对象*                 控制器对象能接收请求,并能将请求的结果显示给用户* 以上都能创建对象,但是@Repository @Service @Controller有角色说明,表示对象是分层的。* 表示对象是属于不同层的,具有额外的功能*/

扫描多个包的三种方式:

不要重复扫描,会导致对象创建多次

  <context:component-scan base-package="com.henu.ba01"/>    <!--扫描多个包的三种方式-->    <!--第一种,多次使用组件扫描器-->		<context:component-scan base-package="com.henu.ba01"/>	    <context:component-scan base-package="com.henu.ba02"/>	    <context:component-scan base-package="com.henu.ba03"/>    <!--第二种,使用分隔符(;或者,或空格,不建议用空格)指定多个包-->	    <context:component-scan base-package="com.henu.ba02;com.henu.ba01"/>    <!--第三种,指定父包-->    <context:component-scan base-package="com.henu"/>
2.给属性赋值
  1. 基本类型数据赋值

    直接使用@Value(value=“属性值”)

    /** * 简单类型属性赋值:@value * 属性value 简单类型属性值 * 位置:1)在属性定义的上面,无需set方法,推荐使用 *      2)在set方法上面,必须要有sei方法 * */@Value(value = "黎明")private String name;//@Value(value = "18")//使用外部属性文件中的数据:${"key"}@Value("${myage}")private int age;
    

    从外部配置文件给基本数据类型赋值

    properties文件:

    schoolname=北京大学schooladdress=南开myname=张三myage=20
    
    <!--Spring配置文件中添加:		读取外部属性配置文件        context:property-placeholder:读取properties这样的文件 Property类。    -->    <context:property-placeholder location="classpath:/myconf.properties" file-encoding="UTF-8"/>//小插曲:未设置properties文件编码格式会出现乱码。使用file-encoding="UTF-8"解决乱码
    

    赋值

    @Value("${myname}")private String name;@Value("${myage}")private int age;
    
  2. 引用数据类型赋值

    @Autowired注解

    /** * 引用类型赋值: * @Autowireed:Spring框架提供的,给他引用类型赋值的,使用自动注入原理 * 支持byName,byType.默认是byType * 属性:required:boolean类型的属性,默认true * true:spring在启动,创建容器对象时,会检查引用类型是否赋值成功。 *      若赋值失败,终止程序执行,并报错 * false:引用类型赋值失败,程序正常执行,不报错。引用类型的只是null。 * 使用位置: * 1)属性定义上面,无需set方法 * 2)set方法上面 * * 使用byName自动注入: * 1)@Autowired:给引用类型赋值 * 2)@Qualifer(value="bean的id"):从容其中找到指定名称的对象;把这个对象赋值给引用类型。 * */@Autowired(required = false)@Qualifier(value = "myschoolAAA")private School school;
    

​ @Resource注解

/** * 引用类型赋值: * @Resource: 来自jdk中,给引用类型赋值的,支持byName,byType,默认是byName *             Spring支持这个注解的使用 *        位置:1)在属性定义的上面,无需set方法,推荐使用 *             2)在set方法上面 *说明:jdk1.8带有@Resource注解,高于1.8.,没有@Resource * 需要加入一个依赖: * <dependency> *       <groupId>javax.annotation</groupId> *       <artifactId>javax.annotation-api</artifactId> *       <version>1.3.2</version> *     </dependency> * * @Resource只使用byName赋值 * 使用注解属性name="bean的id" *///默人byName自动注入//先使用byName赋值,如果赋值失败,再使用byType@Resource(name = "myschool")private School school;

第三章 AOP面向切面编程

3.1 增加功能导致的问题

在源代码中,业务方法增加功能,存在问题

  1. 源代码改动较多
  2. 重复代码比较多
  3. 代码维护困难

3.2 AOP面向切面编程

3.2.1 AOP概念

AOP(Aspect Orient Programming):面向切面编程

Aspect:切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般可以复用

例如:日志功能,事务功能,检查权限,参数检查,统计信息等。

Orient:面向

Programming:编程

OOP:面向对象编程

如何理解?以切面为核心设计开发应用

  1. 设计项目时找出切面功能:如日志
  2. 安排切面的执行时间,执行位置
3.2.2AOP作用
  1. 使切面概念复用
  2. 让开发人员专注业务逻辑,提高开发效率
  3. 实现业务功能与其他非业务功能解耦合
  4. 给存在的业务方法增加功能,不用修改方法

3.3 AOP术语

1)Aspect:切面,给业务方法增加的功能

2)joinPoint:连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能

3)Pointcut:切入点,是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能。表示切面执行的位置。

4)target:目标对象。给哪个对象增加切面的功能,这个对象就是目标对象

5)Advice:通知,表示切面的执行时间,是在目标方法之前执行切面,还是在目标方法之后执行切面

AOP中重要的三个要素:Aspect,Pointcut,Advice。在Advice时间,在Pointcut的位置,执行Aspect

AOP是动态的思想,在程序运行期间,创建代理,使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的。

3.4 什么时候用AOP

要给某些方法增加相同的功能,且源代码不能改。给业务方法增加非业务功能,也可以使用AOP

3.5 AOP技术思想的实现

使用框架实现AOP,实现AOP的框架很多。有名的有两个

1)Spring:Spring 框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重

2)Aspectj:独立的框架,专门做AOP。属于Eclipse

3.6 使用AspectJ框架实现AOP

AspectJ框架可以使用注解和xml配置两种方式实现AOP

3.6.1 通知

AspectJ表示切面执行时间,用的通知(Advice)。这个通知可以用注解表示

学习5个注解,表示切面的5个执行时间。这些注解叫做通知注解。

@Before:前置通知

@AfterReturning:后置通知

@Around:环绕通知

@AfterThrowing:异常通知

@After:最终通知

3.6.2 Pointcut位置

Pointcut用来表示切面执行的位置,使用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 :抛出异常类型

?表示可选的部分

例如:execution(public void com.henu.service.serviceImpl.SomeServiceImpl.doSome(String,Integer));

​ 访问修饰符 返回值 包名 类名 方法名 参数类型

以上表达式包含司部分

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

其中,访问权限 异常类型 可省略;方法返回值 方法声明(参数)不能省略

通配符:

符号表示意义
*0至任意多个字符
用在方法参数中表示任意多个参数;用在包名后,表示当前包和其子包路径
+用在类名后表示当前类及其子类;用在接口后,表示当前接口及其实现类

示例:

**excution(public * *(…))😗*指定切入点为:任意公共方法


excution(public * set(…)):* 指定切入点为任意以“set”开头的方法


excution( com.xyz.service.*.*(…))* 指定切入点为:定义在service包里的任意类的任意方法


excution( com.xyz.service…*.*(…)):* 指定切入点为:定义在service包或者service子包里的任意类的任意方法。"…“出现在类名中时,后面必须跟”*",表示包,子包下的所有类


*excution( *…service.*.*(…))😗*指定任意包下的service子包下所有类(接口)中所有方法为接入点


切入点表达式表达的是哪些方法能够在执行时能够加入切面功能。

3.6.3 前置通知(掌握)

实现通知步骤

ch07-aspectj-before:使用aspectj框架的注解实现前置通知实现步骤:1,新建maven项目2.修改pom.xml加入依赖    spring-context依赖, spring-aspectj依赖(能够使用aspectj框架的功能)    junit3.创建业务接口及其实现类。4.创建一个切面类。是一个普通类    1)在类的上面加入@Aspect    2)在类中定义方法,方法表示切面的功能    在方法的上面加入Aspect框架中的通知注解,例如@Before(value="切入点表达式")5.创建Spring配置文件    1)声明目标对象    2)声明切面类对象    3)声明自动代理生成器6.创建测试类,测试目标方法执行时,增加切面的功能。

@Before

/** * @Before:前置通知 * 属性:value 切入点表达式,表示切面的执行位置。在执行这个方法时,会同时执行切面的功能 * 位置:方法的上面 * 特点: * 1)执行时间:在目标方法之前执行的 * 2)不会影响目标方法执行 * 3)不会修改目标方法执行的结果 *//**     * 同一方法两个前置通知执行顺序不一定,但都会在该方法执行前执行     * 不过意义不大,可将前置通知方法合并为一个即可     */ /**     * 切面类中的通知方法,可以有参数     * jionPoint必须是它     *     * joinPoint:表示正在执行的业务方法,相当于反射的Method     * 使用要求:必须是参数列表的第一个     * 作用:获取方法执行时的信息,例如方法名称,方法参数类型     */    @Before(value="execution(* *..impl.SomeServiceImpl.do*(..))")    public void myBefore2(JoinPoint joinPoint){        //获取方法定义,名称        System.out.println("前置通知,方法定义:"+joinPoint.getSignature());        System.out.println("前置通知,方法定义:"+joinPoint.getSignature().getName());        //获取方法执行参数        Object[] obj=joinPoint.getArgs();        for(Object o:obj){            System.out.println("参数"+o);        }        //根据方法信息可以做自己的业务逻辑。比如是doSome执行什么操作,是doOther执行什么操作        //切面功能代码        System.out.println("前置通知2,切面的功能,在方法之前执行:"+new Date());    }
3.6.4 后置通知(掌握)

@AfterReturning:在目标方法之后执行(目标方法返回值类型为void也可)

目标方法执行有异常,则直接抛出异常,后置通知方法不执行。

package com.henu.handle;

import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 切面类注解,
 * 位置:放置在某个类的上面
 * 作用:表示当前类时切面类
 *
 * 切面类:包含切面功能的类
 */
@Aspect
public class MyAspect {
    /**
     * 后置通知方法定义
     * 1)方法是public
     * 2)方法是void
     * 3)方法名称自定义
     * 4)方法有参数,推荐使用Object类型
     * @param joinPoint
     */
    /**
     * @AfterReturning:后置通知
     * 属性:value:切入点表达式
     * returning 自定义的变量,表示目标方法的返回值
     *           自定义变量名称必须和通知方法的形参名一样
     * 位置:在方法上面
     * 特点:
     * 1.在目标方法之后执行
     * 2.能够获取到目标方法中的执行结果
     * 3.不会影响目标方法结果
     *
     * 方法的参数:
     * Object res:表示目标方法的返回值,使用res接受doOther的调用结果
     * Object res=doOther()
     *
     * 后置通知的执行顺序:
     * Object res=doOther()
     * myAfterReturning(res);
     * Object res作用
     * 根据res的结果做业务逻辑判断
     * 根据res的不同的值,做不同的增强功能。
     *
     *在myAfterReturning修改res值并不影响doOther返回值
     * 注意,若后置通知方法使用参数JoinPoint,则其必须位于参数列表第一个位置,否则,书写不会报错,
     * 运行会抛出异常。
     * */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.do*e*(..))",returning = "res")
    public void myAfterReturning(Object res){
        res="111";
        System.out.println("后置通知,目标方法执行结果为:"+res);

    }
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doStudent(..))",returning = "res")
    public void myAfterReturning2(Object res){
        Student s=(Student)res;
        s.setAge(100);
        s.setName("赵六");
        res=s;
        System.out.println("后置通知,目标方法执行结果为:"+res);

    }
}
  • 思考:
    • 1.doOther方法返回的是String,Integer,…等基本数据类型
    • 在后置通知中,修改返回值,是不会影响目标方法的最后调用结果的
    • 2.doOther方法返回的是对象类型,例如:Student。
    • 在后置通知方法中,修改Student的属性值,会不会影响最后调用结果。
      答案是会。最终输出Student{name=“赵六” ,age=100}
3.6.5 环绕通知(掌握)

@Around(value=“切入点表达式”)

使用环绕通知:就是调用切面类的通知方法

package com.henu.handle;

import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * 切面类注解,
 * 位置:放置在某个类的上面
 * 作用:表示当前类时切面类
 *
 * 切面类:包含切面功能的类
 */
@Aspect
public class MyAspect {
    /**
     * @Around:环绕通知
     * 方法定义:
     * 1)方法是public
     * 2)方法必须有返回值,推荐使用Object类型
     * 3)方法名自定义
     * 4)方法必须有ProceedingJoinPoint参数
     */
    /**
     * @Around:环绕通知
     *  属性:value=切入点表达式
     *  位置:在方法上面
     *
     *
     *  返回值:Object表示调用目标方法希望的得到的结果(不一定是目标方法自己的返回值)
     *  参数:ProceedingJoinPoint相当于反射中的Method
     *  作用:执行目标方法。相当于Method.invoke()
     *  public interface ProcedingJionPoint extends JoinPoint()
     *
     *  特点:
     *  1.在目标执行前和后都能增强功能
     *  2.控制目标方法是否执行
     *  3.修改目标方法的执行结果
     */
    @Around(value = "execution(* *..SomeService.doFirst(..))")
    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("执行了环绕通知myAround");
        //执行目标方法
        Object res=joinPoint.proceed();//执行目标方法
        return "OK";
    }
}

3.6.6@AfterThrowing异常通知

语法:@AfterThrowing(value=切入点表达式,throwing=自定义变量)

package com.henu.handle;

import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.Executor;

/**
 * 切面类注解,
 * 位置:放置在某个类的上面
 * 作用:表示当前类时切面类
 *
 * 切面类:包含切面功能的类
 */
@Aspect
public class MyAspect {
    /**
     * 异常通知方法定义
     * 1)方法是public
     * 2)没用返回值,是void
     * 3)方法名称自定义
     * 4)方法有参数是Exception
     *
     */
    /**
     * @AfterThrowing:异常通知
     *   属性:value 切入点表达式
     *   throwing 自定义变量,表示目标方法抛出的异常
     *             变量名必须和通知方法的参数名一样
     *   位置:方法上面
     *   特点:
     *   1.在目标方法抛出异常后执行。没有异常不执行
     *   2.能获取目标方法的异常信息
     *   3.不是异常处理程序,可以得到发送异常的通知,可以发送邮件,短信通知开发人员
     *   可以看作目标方法的监控程序
     */
    @AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "e")
    public  void myAfterThrowing(Exception e){
        System.out.println("异常通知,在目标方法抛出异常时处理,异常原因是:"+e.getMessage());
        /*
        异常发生可以做:
        1.记录异常发生的时间,位置等信息
        2.发送邮件,短信,通知开发人员
         */
    }
}
3.6.7 @After最终通知

语法:@After(value=“切入点表达式”)

package com.henu.handle;

import com.henu.domain.Student;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.Executor;

@Aspect
public class MyAspect {
    /**
     * 最终通知
     * 1)方法时public
     * 2)方法无返回值,为void
     * 3)方法名称自定义
     * 4)方法没有参数
     */
    /**
     * @After 最终通知
     * 属性:value 切入点表达式
     * 位置:方法上面
     * 特点:
     * 1.在目标方法执行之后执行
     * 2.总会被执行
     * 3.可用来做程序最后收尾动作。例如清理临时数据,变量,清理内存
     *
     * 最终通知,无论程序有无异常都会被执行
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public void myAfter(){
        System.out.println("最终通知总会被改");
    }
}

第四章 Spring集成Mybatis

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

步骤:

ch14
实现步骤:
1.使用MySql数据库使用学生表student(id int primary key,name varchar(80),age int)
2.创建maven项目
3.加入依赖
Spring依赖,MyBatis依赖,MySQL驱动,junit依赖
mybatis-spring依赖(mybatis网站提供的,用来在spring项目中创建mybatis对象)
spring有关事务的依赖。

mybatis与spring整合时,事务默认自动提交。

4.创建实体类:Student
5. 创建Dao接口和mapper文件写sql语句
6.写mybatis主配置文件
7.创建service接口及其实现类
8.创建Spring配置文件
    1)声明数据源DataSource,使用阿里的Druid连接池
    2)声明SqlSessionFactoryBean类,在这个类中创建SqlSessionFactory对象
    3)声明MapperScannerConfiguration类,在内部创建dao代理对象
    创建的对象都在Spring容器中

    4)声明Service对象,将3)中的dao赋值给service属性
9.测试dao访问数据库

pom.xml依赖

ch14
实现步骤:
1.使用MySql数据库使用学生表student(id int primary key,name varchar(80),age int)
2.创建maven项目
3.加入依赖
Spring依赖,MyBatis依赖,MySQL驱动,junit依赖
mybatis-spring依赖(mybatis网站提供的,用来在spring项目中创建mybatis对象)
spring有关事务的依赖。

mybatis与spring整合时,事务默认自动提交。

4.创建实体类:Student
5. 创建Dao接口和mapper文件写sql语句
6.写mybatis主配置文件
7.创建service接口及其实现类
8.创建Spring配置文件
    1)声明数据源DataSource,使用阿里的Druid连接池
    2)声明SqlSessionFactoryBean类,在这个类中创建SqlSessionFactory对象
    3)声明MapperScannerConfiguration类,在内部创建dao代理对象
    创建的对象都在Spring容器中

    4)声明Service对象,将3)中的dao赋值给service属性
9.测试dao访问数据库

Mybatis主配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <!--别名-->
    <typeAliases>
        <package name="com.henu.domain"/>
    </typeAliases>
    <mappers>
        <!--
    resource=“mapper文件按的路径使用 / 分隔路径”
    -->
        <!--使用package要求
        mapper文件与dao接口位于同一目录下,且名称完全相同
        -->
       <package name="com.henu.dao"/>
    </mappers>
</configuration>

dao接口配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.henu.dao.StudentDao">
    <!-- 使用insert,update,delete,select标签写sql语句-->
    <select id="selectStudent" resultType="com.henu.domain.Student">
        select id ,name,age from  student2
    </select>
    <insert id="insertStudent" >
        insert into student2(name ,age) values (#{name},#{age})
    </insert>
</mapper>

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:util="http://www.springframework.org/schema/util"
       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/util https://www.springframework.org/schema/util/spring-util.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}"></property>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <!--声明SqlSessionFactoryBean,在这个类的内部创建SqlSessionFactory-->
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
        <!--指定mybatis主配置文件
        resource可以直接使用value赋值
        -->
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
    <!--声明MapperScannerConfiguration
        SqlSession.getMapper(StudentDao.class)
        MapperScannerConfiguration作用:循环basePackage所表示的包,
        把包中的每个接口都找到,调用SqlSession。getMapper
        把每个dao接口都创建出dao对象,dao代理对象都放在容器中

        相当于:
        ApplicationContext ctx=...
        SqlSessionFactory sqlSessionFactory=ctx.getBean("factory");
        SqlSession session=sqlSessionFactory.openSession();
        for(接口:com.henu.dao){
            接口 对象 = session.getMapper(接口);
            springMap.put(对象名(接口名的首字母小写),对象);

        }
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
     <!--指定SqlSessionFactory对象名称-->
        <property name="sqlSessionFactoryBeanName" value="factory"/>
        <!--指定基本包,dao接口所在的包名-->
        <property name="basePackage" value="com.henu.dao"/>
    </bean>
    <bean id="studentService" class="com.henu.service.serviceImpl.StudentServiceImpl">
        <property name="studentDao" ref="studentDao"/>
    </bean>
</beans>

第五章 Spring事务

5.1 事务的概念

什么是事务?事务是一些sql语句的集合,作为一个整体执行,要么都执行成功,要么都执行失败。

mysql执行事务
beginTransaction 开启事务
insert into student() values......
select * from student where id=1005
endTransaction 事务结束

什么情况下需要事务?

一个操作需要多条(2条或2条以上) sql语句一起完成,操作才能成功

5.2在程序中在哪说明事务

事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务支持

public class AccountService{
    private AccountDao dao;
    private MoneyDao dao2;
    //在service(业务类)的public方法上面需要说明事务
    public void trans(String a,String b,Integer money){
        dao.updateA();
        dao.updateB();
    }
}

public class AccountDao{
    public void updateA();
    public void updateB();
} 
public class MOneyDao{
    public void insertA();
    public void insertB();
} 

5.3 事务管理器

5.3.1 不同的数据库访问技术,处理事务是不同的

1)使用jdbc访问数据库,事务处理

public void updateAccount(){
    Connection conn=...
    conn.setAutoCommit(false);
    stat.insert();
    stat.update();
    conn.commit();
    conn.setAutoCommit(true);
}

2)使用MyBatis访问数据库,事务处理

public class updateAccount(){
    SqlSession session=sqlSessionFactory.openSesssion(false);
    try{
        sesson.insert(.....);
        session.update(....);
        session.commit
    }catch(Exception e){
        session.roolback;
    }
    
    
}
5.3.2 Spring统一管理事务,把不同的数据库访问技术的事务处理统一起来

使用Spring地事务管理,管理不同数据库访问技术的事务处理。开发人员只需要掌握Spring的事务处理方案,就可以实现使用不同数据库访问技术的事务处理。

管理事务面向的Spring,由Spring管理事务,做事务提交,事务回滚

5.3.3 Spring事务管理器

Spring框架使用事务管理器管理所有的事务

事务管理器接口:PlatformTransactionManager

作用:定义了事务的操作,主要是commit(),roolback()

事务管理器有很多的实现类:一种数据库访问技术有一个实现类。由实现类具体完成事务的提交,回滚

意味着:jdbc或者mybatis访问数据库有自己的事务管理器实现类:DataSourceTransactionManager

​ hibernate框架,他的事务管理器对象实现类:HibernateTransactionManager。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6D417mvr-1634019551932)(C:\Users\linhuashang\Desktop\绘图\spring事务.png)]

5.3.4 事务的提交和回滚实际

什么时候提交事务,什么时候回滚事?当你的业务方法正常执行时,没有异常,事务就提交

如果业务方法抛出了运行时异常,事务是回滚的。

异常分类:

Error:严重错误。回滚事务

Exception:异常类,可以处理的异常情况

1)运行时异常:RuntimeException和它的子类都是运行时异常,在程序执行错误过程中抛出的异常。常见的运行时异常:NullPointerException,NumberFormatException,IndexOutOfBoundsException,ArithmeticException

2)受查异常:编写java代码的时候,必须处理的异常。例如:IOException,SQLException,FileNotFoundException(此时默认提交事务)

如何记忆:

方法抛出了运行时异常,事务就回滚,其他情况(正常执行,受查异常)就提交事务

5.3.5 事务使用的AOP的环绕通知

环绕通知:可以在目标方法的前和后都增强功能,不需要修改代码。

Spring给业务方法在执行时,增加事务的切面功能
    @Around("executi(所有业务类中的方法)")
public Object myAroung(ProceedingJointPoint pjp){
    try{
    //使用事务管理器开启事务
    PlatformTransactionManager.beginTransaction();
    pjp.proceed();//执行目标方法
    PlatformTransactionManager.commit();//业务方法正常执行,提交事务
    }catch(Exception e){
        PlatformTransactionManager.rollback();//业务方法正常执行回滚事务
    }
}

5.4 事务定义接口

TransactionDefinition接口:定义了三类常量,定义了有关事务控制的属性

事务的属性:隔离级别;传播行为;事务的超时

给事务方法说明事务属性,与ACID不一样

5.4.1 隔离级别

隔离级别:控制事物之间影响的程度

5个值,四个隔离级别

  1. DEFAULT:采用DB默认的事务隔离级别。MySQL默认为REPEATABLE_READ;Oracle默认READ_COMMITED
  2. READ_UNCOMMIT:读未提交,为解决任何并发问题
  3. READ_COMMITED:读已提交,存在不可重复度与幻读
  4. REPEATABLE_READ:可重复读。解决脏读,不可重复读,存在幻读
  5. SERIALIZABLE:串行化。不存在并发问题
5.4.2 事务超时

超时时间,以秒为单位。整数值,默认-1;

超时时间:表示一个业务方法最长的执行时间。到达时间没有执行完毕,spring回滚事务

5.4.3 传播行为

传播行为有7个值。

传播行为:业务方法在调用时,事务方法之间的传递和使用

使用传播行为:标识方法有无事务

掌握:

PROPAGATION_REQUIRED

PROPAGATION_REQUIRES_NEW

PROPAGATION_SUPPORTS

了解:

PROPAGATION_MANDATORY

PROPAGATION_NESTED

PROPAGATION_NEVER

PROPAGATION_NOT_SUPPORTED

  1. PROPAGATION_REQUIRED:Spring的默认传播行为,方法在调用的时候,如果存在事务,就使用当前的事务;如果没有事务,就创建事务,方法在新事物中执行
  2. PROPAGATION_SUPPORTS:支持。方法有事务可以正常执行,没有事务也可以正常执行。例如:查询操作
  3. PROPAGATION_REQUIRES_NEW:方法需要一个新事务。如果调用方法时,存在一个事务,则原来的事务暂停,直到新事物执行完毕;如果方法调用时没有新事物,则新建一个事务,在新事物执行代码

Spring事务环境示例:

购买商品trans_sale项目

本例要实现购买商品,模拟用户下单,向订单添加销售记录,从商品表减少库存

实现步骤:

1.创建相关表

slae表

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

goods表

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

5.5 Spring框架使用自己的注解@Teansactional控制事务

@Transactional注解,使用注解的属性控制事物(隔离级别,传播行为,超时)。

适合中小项目。

属性:

  1. prooagation:事务的传播行为,使用的是Propagation类的枚举值。例如:Propagation.REQUIRED
  2. isolation:表示隔离级别,使用的是Isolation类的枚举值,表示隔离级别。例如,默认Isolation.DEFAULT
  3. readOnly:boolean类型,表示数据库操作是不是只读,默认false
  4. timeout:事务的超时时间,默认-1,整数值,单位为秒。例如timeout=20
  5. rollbackFor:表示回滚的异常类列表,值为一个数组。每个值是异常类的class。
  6. rollbackForClassName:表示回滚类的异常列表。值为异常类名称,是String类型的值
  7. noRollbackFor:表示不需要回滚的异常类列表,class类型
  8. noRollbackForClassName:不需要回滚的异常类型,是String类型的

位置:1)业务方法上面,是在public方法的上面

​ 2)在类的上面

注解使用步骤:

1)在spring的配置文件声明事务的内容

​ 声明事务管理器对象,说明使用哪个事务管理器

​ 声明使用注解管理事务,开启事务注解驱动

<!--声明事务控制-->
<!--声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
<!--开启事务注解驱动:告诉框架使用注解管理事务
    transaction-manager:指定事务管理器id
-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

2)在类的源代码中加入@Transaction

 /* @Transactional:放在方法上面,表示方法有事务功能
 Transactional(propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,readOnly = false,timeout = 20,
        rollbackFor = {NullPointerException.class,NotEnoughException.class})
 Transactional(propagation = Propagation.REQUIRED,
        isolation = Isolation.DEFAULT,readOnly = false,timeout = 20,

 Transactional
 释roolbackFor的使用:
 1)框架首先检查方法抛出的异常是否在rollbackFor的数组中,如果在一定回滚
 2)如果方法抛出的异常不在rollbackFor数组中,框架会继续检查抛出的异常是不是运行时异常 Runtimeexception,
 如果是则回滚
 例如抛出受查异常:IOException,SQLException,若rollbackFor有,则回滚,没有不会滚
 仅写@Transactional,默认propagation = Propagation.REQUIRED,若抛出RimtimeException也会回滚
*/
 @Transactional
 @Override
 public void buy(Integer goodsId, Integer num) {
     System.out.println("buy方法开始");
     //生成销售记录
     Sale sale=new Sale(goodsId,num);
     saleDao.insertSale(sale);
     //查询商品
     Goods goods=goodsDao.selectById(goodsId);
     if(goods==null){
         throw new NullPointerException(goodsId+"商品不存在");
     }else if(goods.getAmount()<num){
         throw new NotEnoughException(goodsId+"库存不足");
     }
     //更新库存
     Goods buyGoods=new Goods(goodsId,num);
     goodsDao.updateGoods(buyGoods);
     System.out.println("buy方法执行");

 }

事务的控制模式:

1.编程式,在代码中编程控制事务

2.声明式事务,不用编写事务

5.6 使用AsectJ框架在Spring配置文件中,声明事务控制

使用aspectj的aop,声明事务控制叫做声明式事务

使用步骤:

1.pom.xml文件加入spring-aspects依赖

2.在spring的配置文件声明事务的内容

(1)声明事务管理器

(2)声明业务方法需要的事务属性

(3)声明切入点表达式

<?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"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">

    <context:property-placeholder location="classpath:jdbc.properties" file-encoding="UTF-8"/>
    <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}"/>
    </bean>
    <bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="configLocation" value="classpath:mybatis.xml"/>
    </bean>
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="factory"/>
        <property name="basePackage" value="com.henu.dao"/>
    </bean>
    <bean id="buyService" class="com.henu.service.serviceImpl.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.声明事务方法的事务属性(隔离级别,传播行为,超时)
    id:给业务方法配置事务这段代码起个名称
    transaction-manager:事务管理器id
-->
    <tx:advice id="serviceAdvice" transaction-manager="transactionManager">
        <!--给具体的事务方法添加事务的说明-->
        <tx:attributes>
            <!--给具体的业务方法说明它需要的事务属性
    name:业务方法名称。配置name的值:1.业务方法的名称 2.带有部分通配符(*)的方法名称 3.使用*
    propagation:指定传播行为的值
    isolation:指定隔离级别
    read-only:是否只读,默认false
    timeout:超时时间
    rollback-for:指定回滚的异常类列表,使用的是异常的全限定名称
-->
            <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
                       read-only="false" timeout="20"
                       rollback-for="java.lang.NullPointerException,com.henu.exception.NotEnoughException"/>
            <!--在业务方法有命名规则后,可以对一些方法使用事务-->
            <tx:method name="add*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <tx:method name="remove*" propagation="REQUIRED" rollback-for="java.lang.Exception"/>
            <!--以上方法以外的:querySale,findSale,searchSale-->
            <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
        </tx:attributes>
    </tx:advice>
    <!--说明切入点表达式:表示哪些包中的类,类中的方法参与事务
    若aop:config标签报错
    在beans标签内引入aop:
    xmlns:aop="http://www.springframework.org/schema/aop"
    同时在xsi:schemaLocation下加入
     http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
    即可
    -->
    <aop:config>
        <!--声明切入点表达式
            expression:切入点表达式,表示哪些类和类中的方法要参与事务
            id:切入点表达式的名称,唯一值
            expression如何写?
            execution(* *..Service..*.*(..)):service包下及子包的类中的任意方法
        -->
        <aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
        <!--关联切入点表达式和事务通知-->
        <aop:advisor advice-ref="serviceAdvice" pointcut-ref="servicePointcut"/>
    </aop:config>
</beans>

声明式事务缺点:不易理解,配置复杂

声明式事务优点:代码与事务配置分离,控制事务源代码不用修改

​ 能快速的了解和掌控项目的全部事务。适合大型项目

第六章 Spring和Web

6.1现在使用容器对象的原因

1.创建容器对象次数多

2.在多个servlet中,分别创建容器对象

6.2 需要一个什么样的容器对象

1.容器对象只有一个,创建一次就可以了

2.容器对象应该在整个项目中共享使用。多个servlet都能使用同一个容器对象

结局问题使用监听器servletContextListener(两个方法 初始时执行的方法,销毁时执行的方法)

在监听器中,创建好的容器对象,应该放在web应用中的ServletContext作用域中

6.3 ContetxLoaderListener

ContextLoaderListener是一个监听器对象,是spring框架提供的,使用这个监听器的作用:

1.创建容器对象,一次

2.将容器队象放入到ServletContext中

使用步骤:

1.pom.xml加入依赖spring-web

2.在web.xml中声明监听器

依赖

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

在web.xml中配置监听器

<!DOCTYPE web-app PUBLIC
        "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
        "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app >
  <display-name>Archetype Created Web Application</display-name>

    <!--自定义容器使用的配置文件路径
    context-param:叫做上下文参数,给监听器提供参数
    <param-name>contextConfigLocation</param-name>
    名称是固定的,表示自定义spring文件配置文件的路径

    <param-value>classpath:applicationContext.xml</param-value>
    自定义配置文件的路径。路径有多个,多个路径之间使用,隔开
    例如
         <param-value>classpath:applicationContext.xml,springbeans.xml</param-value>
    -->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>
  <listener>
<!--声明监听器
默认监听器:创建容器对象时,读取的配置文件:/WEN-INF/applicationContext.xml
-->
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>
</web-app>

6.4ContextLoaderListener源代码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
   
    public void contextInitialized(ServletContextEvent event) {
        this.initWebApplicationContext(event.getServletContext());
    }

}

 private WebApplicationContext context;


 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
        if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
            throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
        } else {
            servletContext.log("Initializing Spring root WebApplicationContext");
            Log logger = LogFactory.getLog(ContextLoader.class);
            if (logger.isInfoEnabled()) {
                logger.info("Root WebApplicationContext: initialization started");
            }

            long startTime = System.currentTimeMillis();

            try {
                if (this.context == null) {
                    //创建spring的容器对象
                    this.context = this.createWebApplicationContext(servletContext);
                }

               //把容器对象放入到ServletContext作用域中
              //key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
              //value=容器对象  
                servletContext.setAttribute(
                    WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, 							this.context);
                
            } catch (Error | RuntimeException var8) {
                
            }
        }
    }

//WebApplicationContext继承自ApplicationContext。是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext

容器使用:

复杂写法:

//使用监听器已经创建好了容器对象,从Servlet作用域获取容器
String key= WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
ServletContext sc= getServletContext();//ServletContext,servlet中的方法
ServletContext sc1=request.getServletContext();//HttpServletRequest对象的方法
Object attr=sc.getAttribute(key);
WebApplicationContext context=null;
if(attr!=null){
    context=(WebApplicationContext)attr;
    //容器对象拿到
}

Spring框架提供的方法:

ServletContext sc2=getServletContext();
WebApplicationContext context1= WebApplicationContextUtils.getRequiredWebApplicationContext(sc2);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值