Spring入门案例

IOC控制反转

        控制:意思就是创建对象

        反转:把创建对象的权利转移给容器,由容器代替管理对象

        正转:开发人员使用new关键字创建对象就是正转

DI依赖注入

        DI是ioc的技术实现

        DI:依赖注入,只需要在程序中提供要使用的对象名称就可以

第一个例子

(1)创建maven工程,使用普通java的模板

(2)在pom.xml配置文件中添加标签

// 在pom.xml中设置

<build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
  </build>

                 主要是用于编译的时候找到包下的所有配置文件(包括resources配置文件),一起打包到target下的classes目录里

05a575f7bfff4e1783270d3189f90c7e.png

(3)导入spring依赖

                在pom.xml配置spring-context的依赖

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.18</version>
</dependency>

(4)创建spirng配置文件,使用IDEA自带的spring模板来创建

                *Spring配置文件名通常用:applicationContext.xml

a1d3f3c35b2543aa9b9158974f618b1a.png

(5)配置spring的配置文件

                配置文件中<beans>标签是根标签,在里面可以写<bean>标签,声明<bean>标签就是告诉spring要创建某个对象,其中<bean>标签中有id和class属性讲解,请看下方:

                id:对象的自定义名称,必须是唯一值。spring是通过该名称找到对象

                class:被创建的类的全限定名称(不能是接口,因为spring是通过反射创建对象)

b5c408c573f041c39d90803d927b8ff1.png

                 *Spring框架中有一个Map对象用于存放创建好的对象,然后通过<bean>中的id属性用来添加或获取对象

(6)获取对象

               通过创建Spring容器对象,调用getBean方法传入<bean>标签中id属性的值来获取对象

@Test
public void test() {
    // 配置文件的相对路径,相对于target下的classes目录(classes用于存放编译后的文件)
    String path = "bean.xml";

    // 创建Spring容器对象
    ApplicationContext temp = new ClassPathXmlApplicationContext(path);
         
    // 通过spring容器对象的getBean方法获取对象(传入的参数是spring配置文件中<bean>标签中id属性的值)
    Student student = (Student) temp.getBean("student");
}

Spring容器创建对象的时机

        Spring创建对象时机是在创建ClassPathXmlApplicationContext对象时,spring容器会读取spring配置文件,通过<bean>中属性class里类的全限定名称来依次创建对象

实例如下

 spring配置文件

<!-- 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">
    
    <bean id="student" class="com.temp.Student" />
</beans>

// 学生类
package com.temp;

public class Student {
    // 姓名
    private String name;

    public Student() {
        System.out.println("Spring容器创建Student成功!!!");
    }
    
}


// 测试
@Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
         ApplicationContext temp = new ClassPathXmlApplicationContext(path);
    }

***************************************结果

d2260dbcfe684bf19c3ca35b901be2a3.png

Spring创建非自定义对象

        之前创建的都是自己写的类(自定义对象),spring容器也可以创建非自定义的类(JDK自带的类)

        注意:spring容器创建对象时默认调用空参构造

实例如下:

<!-- 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">
    
    <bean id="student" class="com.temp.Student" />

    <!-- 日期类 -->
    <bean id="mydate" class="java.util.Date" />
</beans>
    // 测试
@Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取对象
        Date mydate = (Date) sprint.getBean("mydate");

        // 输出Date对象
        System.out.println(mydate);
    }

***************************************结果

0e36c6a5baaf4e5dab0a847240d3c3ef.png

注入(DI)

        “注入”的意思就是给对象属性赋值

注入(DI)的分类

        set注入:通过调用对象的set方法来赋值(大部分使用set注入)

        构造注入:通过调用对象的构造方法来赋值

基于Set方法的注入

        通过Spring配置文件配置(XML配置),<bean>标签与<bean>之间没有先后之分,spring容器创建对象会进行二次扫描

<!-- 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">

    <!-- <bean>与<bean>标签之间没有先后之分,spring容器创建对象会二次扫描 -->    

    <bean id="student" class="com.temp.Student" >
        <property name="name" value="黄某人" />
        <property name="dog" ref="Teddy" />
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

set注入

        注入简单类型

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <!-- property标签,name表示参数名,value表示要注入简单类型的值 -->
        <property name="name" value="黄某人" /> <!-- 表示setName("黄某人") -->
    </bean>

</beans>

// 要被创建的类
public class Student {
    // 姓名
    private String name;
    
    
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}



// 测试
@Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        // 输出对象
        System.out.println(student);
    }

 ***************************************结果

6291b3120dd948c4a23a3fd20e4ccde2.png

        注入引用类型

                 注入引用类型跟注入基本类型有点类似,不过基本类型<property>用value属性,引用类型<property>用ref属性

实例如下:

        

// 狗类
public class Dog {

    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}



// 学生类
public class Student {
    // 姓名
    private String name;

    // 狗
    private Dog dog;

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

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                '}';
    }
}



    // 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <property name="name" value="黄某人" />
        <property name="dog" ref="Teddy" /><!-- ref中填写的是<bean>的id属性值 -->
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

  

  ***************************************结果

2b2bfbf570a24af79d99297c1bc38105.png

set注入“重点”

        *set注入只关注方法名,不关注属性和方法中代码块的内容

        实例如下:

        

// 学生类
public class Student {
    // 姓名
    private String name;

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

    // 注意,Student类没有age属性,而有age的set方法
    public void setAge(int age){
        System.out.println("年龄为:" + age);
    }


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}



    // 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <property name="name" value="黄某人" />
        <property name="age" value="18" /><!-- 注意这 -->
    </bean>

</beans>

  

  ***************************************结果

4eff0b5af65840049c954ce5feca6b5e.png

基于构造方法的注入

        构造注入使用<constructor-arg>标签,该标签中有四个属性:name、index、value、ref。其中三个属性都很熟系了,就不说了。

        index属性跟name属性功能和用法类似。不过index是跟数组下标一样,都是从0开始,0表示构造方法第一个参数,1表示第二个参数,以此类推。不过index可以省略,默认按0、1、2、3的顺序。

        name属性、index属性和省略index三个可以混合使用,但不建议,特别是省略index

使用index

        index之间没有顺序之分

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <constructor-arg index="0" value="黄某人" /><!-- 注 意 点 -->
        <constructor-arg index="1" value="18" /><!-- 注 意 点 -->
        <constructor-arg index="2" ref="Teddy" /><!-- 注 意 点 -->
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

使用name

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <constructor-arg name="name" value="黄某人" />
        <constructor-arg name="age" value="18" />
        <constructor-arg name="dog" ref="Teddy" />
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

省略index

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <constructor-arg  value="黄某人" />
        <constructor-arg  value="18" />
        <constructor-arg  ref="Teddy" />
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

省略index、name和index混合使用

<!-- 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">
    
    <bean id="student" class="com.temp.Student" >
        <constructor-arg name="name"  value="黄某人" /><!-- 使用name属性 -->
        <constructor-arg index="1" value="18" /><!-- 使用index属性 -->
        <constructor-arg ref="Teddy" /><!-- 使用省略index -->
    </bean>

    <bean id="Teddy" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

