Spring初学者学习笔记(整合)

本文深入浅出地介绍了Spring框架的核心概念,包括控制反转(IOC)和面向切面编程(AOP),并通过手写AOP框架的多个版本展示AOP的应用,详细解析了Spring整合MyBatis及事务管理,最后实践了AspectJ框架的使用,涵盖通知类型、切入点表达式等关键点。

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

目录

Spring学习

一、什么是Spring?

1、介绍

2、Spring的特点

二、控制反转IOC

1、什么是IOC

2、 IOC理解,创建一个模块由Spring管理创建对象,另一个由程序员自己创建对象,两则进行对比。

基于xml的IOC

a>使用setter注入依赖 

b>使用构造器注入依赖

基于注解的IOC

 1)创建对象的注解

2)依赖注入的注解 

 添加包扫描的多种方式

三层架构项目的分析

普通的三层项目

基于xml的IOC三层架构项目(Spring接管对象)

基于注解的IOC三层架构项目(Spring接管对象)

Spring核心配置文件的拆分策略

拆分基于注解的IOC的三层项目的配置文件

三、面向切面编程AOP

手写AOP框架 

手写AOP框架第一个版本

手写AOP框架第二个版本

手写AOP框架第三个版本

手写AOP框架第四个版本

手写AOP框架第五个版本 

Spring原生的AOP

AOP相关术语

面向切面编程的AspectJ框架 

AspectJ常见通知类型 

AspectJ 的切入点表达式(掌握)

1.AspectJ的前置通知@Before

 2.AspectJ的前置通知@AfterReturning

 3.AspectJ的环绕通知@Around

 4.AspectJ的最终通知@After

5、@PointCut给切入点表达式起别名

四、Spring整合MyBatis

五、事务

Spring中添加事务的两种方式

在Spring中通过注解添加事务

声明式事务的实现

设置事务处理的优先级

事务中指定某些错误类型不回滚

事务的隔离级别

Spring事务的传播特性

事务传播特性解析 


Spring学习

业精于勤,荒于嬉。

一、什么是Spring?

1、介绍

        Spring它是一个容器,也是一个框架,它是整合其它框架的框架。Spring看到其他什么框架好用,它就会把它整合进来,比如MyBatis框架就被Spring看上了,然后就整合进Spring里面。
Spring的核心是 控制反转IOC 面向切面编程AOP。它由20多个模块构成.它在很多领域都提供优秀的解决方案。

        Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。 而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。

2、Spring的特点

1)轻量级
由20多个模块构成,每个模块jar包都很小,小于1M,Spring核心包也就3M左右.
编写代码比较自由,不用像MyBatis那样一定要写什么什么配置文件。

2)面向接口编程
使用接口,就是面向灵活,项目的可扩展性好,可维护性都极高.接口不关心实现类的类型.使用时接口指向实现类,只要切换实现类即可切换整个功能.

3)AOP:面向切面编程
就是将公共的,通用的,重复的代码单独开发在一个切面中,在需要的时候再从切面中反织回去.底层的原理是动态代理.

4)整合其它框架

它整合后使其它框架更易用,比如整合了MyBatis框架

二、控制反转IOC

1、什么是IOC

        控制反转IOC(Inversion of Control)是一个概念,是一种思想。由Spring容器进行对象的创建和依赖注入.程序员在使用时直接取出使用。由容器集中进行对象和创建和依赖管理。

        Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”,注入即赋值。

正转:由程序员进行对象的创建和依赖注入称为正转.程序员说了算.

 Student stu = new Student();   ===>程序员自己创建对象
  stu.setName("张三");           ===>程序员自己进行赋值(依赖注入)
  stu.setAge(22);

反转:由Spring容器进行创建对象和依赖注入称为反转,将控制权从程序员手中夺走,由Spring容器进行控制,称为反转。 容器说了算. 

<bean id="stu" class="com.bjpowernode.pojo.Student">     ===>Spring容器负责对象的创建
    <property name="name" value="张三">                    ===>Spring容器依赖注入值
    <property name="age" value="22"> 
</bean>

2、 IOC理解,创建一个模块由Spring管理创建对象,另一个由程序员自己创建对象,两则进行对比。

创建一个空项目,然后在其中创建一个maven子模块: 

其pom.xml文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lin.spring</groupId>
    <artifactId>Spring-demo1</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>

        <!--添加Spring框架依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.RELEASE</version>
        </dependency>

    </dependencies>

    <build>
        <resources>
            <!--
                "**"  表示任意级目录
                "*"  表示任意文件

                意思就是把所有 /src/main/java 中所有 .xml和.properties 文件也打包进包中
                会打包到target目录中,这些文件也会加载到其中,在运行的时候使用。
                如果是jar/war包,则这些xml会原样放到包的相应目录下。
            -->
            <resource>
                <directory>./src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
            <resource>
                <directory>./src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

Spring的依赖配置添加完成,接下来就是创建Spring的核心配置文件起名applicationContext.xml

 

基于xml的IOC

对象的创建和注入依赖都在applicationContext.xml文件中的指定设置好。创建Spring容器的时候指明这个xml文件,Spring就会自动创建这个xml文件的对象并且注入好值。

a>使用setter注入依赖 

a>使用setter注入依赖
            第一种:注入基本数据类型,使用value属性

    <!--创建学校对象-->
    <bean id="schoolBySpring" class="com.lin.spring.bean.School">
        <property name="schoolName" value="莆田学院"></property>
        <property name="address" value="莆田城厢区"></property>
    </bean>
            注意:此方法需要提供相应属性的setter方法,他会调用对象的setXXX()方法

            第二种:注入引用数据类型的数据,使用ref属性
            <property name="school" ref="schoolBySpring"></property>
            此引用数据类型必须在此配置文件.xml种配置过bean,在此文件中的bean实例对象。

applicationContext.xml:

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

    <!--
        基于xml的IOC:

        1.创建对象 和 依赖注入,交给Spring容器管理完成

        id:就是指明创建对象的名称
        class:就是对象对应的全类名,Spring底层会通过反射创建对象(只能是类,不能是接口)

        2.当启动Spring容器的时候,
        默认就会调用无参构造器创建此xml文件中的所有对象,所以必须提供无参构造器。
        相当于Student stuBySpring = new Student();

        3.同时会给创建的对象赋值
            a>使用setter注入依赖
            第一种:注入基本数据类型,使用value属性

            <property name="stuName" value="由spring注入的stuName"></property>
            <property name="age" value="12"></property>
            注意:此方法需要提供相应属性的setter方法

            第二种:注入引用数据类型的数据,使用ref属性
            <property name="school" ref="schoolBySpring"></property>
            此引用数据类型必须在此配置文件.xml种配置过bean,在此文件中的bean实例对象。

            b>使用构造器注入依赖
    -->
    <!--<bean />:用于定义一个实例对象。一个实例对应一个 bean 元素。-->
    <bean id="stuBySpring" class="com.lin.spring.bean.Student">
        <property name="stuName" value="由spring注入的stuName"></property>
        <property name="age" value="12"></property>
        <!--注入引用数据类型数据,真正的注入依赖,依赖学校对象的创建-->
        <property name="school" ref="schoolBySpring"></property>
    </bean>

    <!--创建学校对象-->
    <bean id="schoolBySpring" class="com.lin.spring.bean.School">
        <property name="schoolName" value="莆田学院"></property>
        <property name="address" value="莆田城厢区"></property>
    </bean>
</beans>

测试代码:

public class createInstanceTest {

    // 由程序员自己创建对象,并注入依赖
    @Test
    public void testCreateInstanceByMe(){
        Student stu = new Student();
        System.out.println(stu);
    }

    // 由Spring自己创建对象并注入依赖
    @Test
    public void testCreateInstanceBySpring(){
        // 创建Spring容器对象并启动容器
    (要传入Spring的核心配置文件applicationContext.xml的相对路径,这个是从resources目录下读取)
        // 创建Spring容器对象的同时会创建Spring管理的所有对象并自动注入依赖
        // 会创建并注入依赖Spring配置文件applicationContext.xml中的所有bean实例对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        System.out.println("-----------------");

        // 取出Spring管理的对象
        Student student = (Student) ac.getBean("stuBySpring");
        System.out.println(student);
    }

}

b>使用构造器注入依赖

第一种:使用构造方法的形参名称注入值

public class Dept {
    private Integer did;
    private String dName;

    public Dept() {
        System.out.println("Dept的wu参构造器执行。。。");
    }

    public Dept(Integer did, String dName) {
        System.out.println("Dept的有参构造器执行。。。");
        this.did = did;
        this.dName = dName;
    }

    @Override
    public String toString() {
        return "Dept{" +
                "did=" + did +
                ", dName='" + dName + '\'' +
                '}';
    }
}
    <!--创建部门对象-->
    <!--
        使用有参构造器创建对象,并根据有参构造器的参数名注入值。
        相当于:new Dept(1,"测试部");
    -->   
    <bean id="deptBySpring" class="com.lin.spring.bean.Dept">
        <constructor-arg name="did" value="1"></constructor-arg>
        <constructor-arg name="dName" value="测试部"></constructor-arg>
    </bean>
    @Test
    public void testDept(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Dept dept = (Dept) ac.getBean("deptBySpring");
        System.out.println(dept);
    }

 

第二种:使用构造方法的参数的下标注入值 

package com.bjpowernode.pojo3;

/**
 *
 */
public class Student {

    private String name;
    private int age;

    //引用类型的成员变量
    private School school;