***************************以下是上面使用到的类

// 狗类
public class Dog {

    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}




// 学生类
public class Student {
    // 姓名
    private String name;

    private Integer age;

    // 狗
    private Dog dog;

    public Student(String name, Integer age, Dog dog) {
        this.name = name;
        this.age = age;
        this.dog = dog;
    }

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




    // 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

   ***************************************结果

d2862baebe2149a290d95448d1611c54.png

 引用类型的自动注入

        自动注入分类

                byName注入(按名字注入):类中引用类型的属性名必须跟<bean>中name属性的值相同,且引用类型必须是同源的(同源:类型一致、父子类、接口和实现类

                byTyep注入(按类型注入):类中引用类型的属性的类型必须跟<bean>中class属性的值是同源的,且匹配数量只能有一个,否则报错

        *注意自动注入是通过set注入,不能通过构造方法注入,所以类必须要有set方法

        使用方法:在<bean>中的autowire属性中写入byName或byType

byName

        实例如下:

<!-- 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">
    
    <bean id="student" class="com.temp.Student" autowire="byName"><!-- 在autowire属性中添加byName -->
        <property name="name" value="黄某人" />
        <property name="age" value="18" />
    </bean>

    <bean id="dog" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>
// 狗类
public class Dog {

    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}





// 学生类
public class Student {
    // 姓名
    private String name;

    // 年龄
    private Integer age;

    // 狗
    private Dog dog;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

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




    //测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }


byType

        匹配规则:类中引用类型的属性的类型必须跟<bean>中class属性的值是同源的

        *注意:只能匹配一个,否则报错

        实例如下:

// 狗类
public class Dog {

    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}




// 学生类
public class Student {
    // 姓名
    private String name;

    // 年龄
    private Integer age;

    // 狗
    private Dog dog;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

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



// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

<!-- 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">
    
    <bean id="student" class="com.temp.Student" autowire="byType">
        <property name="name" value="黄某人" />
        <property name="age" value="18" />
    </bean>

    <bean id="dog" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

   ***************************************结果

d6dfc39fd68f4ee78e503e733a4d74b8.png

byType错误展示(多个匹配)

        只修改xml配置文件,类使用上方的代码

<!-- spring配置文件 -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="student" class="com.temp.Student" autowire="byType">
        <property name="name" value="黄某人" />
        <property name="age" value="18" />
    </bean>

    <!-- 有两个Dog类,能匹配到数量为2,所以会报错 -->
    <bean id="dog" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

    <bean id="dog2" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

   ***************************************结果

9fd8750e424d406dbd9ced95f026812e.png

多个配置文件

        优势:

                1、每个文件的大小比一个文件要小很多,效率高

                2、避免多人竞争带来的冲突

                如果在公司项目中只有一个配置文件,多个一起修改这个配置文件可能会带来冲突,而且文件一大,修改起来会很慢

        语法:<import resource="classpath:文件路径" />

                文件路径:是相对于target下的classes目录

实例如下:

<!-- 配置文件名:spring-temp01.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="student" class="com.temp.Student" autowire="byName">
        <property name="name" value="黄某人" />
        <property name="age" value="18" />
    </bean>

</beans>

<!-- 配置文件名:spring-temp02.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="dog" class="com.temp.Dog">
        <property name="name" value="泰迪" />
    </bean>

</beans>

聚合配置文件

        聚合配置文件一般里面不添加<bean>,主要用于导入其他配置文件

<!-- 配置文件名:bean.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">

    <!-- 导入两个配置文件文件 聚合 -->
    <import resource="classpath:sprint-temp01.xml" />
    <import resource="classpath:sprint-temp02.xml" />

    <!-- 
        下面是使用通配符方式
        <import resource="classpath:sprint-temp*.xml" />
     -->

</beans>

// 狗类
public class Dog {

    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}




// 学生类
public class Student {
    // 姓名
    private String name;

    // 年龄
    private Integer age;

    // 狗
    private Dog dog;

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

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

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



// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

    ***************************************结果

def1bc85638647e5aefad8b88ebc740d.png

 通配符

         作用:一次导入多个配置文件

        注意:多个配置文件名开头要一样,且聚合配置文件一定不能被通配符匹配到,否则会出错

        

                错误做法:有三个文件sprint-temp01.xml、sprint-temp02.xml、sprint.xml(聚合配置文件),在聚合配置文件sprint.xml用通配符导入另外两个配置文件,配置如下:

                        <import resource="classpath:sprint*.xml" />

                上面使用通配符匹配,匹配格式可以匹配到聚合配置文件,聚合配置文件会一直重复导入自己,所以会发生错误

               

                正确做法:只要匹配不到聚合配置文件,怎么样都行

                        <import resource="classpath:sprint-temp*.xml" />

 基于注解配置

7种注解

                @Component

                @Respotory

                @Service

                @Controller

                @Value

                @AutoWired

                @Resource

@Component注解

        作用:跟<bean>标签作用一样

                  注解中value属性等同于<bean>中id属性,属性值是唯一

        实例如下:

(1) 添加注解

        @Component中的参数中value可以省略,如:@Component("student")

// 学生类
// value参数可以省略如:  @Component("student")
@Component(value = "student") // @Component作用跟<bean>功能类似,value作用跟id功能类似
public class Student {
    // 姓名
    private String name;

    // 狗
    private Dog dog;

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

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                '}';
    }
}

(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"
       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">

<!--  组件(component)扫描器(scan)
      base-package: 指定使用注解的类的包名
      base-package工作方法: 会扫描指定包下的所有类,包括子包中的类,
                            根据注解的功能来创建对象或注入
  -->
    <context:component-scan base-package="com.temp" />

</beans>

(3)运行

    // 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

    ***************************************结果

3bb8dccd9f4049ed983a6e00766f07e8.png

@Component的value属性三种用法

一、填写value

@Component(value = "student")

二、省略value

@Component("student")

三、不写value

@Component

value值是被该注解修饰的类的首字母小写的名字,

如下中value值是student
@Component
public class Student {
}

如下中value值是dog
@Component
public class Dog {
}

@Respotory、@Service和@Controller

        @repository(用在持久层类的上面):放在dao的实现类上面

        @Service(用在业务层类的上面):放在serviec的实现类上面

        @Controller(用在控制层的上面):放在控制层类的上面

以上三个注解的使用语法和@Component一样。都能创建对象,但三个注解还有额外的功能

这三个用于给项目对象分层的

@Value

        @Value注解用于给基本类型属性赋值

两种用法

        第一种 :用在属性的上方,通过反射注入属性,不是调用set方法或构造方法来注入(建议使用)

// 狗类
@Component(value = "dog")
public class Dog {

    @Value(value = "泰迪")   // <--- 注 意 点, 其中value属性可以被省略
    private String name;

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

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

       第二种 :用在set方法上方,通过调用set方法来赋值

// 狗类
@Component(value = "dog")
public class Dog {

    
    private String name;

    @Value("泰迪")        // <--- 注 意 点,value属性已省略
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}

 注意:@Value放在方法下,不会注重方法的名字和代码块

// 狗类
@Component(value = "dog")
public class Dog {


    @Value("泰迪")                // <--- 注 意 点
    public void abc(String name,String abc) {    // <--- 注 意 点 ,方法名
        System.out.println(name);
        System.out.println(abc);
    }
}

     ***************************************结果

a0c63bc4e14d4bed892f55459827318a.png

@Autowired(自动注入)

        @Autowired的required属性(Boolean类型)作用:

                如果为true:注入失败程序报错,终止执行

                如果为false:注入失败程序继续执行,默认值为null

         @Autowired注解:是通过自动注入给引用类型属性赋值(默认使用byType)

         byName使用方法:在@AutoWired下面添加@Qualifier注解

                                        @Qualifier中的value属性值是注入对象的id值

注意

        添加了@Qualifier注解就是根据byName自动注入

        没有添加@Qualifier注解就是根据byType自动注入

两种用法

        第一种: 用在引用类型的属性上方,通过反射注入属性,不是调用set方法或构造方法来注入(推荐使用)

// 狗类
@Component(value = "dog")
public class Dog {

    @Value("泰迪")
    private String name;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}



// 学生类
@Component("student")
public class Student {
    // 姓名
    @Value("黄某人")
    private String name;

    // 狗
    @Autowired                    // <--- 注 意 点
    // @Qualifier("dog")             <--- 注 意 点
    private Dog dog;

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                '}';
    }
}




// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

      ***************************************结果

6742e3f6a74f4fd78fe02247200b5f65.png

        第二中:用在set方法上方

// 狗类
@Component(value = "dog")
public class Dog {

    @Value("泰迪")
    private String name;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}




// 学生类
@Component("student")
public class Student {
    // 姓名
    @Value("黄某人")
    private String name;

    // 狗
    private Dog dog;

    @Autowired                      //  <--- 注 意 点
    // @Qualifier("dog")                <--- 注 意 点
    public void setDog(Dog dog) {
        this.dog = dog;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                '}';
    }
}




// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

       ***************************************结果

6742e3f6a74f4fd78fe02247200b5f65.png

 @Resource(自动注入)

        @Resource注解是JDK提供的,跟@Resource用法一样,可以在属性上使用,也可以在set方法上使用

       

        @Resource注解支持byName和byType,默认是byName方式

        @Resource注解方式顺序:如果byName的方式注入失败,再使用byType的方式注入

        如果只想的byName注入,不使用byType注入,需要给@Resource的name属性传入值,如果byName方式注入失败,不会使用byType的方法注入,而是直接报错

实例如下:

// 狗类
@Component(value = "dog")
public class Dog {

    @Value("泰迪")
    private String name;

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                '}';
    }
}



// 学生类
@Component("student")
public class Student {
    // 姓名
    @Value("黄某人")
    private String name;

    // 狗
    @Resource()                // <--- 注 意 点
    private Dog dog;


    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", dog=" + dog +
                '}';
    }
}



    // 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Student student = (Student) sprint.getBean("student");

        System.out.println(student);

    }

       ***************************************结果

6742e3f6a74f4fd78fe02247200b5f65.png

 面向切面编程(AOP)

        AOP就是动态代理的规范化,把动态代理的实现步骤和方式都定义好了,让开发人员使用一种统一的方法来使用动态代理

        底层:AOP底层是动态代理

        AOP采用两种动态代理:JDK动态代理CGLIB动态代理

                JDK动态代理:使用JDK中的Proxy、Method、InvocaitionHanderl创建对象。

                                          JDK动态代理条件:必须有接口和接口的实现类

                CGLIB动态代理:原理是通过继承,通过继承目标类(要被增强的类)来创建子类

                                        CGLIB动态代理条件:目标类和目标方法不能被final关键字修饰

                动态代理的作用

                        1、在不改变目标类的源代码的情况下,来增加功能

                        2、减少代码的重复

                        3、专注业务逻辑代码

                        4、解耦合,功能分离度高 

        注意:在有接口的情况下可以使用JDK动态代理,也可以使用CGLIB动态代理

                   而且没有接口的情况下,只能使用CGLIB动态代理

spring术语

        Aspect:切面,表示在原来的功能上,新增的功能代码(增强的功能)就是切面

        JoinPoint:连接点,连接点就是原来的功能和新增的功能代码(切面)连接处就是连接点,(业务方法和切面的位置)

        Pointcut:切入点,指的是多个连接点的集合

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

        Advice:通知,通知表示切面功能的执行时间

AOP的技术实现框架:

        Spring框架:Spring在内部实现了AOP规范,能做AOP的工作

        AspectJ框架::是一个开源的专门做AOP的框架。spring框架中集成了AspectJ框架,通过Spring就能使用AspectJ的功能。

                AspectJ框架实现AOP有两种方式:

                        1、使用XML配置文件:配置全局事务

                        2、使用注解,在项目中要做aop功能,一般使用注解

AspectJ框架

        切面的执行时间,这个执行时间在规范中叫做Advice(通知)

五种Advice通知注解

@Before                                     前置通知

@AfterReturning                        后置通知

@Around                                    环绕通知

@AfterThrowing                         异常通知

@After                                          最终通知

AspectJ的切入点表达式

切入点表达式的原型

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

    modifiers-pattern 访问权限类型(public,protected,private,默认)
    ret-type-pattern 返回值类型 (void,简单类型,引用类型等)
    declaring-type-pattern 包名类型
    name-pattern(param-pattern) 方法名(参数类型和参数个数)
    throws-pattern 抛出异常类型
    ? 表示可选的部分

    以上表达式共四个部分.
    execution(访问权限 方法返回值 包名.方法声明(参数) 异常类型)

     方法返回值和方法声明(参数)不能省略

通配符

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

实例如下:

//指定切入点为任意公共方法
execution(public * *(..))

//指定切入点为任意"set"开头的方法
ececution(* set*(..))

//指定切入点: Student包中的任意类的任意方法
execution(* com.temp.Student.*.*(..))

//指定切入点: 定义在Student包或者其子包中的任意类中的任意方法
execution(* com.temp.Student..*.*(..))

//指定切入点: 表示所有包在的Student子包中的所有类中的所有方法
execution(* *..Student.*.*(..))

第一个案例(@Before前置通知)

(1)、创建Maven项目

(2)、导入依赖

        导入Spring和AspectJ框架的依赖

    <!-- Spring框架 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.3.18</version>
    </dependency>

    <!-- AspectJ框架 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.3.18</version>
    </dependency>

(3)、Spring配置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"
       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/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--  组件扫描器  -->
    <context:component-scan base-package="com.temp.ba02" />

    <!--  自动代理生成器  -->
    <aop:aspectj-autoproxy />

</beans>

(4)、类创建and测试(@Before前置通知)

*前置方法的定义要求:
        1、公共方法
        2、方法没有返回值
        3、方法名自定义
        4、方法可以有参数,也可以没有
            如果有参数不能是自定义,可以有JoinPoint

// 人接口
public interface Person {

    void speak(String name);
}


// 学生类,继承 人接口
@Component("student")
public class Student implements Person {

    @Override
    public void speak(String name) {
        System.out.println(name + "早晨在阳台上晨读!!!=============目标正在执行==============");
    }
}




/* @Aspect是aspectj框架的注解
 * 作用:表示当前类是切面类
 * 切面类:是用类给目标方法增加功能的类
 */
@Aspect                    
// 切面类
@Component("section")
public class Section {

    // 前置通知
    @Before(value = "execution(public void com.temp.ba02.Student.speak(String))")
    public void temp() {
        System.out.println("目标执行前时间:" + new Date());
    }
    /*
    前置方法的定义要求:
        1、公共方法
        2、方法没有返回值
        3、方法名自定义
        4、方法可以有参数,也可以没有
            如果有参数不能是自定义,可以有JoinPoint
     */
}



// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak("黄某人");

    }