    // 有参构造方法的参数是有下标的:0,1,2
    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    @Override
    public 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">
  
  
  <!--创建学校的对象,使用有参构造方法参数名称注入值-->
  <bean id="school" class="com.bjpowernode.pojo3.School">
    <constructor-arg name="address1" value="海淀区"></constructor-arg>
    <constructor-arg name="name1" value="清华大学"></constructor-arg>
  </bean>
  
  <!--创建学生对象,使用有参构造方法的参数的下标注入值-->
  <bean id="stu" class="com.bjpowernode.pojo3.Student">
    <constructor-arg index="0" value="钱七"></constructor-arg>
    <constructor-arg index="2" ref="school"></constructor-arg>
    <constructor-arg index="1" value="22"></constructor-arg>
  </bean>
  
</beans>

 第三种:使用构造方法参数默认顺序

package com.bjpowernode.pojo3;

/**
 *
 */
public class Student {

    private String name;
    private int age;

    //引用类型的成员变量
    private School school;

    // 有参构造方法的参数的顺序:name,age,school
    public Student(String name, int age, School school) {
        this.name = name;
        this.age = age;
        this.school = school;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
<!--创建学生对象,使用有参构造方法的参数的默认顺序-->
<!--按照有参构造方法定义的时候的参数书写顺序赋值-->
<bean id="stuSequence" class="com.bjpowernode.pojo3.Student">
  <constructor-arg value="陈十"></constructor-arg>
  <constructor-arg value="22"></constructor-arg>
  <constructor-arg ref="school"></constructor-arg>
</bean>

基于注解的IOC

  • 之前是在xml文件中,指明要创建的对象,现在是使用注解指明要创建对象的类。
  • 基于注解的IOC也称为DI(Dependency Injection),它是IOC(思想)的具体实现的技术.
  • 基于注解的IOC,必须要在Spring的核心配置文件中添加包扫描,这样Spring才会去这个包下去扫描,一旦扫描到某个类有用创建对象的注解,就创建对象。
    • <context:component-scan base-package="com.lin.s01"></context:component-scan>

 1)创建对象的注解

  • @Component:可以创建任意对象。创建的对象的默认对象名称是类名的驼峰命名法(Student类==》student)。也可以指定对象的名称@Component("指定名称").
  • @Controller:专门用来创建界面层的控制器的对象(Servlet),这种对象可以接收用户的请求,可以返回处理结果给客户端.
  • @Service:专门用来创建业务逻辑层的对象(UserService),负责向下访问数据访问层,处理完毕后的结果返回给界面层.
  • @Repository:专门用来创建数据访问层的对象(UserMapper),负责数据库中的增删改查所有操作。

使用方法:在要创建对象的类上使用注解标识。

package com.bjpowernode.s01;

/**
    1.在要创建对象的类上面使用创建对象的注解
    2.交给Spring去创建对象,就是在容器启动时创建
    3.然后在Spring核心配置文件中的包扫描中指明这个要创建对象类所在的包,
       这样Spring在启动的时候就会去这些包中扫描有没有创建对象的注解,有则创建。
*/
@Component("stu")  
public class Student {
    private String name;
    private int age;
    
    public Student() {
        System.out.println("学生对象的无参构造方法.........");
    }
    
    @Override
    public String toString() {
        return "Student{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}
<?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核心配置文件中添加包扫描
    届时Spring就会扫描com.bjpowernode.s01这个包下有没有创建对象的注解,有则创建对象。 
-->
  <context:component-scan base-package="com.bjpowernode.s01"></context:component-scan>
</beans>
public class MyTest01 {
    
    @Test
    public void testStudent(){
        //创建容器对象并启动
        ApplicationContext ac = new ClassPathXmlApplicationContext("s01/applicationContext.xml");

        //取出对象
        Student stu = (Student) ac.getBean("stu");
        System.out.println(stu);
    }
}
//学生对象的无参构造方法.........
//Student{name='null', age=0}

  

2)依赖注入的注解 

给上面创建好的对象中的属性进行赋值。    

    简单类型(8种基本类型+String)数据的注入
    @Value:用来给简单类型注入值

    引用类型的数据注入
    A.@Autowired:翻译成自动装配,使用类型注入值,Spring会从整个Bean工厂中搜索同源类型的对象进行注入。
    同源类型也可注入.
    什么是同源类型:
      a.被注入的类型(Student中的school)与注入的类型是完全相同的类型
      b.被注入的类型(Student中的school父)与注入的类型(子)是父子类
      c.被注入的类型(Student中的school接口)与注入的类型(实现类)是接口和实现类的类型

    注意: 当要注入的引用类型对象有父子类对象的情况下,如果使用按类型注入,就意味着有多个可注入的对象。此时Spring会按照名称进行二次筛选,选则与被注入对象相同名称的对象进行注入。


比如:

已知bean工厂中有一个School对象(默认名称school)和subSchool(默认名称SubSchool)对象,按同源类型注入的话,school和subSchool都可以注入,然后会按照名称进行二次筛选,就会选父类的School对象,因为它的名称和要被注入的对象名称相同,所以有这种情况最好使用名称注入。

@Autowired

private School school;



    B.@Autowired
      @Qualifier("名称")

       两个标签一起使用,采用名称注入值,Spring会从整个Bean工厂中搜索指定名称的对象进行注入。

    注意:如果有父子类的情况下,建议直接按名称进行注入值.

C.@Resource 即可根据类型注入,也可以根据名称注入

@Resource

private School school;

@Resource(name = "school")

private School school;

这个注解包含以上AB两种方式的作用,但是这个注解不是spring的,是Java的扩展包javax包中的,所以spring不推荐使用。

A.@Autowired案例: 

// 创建School对象
@Component
public class School {
    // 简单类型的数据注入
    @Value("莆田学院")
    private String schoolName;
    @Value("莆田")
    private String address;

    public School() {
        System.out.println("School的无参构造器");
    }

    public School(String schoolName, String address) {
        System.out.println("School的you参构造器");
        this.schoolName = schoolName;
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "schoolName='" + schoolName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
package com.lin.spring.bean2;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * @author shkstart
 * @create 2022-06-06 16:34
 */
@Component()
public class Student {
    @Value("测试")
    private String stuName;
    private int age;

    /*
    * 引用数据类型的数据注入
    * Spring会自动去bean工厂中找同源类型的对象进行注入,赋值给school=xxx同源类型对象
    * 而我们已经使用注解创建了School对象。
    * */
    @Autowired
    private School school;

    public Student() {
        System.out.println("Student的无参构造方法执行了。。。");
    }

    public Student(String stuName, int age) {
        System.out.println("Student的you参构造方法执行了。。。");
        this.stuName = stuName;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuName='" + stuName + '\'' +
                ", 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"
       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核心配置文件中添加包扫描
      届时Spring就会扫描com.lin.spring.bean1这个包下有没有创建对象的注解,有则创建对象。
  -->
    <context:component-scan base-package="com.lin.spring.bean1"></context:component-scan>
    <context:component-scan base-package="com.lin.spring.bean2"></context:component-scan>
</beans>
    @Test
    public void testCreateObjectByAnno2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Student stu = (Student) ac.getBean("student");
        System.out.println(stu);
    }

B.@Autowired
      @Qualifier("名称"):案例:

// 创建School对象
@Component("schoolNew")
public class School {
    // 简单类型的数据注入
    @Value("清华大学")
    private String schoolName;
    @Value("北京")
    private String address;

    public School() {
        System.out.println("School的无参构造器");
    }

    public School(String schoolName, String address) {
        System.out.println("School的you参构造器");
        this.schoolName = schoolName;
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "schoolName='" + schoolName + '\'' +
                ", address='" + address + '\'' +
                '}';
    }
}
@Component
public class Student {
    @Value("测试")
    private String stuName;
    private int age;

    /*
     * 引用数据类型的数据注入采用@Autowired和@Qualifier("名称")注解
     * Spring会自动去bean工厂中找@Qualifier("名称")指定名称的对象进行注入,赋值给school引用
     * 我们已经使用注解创建了School对象。
     * */
    @Autowired
    @Qualifier("schoolNew")
    private School school;

    public Student() {
        System.out.println("Student的无参构造方法执行了。。。");
    }

    public Student(String stuName, int age) {
        System.out.println("Student的you参构造方法执行了。。。");
        this.stuName = stuName;
        this.age = age;
    }

    @Override
    public String toString() {
        return "Student{" +
                "stuName='" + stuName + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}

 添加包扫描的多种方式

当我们使用基于注解的IOC时,需要在applicationContext.xml文件中指定Spring要去扫描的包,这样它就会去扫描指定包下的类,看有没有要创建对象的注解。

指定包扫描的多种方式:

  1)单个包扫描(推荐使用)

要创建哪些对象,就只指定这些对象所在的包。
    <context:component-scan base-package="com.bjpowernode.controller"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.service.impl"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.dao"></context:component-scan>
  2)多个包扫描,多个包之间以逗号或空格或分号分隔
    <context:component-scan base-package="com.bjpowernode.controller com.bjpowernode.service ,com.bjpowernode.dao"></context:component-scan>
  3)扫描根包(不推荐)
    <context:component-scan base-package="com.bjpowernode"></context:component-scan>
    会降低容器启动的速度,导致多做无用功.

三层架构项目的分析

界面层view和controller

业务逻辑层service

数据持久层bean和dao/mapper

普通的三层项目

/**
 * 三层架构之界面层的controller
 * 
 * @author shkstart
 * @create 2022-06-06 9:42
 */
public class UserServlet {
    // 面向接口编程
    private UserService userService = new UserServiceImpl();


    public void doPost(){
        System.out.println("获取要注册的用户信息");
        Integer newUId = 1;
        String newUName = "纯氧";
        User newUser = new User(newUId, newUName);

        // 调用业务逻辑层进行用户注册的业务处理
        int result = userService.userRegister(newUser);

        if (result == 1) {
            System.out.println(newUName + "注册成功");
        }
    }
}
/**
 * 三层架构之业务逻辑层
 * 
 * @author shkstart
 * @create 2022-06-06 9:47
 */
public class UserServiceImpl implements UserService{
    private UserMapper userMapper = new UserMapperImpl();

    @Override
    public int userRegister(User user) {
        // 调用数据持久层将注册的信息插入到底层数据库

        return userMapper.insertUser(user);
    }
}
/**
 * 三层架构之数据访问层
 * 
 * @author shkstart
 * @create 2022-06-06 9:49
 */
public class UserMapperImpl implements UserMapper{
    @Override
    public int insertUser(User user) {
        System.out.println(user.getuName() + "用户信息已经保存到数据库中");

        return 1;
    }
}

测试:

    @Test
    public void test1(){
        UserServlet userServlet = new UserServlet();
        userServlet.doPost();
    }

基于xml的IOC三层架构项目(Spring接管对象)

由Spring管理三层架构项目中的对象创建,包含界面层、业务逻辑层和数据访问层的对象创建。要创建的对象以及依赖信息在applicationContext.xml文件中配置。

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

    <!--创建项目中需要的各种对象-->

    <!--创建数据访问层的对象-->
    <bean id="userMapperBySpring" class="com.lin.sanceng.mapper.UserMapperImpl"></bean>

    <!--创建业务逻辑层的对象,实现类对象赋给接口引用(多态)-->
    <bean id="userServiceBySpring" class="com.lin.sanceng.service.UserServiceImpl">
        <!--注入业务逻辑层对象所需的依赖-->
        <property name="userMapper" ref="userMapperBySpring"></property>
    </bean>

    <!--创建界面层的对象-->
    <bean id="uerController" class="com.lin.sanceng.controller.UserServlet">
        <!--注入userServlet对象所需的依赖,实现类对象赋给接口引用(多态)-->
        <property name="userService" ref="userServiceBySpring"></property>
    </bean>
</beans>
/**
 * 三层架构之数据访问层
 *
 * @author shkstart
 * @create 2022-06-06 9:49
 */
public class UserMapperImpl implements UserMapper{
    @Override
    public int insertUser(User user) {
        System.out.println(user.getuName() + "用户信息已经保存到数据库中");

        return 1;
    }
}
/**
 * 三层架构之业务逻辑层
 *
 * @author shkstart
 * @create 2022-06-06 9:47
 */
public class UserServiceImpl implements UserService{

    // 这个数据访问层的对象userMapper交给Spring创建了,因此这个依赖也交给Spring注入(需要提供setXXX方法)
//    private UserMapper userMapper = new UserMapperImpl();
    private UserMapper userMapper;

    public void setUserMapper(UserMapper userMapper) {
        this.userMapper = userMapper;
    }

    @Override
    public int userRegister(User user) {
        // 调用数据持久层将注册的信息插入到底层数据库

        return userMapper.insertUser(user);
    }
}
/**
 * 三层架构之界面层的controller
 *
 * @author shkstart
 * @create 2022-06-06 9:42
 */
public class UserServlet {
    // 面向接口编程
    // 这个业务逻辑层的对象userService交给Spring创建了,因此这个依赖也交给Spring注入(需要提供setXXX方法)
//    private UserService userService = new UserServiceImpl();

    private UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    public void doPost(){
        System.out.println("获取要注册的用户信息");
        Integer newUId = 1;
        String newUName = "纯氧";
        User newUser = new User(newUId, newUName);

        // 调用业务逻辑层进行用户注册的业务处理
        int result = userService.userRegister(newUser);

        if (result == 1) {
            System.out.println(newUName + "注册成功");
        }
    }
}

测试: 

    @Test
    public void test1(){
        // 创建Spring容器对象同时启动容器,创建里面的所有对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 从Spring容器中获取界面控制层的userController对象
        UserServlet uerController = (UserServlet) ac.getBean("uerController");
        uerController.doPost();
    }

基于注解的IOC三层架构项目(Spring接管对象)

由Spring管理三层架构项目中的对象创建,包含界面层、业务逻辑层和数据访问层的对象创建。要创建的对象以及依赖信息都直接使用注解配置即可。

/**
 * 三层架构之数据访问层
 *
 * @author shkstart
 * @create 2022-06-06 9:49
 */
@Repository // 此注解说明交给Spring用来创建数据访问层的对象
public class UserMapperImpl implements UserMapper{
    @Override
    public int insertUser(User user) {
        System.out.println(user.getuName() + "用户信息已经保存到数据库中");

        return 1;
    }

    public UserMapperImpl() {
        System.out.println("UserMapperImpl的无参构造方法。。。。");
    }
}
/**
 * 三层架构之业务逻辑层
 *
 * @author shkstart
 * @create 2022-06-06 9:47
 */
@Service // 此注解说明交给Spring来专门创建Service业务逻辑层的对象
public class UserServiceImpl implements UserService{
    /*
    * 依赖注入也交给Spring
    * */
    @Autowired
    private UserMapper userMapper;
//    private UserMapper userMapper = new UserMapperImpl();

    @Override
    public int userRegister(User user) {
        // 调用数据持久层将注册的信息插入到底层数据库

        return userMapper.insertUser(user);
    }

    public UserServiceImpl() {
        System.out.println("UserServiceImpl的无参构造方法。。。。");
    }
}
/**
 * 三层架构之界面层的controller
 *
 * @author shkstart
 * @create 2022-06-06 9:42
 */
@Controller // 此注解说明交给Spring专门用来创建界面层之控制层的对象
public class UserServlet {
    // 面向接口编程

    @Autowired
    private UserService userService;
//    private UserService userService = new UserServiceImpl();

    public void doPost(){
        System.out.println("获取要注册的用户信息");
        Integer newUId = 1;
        String newUName = "纯氧";
        User newUser = new User(newUId, newUName);

        // 调用业务逻辑层进行用户注册的业务处理
        int result = userService.userRegister(newUser);

        if (result == 1) {
            System.out.println(newUName + "注册成功");
        }
    }

    public UserServlet() {
        System.out.println("UserServlet的无参构造方法。。。。");
    }
}
<?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扫描的包-->
    <context:component-scan base-package="com.lin.sanceng"></context:component-scan>
</beans>

测试: 

    @Test
    public void test1(){
        // 创建Spring容器的对象同时启动容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        // 取出容器中的UserServlet对象,调用方法执行业务
        UserServlet userServlet = (UserServlet) ac.getBean("userServlet");
        userServlet.doPost();
    }

Spring核心配置文件的拆分策略

  当项目越来越大,需要多人合作开发,一个配置就存在很大隐患.

拆分配置文件的策略
    A.按层拆
      三层之控制层:applicationContext_controller.xml        
        <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="bController" class="com.bjpowernode.controller.BookController">
      三层之业务逻辑层:applicationContext_service.xml        
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="bService" class="com.bjpowernode.controller.BookService">
      三层之数据访问层:applicationContext_mapper.xml        
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">

    B.按功能拆
      和用户相关的功能:applicationContext_users.xml        
        <bean id="uController" class="com.bjpowernode.controller.UsersController">
        <bean id="uService" class="com.bjpowernode.controller.UsersService">
        <bean id="uMapper" class="com.bjpowernode.controller.UsersMapper">
      和图书相关的功能:applicationContext_book.xml      
        <bean id="bController" class="com.bjpowernode.controller.BookController">
        <bean id="bService" class="com.bjpowernode.controller.BookService">
        <bean id="bMapper" class="com.bjpowernode.controller.BookMapper">

拆分基于注解的IOC的三层项目的配置文件

<?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扫描的包-->
    <!--数据访问层对象所在的包-->
    <context:component-scan base-package="com.lin.sanceng.mapper"></context:component-scan>
</beans>
<?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扫描的包-->
    <!--业务逻辑层对象所在的包-->
    <context:component-scan base-package="com.lin.sanceng.service"></context:component-scan>
</beans>
<?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扫描的包-->
    <!--界面层之控制层中的对象所在的包-->
    <context:component-scan base-package="com.lin.sanceng.controller"></context:component-scan>
</beans>
<?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核心配置文件-->

    <!--一个一个导入整合-->
<!--    <import resource="applicationContext_mapper.xml"></import>-->
<!--    <import resource="applicationContext_service.xml"></import>-->
<!--    <import resource="applicationContext_controller.xml"></import>-->

    <!--批量导入-->
    <import resource="applicationContext_*.xml"></import>
</beans>
// 创建Spring容器的对象同时启动容器,只需要将那个整合的配置文件作为参数就可以。
ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");

三、面向切面编程AOP

 AOP(Aspect Orient Programming),面向切面编程。

切面:存放公共的,通用的,一些固定的,重复的代码。

面向切面编程开发就是将公共的,通用的,固定的,重复的代码单独开发在一个切面中,在需要的时候再从切面中反织回去。底层的原理是动态代理.

/**
 * 事务切面,做一些和事务相关的重复操作
 * 事务的处理,就都是这些操作:事务开启、事务提交、事务回滚。
    所以可以将事务的处理单独开发在一个切面中。
 *
 * @author shkstart
 * @create 2022-06-09 16:09
 */
public class TransactionAspect implements Aspect{


    @Override
    public void before() {
        System.out.println("事务开启....");
    }

    @Override
    public void after() {
        System.out.println("事务提交......");
    }