       ***************************************结果

1a1b4eceee564781963ee7295aab7dc9.png

 JoinPoint对象

        JoinPoint:业务方法

        作用:可以在通知方法中获取目标方法执行的信息,例如方法名称,方法的实参。

                如果你的切面功能中需要用到方法的信息,就加入JoinPoint

        位置:在切面类中的切面方法参数位置,JoinPoint类型的形参必须是第一个位置

         (后面的通知注解方法中都可以使用)

        注意:环绕通知除外,因为环绕通知中的固定属性ProceedingJoinpoint类继承JoinPoint类

实例如下:

// 人接口
public interface Person {

    void speak(String name);
}


// 学生类,继承 人接口
@Component("student")
public class Student implements Person {

    @Override
    public void speak(String name) {
        System.out.println(name + "早晨在阳台上晨读!!!=============目标正在执行==============");
    }
}





@Aspect                    
// 切面类
@Component("section")
public class Section {

    // 前置通知
    @Before(value = "execution( void *..ba02.Student.speak(..))")
    public void temp(JoinPoint jp) {                            // <--- 注 意 点****
        System.out.println("目标方法名:"+jp.getSignature().getName());
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println("目标参数值:" + arg);
        }
        System.out.println("切面----------目标执行前时间:" + new Date());
    }
}



// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak("黄某人");

    }

***************************************结果

 19d3d6d6f0864bd98cc80278f6f559a7.png

@AfterReturning后置通知 

        @AfterReturning注解属性:

                value:切入表达式

                returning:自定义的变量,表示目标方法返回值

                                自定义变量名(Objec类型的形参名)必须和通知方法的形成名一样

                                如果目标方法没有返回值,那么可以不用写该属性

*后置方法的定义要求:
        1、公共方法
        2、方法没有返回值
        3、方法名自定义
        4、方法可以有参数

                有参数情况:目标方法有返回值,该参数作用是保存目标方法返回值,参数类型建议是Objec类型

                没有参数情况:目标方法没有返回值,因为目标方法没有返回值,所以该参数也就没用了

                如果需要JoinPoint对象,JoinPoint必须是第一位,Object类型第二位

                Object参数表示的是:目标方法的返回值

实例如下:

// 人接口
public interface Person {

    String speak(String name);
}


// 学生类(目标类)
@Component("student")
public class Student implements Person {

    // 目标方法
    @Override
    public String speak(String name) {
        System.out.println(name + "早晨在阳台上晨读!!!=============目标正在执行==============");
        return name;
    }
}


// 切面类
@Component("section")
@Aspect
public class Section {
    
    // 后置通知
    @AfterReturning(value = "execution( String *..ba02.Student.speak(..))", returning = "ref")
    public void temp(Object ref) {
        System.out.println("目标方法的返回值:"+ref);
        System.out.println("切面----------目标执行后时间:" + new Date());
    }
}




// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak("黄某人");

    }

***************************************结果

37a1000dfae44089a8a2a673a5111609.png

         如果目标对象返回给Objec参数的是基本数据类型的值,修改Objec参数不会影响到结果的返回结果

        如果是引用类型的是可以被影响到的

实例如下(基本类型):

// 人接口
public interface Person {

    Integer speak(String name);
}




// 学生类    目标类
@Component("student")
public class Student implements Person {

    // 目标方法
    @Override
    public Integer speak(String name) {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
        return 50;
    }
}



// 切面类
@Component("section")
@Aspect
public class Section {

    // 切面方法
    @AfterReturning(value = "execution( Integer *..ba02.Student.speak(..))", returning = "ref")
    public void temp(Object ref) {
        System.out.println("返回值修改前基本类型:"+ref);
        ref = 18;
        System.out.println("返回值修改后基本类型:"+ref);
    }
}



// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        Integer temp = student.speak("黄某人");
        System.out.println("结果:"+temp);
    }

***************************************结果

e073779a7e794fd8890b1da1b3143d44.png

实例如下(引用类型):

// 人接口
public interface Person {

    Student speak(String name);
}


// 学生类        目标类
@Component("student")
public class Student implements Person {

    private String name;

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

    // 目标方法
    @Override
    public Student speak(String name) {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
        Student student = new Student();
        student.setName(name);
        return student;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}




// 切面类
@Component("section")
@Aspect
public class Section {

    // 切面方法
    @AfterReturning(value = "execution( Student *..ba02.Student.speak(..))", returning = "ref")
    public void temp(Object ref) {
        System.out.println("返回值修改 前 引用类型: "+ref);
        Student temp = (Student) ref;
        temp.setName("李某人");
        System.out.println("返回值修改 后 引用类型: "+temp);
    }
}




// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        Student temp = student.speak("黄某人");
        System.out.println("结果:"+temp);
    }

***************************************结果

003a106a65e74f9da199aba9f5fefe5b.png

 @Around环绕通知

*环绕方法的定义要求:
        1、公共方法
        2、方法可以有返回值        返回值类型推荐Objec

                是否有返回值是根据目标方法有无返回值来定
        3、方法名自定义
        4、方法有固定的参数:ProceedingJoinPoint类型

注意

       (1)、 包含了前置通知后置通知的功能

        (2)、可以控制目标方法是否被调用执行

        (3)、可以修改目标方法返回值的结果

实例如下:

// 人类
public interface Person {

    Student speak(String name);
}



// 学生类        目标类
@Component("student")
public class Student implements Person {

    private String name;

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

    // 目标方法
    @Override
    public Student speak(String name) {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
        Student student = new Student();
        student.setName(name);
        return student;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                '}';
    }
}



// 切面类
@Component("section")
@Aspect
public class Section {

    @Around(value = "execution( Student *..ba02.Student.speak(..))")
    public Object temp(ProceedingJoinPoint pj) throws Throwable {
        Object temp;
        System.out.println("==================前置通知执行了==================");
        temp = pj.proceed();                            // 调用proceed方法,相当于执行目标方法,得到返回结果
        System.out.println("目标方法返回值:" + temp);
        System.out.println("==================后置通知执行了==================");
        Student student = (Student) temp;
        student.setName("李某人");                       // 修改返回值
        return temp;                          // 此处的return返回的就是结果的值
    }
}




// 测试
    @Test
    public void test() {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        Student temp = student.speak("黄某人");
        System.out.println("结果:"+temp);
    }

***************************************结果

8bd1e68a9d4346adbe6aa490dd60bc51.png

实例如下(修改目标方法返回值的结果):

// 切面类
@Component("section")
@Aspect
public class Section {

    // 切面方法
    @Around(value = "execution( Student *..ba02.Student.speak(..))")
    public Object temp(ProceedingJoinPoint pj) throws Throwable {
        Object temp;
        System.out.println("==================前置通知执行了==================");
        temp = pj.proceed();                            // 调用proceed方法,相当于执行目标方法,得到返回结果
        System.out.println("目标方法返回值:" + temp);
        System.out.println("==================后置通知执行了==================");
        return null;            // <--- 注 重 点
    }
}

***************************************结果

3c0a1e22aee94c58838f596c7be3c8af.png

ProceedingJoinpoint和JoinPoint关系

 ProceedingJoinpoint继承JoinPoint,所以环绕通知不用加入JoinPoint

f662494f3f3c44059df280ac043f84c2.png

@AfterThrowing异常通知

        @AfterThrowing属性:

                value:切入表达式

                throwing:自定义的变量,表示目标方法抛出的异常对象,变量名必须和方法的参数名(Exception类型)一致

*异常通知方法的定义要求:
        1、公共方法
        2、方法没有返回值
        3、方法名自定义
        4、方法有参数:Exception类型(必须有)、JoinPoint类型(可以有,但必须是第一个)

        *作用:当目标方法发生异常后,才会执行异常通知方法,如果没有发生异常就不会执行异常通知

@AfterThrowing注解作用相当于catch

try {
        代码
} catch (Exception e) {
        代码
} finally {
        代码
}

实例如下:

// 人接口
public interface Person {

    void speak() throws Exception;
}



// 学生类        目标类
@Component("student")
public class Student implements Person {

    // 目标方法
    @Override
    public void speak() throws Exception {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
        throw new Exception();    // 抛出异常
    }

}



// 切面类
@Component("section")
@Aspect
public class Section {
    
    // 切面方法     异常通知
    @AfterThrowing(value = "execution( void *..ba02.Student.speak(..))", throwing = "e")
    public void temp(Exception e) {                                    // 形成名和上方的值必须一致  
        System.out.println("目标方法发生了异常!!!!"+e.getMessage());
    }
}




// 测试
    @Test
    public void test() throws Exception {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak();
    }

***************************************结果

71a3c1219f2d43d289c720a2f096b88a.png

@After最终通知

@After注解作用相当于finally,所以该注解用于释放资源

try {
        代码
} catch (Exception e) {
        代码
} finally {
        代码
}

实例如下:

// 人接口
public interface Person {

    void speak() throws Exception;
}


// 学生类
@Component("student")
public class Student implements Person {

    @Override
    public void speak() throws Exception {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
        throw new Exception();
    }

}





// 切面类
@Component("section")
@Aspect
public class Section {

    // 切面方法
    @After(value = "execution( void *..ba02.Student.speak(..))")
    public void temp() {
        System.out.println("@after注解执行了!!!");
    }
}





// 测试
    @Test
    public void test() throws Exception {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak();
    }

***************************************结果

6c1757766ca5470c939e0d0c91305fff.png

@Pointcut注解

        该注解不是通知,而且是用于辅助切入表达式的

实例如下:

// 人接口
public interface Person {

    void speak() throws Exception;
}


// 学生类
@Component("student")
public class Student implements Person {

    @Override
    public void speak() {
        System.out.println("早晨在阳台上晨读!!!=============目标正在执行==============");
    }

}



// 切面类
@Component("section")
@Aspect
public class Section {

    // 前置通知
    @Before(value = "temp3()")    // 被@Pointcut注解修饰的方法名
    public void temp() {
        System.out.println("前置通知@Before注解执行了!!!");
    }
    // 后置通知
    @AfterReturning(value = "temp3()")  // 被@Pointcut注解修饰的方法名
    public void temp2() {
        System.out.println("后置通知@AfterReturning注解执行了!!!");
    }

    // Pointcut注解        <--- 注 重 点
    @Pointcut(value = "execution( void *..ba02.Student.speak(..))")
    public void temp3() {
    }
}




// 测试
    @Test
    public void test() throws Exception {

        String path = "bean.xml";

        // 创建Spring容器对象
        ApplicationContext sprint = new ClassPathXmlApplicationContext(path);

        // 获取Student对象
        Person student = (Person) sprint.getBean("student");

        student.speak();
    }

***************************************结果

d59f218f16f24165b7c7aa85682b8809.png

Spring集成MyBatis

1、创建项目

2、添加maven依赖

  <dependencies>

<!--  单元测试  -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>


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

<!--  下面两个用于做事务  -->
    <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和Spring整合的依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>
    
<!--  数据库依赖  -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>

<!--  mybatis依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

<!--  德鲁伊数据库连接池  -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>

  </dependencies>

3、实体类

public class Author {
    private Integer author_id;
    public String author_name;
    private String author_discription;
    
    // 此处省略set、get和toString方法
    
}

4、dao接口和Mapper映射文件

// 接口
public interface AuthorDao {

    ArrayList<Author> selectAll();

    void insertAuthor(Author author);

    void deleteAuthorById(Integer id);