    @Override
    public void exception() {
        System.out.println("事务回滚.......");
    }
}
/**
 * 日志切面
 * 做一些和日志相关的重复操作
 *
 * @author shkstart
 * @create 2022-06-09 16:28
 */
public class LogAspect implements Aspect{
    @Override
    public void before() {
        System.out.println("日志输出...........");
    }

}
public class ProxyFactory {

    /**
     * 使用动态代理,获取代理对象。
     *
     * @param target 被代理对象,目标业务对象
     * @param aspect 切面对象。需要哪个切面中的功能就传哪个切面的实现类(反织)
     * @return
     */
    public static Object getAgent(Service target, Aspect aspect){

        Object proxyInstance = Proxy.newProxyInstance(
                // 类加载器,用于加载代理类对象
                target.getClass().getClassLoader(),
                // 目标对象就是被代理对象实现的所有接口(通过反射获取)
                target.getClass().getInterfaces(),
                // 调用处理器(使用匿名实现类)
                new InvocationHandler() {
                    // proxy生成的代理类对象,method代理对象调用的方法buy()show(),args方法的参数
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;
                        try {
                            // 切面功能
                            aspect.before();
                            // 执行被代理对象的业务方法
                            returnValue = method.invoke(target, args);
                            // 切面功能
                            aspect.after();
                        } catch (Exception e) {
                            aspect.exception();
                        }

                        return returnValue;// 业务方法的返回值
                    }
                }

        );

        return proxyInstance;
    }
}

手写AOP框架 

  业务:图书购买业务
  切面:事务(此处事务的处理代码就是重复的?)
  1)第一个版本:业务和切面紧耦合在一起,没有拆分.
  2)第二个版本:使用子类代理的方式拆分业务和切面.
  3)第三个版本:使用静态代理拆分业务和切面。业务和业务接口已拆分。此时切面紧耦合在业务中.
  4)第四个版本:使用静态代理拆分业务和业务接口,切面和切面接口.
  5)第五个版本:使用动态代理完成第四个版本的优化.

手写AOP框架第一个版本

package com.lin.spring.service;

/**
 * 此时图书购买业务和事务切面耦合在一起.
 *
 * @author shkstart
 * @create 2022-06-06 22:22
 */
public class BookServiceImpl {

    // 购买图书业务
    public void buyBook(){
        try {
            System.out.println("事务开启.....");

            System.out.println("购买图书功能代码实现.....");

            System.out.println("事务提交.........");
        } catch (Exception e) {

            System.out.println("事务回滚.........");
            e.printStackTrace();
        }
    }
}

手写AOP框架第二个版本

/**
 * 手写AOP框架的第二个版本
 *
 * 使用子类代理的方式拆分图书业务和事务切面,解耦合
 *
 * @author shkstart
 * @create 2022-06-06 22:22
 */
public class BookServiceImpl {

    // 在父类中只有购买图书业务
    public void buyBook(){
            System.out.println("购买图书功能代码实现.....");

    }
}
/**
 * 子类代理类,代理类负责做一些公共的工作,实际的核心工作还是由被代理对象自己做
 * 这里,代理类负责事务的切面
 *
 * @author shkstart
 * @create 2022-06-07 15:54
 */
public class SubBookServiceImpl extends BookServiceImpl{

    @Override
    public void buyBook() {
        try {
            // 事务切面
            System.out.println("事务开启........");

            // 调用父类被代理对象执行购买图书业务
            super.buyBook();

            // 事务切面
            System.out.println("事务提交.......");
        } catch (Exception e) {
            System.out.println("事务回滚..........");
            e.printStackTrace();
        }
    }
}
    @Test
    public void test1(){
        BookServiceImpl service = new SubBookServiceImpl();

        service.buyBook();
    }

手写AOP框架第三个版本

以上版本业务固定了。第三个版本,我们采用业务接口,在接口中定义业务的方法。这样只要面向业务接口编程,只需切换业务实现类对象,即可切换业务,让业务灵活起来

public interface Service {

    // 购买业务
    void buy();

}
/**
 * 手写AOP框架的第三个版本
 *
 * 目标对象. 也就是被代理对象
 * 实现具体业务
 *
 *
 * @author shkstart
 * @create 2022-06-07 16:26
 */
// 图书业务
public class BookService implements Service {

    @Override
    public void buy() {
        System.out.println("图书购买业务实现............");
    }
}
/**
 * 代理类.
 *
 * @author shkstart
 * @create 2022-06-07 16:28
 */
public class Agent implements Service {
    // 要被代理的目标对象, 设置成接口,只要切换目标对象即可实现灵活切换.
    private Service target;


    // 在代理对象创建的时候传入 被代理对象
    public Agent(Service target){
        this.target = target;
    }


    // 代理购买业务实现,
    // 其实是代理做一些比如事务的开启、事务的提交以及事务的回滚一些不用被代理对象做的,
    // 实际业务还是要被代理对象自己做.
    @Override
    public void buy() {
        // 事务切面
        // 此时有个问题,这个切面是写死的,要想切换切面为日志或者权限验证都要手动修改代码.
        System.out.println("事务开启..............");

        // 实际业务功能
        target.buy();

        // 事务切面
        System.out.println("事务提交..............");
    }

}
    @Test
    public void test1(){
        // 传入被代理对象
//        Service agent = new Agent(new BookService());

        // 只要切换被代理对象,即可代理其他目标对象.
        Service agent = new Agent(new ClothService());


        agent.buy();
    }

手写AOP框架第四个版本

第三个版本,有个问题,这个切面是写死的,要想切换切面为日志或者权限验证都要手动修改代码。

可以就切面定义为一个接口,这样就可以提供不同的切面实现类,实现不同的切面功能。

/**
 * 切面,定义各种重复的操作.
 *
 * @author shkstart
 * @create 2022-06-09 16:07
 */
public interface Aspect {


    // 在主业务执行之前执行
    void before();

    // 在主业务执行之后执行
    void after();

    // 在主业务执行出错后执行
    void exception();
}
/**
 * 事务切面,做一些和事务相关的重复操作
 *
 * @author shkstart
 * @create 2022-06-09 16:09
 */
public class TransactionAspect implements Aspect{


    @Override
    public void before() {
        System.out.println("事务开启....");
    }

    @Override
    public void after() {
        System.out.println("事务提交......");
    }

    @Override
    public void exception() {
        System.out.println("事务回滚.......");
    }
}
/**
 * 日志切面
 * 做一些和日志相关的重复操作
 *
 * @author shkstart
 * @create 2022-06-09 16:28
 */
public class LogAspect implements Aspect{
    @Override
    public void before() {
        System.out.println("日志输出...........");
    }

    @Override
    public void after() {
    }

    @Override
    public void exception() {
    }
}
/**
 * 代理类.
 *
 * @author shkstart
 * @create 2022-06-07 16:28
 */
public class Agent implements Service{
    // 要被代理的目标对象, 设置成接口,只要切换目标对象即可实现灵活切换.
    private Service target;

    // 切面,设置成接口,只要切换切面实现对象即可实现灵活切换想要用的切面,不用写死代码.
    private Aspect aspect;

    // 在代理对象创建的时候传入 被代理对象 和 切面对象
    public Agent(Service target,Aspect aspect){
        this.target = target;
        this.aspect = aspect;
    }

    @Override
    public void buy() {
        try {
            // 切面功能
            aspect.before();

            // 实际业务功能
            target.buy();

            // 切面功能
            aspect.after();
        } catch (Exception e) {
            aspect.exception();
        }
    }
}

一个业务中有多个切面功能

    @Test
    public void test3(){

        Service agent = new Agent(new BookService(),new TransactionAspect());
        Service agent2 = new Agent(agent,new LogAspect());

        agent2.buy();
    }

 

手写AOP框架第五个版本 

实现传哪个被代理类对象就创建相应的代理类对象,并返回

public class ProxyFactory {

    /**
     * 使用动态代理,获取代理对象。
     *
     * @param target 被代理对象,目标业务对象
     * @param aspect 切面对象。
     * @return
     */
    public static Object getAgent(Service target, Aspect aspect){

        Object proxyInstance = Proxy.newProxyInstance(
                // 类加载器,用于加载代理类对象
                target.getClass().getClassLoader(),
                // 目标对象就是被代理对象实现的所有接口(通过反射获取)
                target.getClass().getInterfaces(),
                // 调用处理器(使用匿名实现类)
                new InvocationHandler() {
                    // proxy生成的代理类对象,method代理对象调用的方法buy()show(),args方法的参数
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue = null;
                        try {
                            // 切面功能
                            aspect.before();
                            // 执行被代理对象的业务方法
                            returnValue = method.invoke(target, args);
                            // 切面功能
                            aspect.after();
                        } catch (Exception e) {
                            aspect.exception();
                        }

                        return returnValue;// 业务方法的返回值
                    }
                }

        );

        return proxyInstance;
    }
}
    @Test
    public void test(){
//        Service agent = (Service) ProxyFactory.getAgent(new ClothService(), new TransactionAspect());

        // 只需切换业务实现类 以及 切面实现类,即可完成自己想要的功能
        Service agent = (Service) ProxyFactory.getAgent(new BookService(), new TransactionAspect());

        // 可以任意切换业务功能
        agent.buy();
        String show = agent.show();
        System.out.println(show);

        /*Service agent2 = (Service) ProxyFactory.getAgent(agent, new LogAspect());

        agent2.buy();*/
    }

Spring原生的AOP

这个了解即可,重要的是AspectJ框架。