    void modifyAuthor(Author author);

}

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

    <select id="selectAll" resultType="org.example.entity.Author">
        select * from author
    </select>

    <update id="modifyAuthor">
        update author set author_name = #{author_name}, author_discription = #{author_discription} where author_id = #{author_id}
    </update>

    <insert id="insertAuthor" >
        insert into author values(default,#{author_name},#{author_discription},1);
    </insert>

    <delete id="deleteAuthorById">
        delete from author where author_id = #{id}
    </delete>

</mapper>

5、mybatis主配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    
    <settings>
<!--    设置日志    -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

<!-- 我们不使用mybatis的数据源,所以不用写数据源,我们使用Druid数据源 -->

    
<!--  映射文件配置  -->
    <mappers>
        <mapper resource="org/example/dao/AuthorDao.xml"/>
    </mappers>
</configuration>

6、service类

// 实体接口
public interface AuthorService {

    ArrayList<Author> selectAll();

    void insertAuthor(Author author);

    void deleteAuthorById(Integer id);

    void modifyAuthor(Author author);
    
}


// 实体类
public class AuthorServiceImpl implements AuthorService {

    private AuthorDao authorDao;

    public void setAuthorDao(AuthorDao authorDao) {
        this.authorDao = authorDao;
    }

    @Override
    public ArrayList<Author> selectAll() {
        ArrayList<Author> authors = authorDao.selectAll();
        return authors;
    }

    @Override
    public void insertAuthor(Author author) {
        authorDao.insertAuthor(author);
    }

    @Override
    public void deleteAuthorById(Integer id) {
        authorDao.deleteAuthorById(id);
    }

    @Override
    public void modifyAuthor(Author author) {
        authorDao.modifyAuthor(author);
    }
}

7、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标签中有两个属性:init-method="方法名" destroy-method="方法名"
  init-method="方法名"属性表示:Druid连接池对象创建时调用该方法
  destroy-method="方法名"属性表示:Druid连接池对象销毁时调用该方法
  -->

    <!--  Druid德鲁伊数据库连接池  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="${url}" />
        <property name="username" value="${name}" />
        <property name="password" value="${pass}" />

        <!--  设置最大连接数  -->
        <property name="maxActive" value="${maxActive}" />
        <!--  设置初始连接数量  -->
        <property name="initialSize" value="${initialSize}" />
        <!--
            设置连接数据库的等待时间,以毫秒为单位,
            如一下配置,如果60秒后还是没有连接成功,会抛出异常
        -->
        <property name="maxWait" value="${maxWait}" />
    </bean>



    <!--
        创建SqlSessionFactoryBean类,该类是spirng和mybatis整合包中的类
        该类内部创建了SqlSessionFactory对象
      -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--  通过set方法设置数据库的连接池,代替mybatis主配置文件中的数据库连接池  -->
        <property name="dataSource" ref="dataSource" />

        <!--
            通过set方法设置mybatis主配置文件的路径
            spirng配置文件中要映入其他配置文件,必须在路径前面加上classpath:
          -->
        <property name="configLocation" value="classpath:MyBatis-config.xml" />
    </bean>


    <!--
      创建dao对象,使用sqlSession的getMapper(AuthorDao.class)
      MapperScannerConfiguer:在内部调用getMapper()生成每一个dao接口的代理对象
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--  指定sqlSessionFactory对象的id值, 注意点,是使用value属性  -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--
            指定包名:包名是dao接口所在的包名
            MapperScannerConfiguer会扫描这个包中所有接口
            把所有接口都执行一次getMapper()方法,把每个得到的dao对象放入到spring容器中
            dao对象在spirng容器中的名称是接口名(首字母小写)

            指定包,如果有多个包,可以在后面加逗号分隔
            如:value="org.example.dao01,org.example.dao02"
        -->
        <property name="basePackage" value="org.example.dao" />
    </bean>

    <!-- 创建AuthorServiceImpl对象,调用setAuthorDao方法,给AuthorDao赋值 -->
    <bean id="authorService" class="org.example.service.impl.AuthorServiceImpl">
        <property name="authorDao" ref="authorDao" />
    </bean>
</beans>

 下面是jdbc.properties配置文件

url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8
name=root
pass=123456
maxActive=20
initialSize=1
maxWait=6000

9、测试

@Test
    public void test() {
        String temp = "ApplicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(temp);
        AuthorService authorService = (AuthorService) ac.getBean("authorService");
        ArrayList<Author> authors = authorService.selectAll();
        System.out.println(authors);
    }

 事务

什么是事务?

        事务是指一组sql语句的集合,集合中有多条sql语句,可能是insert、update、select、delete。事务可以保证多个sql语句都能成功或者都失败,保证sql语句的执行是一致的

什么时候使用事务?

        当我们的操作涉及到多个表,或执行多个sql语句的时候,且需要保证多条sql的一致性的时候 

事务用在哪里?

        用在service类上,因为业务方法会调用多个dao的方法,执行多个sql语句

事务中的问题

        1、不同的数据库处理事务的对象和方法不同,需要了解不同的数据库事务的使用原理

        2、掌握多种数据库中事务的处理逻辑,如什么时候提交事务,什么时候回滚

        3、处理事务的多种方法

解决事务中的问题

        spirng提供一种事务的统一模型,能使用统一步骤,方式完成不同数据库访问技术的事务处理

处理事务

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

        事务的提交和回滚,是事务管理器来代替你完成的,是事务管理器内部调用的commit方法和rollback方法;(底层就是用环绕通知给方法加事务功能)

        管理器的接口:PlatformTransactionManager

        管理器的实现类:spring把每一种数据库访问技术对应的处理类都写好了。

                mybatis访问数据库-----------------spirng创建好的DataSourceTransactionManager

                hibernate访问数据库---------------spirng创建好的HibernateTransactionManager

        如何告诉spring我们使用的是哪种数据库访问技术:在spring的配置文件中使用<bean>声明数据库对应的管理器类就可以了,如使用mybatis访问数据库,应该在配置文件中设置:

<bean id="自定义" class="包名.DataSourceTransactionManager">

事务类型

        1、事务的隔离级别:有5个

                default(默认):mysql默认使用 可重复读,oracle默认使用读已提交

                Read uncommitted(读未提交数据): 会出现脏读、不可重复读、幻读

                Read committed(读已提交数据): 会出现不可重复读、幻读

                Repeatable read(可重复读): 会出现幻读

                Serializable(串行化): 什么都不会出现,不过效率低

        2、事务的超时时间:表示一个事务的最长的执行时间,如果执行的事务超过了时间,

                事务就回滚。单位是秒,整数值,默认是-1,-1的意思就是没有超时时间

        3、事务的传播行为:控制义务方法是不是有事务,是什么样的事务。

                7种传播行为:(加粗的必须掌握)

                        PROPAGATION_REQUIRED
                        PROPAGATION_SUPPORTS
                        PROPAGATION_REQUIRES_NEW

                        PROPAGATION_MANDATORY
                        PROPAGATION_NOT_SUPPORTED
                        PROPAGATION_NEVER
                        PROPAGATION_NESTED

                PROPAGATION_REQUIRED表示是,如果当前有事务,就在该事务中执行,

                        如果没有事务,就重新开启一个事务

                PROPAGATION_SUPPORTS表示是,如果当前有事务,就在该事务中执行,

                        如果没有事务,就非事务执行,不会开启一个事务

                PROPAGATION_REQUIRES_NEW表示是,不管当前有没有事务,都会开启一

                        个新事物,如果当前有事务,当前事务会先挂起,等新创建的事务结束

                        后,该事务才会恢复

提交事务和回滚事务的时机

        1、当业务方法没有异常抛出、执行完毕且成功后事务管理器才提交事务,

        2、当业务方法抛出运行时异常ERROR(错误),事务管理器会调用回滚

        3、当业务抛出非运行时异常,主要是受查异常是会提交事务

                受查异常:在我们写的代码中,必须要处理的异常

使用@Transactional注解完成事务(中小项目使用)

@Transactional可以放在方法上面(方法必须是public的)

        放在上面表示该类的所有方法都具有事务功能

        放在方法上面表示该方法具有事务功能

@Transactional的属性:

  • propagation:用于设置传播级别,默认值是:Propagation.Required
  • isolation:用于设置隔离级别,默认值是:isolation.default
  • readOnly:用于设置该方法对数据库的操作是否只读,默认值是false
  • timeout:设置连接数据库的超时时间,单位为秒,int类型。默认值是-1,没有时间限制
  • rollbackFor:指定需要回滚的异常类。类型为Class[],默认空数组,若只有一个异常类时,可以不用数组
  • rollbackForClassName:指定需要回滚的异常类的类名。类型为String[],默认空数组,若只有一个异常类时,可以不用数组
  • noRollbackFor:指定不需要回滚的异常类。类型为Class[],默认空数组,若只有一个异常类时,可以不用数组
  • noRollbackForClassName:指定不需要回滚的异常类的类名。类型为String[],默认空数组,若只有一个异常类时,可以不用数组

语法:

@Transactional(
        propagation = Propagation.REQUIRED,         // 传递类型
        isolation = Isolation.DEFAULT,              // 事务隔离级别
        timeout = -1,                               // 超时时间
        readOnly = false,                           // 是否只读
        rollbackFor = {MyException.class},          //  指定回滚异常类的类型
        rollbackForClassName = {"MyException"},     // 指定回滚异常类的类名
        noRollbackFor = {},                         // 指定不回滚异常类的类型
        noRollbackForClassName = {}                 // 指定不回滚异常类的类名
)
public void transferAccounts(Account remitter, Account recipient){
        代码
}

案例

演示的案例是:两个人互相转账的功能

数据库

 

 注入依赖

<dependencies>

<!--  单元测试  -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

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

      <!--  下面两个用于做事务  -->
    <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和Spring桥接的依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>

<!--  数据库依赖  -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>

<!--  mybatis依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

<!--  德鲁伊数据库连接池  -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
  </build>

实体类

public class Account {

    private Integer id;

    private String name;

    private Integer balance;

    // 此处省略get、set和toString方法
}

自定义异常类

public class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

dao接口和Mapper文件

public interface AccountDao {

    /**
     *
     * @param recipientId   收款人
     * @param balance   增加的余额
     */
    void addBalance(@Param("id") Integer recipientId,@Param("balance") Integer balance);

    /**
     * 减少余额
     * @param remitterId    汇款人
     * @param balance   减少的余额
     */
    void reduceBalance(@Param("id") Integer remitterId,@Param("balance")  Integer balance);

    /**
     * 查询余额
     * @param id    要查询的用户id
     * @return  返回查询的用户余额
     */
    int selectBalance(Integer id);

}
<?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="org.example.dao.AccountDao">

    <update id="addBalance">
        update account set balance = balance + #{balance} where id = #{id}
    </update>

    <update id="reduceBalance">
        update account set balance = balance - #{balance} where id = #{id}
    </update>

    <select id="selectBalance" parameterType="int" resultType="int">
        select balance from account where id = #{id}
    </select>
</mapper>

service层

/**
 * 接口
 */
public interface AccountService {

    /**
     * 转账功能
     * @param remitter  汇款人
     * @param recipient 收款人
     */
    void transferAccounts(Account remitter, Account recipient) throws MyException;
}



/**
 * 实现类
 */
public class AccountServiceImpl implements AccountService {


    private AccountDao accountDao;

    /**
     *  通过Spring注入给dao对象赋值
     * @param accountDao
     */
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    /**
     * 转账方法
     * @param remitter  汇款人
     * @param recipient 收款人
     * @throws MyException  余额不足异常
     */
    @Transactional(
            propagation = Propagation.REQUIRED,    // 事务传播类型
            isolation = Isolation.DEFAULT,         // 事务隔离级别
            timeout = -1,                          // 超时时间
            readOnly = false,                      // 是否只读
            rollbackFor = MyException.class        // 设置哪些异常类要回滚
            /*
                检查机制:spirng会检查列表中是否跟抛出的异常类一致,一致就执行回滚,
                        如果不一致,就会判断是否是运行时异常,是就回滚,不是就提交
            */        
    )
    @Override
    public void transferAccounts(Account remitter, Account recipient) throws MyException {

        // 汇款人属性
        Integer remitterId = remitter.getId();
        Integer remitterBalance = remitter.getBalance();

        // 收款人信息
        Integer recipientId = recipient.getId();

        accountDao.addBalance(recipientId,remitterBalance);

        int i = accountDao.selectBalance(remitterId);
        if (i < remitterBalance) {
            throw new MyException("余额不足!!!");
        }
        accountDao.reduceBalance(remitterId,remitterBalance);

    }
}

mybatis配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <mappers>
        <mapper resource="org/example/dao/AccountDao.xml"/>
    </mappers>
</configuration>

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"
       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">

   

    <!--  德鲁伊数据库连接池  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="initialSize" value="1" />
        <property name="maxActive" value="20" />
        <property name="maxWait" value="60000" />
    </bean>

    <!--  SqlSessionFactory工厂  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--  德鲁伊连接池对象  -->
        <property name="dataSource" ref="dataSource" />
        <!--  mybatis主配置文件路径  -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--  给所有Dao接口创建代理对象,并且放入spring容器中  -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
        <!--  设置SqlSessionFactory工厂对象,注意用的是value属性  -->         <!-- 注重点 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--  所有dao接口的包  -->
        <property name="basePackage" value="org.example.dao" />
    </bean>

    <!--  创建AccountServiceImpl对象  -->
    <bean id="accountService" class="org.example.service.impl.AccountServiceImpl">
        <!--  把dao接口的代理对象set注入给对象属性,该对象是通过上面的bean中自动加入到spring容器中的  -->
        <property name="accountDao" ref="accountDao" />
    </bean>
    
    <!--  创建Mybatis对应的事务管理器  -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--  传入德鲁伊数据库对象,用于告诉管理器我们用的什么数据库  -->
        <property name="dataSource" ref="dataSource" />
    </bean>

    <!--  表示开启事务,transaction-manager="事务管理器对象"  -->
    <tx:annotation-driven transaction-manager="transactionManager" />

</beans>

测试

    @Test
    public void test() throws MyException {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = (AccountService) ac.getBean("accountService");
        // 汇款人
        Account account = new Account(); 
        account.setId(1);
        // 转账余额
        account.setBalance(1000);
        // 收款人
        Account account1 = new Account();
        account1.setId(2);
        // 转账
        accountService.transferAccounts(account,account1);
    }

结果

因为id为1的用户余额只有500块,而要转出1000块,所以会报异常,且回滚

使用AspectJ完成事务(适用大型项目)

注意:记得要导入AspectJ依赖

<!-- 配置业务方法的事务属性(传播行为、隔离级别...) -->
<tx:advice id="自定义名称" transaction-manager="事务管理器id">
        <!-- 配置事务属性 -->
        <tx:attributes>
            <!-- 
            tx:method用于给具体方法配置事务属性,tx:method可以有多个,分别给不同方法设置不同属性 
            name:方法名:1、完整的方法名,不带包名和类名
                         2、使用通配符,通配符:*,表示任意字符
            -->
            <tx:method name="方法名" propagation="传播行为" isolation="隔离级别" read-only="是否只读" rollback-for="指定回滚" no-rollback-for="指定不回滚" timeout="超时时间"/>
        </tx:attributes>
</tx:advice>



<aop:config>
    <!--
        切入点表达式:指定哪些包中的类要使用事务
        id:切入点表达式的名称
        expression:切入点表达式,用于指定哪些类使用事务,aspectJ会创建代理对象
        aop:pointcut标签可以有多个
     -->
    <aop:pointcut id="servicePointcut" expression="execution(* *..service.*.*(..))"/>
        

    <!--
        配置增强器:关联adivce和pointcut
        advice-ref:上面tx:advice的id值
        pointcut-ref:上面tx:aop:pointcut标签的id值
        aop:advisor标签可以有多个
    -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePointcut" />
</aop:config>




案例

依旧是两个人互相转账的功能

添加依赖

  <dependencies>

<!--  单元测试  -->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>

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


      <!--  下面两个用于做事务  -->
    <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和Spring桥接的依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.2</version>
    </dependency>

<!--  数据库依赖  -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>

<!--  mybatis依赖  -->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.6</version>
    </dependency>

<!--  德鲁伊数据库连接池  -->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
<!--  AspectJ依赖  -->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.3.18</version>
      </dependency>

  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
    </resources>
  </build>

实体类

public class Account {
 
    private Integer id;
 
    private String name;
 
    private Integer balance;
 
    // 此处省略get、set和toString方法
}

自定义异常类

public class MyException extends Exception {
    public MyException(String message) {
        super(message);
    }
}

dao类和Mapper文件

public interface AccountDao {
 
    /**
     *
     * @param recipientId   收款人
     * @param balance   增加的余额
     */
    void addBalance(@Param("id") Integer recipientId,@Param("balance") Integer balance);
 
    /**
     * 减少余额
     * @param remitterId    汇款人
     * @param balance   减少的余额
     */
    void reduceBalance(@Param("id") Integer remitterId,@Param("balance")  Integer balance);
 
    /**
     * 查询余额
     * @param id    要查询的用户id
     * @return  返回查询的用户余额
     */
    int selectBalance(Integer id);
 
}
<?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="org.example.dao.AccountDao">
 
    <update id="addBalance">
        update account set balance = balance + #{balance} where id = #{id}
    </update>
 
    <update id="reduceBalance">
        update account set balance = balance - #{balance} where id = #{id}
    </update>
 
    <select id="selectBalance" parameterType="int" resultType="int">
        select balance from account where id = #{id}
    </select>
</mapper>

service层

/**
 * 接口
 */
public interface AccountService {
 
    /**
     * 转账功能
     * @param remitter  汇款人
     * @param recipient 收款人
     */
    void transferAccounts(Account remitter, Account recipient) throws MyException;
}
 
 
 
/**
 * 实现类
 */
public class AccountServiceImpl implements AccountService {
 
 
    private AccountDao accountDao;
 
    /**
     *  通过Spring注入给dao对象赋值
     * @param accountDao
     */
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }
 
    /**
     * 转账方法
     * @param remitter  汇款人
     * @param recipient 收款人
     * @throws MyException  余额不足异常
     */
    @Override
    public void transferAccounts(Account remitter, Account recipient) throws MyException {
 
        // 汇款人属性
        Integer remitterId = remitter.getId();
        Integer remitterBalance = remitter.getBalance();
 
        // 收款人信息
        Integer recipientId = recipient.getId();
 
        accountDao.addBalance(recipientId,remitterBalance);
 
        int i = accountDao.selectBalance(remitterId);
        if (i < remitterBalance) {
            throw new MyException("余额不足!!!");
        }
        accountDao.reduceBalance(remitterId,remitterBalance);
 
    }
}