Spring支持AOP的编程,常用的有以下几种:
  1)Before通知:在目标方法被调用前调用,涉及接口org.springframework.aop.MethodBeforeAdvice; 
  2)After通知:在目标方法被调用后调用,涉及接口为org.springframework.aop.AfterReturningAdvice; 
  3)Throws通知:目标方法抛出异常时调用,涉及接口org.springframework.aop.ThrowsAdvice; 
  4)Around通知:拦截对目标对象方法调用,涉及接口为org.aopalliance.intercept.MethodInterceptor。

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

    <!--创建业务对象-->
    <bean name="BookServiceImpl" class="com.lin.spring.proxy3.BookService"></bean>

    <!--创建切面对象-->
    <bean name="LogAspect" class="com.lin.spring.proxy3.aspect.LogAspect"></bean>

    <!--绑定业务和切面-->
    <!--spring中的通过动态代理创建代理对象的工厂-->
    <bean name="bookServiceAgent" class="org.springframework.aop.framework.ProxyFactoryBean">
        <!--配置业务接口-->
        <property name="interfaces" value="com.lin.spring.proxy3.Service"></property>
        
        <!--配置切面-->
        <property name="interceptorNames">
            <list>
                <!--上面那个切面对象-->
                <value>LogAspect</value>
            </list>
        </property>

        <!--目标对象,就是被代理类对象-->
        <property name="target" ref="BookServiceImpl"></property>
    </bean>

</beans>
/**
 * 日志切面
 * 做一些和日志相关的重复操作
 *
 * @author shkstart
 * @create 2022-06-09 16:28
 */
public class LogAspect implements MethodBeforeAdvice {

    /**
     *
     * @param method 目标对象方法
     * @param objects 方法参数
     * @param o 目标对象
     * @throws Throwable
     */
    @Override
    public void before(Method method, Object[] objects, Object o) throws Throwable {
        System.out.println("日志输出" + method.getName());
        System.out.println(objects);
        System.out.println(o);
    }
}
    @Test
    public void test1(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

        Service bookServiceAgent = (Service) ac.getBean("bookServiceAgent");

        bookServiceAgent.buy();
    }

AOP相关术语

  •   1)切面:泛指交叉业务逻辑,或是公共的,通用的业务。上例中的事务处理、日志处理以及权限验证就可以理解为切面。
  •   2)连接点:就是目标方法,因为在目标方法中要实现目标方法的功能和切面功能。需要将切面功能和目标方法进行连接共同完成功能。连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
  •   3)切入点(Pointcut):一个连接点或多个连接点的集合。指定切入的位置,多个连接点构成切入点。切入点可以是一个目标方法,也可以是一个类中的所有方法,可以是某个包下的所有类中的目标方法。
  •   4)目标对象:操作谁,谁就是目标对象。(比如BookServiceImpl对象)
  •   5)通知(Advice):表示切面功能的执行时间,来指定切入的时机。是在目标方法执行前(前置通知)还是执行后(后置通知)还是出错时,还是环绕目标方法(环绕通知)切入切面功能。

面向切面编程的AspectJ框架 

AspectJ 是一个优秀面向切面的框架,它扩展了 Java 语言,提供了强大的切面实现,优化了面向切面编程的步骤。它因为是基于java语言开发的,所以无缝扩展.easy to learn and use(易学易用).

Spring看它好用就把它整合进来了。

<!--
    当业务实现类没有父接口时,可以使用CGLib子类代理,然后用实现类接代理对象。
        SomeServiceImpl service = (SomeServiceImpl)ac.getBean("someServiceImpl");
    当业务实现类有父接口时,就直接用jdk动态代理,用接口接收代理对象。
        SomeService service = (SomeService)ac.getBean("someServiceImpl");
-->

AspectJ常见通知类型 

  • (1)前置通知@Before:在目标方法执行之前执行切面功能。
  • (2)后置通知@AfterReturning:在目标方法正常执行之后返回值后执行切面功能。
  • (3)环绕通知@Around:方法执行前后分别执行切面功能。
  • (4)最终通知@After:不管目标方法有没有执行完成都会切入切面功能。
  • (5)定义切入点@Pointcut(了解)

AspectJ 的切入点表达式(掌握)

切入点表达式要匹配的对象就是目标方法的方法名。

表达式:public void main(参数)

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

简化版:

execution(方法返回值 方法声明(参数) ) 

举例说明: 

execution(public * *(..))

公共的,任意返回值类型且方法名任意,方法参数任意的方法。就是任意的公共方法。

execution(* set*(..))

任意返回值类型,方法名以set开头且任意参数的方法。切入点就是任意setXXX方法。

execution(* com.xyz.service.impl.*.*(..))

任意返回值类型,com.xyz.service.impl包下的任意类中的任意方法且参数也是任意的方法。切入点是:com.xyz.service.impl包下的任意类中的任意方法。

1.AspectJ的前置通知@Before

在目标方法执行前切入切面功能。在切面方法中不可以获得目标方法的返回值,因为是在目标方法执行前就执行了,只能得到目标方法的签名。

方法的签名?方法的访问权限、返回值、方法名、方法的参数 

1)先引入依赖: 

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

2)创建业务接口和业务实现类

/**
 * 定义各种业务功能
 *
 *
 * @author shkstart
 * @create 2022-06-11 22:31
 */
public interface SomeService {

    public String showName(String name,int age);
}
/**
 * 具体业务实现类
 * 
 * @author shkstart
 * @create 2022-06-11 22:32
 */
public class UserServiceImpl implements SomeService{
    @Override
    public String showName(String name, int age) {
        System.out.println("showName方法执行");
        return name;
    }
}

3)创建切面实现类

@Aspect // 指明这是一个切面,交给AspectJ框架管理,扫描到这个注解就会知道这个是一个切面类然后进行切面的操作
@Component // 创建切面对象
public class LogAspect {

    // 前置通知,在目标方法执行之前执行。
    /*
    * 切面方法,前置通知
     * 1)访问权限是public
     * 2)方法的返回值是void
     * 3)方法名称自定义
     * 4)方法没有参数,如果有也只能是JoinPoint类型
     * 5)必须使用@Before注解来声明切入的时机是前置通知和切入点
     *   参数:value  指定切入点表达式,
     *   这里是切入点就是一个连接点com.lin.aspectj1.UserServiceImpl.showName(方法名)
     *   在此showName方法执行前执行这个前置通知。
    * */
    @Before(value = "execution(public String com.lin.aspectj1.UserServiceImpl.showName(String,int ))")
    public void printLog(JoinPoint jp){
        System.out.println("打印日志信息");
        System.out.println("目标方法的签名:" + jp.getSignature());
        System.out.println("目标方法的参数:" + Arrays.toString(jp.getArgs()));
    }
}

4)在applicationContext.xml文件中绑定业务和切面

<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"
       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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--创建对象改为注解方式
    &lt;!&ndash;创建业务对象&ndash;&gt;
    <bean id="UserService" class="com.lin.aspectj.UserServiceImpl"></bean>

    &lt;!&ndash;创建切面对象&ndash;&gt;
    <bean id="LogAspect" class="com.lin.aspectj.LogAspect"></bean>-->


    <!--添加包扫描-->
    <context:component-scan base-package="com.lin.aspectj"></context:component-scan>

    <!--
        当业务实现类没有父接口时,可以使用CGLib子类代理,然后用实现类接代理对象。
            SomeServiceImpl service = (SomeServiceImpl)ac.getBean("someServiceImpl");
        当业务实现类有父接口时,就直接用jdk动态代理,用接口接收代理对象。
            SomeService service = (SomeService)ac.getBean("someServiceImpl");
    -->
    
    <!--绑定业务对象 和 切面对象-->
    <!--底层自动通过jdk动态代理创建业务对象 + 切面对象 的代理对象(只能用接口接收)-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

    <!--设置为CGLib子类代理,可以使用接口和实现类接代理对象-->
<!--    <aop:aspectj-autoproxy proxy-target-class="true"></aop:aspectj-autoproxy>-->
</beans>

测试:

    @Test
    public void test(){

        ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj1/applicationContext.xml");

        SomeService userService = (SomeService) ac.getBean("UserService");

        // 底层aspectj通过动态代理将业务对象的代理对象创建了
        System.out.println(userService.getClass());// class com.sun.proxy.$Proxy12

        // 这个业务方法绑定了一个前置通知
        userService.showName("dfd",34);
    }

 2.AspectJ的前置通知@AfterReturning

        1、在目标方法正常执行之后执行,目标方法没有发生异常。由于是目标方法之后执行,所以可以获取到目标方法的返回值。被注解为后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接收返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。

       

2、

/**
 * 后置通知的方法的规范
 * 1)访问权限是public
 * 
 * 2)方法没有返回值void
 * 
 * 3)方法名称自定义
 * 
 * 4)方法有参数(可以是JoinPint获取目标方法签名 和 Object获取目标方法的返回值)
 * (也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样可以处理无参可以处理有参),
 * 这个切面方法的参数就是目标方法的返回值,目标方法执行完成后将返回值赋给Object goalMethodReturn
 *
 * 5)使用@AfterReturning注解表明是后置通知
 *   参数:
 *      value:指定切入点表达式
 *      returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致.
 */

后置方法获取到目标方法的返回值后可以根据情况修改:

        如果如果目标方法的返回值类型是8中基本类型+String,则不能修改(传递的只是数据值)

       如果如果目标方法的返回值类型是引用数据类型,则可以修改(传递的是地址值) 

切面:

@Aspect
@Component
public class MyAspect {

    @Before(value = "execution(* com.lin.aspectj.UserServiceImpl.*(..))")
    public void MyBefore(){}

    // public String doSome(String name, int age) 目标方法
    @AfterReturning(returning = "goalMethodReturn",value = "execution(String com.lin.aspectj2.UserService.doSome(String,int)),")
    public void MyAfterReturning(Object goalMethodReturn){
        System.out.println("目标方法执行完毕,接入后置通知方法....");

        // 试图修改目标方法的返回值,此时返回值类型是String,修改失败(数据值传递)
        goalMethodReturn = "修改返回值";

        System.out.println("后置通知方法接收到目标方法的返回值:" + goalMethodReturn);
    }

    /**
     * 后置通知的方法的规范
     * 1)访问权限是public
     *
     * 2)方法没有返回值void
     *
     * 3)方法名称自定义
     *
     * 4)方法有参数(可以是JoinPint获取目标方法签名 和 Object获取目标方法的返回值)
     * (也可以没有参数,如果目标方法没有返回值,则可以写无参的方法,但一般会写有参,这样可以处理无参可以处理有参),
     * 这个切面方法的参数就是目标方法的返回值,目标方法执行完成后将返回值赋给Object goalMethodReturn
     *
     * 5)使用@AfterReturning注解表明是后置通知
     *   参数:
     *      value:指定切入点表达式
     *      returning:指定目标方法的返回值的名称,则名称必须与切面方法的参数名称一致.
     */
    @AfterReturning(value = "execution(* com.lin.aspectj2.*.get*(..))",returning = "goalMethodReturn")
    public void myAfterReturning2(Object goalMethodReturn){
        System.out.println("目标方法执行完毕,接入后置通知方法........");

        // 试图修改目标方法的返回值,此时返回值类型是User引用数据类型,修改成功(地址值传递)
        if (goalMethodReturn instanceof User){
            ((User) goalMethodReturn).setName("修改过的");
        }

        System.out.println("在后置通知方法中将目标方法的返回值改为:" + goalMethodReturn);
    }
}

 业务方法:

@Service
public class UserService implements SomeService{

    @Override
    public String doSome(String name, int age) {

        System.out.println("用户" + name + "今年" + age + "了.");
        return name;
    }

    @Override
    public User getUser(String name, int age) {

        System.out.println("目标方法getUser()执行...");

        return new User(name,age);
    }
}

xml:

<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"
       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 http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!--添加包扫描-->
    <context:component-scan base-package="com.lin.aspectj2"></context:component-scan>

    <!--声明自动代理,底层动态代理生成代理对象-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

测试:

    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj2/applicationContext.xml");
        SomeService userService = (SomeService) ac.getBean("userService");

        String name = userService.doSome("lin", 21);
        System.out.println("在测试中,目标方法的返回值:" + name);
    }

    @Test
    public void test2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj2/applicationContext.xml");
        SomeService userService = (SomeService) ac.getBean("userService");

        User user = userService.getUser("lin", 21);
        System.out.println("在测试中,目标方法的返回值:" + user);
    }

 3.AspectJ的环绕通知@Around

在目标方法执行之前之后执行。

它是通过拦截目标方法的方式 ,在目标方法前后增强功能的通知。它是功能最强大的通知,一般事务使用此通知。它可以轻易的改变目标方法的返回值。

@Aspect// 交给AspectJ框架管理切面
@Component // 交给Spring创建对象
public class MyAspect {

    /**
     * 环绕通知方法的规范
     * 1)访问权限是public
     * 2)切面方法有返回值,此返回值就是目标方法的返回值
     * 3)方法名称自定义
     * 4)方法有参数,此参数就是目标方法
     * 5)回避异常Throwable
     * 6)使用@Around注解声明是环绕通知
     *   参数:
     *      value:指定切入点表达式
     */

    /**
     *
     * @param pjp 目标方法,可以理解为目标方法被拦截下来了。
     * @return 目标方法的返回值
     */
    @Around(value = "execution(String com.lin.aspectj3.UserService.doSome(String))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        // 切面功能1
        System.out.println("事务开启" + Arrays.toString(pjp.getArgs()));

        // 业务功能执行(反射),pjp.getArgs()目标方法的参数
        Object goalMethodReturn = pjp.proceed(pjp.getArgs());//method.invoke();

        // 切面功能2
        System.out.println("事务提交");
        return goalMethodReturn;
    }
}
    @Test
    public void test(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("aspectj3/applicationContext.xml");

        // 获取代理类对象
        SomeService userService = (SomeService) ac.getBean("userService");

        System.out.println(userService.getClass());// class com.sun.proxy.$Proxy18

        String s = userService.doSome("lin");
        System.out.println("测试方法中,目标方法的返回值:" + s);
    }

 4.AspectJ的最终通知@After

无论目标方法是否可以正常执行完毕,最终通知的方法都会在目标方法后执行。

可以理解为try catch finally中的finally。

@Aspect
@Component
public class MyAspect {
    /**
     * 最终通知方法的规范
     * 1)访问权限是public
     * 2)方法没有返回值
     * 3)方法名称自定义
     * 4)方法没有参数,如果有也只能是JoinPoint
     * 5)使用@After注解表明是最终通知
     *   参数:
     *     value:指定切入点表达式
     */
    @After("execution(* com.lin.aspectj4.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知:释放资源........");
    }

5、@PointCut给切入点表达式起别名

可以给切入点表达式起别名,相当于起一个变量。

起别名前: 

@Aspect
@Component
public class MyAspect {
    /**
     * 最终通知方法的规范
     * 1)访问权限是public
     * 2)方法没有返回值
     * 3)方法名称自定义
     * 4)方法没有参数,如果有也只能是JoinPoint
     * 5)使用@After注解表明是最终通知
     *   参数:
     *     value:指定切入点表达式
     */
    @After("execution(* com.lin.aspectj4.*.*(..))")
    public void myAfter(){
        System.out.println("最终通知功能:释放资源........");
    }

    @Before("execution(* com.lin.aspectj4.*.*(..))")
    public void myBefore(){
        System.out.println("前置通知功能:日志输出........");
    }

    @AfterReturning(value = "execution(* com.lin.aspectj4.*.*(..))",returning = "goalReturn")
    public void myAfterReturning(Object goalReturn){
        System.out.println("后置通知功能:日志输出........");
    }

    @Around(value = "execution(* com.lin.aspectj4.*.*(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知中的前置通知功能:事务开启...........");

        Object re = pjp.proceed(pjp.getArgs());

        System.out.println("环绕通知中的后置通知功能:事务提交...........");
        return re;
    }

    // 给这个切入点execution(* com.lin.aspectj4.*.*(..))起别名mycut
    @Pointcut("execution(* com.lin.aspectj4.*.*(..))")
    public void mycut(){}
}

 起别名后:

@Aspect
@Component
public class MyAspect {
    /**
     * 最终通知方法的规范
     * 1)访问权限是public
     * 2)方法没有返回值
     * 3)方法名称自定义
     * 4)方法没有参数,如果有也只能是JoinPoint
     * 5)使用@After注解表明是最终通知
     *   参数:
     *     value:指定切入点表达式
     */
    @After("mycut()")
    public void myAfter(){
        System.out.println("最终通知功能:释放资源........");
    }

    @Before("mycut()")
    public void myBefore(){
        System.out.println("前置通知功能:日志输出........");
    }

    @AfterReturning(value = "mycut()",returning = "goalReturn")
    public void myAfterReturning(Object goalReturn){
        System.out.println("后置通知功能:日志输出........");
    }

    @Around(value = "mycut()")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
        System.out.println("环绕通知中的前置通知功能:事务开启...........");

        Object re = pjp.proceed(pjp.getArgs());

        System.out.println("环绕通知中的后置通知功能:事务提交...........");
        return re;
    }

    // 给这个切入点execution(* com.lin.aspectj4.*.*(..))起别名mycut
    @Pointcut("execution(* com.lin.aspectj4.*.*(..))")
    public void mycut(){}
}

四、Spring整合MyBatis

Spring看mybatis好用,就把Mybatis整合进来了,而且优化了很多操作。

比如自己创建Mapper接口对应的实现类对象。

/*
* 1、之前MyBatis获取UserMapper实现类的方式:
* InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
* SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
* SqlSession sqlSession = ssf.openSession(true);
* // 底层通过动态代理的方式获取UserMapper接口的实现类
* UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
*
* 2、现在只需要使用@Autowired注解,交给Spring的IOC思想,创建Mapper接口的实现类对象就交给Spring。
* UserMapper接口对应的动态代理的实现类对象,spring底层已经帮我们创建完了
      <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指明mapper.xml所在的包-->
        <property name="basePackage" value="com.lin.sm.mapper"></property>
      </bean>
* */

D:\workspaces\maven-workspace\ideaSpace\Spring\Spring_009_sm

整合步骤:

  1. 建表
  2. 新建maven项目Spring_009_sm
  3. 修改pom.xml文件,添加相关的依赖(使用老师提供)
  4. 添加MyBatis相应的模板(mybatis-config.xml和XXXMapper.xml文件)
  5. 添加SqlMapConfig.xml文件(MyBatis核心配置文件),并拷贝jdbc.propertiest属性文件(连接数据库的四个基本元素)到resources目录下
  6. 添加applicationContext_mapper.xml(数据访问层配置文件)
  7. 添加applicationContext_service.xml(业务逻辑层控制文件)
  8. 添加Users实体类,Accounts实体类
  9. 添加mapper包,添加UsersMapper接口和UsersMapper.xml文件并开发
  10. 添加service包,添加UsersService接口和UsersServiceImpl实现类
  11. 添加测试类进行功能测试

建表sql:

CREATE DATABASE ssm;

use ssm;

create table users(
  userid int primary key,
  uname varchar(20),
  upass varchar(20)
);