mybatis配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
 
    <mappers>
        <mapper resource="org/example/dao/AccountDao.xml"/>
    </mappers>
</configuration>

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">

    <context:property-placeholder location="classpath:jdbc.properties" />

    

    <!--  德鲁伊数据库连接池  -->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="url" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=utf-8" />
        <property name="username" value="root" />
        <property name="password" value="123456" />
        <property name="initialSize" value="${initsize}" />
        <property name="maxActive" value="20" />
        <property name="maxWait" value="60000" />
    </bean>

    <!--  SqlSessionFactory工厂  -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--  德鲁伊连接池对象  -->
        <property name="dataSource" ref="dataSource" />
        <!--  mybatis主配置文件路径  -->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--  给所有Dao接口创建代理对象,并且放入spring容器中  -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer" >
        <!--  设置SqlSessionFactory工厂对象,注意用的是value属性  -->         <!-- 注重点 -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--  所有dao接口的包  -->
        <property name="basePackage" value="org.example.dao" />
    </bean>

    <!--  创建AccountServiceImpl对象  -->
    <bean id="accountService" class="org.example.service.impl.AccountServiceImpl">
        <!--  把dao接口的代理对象set注入给对象属性,该对象是通过上面的bean中自动加入到spring容器中的  -->
        <property name="accountDao" ref="accountDao" />
    </bean>

    <!--  创建Mybatis对应的事务管理器  -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--  传入德鲁伊数据库对象,用于告诉管理器我们用的什么数据库  -->
        <property name="dataSource" ref="dataSource" />
    </bean>


    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="transferAccounts" propagation="REQUIRED" isolation="DEFAULT" read-only="false" rollback-for="org.example.exception.MyException"  timeout="-1"/>
        </tx:attributes>
    </tx:advice>