create table accounts(
 aid  int primary key,
 aname varchar(20),
 acontent varchar(50)
);

select userid,uname,upass from users;
select aid,aname,acontent from accounts;

pom.xml文件:

    <dependencies>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--aspectj依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.2.5.RELEASE</version>
        </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.6</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.32</version>
        </dependency>
        <!--阿里公司的数据库连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
    </dependencies>

    <build>
        <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
        <resources>
            <resource>
                <directory>src/main/java</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory><!--所在的目录-->
                <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

jdbc.propertiest属性文件:

jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm?useUnicode=true&characterEncoding=utf8
jdbc.username=root
jdbc.password=123

Spring接管MyBatis框架的配置文件:applicationContext_mapper.xml(数据访问层配置文件)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--以下就是Spring接管MyBatis框架配置-->


    <!--引入properties属性配置文件,用于连接数据库的四个基本元素-->
    <context:property-placeholder location="jdbc.properties"></context:property-placeholder>

    <!--创建数据源对象-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <!--设置数据库连接的四要素-->
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--配置SqlSessionFactoryBean类,用于创建SqlSession对象
    这里不设置id,是因为下面没有要引用到的地方-->
    <bean class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
        
        <!--配置mybatis的核心配置文件-->
        <property name="configLocation" value="mybatis-config.xml"></property>

        <!--给实体类起别名-->
        <property name="typeAliasesPackage" value="com.lin.sm.bean"></property>
    </bean>

    <!--引入映射文件,注册mapper.xml文件-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指明mapper.xml所在的包-->
        <property name="basePackage" value="com.lin.sm.mapper"></property>
    </bean>
</beans>

由于Spring接管了MyBatis框架,所以在MyBatis的核心配置文件中,就可以删除一些配置,因为Spring替MyBatis干了,但是有些活还是得由MyBatis自己干,所以要保留MyBatis的核心配置文件写一些配置由MyBatis自己做的活。

经Spring接管后的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>

    <!--Spring替MyBatis做了-->
<!--    <properties resource="jdbc.properties"/>-->

    <!--Spring替MyBatis做了-->
<!--    <typeAliases>-->
<!--        &lt;!&ndash;给包中的全类名起别名&ndash;&gt;-->
<!--        <package name="com.lin.sm.bean"/>-->
<!--    </typeAliases>-->

    <!--Spring替MyBatis做了-->
<!--    <environments default="development">-->
<!--        <environment id="development">-->
<!--            <transactionManager type="JDBC"/>-->
<!--            <dataSource type="POOLED">-->
<!--                <property name="driver" value="${jdbc.driver}"/>-->
<!--                <property name="url" value="${jdbc.url}"/>-->
<!--                <property name="username" value="${jdbc.username}"/>-->
<!--                <property name="password" value="${jdbc.password}"/>-->
<!--            </dataSource>-->
<!--        </environment>-->
<!--    </environments>-->

    <!--Spring替MyBatis做了-->
<!--    <mappers>-->
<!--        &lt;!&ndash;以包的形式引入映射文件&ndash;&gt;-->
<!--        <package name="com.lin.sm.mapper"/>-->
<!--    </mappers>-->
</configuration>

applicationContext_service.xml:

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

    <!--添加Service的包扫描-->
    <context:component-scan base-package="com.lin.sm.service.impl"></context:component-scan>
</beans>

UserServiceImpl:

@Service// 交给Spring创建对象
public class UserServiceImpl implements UserService {


    // Service层需要有一个数据持久层对象的依赖,这个依赖交给Spring来注入
  /*
    * 1、之前MyBatis获取UserMapper实现类的方式:
    * InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
    * SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(is);
    * SqlSession sqlSession = ssf.openSession(true);
    * // 底层通过动态代理的方式获取UserMapper接口的实现类
    * UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
    *
    * 2、现在只需要使用@Autowired注解,交给Spring的IOC思想,创建Mapper接口的实现类对象就交给Spring。
    * UserMapper接口对应的动态代理的实现类对象,spring底层已经帮我们创建完了
          <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!--指明mapper.xml所在的包-->
            <property name="basePackage" value="com.lin.sm.mapper"></property>
          </bean>
    * */
    @Autowired
    private UserMapper userMapper;


    @Override
    public int userRegister(User user) {
        // 调用数据持久层的方法将用户的信息保存到底层数据库
        int count = userMapper.insertUser(user);
        return count;
    }
}

sm整合测试:

    @Test
    public void testUser(){
        // 创建并启动Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("total.xml");

        // 取出userServiceImpl对象
        UserService userServiceImpl = (UserService) ac.getBean("userServiceImpl");


        // 要注册的用户
        User bat = new User(4, "bat", "234");

        // 注册业务实现
        int count = userServiceImpl.userRegister(bat);
        System.out.println(count);
    }

五、事务

由问题引出事务的重要性

@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;

    @Override
    public int addAccount(Account account) {
        int influencedRows = 0;
        influencedRows = accountMapper.insertAccount(account);
        System.out.println("增加账户成功,influencedRows="+influencedRows);

        // 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
        System.out.println(1/0);
        
        return influencedRows;
    }
}
    @Test
    public void testAccount(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");

        AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");


        // 要增加的账户
        Account account = new Account(3, "user3", "账户正常3");

        // 没有添加事务切面之前,业务对象
        System.out.println(accountService.getClass());// class com.lin.sm.service.impl.AccountServiceImpl

        int count = accountService.addAccount(account);
        System.out.println(count);
    }

 

从以上的运行截图,可以看出即使出错了,数据还是插入到了底层数据库,这样是不安全的。 

正常是使用事务,一旦出错就回滚,撤销出错前的代码。

Spring能够自动提交事务,但是不能自动回滚,所以需要手动添加。

Spring中添加事务的两种方式

  1)注解式的事务
    使用@Transactional注解完成事务控制,此注解可添加到类上,则对类中所有的方法执行事务的设定。此注解也可添加到方法上,只是对此方法执行事务的处理。

此方式有缺点,事务一般用于增删改操作,有出错则回滚增删改操作。倘若一个类中有查询方法的话,@Transactional注解声明在类上的话,就不合适了,因为查询方法不需要事务,这样就得在每个增删改方法上面声明@Transactional注解,代码量太大,如果只有增删改方法,则可以用用。
    
  2)声明式事务(必须掌握),在配置文件中添加一次,整个项目遵循事务的设定.

在Spring中通过注解添加事务

@Transactional注解参数详解
    @Transactional(propagation = Propagation.REQUIRED,//事务的传播特性
            noRollbackForClassName = "ArithmeticException", //指定发生什么异常不回滚,使用的是异常的名称
            noRollbackFor = ArithmeticException.class,//指定发生什么异常不回滚,使用的是异常的类型
            rollbackForClassName = "",//指定发生什么异常必须回滚
            rollbackFor = ArithmeticException.class,//指定发生什么异常必须回滚
            timeout = -1, //连接超时设置,默认值是-1,表示永不超时
            readOnly = false, //默认是false,如果是查询操作,必须设置为true.
            isolation = Isolation.DEFAULT//使用数据库默认的隔离级别        
    )

为什么要添加事务管理器?
  JDBC的事务处理:  Connection   con.commit();   con.rollback();
  MyBatis的事务处理:  SqlSession   sqlSession.commit();  sqlSession.rollback();
  Hibernate的事务处理:  Session    session.commit();   session.rollback();

  事务管理器用来生成相应技术的连接+执行语句的数据库操作对象.


  1)如果使用MyBatis框架,必须使用DataSourceTransactionManager类完成处理,用于生成SqlSession对象。

<!--添加事务管理器对象,用于生成数据库操作对象。-->  
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务必须关联数据库处理,所以要配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
</bean>

2)如果使用Hibernate框架,事务管理器对象

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">

                  <property name="sessionFactory" ref="sessionFactory" />

</bean> 

  项目中的所有事务,必须添加到业务逻辑层上.

1.在Spring注解文件中添加事务处理

    <!--事务处理-->
    <!--1.添加事务管理器对象-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--因为事务要关联数据库处理,所以得配置数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2.添加事务的注解驱动-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

2.在需要事务切面的业务逻辑的实现类上添加注解@Transactional(propagation = Propagation.REQUIRED)

    @Test
    public void testAccount2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");

        AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");


        // 要增加的账户
        Account account = new Account(4, "user4", "账户正常4");

        // 添加事务切面之后,业务对象是动态代理对象
        System.out.println(accountService.getClass());// com.sun.proxy.$Proxy22

        int count = accountService.addAccount(account);
        System.out.println(count);
    }

 

 可以看到,底层数据没有插入。添加完事务切面后,即使在执行完插入数据库的代码后,在出错后,也能回滚事务。

声明式事务的实现

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


    <!--添加包扫描,告知Spring哪些包中包含要交给Spring创建对象的类-->
    <context:component-scan base-package="com.lin.sm.service.impl"></context:component-scan>

    <!--导入Spring整合MyBatis的配置文件-->
    <import resource="applicationContext_mapper.xml"></import>

    <!--Spring的声明式事务处理-->
    <!--1.创建事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--事务管理器需要配合数据库操作,所以需要添加数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2.配置事务切面功能-->
    <tx:advice transaction-manager="transactionManager" id="myCut">
        <!--指定哪些业务方法拥有此事务切面功能-->
        <tx:attributes>
            <tx:method name="*insert*" propagation="REQUIRED"/><!--只要方法名包含insert的方法,都拥有传播特性属性为REQUIRED的事务-->
            <tx:method name="*add*" propagation="REQUIRED"/>
            <tx:method name="*Register*" propagation="REQUIRED"/>

            <tx:method name="*delete*" propagation="REQUIRED"/>
            <tx:method name="*remove*" propagation="REQUIRED"/>

            <tx:method name="*update*" propagation="REQUIRED"/>
            <tx:method name="*modify*" propagation="REQUIRED"/>

            <tx:method name="*select*" read-only="true"/>
            <tx:method name="*get*" read-only="true"/><!--只要方法名包含get的方法,都拥有只读属性为true的事务-->
        </tx:attributes>
    </tx:advice>

    <!--3.将事务切面 和 切入点 绑定,指定哪些接入点拥有上面的事务切面-->
    <aop:config>
        <!--给切入点起个id标识-->
        <aop:pointcut id="myServiceCut" expression="execution(* com.lin.sm.service.impl.*.*(..))"/>
        <!--绑定-->
        <aop:advisor advice-ref="myCut" pointcut-ref="myServiceCut"></aop:advisor>
    </aop:config>