<!--
    就可以不用下面该标签
    <tx:annotation-driven transaction-manager="transactionManager" /> 开启事务的标签
-->

    <aop:config>
<!--
    切入点表达式:指定哪些包中的类要使用事务
    id:切入点表达式的名称
    expression:切入点表达式,用于指定哪些类使用事务,aspectJ会创建代理对象
 -->
        <aop:pointcut id="servicePointcut" expression="execution(void *..service.*.transferAccounts(..))"/>

    <!--
        配置增强器:关联adivce和pointcut
        advice-ref:上面tx:advice的id值
        pointcut-ref:上面tx:aop:pointcut标签的id值
    -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePointcut" />


    </aop:config>

</beans>

测试

    @Test
    public void test() throws MyException {
        ApplicationContext ac = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = (AccountService) ac.getBean("accountService");
        // 汇款人
        Account account = new Account(); 
        account.setId(1);
        // 转账余额
        account.setBalance(2000);
        // 收款人
        Account account1 = new Account();
        account1.setId(2);
        // 转账
        accountService.transferAccounts(account,account1);
    }

结果

因为id为1的用户余额只有500块,而要转出2000块,所以会报异常,且回滚

 

  • 18
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

18岁_老大爷

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

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

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

打赏作者

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

抵扣说明:

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

余额充值