</beans>

@Service// 交给Spring创建对象

public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AccountService accountService;


    /*
    * userRegister这个方法可以看成事务A,在事务A中包含事务B
    * */
    @Override
    public int userRegister(User user) {
        // 调用数据持久层的方法将用户的信息保存到底层数据库
        int count = userMapper.insertUser(user);
        System.out.println("用户注册成功");

        // 增加账户,包含事务B addAccount
        accountService.addAccount(new Account(5,"user5","用户5"));
        return count;
    }
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;

    /*
    * addAccount可以看成事务B
    * */
    @Override
    public int addAccount(Account account) {
        int influencedRows = 0;
        influencedRows = accountMapper.insertAccount(account);
        System.out.println("增加账户成功,influencedRows="+influencedRows);

        // 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
        System.out.println(1/0);

        return influencedRows;
    }
}
    @Test
    public void testUserByTrans(){
        // 创建并启动Spring容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_serviceTrans.xml");

        // 取出userServiceImpl对象
        UserService userServiceImpl = (UserService) ac.getBean("userServiceImpl");
        System.out.println(userServiceImpl.getClass());

        // 要注册的用户
        User bat = new User(4, "bat", "234");

        // 注册业务实现
        int count = userServiceImpl.userRegister(bat);
        System.out.println(count);
    }

  可以看到,声明式事务起作用了,底层数据没有插入。即使在执行完插入数据库的代码后,在出错后,也能回滚事务。

设置事务处理的优先级

当我们使用了声明式注解,又想使用注解式注解,就是某些不想受声明式事务管理。

就可以开启注解式事务,然后将注解式事务的优先级设置的比声明式事务的高,就会优先使用注解式事务。

<!--注解式事务的驱动,开启注解式事务。优先级order="100"-->
<tx:annotation-driven transaction-manager="transactionManager" order="100"></tx:annotation-driven>

 <!--3.将事务切面 和 切入点 绑定,指定哪些接入点拥有上面的事务切面-->
<aop:config>
        <!--给切入点起个id标识-->
        <aop:pointcut id="myServiceCut" expression="execution(* com.lin.sm.service.impl.*.*(..))"/>
        <!--绑定 advice-ref切面 pointcut-ref切入点-->
        <aop:advisor order="1" advice-ref="myCut" pointcut-ref="myServiceCut"></aop:advisor>
</aop:config>
@Service
// 不使用声明式事务,使用自己的注解式事务
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;

    /*
    * addAccount可以看成事务B
    * */
    @Override
    public int addAccount(Account account) {
        int influencedRows = 0;
        influencedRows = accountMapper.insertAccount(account);
        System.out.println("增加账户成功,influencedRows="+influencedRows);

        // 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
        System.out.println(1/0);

        return influencedRows;
    }
}

事务中指定某些错误类型不回滚

// 指定算术异常不回滚
@Transactional(propagation = Propagation.REQUIRED,noRollbackForClassName = "ArithmeticException")

noRollbackForClassName属性:指定不回滚的异常类型名称。

@Service
// 指定算术异常不回滚
@Transactional(propagation = Propagation.REQUIRED,noRollbackForClassName = "ArithmeticException")
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;

    @Override
    public int addAccount(Account account) {
        int influencedRows = 0;
        influencedRows = accountMapper.insertAccount(account);
        System.out.println("增加账户成功,influencedRows="+influencedRows);

        // 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
        System.out.println(1/0);

        return influencedRows;
    }
    @Test
    public void testAccount2(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext_service.xml");

        AccountService accountService = (AccountService) ac.getBean("accountServiceImpl");


        // 要增加的账户
        Account account = new Account(4, "user4", "账户正常4");

        // 添加事务切面之后,业务对象是动态代理对象
        System.out.println(accountService.getClass());// com.sun.proxy.$Proxy22

        int count = accountService.addAccount(account);
        System.out.println(count);
    }

可以看到就算出现了异常也可以规定哪个错误类型不回滚,更加灵活。 

数据库的隔离级别

多个事务并发访问数据库时,可能会导致各种数据并发问题,为了维护数据库事务的隔离性,常见的数据库隔离级别有四种:

一个数据库同时运行多个事务,就如多线程,数据是共享数据,需要一定的隔离级别。

  1. 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
  2. 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
  3. 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
  4. 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞
  5. isolation = Isolation.DEFAULT//使用数据库默认的隔离级别

数据库事务隔离的重要性: 

对于同时运行的多个事务,当这些事务同时操作同一个数据库中相同的数据时,
如果没有采取必要的隔离机制,就会导致各种并发问题,事务就相当于一个线程。(类似于Java中的线程安全问题,数据库就是共享数据):

--脏读: 对于两个事务T1, T2;T1读取了T2 还没提交的数据,读到的是T2更新的数据.
之后, 若T2 回滚, T1读取的内容就是临时且无效的。

--不可重复读: 对于两个事务T1, T2;T1 读取了一个字段, 然后T2 更新了该字段. 
之后, T1再次读取同一个字段, 值就不同了。

--幻读: 对于两个事务T1, T2; T1在T2提交插入语句之前对表进行查询, 然后T2 在该表中插入了一些新的行。
之后, 如果T1 再次查询这个表, 就会多出几行,读到插入的数据,简单说就是在同一个事务T1中,两次查询的结果不一样.

6.2数据库事务的隔离性: 数据库系统必须具有隔离并发运行各个事务的能力, 使它们不会相互影响, 避免各种并发问题. 
一个事务与其他事务隔离的程度称为隔离级别. 数据库规定了多种事务隔离级别, 
不同隔离级别对应不同的干扰程度, 隔离级别越高, 数据一致性就越好, 但并发性越弱,效率越低
通过设置隔离级别来解决并发问题

事务的隔离级别:
                                    脏读    不可重复读    幻读
read uncommitted:    √                √                    √
读取未提交的

read committed:    ×                √                    √
读取已提交的

repeatable read    :    ×                ×            √
可重复读

serializable:        ×                       ×                ×
串行化

说明:
    1.打对勾√的表示不可以避免,打叉的说明此问题可以避免
    2.MySQL中默认的隔离级别:repeatable read可重复读
    3.Oracle中默认的隔离级别:read committed读取已提交的

Spring事务的传播特性

  多个事务之间的合并,互斥等都可以通过设置事务的传播特性来解决。方法中拥有多个事务方法的调用,此时可以用事务的传播特性来控制事务。
  常用
  PROPAGATION_REQUIRED:必被包含事务(增删改必用)
  PROPAGATION_REQUIRES_NEW:自己新开事务,不管之前是否有事务
  PROPAGATION_SUPPORTS:支持事务,如果加入的方法有事务,则支持事务,如果没有,不单开事务
  PROPAGATION_NEVER:不能运行中事务中,如果包在事务中,抛异常
  PROPAGATION_NOT_SUPPORTED:不支持事务,运行在非事务的环境
  不常用
  PROPAGATION_MANDATORY:必须包在事务中,没有事务则抛异常
  PROPAGATION_NESTED:嵌套事务 

事务传播特性解析 

ok表示数据成功插入了,出现异常没有事务处理如回滚等。no表示数据没有插入成功,有事务处理。

场景三:

@Service// 交给Spring创建对象
@Transactional(propagation = Propagation.REQUIRED)// 增加事务切面
public class UserServiceImpl implements UserService {


    // Service层需要有一个数据持久层对象的依赖,这个依赖交给Spring来注入
    @Autowired
    private UserMapper userMapper;

    @Autowired
    private AccountService accountService;


    /*
    * userRegister这个方法可以看成事务A,在事务A中包含事务B
    * */
    @Override
    public int userRegister(User user) {
        // 调用数据持久层的方法将用户的信息保存到底层数据库
        int count = userMapper.insertUser(user);

        // 增加账户,包含事务B addAccount
        accountService.addAccount(new Account(5,"user5","用户5"));
        return count;
    }
}
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountMapper accountMapper;

    /*
    * addAccount可以看成事务B
    * */
    @Override
    public int addAccount(Account account) {
        int influencedRows = 0;
        influencedRows = accountMapper.insertAccount(account);
        System.out.println("增加账户成功,influencedRows="+influencedRows);

        // 手动抛出一个异常,模拟网络出错,出错的话应该不要让数据插入到底层数据库
        System.out.println(1/0);

        return influencedRows;
    }
}

  • 可以看到两个事务都没有成功插入数据,都回滚了。
  • 事务A有事务的控制, 在accountService.addAccount(new Account(5,"user5","用户5"));代码处出现异常了,所以回滚了。
  • 事务B没有事务的功能,但是还是回滚了,是因为它包含在事务A中,所以也具有了事务A的事务功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

躺着听Jay

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值