Java-Spring框架

了解什么是Spring

  • 优点

    • 轻量

      1
      Spring 框架使用的 jar 都比较小,一般在 1M 一下或者几百 KB,Spring 核心功能的所需的 jar 总共在 3M 左右	 
    • 针对接口编程

      1
      Spring 提供了 ioc 控制反转,由容器管理对象,对象的依赖关系,原来在程序代码中的对象创建方式,现在由容器来完成,对象之间的依赖解耦合。
    • AOP 编程支持

      1
      2
      通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付
      在 Spring 中,开发人员可以从繁琐的事物管理代码中解脱出来,通过声明方式灵活的进行事物的管理,提高开发效率和质量
    • 方便各种优秀框架的集成

      1
      Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度,Spring 提供了对各种优秀框架 (如: Struts,Hibernate,MyNatis)等的直接支持,简化框架的使用,Spring 像插线板一样,其他框架是插头,可以容易的组合到一起,需要使用那个框架,就把这个插头放入插线板中,不需要可以轻易的移除。
  • Spring 体系结构

    体系结构
    springtixi

使用Spring 框架

Spring核心功能

  • 第一个核心功能:IOC

    • 控制反转,是一个理论,思想

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

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

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

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

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

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

    • IOC 的技术实现:

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

IOC 控制反转

第一个Spring程序

  • 创建新项目-maven 项目

    创建 下一步
    createspring createnext
  • Spring 目录结构

    spring tree
    spring
  • 数据测试

    测试
  • 创建配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29


    <?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="" class="" />

    </beans>

    <!--
    作用: 告诉 spring 创建对象
    声明 bean 就是告诉 spring 要创建某个类的对象
    id: 对象的自定义名称,唯一值,spring 通过这个名称找到对象
    class: 类的全限定名称(不能是接口,因为 spring是反射机制创建对象,必须使用类)

    spring的配置文件:
    1. beans: 是根标签,spring把 java 对象成为 bean
    2. spring-beans.xsd 是约束文件,和 mybatis 指定, dtd 是一样的


    spring 就实现 => UserInfo userInfo = new UserInfo();
    spring 是吧创建好的对象放入到 map 中,spring 框架有一个 map 存放对象的.
    springMap.put(id的值,对象);

    一个 bean 标签声明一个对象
    -->

  • 特别的注意点

    | 注意 |

| :———————————————————-: |
| explainIDClass |

  • 使用spring

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    package org.coderitl.impl;

    import org.coderitl.UserInfo;

    /**
    * @author coder-itl
    */
    public class UserInfoImpl implements UserInfo {
    @Override
    public void doSome() {
    System.out.println("do some is using...");
    }
    }
    =>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>=>

    @Test
    public void testSpring() {
    /**
    * 使用 Spring 容器创建的对象
    */
    /* 1. 指定 spring 配置文件的名称 */
    String config = "Beans.xml";
    /* 2. 创建表示 spring 容器的对象, ApplicationContext
    * ApplicationContext 就是表示 Spring 容器 通过容器获取对象
    * ClassPathXmlApplicationContext: 表示从 类 路径中加载 spring 的配置文件
    * */
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
    /* 从容器中获取某个对象,你要调用的对象的方法 参数: 配置文件中的 id 值,转换成接口类型 */
    UserInfo userInfo = (UserInfo) applicationContext.getBean("UserInfo");

    /* 使用 spring 创建好的对象 */
    userInfo.doSome(); // do some is using...

    }
  • spring 创建对象的时机

    1
    2
    3
    4
    5
    /* 
    spring默认创建对象的时间: 在创建 spring 的容器时,会创建配置文件中的所有对象
    spring创建对象: 默认调用的是无参构造方法
    */
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext(config);
  • 获取spring 信息

    Bean
    springInfo.png
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /* 获取容器中的对象名称 */
    String names[] = ac.getBeanDefinitionNames();
    for (String name : names) {
    System.out.println("容器中的所有对象名称: " + name);
    }
    /*
    输出:
    容器中的所有对象名称: UserInfo
    容器中的所有对象名称: UserInfo2
    */
  • 获取一个非自定义的类对象

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    Beans.xml:
    <!-- spring 能创建一个非定义类的对象吗? 创建一个存在的某个类的对象 -->
    <bean id="myDate" class="java.util.Date"/>


    @Test
    public void testGetDate() {
    String config = "Beans.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    /* 使用 getBean() */
    Date date = (Date) ac.getBean("myDate");
    System.out.println("时间为: " + date);
    }

Bean的基本配置

类别 描述
名称 bean
类型 标签
所属 bean 标签
功能 定义spring 核心容器管理的对象
列表属性 id:bean id 使用容器可以通过id 值获取对应的bean 在一个容器中id 值唯一,class:bean 的类型,即配置的bean 的全路径类名
范例 <bean id="bookDao" class="com.example.springheima.dao.impl.BookDaoImpl"/>
  • bean 配置别名

    类别 描述
    名称 name
    类型 属性
    所属 bean 标签
    功能 定义bean 的别名,使用逗号、分号、空格分隔
    范例 <bean id="bookDao" name="dao bookDaoImpl" class="com.example.springheima.dao.impl.BookDaoImpl"/>

数据源管理

  • 数据源管理

    1
    2
    3
    4
    5
    6
    7
    8
    9

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">

    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/spring_db"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    </bean>

    • 输出测试

      数据源管理测试
      数据源管理测试
  • c3p0 数据源

    • 依赖

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      <dependency>
      <groupId>c3p0</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.1.2</version>
      </dependency>

      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.35</version>
      </dependency>
    • 数据源管理

      1
      2
      3
      4
      5
      6
      <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
      <property name="driverClass" value="com.mysql.jdbc.Driver"/>
      <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_db"/>
      <property name="user" value="root"/>
      <property name="password" value="root"/>
      </bean>
    • 测试输出

      c3p0
      c3p0

加载Properties

  • 使用

    1
    2
    3
    4
    5
    6

    xmlns:context="http://www.springframework.org/schema/context"

    http://www.springframework.org/schema/context
    https://www.springframework.org/schema/context/spring-context.xsd

    • 分析

      配置加载properties
      配置加载properties

基于XML DI

注入分类
set注入
  • 配置文件对属性赋值

    1
    2
    3
    4
    5
    6
    7
    注意: 一个 preperty 只能给一个属性赋值 
    <!-- 给属性复赋值 -->
    <bean id="student" class="org.coderitl.impl.StudentImpl">
    <property name="name" value="张三"/> <!-- => setName("张三") -->
    <property name="age" value="18"/> <!-- => setAge("18") -->
    <property name="score" value="88"/> <!-- => setScore("88") -->
    </bean>
  • 测试设置值后的输出

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    注意: 私有属性必须提供对应的setXXX()

    @Test
    public void testSetValue() {
    String config = "Beans.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    /* 从容器中获取 student 对象 验证: set 方法 => */
    StudentImpl student = (StudentImpl) ac.getBean("student");
    /* StudentImpl{name='张三', age=18, score=88.0} */
    System.out.println(student);

    }
  • 验证set 注入

    set 注入
    setvalue.png
  • 引用类型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package org.coderitl.other;

    public class School {
    private String schooleName;
    private String schooleAddres;

    public void setSchooleName(String schooleName) {
    this.schooleName = schooleName;
    }

    public void setSchooleAddres(String schooleAddres) {
    this.schooleAddres = schooleAddres;
    }

    @Override
    public String toString() {
    return "School{" +
    "schooleName='" + schooleName + '\'' +
    ", schooleAddres='" + schooleAddres + '\'' +
    '}';
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    package org.coderitl.other;

    public class StudentInfo {
    private int setDates;
    /**
    * 创建一个引用类型
    */
    private School school;

    public void setSetDates(int setDates) {
    this.setDates = setDates;
    }

    public void setSchool(School school) {
    this.school = school;
    }

    @Override
    public String toString() {
    return "StudentInfo{" +
    "setDates=" + setDates +
    ", school=" + school +
    '}';
    }
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    package org.coderitl;

    import org.coderitl.other.School;
    import org.coderitl.other.StudentInfo;
    import org.junit.Test;

    public class TestStudentInfo {
    @Test
    public void testStudentInfo() {
    /* 普通对象赋值 */
    School school = new School();
    school.setSchooleName("泾川利于");
    school.setSchooleAddres("西外");

    StudentInfo studentInfo = new StudentInfo();
    /* 引用类型的使用 */
    studentInfo.setSetDates(2021);
    studentInfo.setSchool(school);

    /* StudentInfo{setDates=2021, school=School{schooleName='泾川利于', schooleAddres='西外'}} */
    System.out.println(studentInfo);
    }
    }
    引用类型配置文件
    yinyong.png
  • 构造注入

    构造注入: spring 调用类的有参构造,在创建对象的同时,在构造方法中给属性赋值,构造注入使用<constructor-arg> 表示构造方法一个参数

    <constructor-arg> 标签属性:

    • name: 表示构造方法的形参名
    • index: 表示构造方法的参数的位置,参数从左往右位置是0,1,2.. 的顺序
      • value: 构造方法的形参类型是简单类型的,使用value
      • ref: 构造方法的形参类型是引用类型的,使用ref
    • 普通类

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      // 提供有参构造 
      public Student(String stuName, Integer stuAge, School school) {
      this.stuName = stuName;
      this.stuAge = stuAge;
      this.school = school;
      }

      // set..
      // toString...

    • 配置文件

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      // name 属性实现构造注入 | index | 还可以省略 index,name (推荐 name 可读性高) 
      <bean id="constructorset" class="org.example.day02.Student">
      <constructor-arg name="stuName | index(0)" value="coder-itl"/>
      <constructor-arg name="stuAge" value="18"/>
      <!-- 引用类型赋值 -->
      <constructor-arg name="school" ref="day02School"/>
      </bean>

      <bean id="day02School" class="org.example.day02.School">
      <property name="schoolName" value="育才中学"/>
      </bean>
    • 测试

      1
      student = Student{stuName='coder-itl', stuAge=18, school=School{schoolName='育才中学'}}
引用类型属性自动注入
  1. byName 方式自动注入

    • 按名称注入

      byName
    普通方式实现引用类型赋值
    通过byName 实现引用类型赋值
    通过byName实现引用类型赋值
  2. byType 方式自动注入

    • 按类型注入

      byType
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
      /* 实现一: java类中引用数据类型的数据类型和 bean 的 class 的值是一样的 */
    <bean id="myStudent" class="org.coderitl.school.Student" autowire="byType">
    <!-- student 普通属性赋值操作 -->
    <property name="stuName" value="byType"/>
    <property name="stuAge" value="18"/>
    <property name="stuSex" value="男"/>
    </bean>

    <!-- 修改 id 区分 byName -->
    <bean id="mySchool" class="org.coderitl.school.School">
    <property name="schoolName" value="byType中学"/>
    <property name="schoolSetUpDate" value="2021-10-02"/>
    </bean>

    /* 实现二: java类中引用数据类型的数据类型和 bean 的 class 的值是父子类关系 */
    1. 创建引用类型的子类
    2. 子类继承引用类型的类
    3. 配置文件进行属性赋值

    /* 实现三: java类中引用数据类型的数据类型和 bean 的 class 的值是接口与实现类的关系 */
    ...
集合注入
  • 注入

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    <bean id="bookDao" class="com.example.springheima.dao.impl.BookDaoImpl">
    <property name="array">
    <!-- 数组 -->
    <array>
    <value>100</value>
    <value>300</value>
    <value>200</value>
    <value>500</value>
    </array>
    </property>

    <property name="list">
    <list>
    <value>1</value>
    <value>23</value>
    <value>11</value>
    <value>45</value>
    </list>
    </property>

    <property name="map">
    <map>
    <entry key="username" value="coder-itl"></entry>
    </map>
    </property>
    <property name="set">
    <set>
    <value>12</value>
    <value>11</value>
    </set>
    </property>

    <property name="properties">
    <props>
    <prop key="url">localhost</prop>
    </props>
    </property>

    </bean>
  • 输出

    集合注入输出
    集合注入输出
为什么使用多配置文件
  1. 每个文件的大小比一个文件要小的多,效率高
  2. 避免多人竞争带来的冲突
  1. 按功能模块,一个模块一个配置文件
  2. 按类的功能,数据库相关的配置一个文件配置文件,做事物的功能一个配置文件,service功能的一个配置文件等
  • 多配置文件使用

    多配置文件使用
  • 文件目录

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    配置文件路径: resource/multipleconfiguration

    <!--
    包含关系的配置文件中,可以使用通配符(*: 表示任意字符) 主配置文件不能被包含
    否则会陷入死循环,配置文件需要放置在同一目录下,注意命名规范,以便于书写通配符

    在测试用例中,依然只读取主配置(Eg: mergeconfig.xml)文件
    -->

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

基于注解的DI
  • 使用注解的步骤:
    1. 加入 maven 的依赖: Spring-context,在加入Spring-context 的同时,间接加入 Spring-aop 的依赖,使用注解必须使用 Spring-aop 依赖
    2. 在类中加入 Spring 的注解(多个不同功能的注解)
    3. Spring 的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置
  • 基于注解的DI:通过注解完成java 对象的创建,属性赋值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/*
@Component: 注意导包的位置(import org.springframework.stereotype.Component;)

@Respotory (用在持久层类的上面): 放在 dao 的实现类的上面,表示创建dao对象,dao对象是能访问数据库的

@Service (用在业务层类的上面): 放在 service的实现类上面,创建service对象,service对象就是做业务逻辑处理,可以有事物

@Controller(用在控制器的上面): 放在控制器(处理器)类的上面,创建控制器对象的,控制器对象,能够接受用户提交的参数,显示请求的处理结果

@Component(value="值") || @Component("值") || @Component(getBean("该类的小写形式作为获取的对象名"))

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

@Respotory 、@Service、@Controller 是给项目的对象分层的


@Value
@Autowired
@Resource
*/

  • @Component(value="bean id")

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package org.example.day02.anno;

    import org.springframework.stereotype.Component;


    /**
    * @Component(value = "myStudent"): 创建对象的,等同于 bean 的功能
    * 属性: value 就是对象的名称,也就是 bean 的 id 值
    * value 的值是唯一的,创建的对象在整个 spring 容器中就一个
    * 位置: 在类的上面
    */
    @Component(value = "myStudent")
    public class Student {
    private String name;
    private int age;

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

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

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

  • 组件扫描器

    组件扫描器
  • 组件扫描器的使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    组件扫描器生成方式:
    IDEA中输入:<component-scan 即可出现对应提示

    <!-- 指定多个包的三种方式 -->

    <context:component-scan base-package="com.coderitl.component1"/>
    <context:component-scan base-package="com.coderitl.component2"/>


    <context:component-scan base-package="com.coderitl.component1;com.coderitl.component2;"/>

    <!-- 第三种方式: 指定父包 -->
    <context:component-scan base-package="com.coderitl"/>
  • 单元测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    package org.example.setdi.day02.anno;

    import org.example.day02.anno.Student;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class TestComponent {
    @Test
    public void testComponent() {
    // 注意读取配置文件路径
    String config = "day02/anno/anno.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // getBean("使用注解的value的值")
    Student student = (Student) ac.getBean("myStudent");
    System.out.println("student = " + student);
    }
    }

  • 注解的几种写法

    1
    2
    3
    4
    5
    6
    // 第一种
    @Component(value="xxx")
    // 第二种
    @Component("xxx") => 推荐使用方式
    // 第三种
    @Component => 默认是类的小写(beanid就等于该类小写名称)
1
2
3
4
5
@Value: 简单类型的属性赋值
属性: value 是 String 类型的,表示简单类型的属性值
位置:
1. 在属性定义的上面,无需 set 方法 => 推荐使用
2. 在 set 方法的上面
  • 属性值为什么是String

    属性值类型
  • 引起错误的原因

    错误总结
  • 语法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    /**
    * 添加引用类型
    *
    * @Autowired: Spring 框架提供的注解,实现引用类型的赋值
    * Spring 中通过注解给引用类型赋值,使用的是自动注入原理,支持 byName,byType
    * @Autowired: 默认使用的是 byName 自动注入
    *
    * 位置:
    * 1. 在属性定义的上面 无需 set 方法 => 推荐使用
    * 2. 在 set 方法的上面
    */
  • 被引用类型依然使用注解赋值

    注解
  • 语法

    1
    2
    3
    4
    5
    6
    7
    8
     /* 添加注解 @Qualifier 和 @Autowired 没有先后顺序  */
    @Autowired
    @Qualifier("mySchool")
    private School school;

    属性: required 是一个 boolean 类型的,默认 true
    @Autowired(required=true): 表示引用类型赋值失败,程序报错,并终止执行 => 推荐
    @Autowired(required=false): 引用类型如果赋值失败,程序正常执行,引用类型的结果为: null
  • 使用流程

    ByName 的使用流程
1
2
3
4
5
6
7
默认是 byName: 先使用 byName 自动注入,如果 byName赋值失败,再使用 byType

/**
* 对引用类型使用 @Resource 注解 方式一: 使用默认类型
* @Resource
* private School school;
*/
1
2
3
4
5
6
7
@Resource 只使用 byName方式,需要增加一个属性 name name的值是 bean 的 id(名称) 

/**
* 只使用 byName的方式: 必须提供 name 属性,name=bean id 的值
*/
@Resource(name = "mySchool")
private School school;
  • 配置文件

    • ByName

      ByName
      ByName
    • ByType

      byName 输出 修改name 名称 byType
      ByName正常输出 修改名称后 byType
  • 注解分析

    • byType

      注解byType 使用分析
      byType
    • byName

      注解byName 使用分析
      byName
  • Autowired
    • 属性注解,方法注解(set方法),声明当前属性自动装配,默认为byType
    • @Autowired(required=false) 通过required 属性设置当前自动装配是否为必须(默认必须,如果没有找到类型与属性类型匹配的bean 则抛出异常
    • spring 提供的注解
  • @Resource
    • 属性注解,也用于声明属性自动装配
    • 默认装配方式为byName,如果根据byName 没有找到对应的bean,则继续根据byType 寻找对应的bean,根据byType 如果依然没有找到Bean 或者找不到一个类型匹配的bean 则抛出异常
  • DI

    DI
    dao与service的关系

IOC常用注解

  • @Component

    • 类注解,声明此类被spring 容器进行管理,相当于bean 标签的作用
    • @Component(value="stu") ,value 属性用于指定当前bean idvalue 属性也可以省略,如果省略当前类的id 默认为类名首字母改小写
  • @Scope

    • 类注解,用于声明当前类单例模式还是非单例模式,相当于bean 标签的scope 属性
    • @Scope(value="prototype")表示声明当前类为非单例模式,默认单例模式
  • @Lazy

    • 类注解,声明一个单例模式的Bean 是否为懒汉模式
    • @Lazy(true)表示声明为懒汉模式
  • @PostConstruct

    • 方法注解,声明一个方法为当前类的初始化方法(在构造器之后执行),相当于bean 标签的init-method 属性

      1
      2
      3
      4
      5
      6
      // 初始化方法
      @PostConstruct
      public void init() {
      System.out.println("init...");
      }

  • @PreDestroy

    • 方法注解,声明一个方法为当前类的销毁方法(在对象从容器中释放之前执行),相当于bean 标签的destory-method 属性

      1
      2
      3
      4
      5
      6
      // 销毁方法

      @PreDestroy
      public void destroy() {
      System.out.println("destroy....");
      }

动态代理

动态代理是指: 程序在整个运行过程中根本就不存在的目标类的代理类,目标对象的代理只是由代理生成工具(不是真实定义的类)在程序运行时由JVM 根据反射等机制动态生成的,代理对象与目标对象的代理关系在程序运行时才确立

  • 普通功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.example.day03.impl;

    import org.example.day03.SomeService;
    import org.example.day03.utils.DynamicProxyUtils;

    public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome() {
    System.out.println("doSome...");
    }

    @Override
    public void doOther() {
    System.out.println("doOther...");
    }
    }

  • 功能增加

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package org.example.day03.utils;

    import java.util.Date;

    public class DynamicProxyUtils {
    public static void doLog() {
    System.out.println("doLog ...");
    }

    public static void getTime() {
    System.out.println("当前系统时间是: " + new Date());
    }
    }

  • myInvocationHandler

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    package org.example.day03.handler;

    import org.example.day03.SomeService;
    import org.example.day03.utils.DynamicProxyUtils;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;

    public class MyInvocationHandler implements InvocationHandler {
    private Object target; // 目标对象 SomeServiceImpl 类

    public MyInvocationHandler(Object target) {
    this.target = target;
    }

    public void setTarget(Object target) {
    this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    // 通过代理对象执行方法时,会调用这个 invoke
    Object res = null;
    // 增加的功能
    DynamicProxyUtils.doLog();
    // 执行目标类的方法,通过 Method 类实现
    res = method.invoke(target, args); // => SomeServiceImpl.doLog() | SomeServiceImpl.doSome()
    DynamicProxyUtils.doLog();
    // 目标方法的执行结果
    return res;
    }
    }

  • 创建代理

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package org.example.day03;

    import org.example.day03.handler.MyInvocationHandler;
    import org.example.day03.impl.SomeServiceImpl;
    import org.junit.Test;

    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Proxy;

    public class TestDynamicProxy {
    /**
    * 测试动态代理
    */
    @Test
    public void testDynamicProxy() {
    // JDK 的 Proxy 实现动态代理
    // 1. 创建目标对象
    SomeService target = new SomeServiceImpl();
    // 2. 创建 InvocationHandler
    InvocationHandler handler = new MyInvocationHandler(target);
    // 3. 使用 Proxy 创建代理
    SomeService proxy = (SomeService) Proxy.newProxyInstance(
    target.getClass().getClassLoader(),
    target.getClass().getInterfaces(), handler);

    // 4. 通过代理执行方法,会调用 handler 中的 invoke()
    proxy.doSome();

    }
    }

AOP面向切面编程

基本概念

  • 动态代理

    • jdk

      实现方式: jdk 动态代理,使用jdk 中的Proxy,Method,InvocaitonHandler 创建代理对象

      jdk 动态代理要求目标类必须实现接口

    • cglib

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

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

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

  • AOP(Aspect Orient Programming) 面向切面编程
    • Aspect: 切面,给你的目标类增加的功能,就是切面.像上面的日志,事物都是切面。

·

  • 需要分析项目功能时,找出切面
  • 合理的安排切面的执行时间,(在目标方法前还是目标方法后)
  • 合理的安排切面执行的位置,在哪个类,那个方法增加增强功能

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

AOP 的技术实现框架:

  1. Spring
    • SpringSpring 在内部实现了AOP 的规范.能做AOP 的工作
    • Spring 主要在事物处理时使用AOP
  2. aspectJ
    • 一个开源的专门做AOP 的框架,Spring 框架中集成了aspectJ 框架,通过Spring 就能使用aspectJ 的功能。
    • aspectJ 框架实现AOP 的两种方式
      1. 使用XML 的配置文件配置全局事务
      2. 使用注解,aspectJ 5 个注解
  • 术语

    术语解释
  • 关键三要素

    切面三要素

AspectJ的使用

  1. 切面的执行时间,这个执行之间在规范中叫做advice(通知,增强),在aspectJ 框架中使用注解表示的,也可以使用XML 配置文件中的标签

    1
    2
    3
    4
    5
    @Before
    @AfterReturning
    @Around
    @AfterThrowing
    @After
  • 详细信息

    切入点表达式

  • 简单实用

    1
    2
    3
    4
    5
    6
    execution(访问权限 方法返回值 方法声明(参数) 异常类型 ) => 方法返回值 方法声明(参数) 必须有的

    切入点表达式要匹配的对象就是目标方法的方法名,所以 execution 表达式中明显就是方法的签名

    注意: 表达式中必不可少的部分内容,各部分使用空格分开.在其中可以使用以下符号

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // 指定切入点为: 任意公共方法
    execution(public * *(...))

    // 指定切入点为: 任何以 coder 开始的方法
    execution(* com.coder*(...))

    // 指定切入点为: 定义在 service 包里的任意类的任意方法
    execution(* com.coderitl.service.*.*(...))

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

    // 指定所有包下的 service 子包下所有类(接口)中所有方法为切入点
    execution(* *..service.*.*(..))


  1. 新建maven 项目

  2. 加入依赖

    • spring 依赖

      1
      2
      3
      4
      5
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
      </dependency>
    • aspectj 依赖

      1
      2
      3
      4
      5
      6
        <!--  aspects 框架依赖 -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
      </dependency>
    • junit 单元测试

      1
      2
      3
      4
      5
      6
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
      </dependency>
  3. 创建目标类: 接口和它的实现类-> 要做的是给类中方法增加功能

  4. 创建切面类: 普通类

    • 在类的上面加@Aspect
    • 在类中定义方法,方法就是切面要执行功能代码
      • 在方法的上面加入aspect 中的通知注解,例如:@Before
      • 有需要指定切入点表达式execution()
  5. 创建spring 的配置文件:声明对象,把对象交个容器统一管理,声明对象你可以使用注解或者xml 配置文件

  6. 声明目标对象

  7. 声明切面类对象

  8. 声明aspectj 框架中的自动代理生成器标签,自动代理生成器: 用来完成代理对象的自动创建功能的

  9. 创建测试类,spring 容器中获取目标对象(实际就是代理对象),通过代理执行方法,实现aop 的功能增强

  • 创建maven 项目

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    <!-- 添加依赖 -->
    <dependencies>
    <!-- 单元测试依赖 -->
    <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.11</version>
    <scope>test</scope>
    </dependency>
    <!-- spring 依赖 -->
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.5.RELEASE</version>
    </dependency>
    <!-- aspects 框架依赖 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
    </dependency>
    </dependencies>
  • 创建普通接口并实现该接口

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    // 普通类的接口
    package org.example;

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

    // 接口实现
    package org.example.impl;

    import org.example.SomeService;
    import org.springframework.stereotype.Component;

    /**
    * 目标类
    */
    @Component("mySomeServiceImpl") // <bean id="mySomeServiceImpl" class="org.example.impl.SomeServiceImpl">
    public class SomeServiceImpl implements SomeService {
    @Override
    public void doSome(String name, int age) {
    System.out.println("doSome is used....");
    }
    }

  • 实现切面(用于增强业务功能)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    // 切面类
    package org.example;

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;

    /**
    * MyAspectJ => 增强功能
    *
    * @Aspect: 是 aspectj 框架中的注解
    * 作用: 表示当前类是切面类
    * 切面类: 是用来给业务方法增加功能的类,在这个类中有切面的功能代码
    * 位置: 在类定义的上面
    */
    @Component("myAspectj") // <bean id="myAspectj" class=" org.example.MyAspectJ">
    @Aspect
    public class MyAspectJ {
    /**
    * 定义方法,方法是实现切面功能的
    * 方法的定义要求:
    * 1. 公共方法 public
    * 2. 方法没有返回值
    * 3. 方法名称自定义
    * 4. 方法可以有参数,也可以没有参数
    * 如果有参数,参数不是自定义的,有几个参数类型可以使用
    */

    /**
    * @Before: 前置通知注解
    * 属性: value,是切入点表达式,表示切面的功能执行的位置
    * 位置: 在方法上面
    * 特点:
    * 1. 在目标方法之前先执行
    * 2. 不会改变目标方法的执行结果
    * 3. 不会影响目标方法的执行
    */
    @Before("execution(public void org.example.impl.SomeServiceImpl.doSome(String,int))")
    public void doLog() {
    // 切面的功能代码
    System.out.println("日志信息.....");
    }
    }
  • 配置文件

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <?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="org.example"/>
    <!--
    声明自动代理生成器: 使用 aspectj 框架内部的功能,创建目标对象的代理对象
    创建代理对象是在内存中实现的,修改目标对象的内存中的结构
    构建为代理对象,所以目标对象就是被修改后的代理对象

    aspectj-autoproxy: 会把 spring 容器中的所有目标对象,一次性都生成代理对象
    -->
    <aop:aspectj-autoproxy/>

    </beans>
  • 测试功能

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package org.example;

    import static org.junit.Assert.assertTrue;

    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    /**
    * Unit test for simple AspectJ.
    */
    public class TestAspectJBefore {
    /**
    * 测试 aspectJ 的 Before(前置通知)
    */
    @Test
    public void testAspectjBefore() {
    // 获取配置文件路径
    String config = "aspectj_before/aspectj_before.xml";
    // 创建容器对象
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 生成代理对象
    SomeService proxy = (SomeService) ac.getBean("mySomeServiceImpl");
    // 通过代理对象执行方法,实现目标方法执行时,增强了功能
    proxy.doSome("coder-itl", 18);
    // 目标类有接口是 jdk 动态代理
    // com.sun.proxy.$Proxy14
    System.out.println(proxy.getClass().getName());
    }
    }

  • 测试输出

    AspectJ Before
  • 切面表达式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 最全面写法
    @Before("execution(public void org.example.impl.SomeServiceImpl.doSome(String,int))")
    // 省略权限修饰符
    @Before("execution(void org.example.impl.SomeServiceImpl.doSome(String,int))")
    @Before("execution( * org.example.impl.SomeServiceImpl.doSome(String,int))")
    @Before("execution( * *..SomeServiceImpl.doSome(String,int))")
    @Before("execution( * *..SomeServiceImpl.do*(String,int))")
    @Before("execution(* do*(String,int))")
    // 执行所有 do 开头的方法
    @Before("execution(* do*(..))")
  • 依赖

  • 接口定义

    1
    2
    3
    4
    5
    6
    7
    8
    9
    package org.coderitl;

    /**
    * coderitl package: 后置通知所有测试用例
    */
    public interface SomeService {
    public Student doSome(String name, int age);
    }

  • 接口实现类(目标类)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.coderitl.impl;

    import org.coderitl.SomeService;
    import org.coderitl.Student;
    import org.springframework.stereotype.Component;

    @Component("SomeServiceImplAfterRuturning")
    public class SomeServiceImpl implements SomeService {
    @Override
    public Student doSome(String name, int age) {
    Student student = new Student(name, age);
    System.out.println("SomeServiceImpl dome: " + student);
    // SomeServiceImpl dome: Student{name='coder-itl', age=18}
    return student;
    }
    }

  • 后置通知(增强功能代码块)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    package org.coderitl;

    import org.aspectj.lang.annotation.AfterReturning;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;


    @Component("myAspectAfter")
    @Aspect
    public class MyAfterRuturning {
    /**
    * 后置通知定义方法,方法的是实现切面功能的
    * 方法的定义要求:
    * 1. 公共方法 public
    * 2. 方法没有返回值
    * 3. 方法名称自定义
    * 4. 方法有参数,推荐是 Object,参数名自定义
    *
    * @param result 后置通知返回值
    */
    // execution => 目标方法的表达式

    /**
    * @AfterReturning: 后置通知
    * 属性:
    * 1. value: 切入点表达式
    * 2. returning 自定义的变量,表示目标方法的返回值的,自定义变量名必须和通知方法的形参名一样(Object result <= returning = "result" )
    * @AfterReturning(value = "execution(* *..SomeServiceImpl.*(..))", returning = "result")
    * JoinPoint 要i使用必须在参数的第一位
    */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.*(..))", returning = "result")
    public void myAfterRuturning(Object result) {
    // 增强内容代码块
    System.out.println("后置通知执行时student值: " + result);

    // 修改是不会影响原先内容信息的
    if (result != null) {
    // 修改地址指向
    // result = new Student("address", 80);
    // System.out.println("修改 result 地址指向: " + result);
    // 真正的修改
    Student student = (Student) result;
    System.out.println("后置通知中修改前: " + student);
    student.setName("fixName");
    student.setAge(19);
    System.out.println("后置通知中修改后: " + student);
    }
    }
    }

  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    package org.coderitl;

    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class TestAfterReturning {
    @Test
    public void testAfterReturning() {
    String config = "aspectj_afterreturning/aspectj_afterreturning.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    SomeService proxy = (SomeService) ac.getBean("SomeServiceImplAfterRuturning");
    // 注意参数
    System.out.println("测试方法中实参(也是最终结果): " + proxy.doSome("coder-itl", 18));
    }
    }

  • 疑问: spring 后置通知修改返回值会影响最终返回值吗?

    • (上述中附有实现)
  • 输出

    结果输出 执行流程分析
  • 依赖

  • 接口

  • 实现类

  • 环绕通知

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    package org.around;

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;

    import java.util.Date;

    @Aspect
    @Component("AspectAround")
    public class AroundAspect {
    /**
    * 环绕通知方法的定义格式
    * 1. public
    * 2. 必须有一个返回值,推荐使用 Object
    * 3. 方法名称自定义
    * 4. 方法有参数,固定的参数 P
    */
    /**
    * @Around: 环绕通知
    * 属性: value 切入点表达式
    * 位置: 在方法的定义上
    * 特点:
    * 1. 他是功能最强的通知
    * 2. 在目标方法的前和后都能增强功能
    * 3. 控制目标方法是否被调用执行
    * 4. 修改原来的目标方法的执行结果, 影响最后的调用结果
    * 环绕通知,等同于 jdk 动态代理的 InvocationHandler 接口
    * <p>
    * 参数: ProceedingJoinPoint 等同于 动态代理的(Invoceke的)Method
    * 作用: 执行目标的方法
    * 返回值: 就是目标方法的执行结果,可以被修改
    */
    @Around(value = "execution(* *..SomeServiceImpl.*(..))")
    public Object MyAroundAspect(ProceedingJoinPoint point) throws Throwable {
    // public interface ProceedingJoinPoint extends JoinPoint { ... } ,所以 ProceedingJoinPoint== JoinPoint 也就可以使用它的方法
    // 实现环绕通知
    Object result = null;
    System.out.println("环绕通知,在目标方法之前执行: " + new Date());
    /* 目标方法调用(在目标方法前后加入功能) */
    result = point.proceed(); /* 等同于执行动态代理的 method.invoke(); Object result=doSome(); */
    System.out.println("环绕通知,在目标方法之后,提交事务");
    /********************** 测试: 控制调用、修改值 **********************************/
    // 获取实参
    String name = "";
    Object[] args = point.getArgs();
    if (args != null && args.length > 1) {
    Object arg = args[0];
    name = (String) arg;
    }
    // 实现是否调用目标方法
    if ("coderitl".equals(name)) {
    // result = point.proceed();
    } else {
    // ...
    }
    // 修改返回值
    if (result != null) {
    result = "coder-itl";
    }
    /********************************************************/

    // 返回目标方法的执行结果
    return result;
    }
    }

  • 测试

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    package org.around;

    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;

    public class TestAspectAround {
    @Test
    public void testAround() {
    String config = "aspectj_around/aspectj_around.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    SomeService proxy = (SomeService) ac.getBean("AroundSomeServiceImpl");
    // 执行了切面的MyAroundAspect()
    String result = proxy.doSome("coder-itl", 18);
    System.out.println("(最终结果)result: " + result);
    }
    }

  • 输出

    环绕通知
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    /**
    * @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的, 可以复用 可以使用 @Pointcut
    * 属性: value 切入点表达式
    * 位置: 在自定义的方法上面
    * 特点:
    * 当使用 @Pointcut 定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名
    * 其他的通知中,value 属性就可以使用这个方法的名称,代替切入点表达式了
    */
    @Pointcut(value = "execution(* *..SomeServiceImpl.*(..))")
    private void myexcution() {
    // 无需代码
    }


    @Around(value = "myexcution()")
    public Object MyAroundAspect(ProceedingJoinPoint point) throws Throwable { ... }

目标类没有接口,使用的是cglib 动态代理,spring 框架会自动应用cglib

目标类如果有接口,又想使用cglib,则只需要修改配置文件<aop:aspectj-autoproxy proxy-target-class="true">

  • @Before 参数

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    /**
    * 指定通知方法中的参数: JoinPoint(Aspect的其他注解同理)
    * JoinPoint: 业务方法,要加入切面功能的业务方法
    * 作用是: 可以在通知方法中获取方法执行时的信息,例如方法名称,方法的实参
    * 如果你的切面功能中需要用到方法的信息,就加入 JoinPoint
    * 这个 JoinPoint 参数的值是由框架赋予,必须是第一个位置的参数
    */
    @Before("execution(* do*(..))")
    // 参数
    public void doTransform(JoinPoint joinPoint) {
    System.out.println("******************************************");
    System.out.println("事务控制......");
    // 获取完整定义
    System.out.println("完整定义: "+joinPoint.getSignature());
    // 获取方法的声明类型
    System.out.println("方法的声明类型: "+joinPoint.getSignature().getDeclaringType());
    // 获取方法的名称
    System.out.println("名称: "+joinPoint.getSignature().getName());
    // 获取方法的实参
    Object[] args = joinPoint.getArgs();
    for (Object arg : args) {
    System.out.println("参数名称: "+arg);
    }
    }

spring-整合mybatis

  • spring-mybatis 集成用的技术是IOC
    • 为什么是IOC
      • 能把spring mybatis 集成在一起,像一个框架一样,是因为ioc 能创建对象,可以把mybatis 框架中的对象交给spring 统一创建,开发人员从spring 中获取对象,开发人员就不用同时面对两个或多个框架了,就面对一个spring
  1. 新建maven 项目

  2. 添加maven 依赖

    • spring 依赖

    • mybatis 依赖

    • mysql 驱动

    • spring 的事务依赖

    • mybats spring 集成的依赖,mybatis 官方提供的,用来在spring 项目中创建mybatis SqlSessionFactory,dao 对象的

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      <dependencies>
      <!-- 单元测试依赖 -->
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
      </dependency>

      <!-- spring 依赖 -->
      <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
      <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
      </dependency>

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

      <!-- mybatis 依赖 -->
      <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</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.1</version>
      </dependency>

      <!-- mysql 驱动 -->
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.34</version>
      </dependency>

      <!-- 阿里公司数据库连接池 -->
      <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
      </dependency>
      </dependencies>


      <resources>

      <resource>
      <!-- 所在目录 -->
      <directory>src/main/java</directory>
      <includes>
      <!-- 包括目录下的 下的 properties,xml 过滤 -->
      <include>**/*.properties</include>
      <include>**/*.xml</include>
      </includes>
      <filtering>false</filtering>
      </resource>
      <!-- resource 下的 properties,xml 过滤 -->
      <resource>
      <directory>src/main/resources</directory>
      <includes>
      <include>**/*.properties</include>
      <include>**/*.xml</include>
      </includes>
      <filtering>true</filtering>
      </resource>
      </resources>
  3. 创建实体类

  4. 创建dao 接口和mapper 文件

  5. 创建mybatis 主配置文件

  6. 创建Service 接口和实现类,属性是dao

  7. 创建spring 的配置文件,声明mybatis 的对象交给spring 创建

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
create database springdb CHARACTER SET utf8 COLLATE utf8_general_ci;

create table student
(
id int not null primary key comment '主键id',
name varchar(20),
email varchar(20),
age int
) default charset=utf8;

desc student;

-- 插入数据
insert into student values(1001,'张飞','zhangfei@163.com',20);
insert into student values(1002,'李四','lisi@163.com',20);
insert into student values(1003,'刘备','liubei@163.com',20);
insert into student values(1004,'孙尚香','ssxiang@163.com',20);
insert into student values(1005,'关羽','guanyu@163.com',20);
insert into student values(1006,'盾山','dunshan@163.com',20);
insert into student values(1007,'李飞','lifei@163.com',20);
insert into student values(1008,'东方耀','dfyao@163.com',20);

-- 表查询
mysql> select * from STUDENT; -- 测试是否略大小写
+------+-----------+------------------+------+
| id | name | email | age |
+------+-----------+------------------+------+
| 1001 | 张飞 | zhangfei@163.com | 20 |
| 1002 | 李四 | lisi@163.com | 20 |
| 1003 | 刘备 | liubei@163.com | 20 |
| 1004 | 孙尚香 | ssxiang@163.com | 20 |
| 1005 | 关羽 | guanyu@163.com | 20 |
| 1006 | 盾山 | dunshan@163.com | 20 |
| 1007 | 李飞 | lifei@163.com | 20 |
| 1008 | 东方耀 | dfyao@163.com | 20 |
+------+-----------+------------------+------+
8 rows in set (0.00 sec)

mysql>

  • 整合配置文件(springxml)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 加载属性配置文件 -->
    <context:property-placeholder location="classpath:mapper/jdbc.properties"/>

    <!-- 配置数据源 https://github.com/alibaba/druid/wiki/DruidDataSource%E9%85%8D%E7%BD%AE -->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
    <!-- set 注入 给 DruidDataSource 提供连接数据库信息 -->
    <property name="url"
    value="${jdbc.url}}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <property name="maxActive" value="${jdbc.maxActive}"/>
    </bean>


    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

    <property name="dataSource" ref="myDataSource"/>
    <!-- mybatis 主配置文件的位置
    configLocation 属性是 Resource 类型,作用: 读取配置文件
    它的赋值,使用 value,指定文件的路径,使用 classpath: 表示文件的位置
    -->
    <property name="configLocation" value="classpath:mapper/mybatis-config.xml"/>
    </bean>

    <!--
    创建 dao 对象,使用 SqlSession 的 getMapper(studentDao.class)
    MapperScannerConfigurer: 在内部调用 getMapper() 生成每个 dao 接口的代理对象
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">

    <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>

    MapperScannerConfigurer 会扫描这个包中的所有接口,把每个接口都执行一次 getMapper() 方法,得到每个接口的 dao 对象
    创建好的 dao 对象放入到 spring 容器中 dao 对象的默认名称是 接口名首字母小写
    -->
    <property name="basePackage" value="org.springmybatis.dao"/>
    </bean>


    <bean id="studentService" class="org.springmybatis.service.impl.StudentServiceImpl">
    <property name="studentDao" ref="studentDao"/>
    </bean>
    </beans>
  • 获取信息

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 测试 spring 集成 mybatis 的使用
    @Test
    public void testSpringMybatis() {
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    String[] names = ac.getBeanDefinitionNames();
    for (String name : names) {
    System.out.println("name + ac.getBean(name) = " + name + ac.getBean(name));
    }
    }
  • 使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 通过 spring 获取对象

    @Test
    public void testSpringMybatisInsertStudents() {
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // 对象名为 dao 接口的首字母小写
    // StudentDao dao = sqlSession.getMapper(StudentDao.class);
    // List<Student> studentList = dao.selectStudents();
    StudentDao studentDao = (StudentDao) ac.getBean("studentDao");
    Student student = new Student(1009, "spring-mybatis", "spring@qq.com", 1);
    int insertResult = studentDao.insertStudent(student);
    if (insertResult > 0) {
    System.out.println("数据添加成功!");
    } else {
    System.out.println("数据添加失败!");
    }
    }
  • 使用service 对象

    1
    2
    3
    4

    <bean id="studentService" class="org.springmybatis.service.impl.StudentServiceImpl">
    <property name="studentDao" ref="studentDao"/>
    </bean>
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16


    @Test
    public void testSpringMybatisService() {
    String config = "applicationContext.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    // studentService => 来自于 applicationContext.xml 对service 定义
    StudentService studentService = (StudentService) ac.getBean("studentService");
    Student student = new Student(1010, "spring-mybatis", "spring@qq.com", 1);
    int insertResult = studentService.addStudent(student);
    if (insertResult > 0) {
    System.out.println("数据添加成功!");
    } else {
    System.out.println("数据添加失败!");
    }
    }
  • 什么是事务

    事务是一组sql 语句的集合,集合中有多条sql 语句,可能是insert、update、delete 我们希望这些多条sql 语句都能成功或者都失败,这些sql 语句的执行是一致的,作为一个整体执行

  • 在什么时候想到使用事务

    当操作涉及到多个表时,或者是多个sql 语句的insert、update、delete 需要保证这些语句都是成功才能完成功能时或者都失败,保证操作是符合要求的

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

  • 通常使用JDBC 访问数据库,还是mybatis 访问数据据库怎么处理事务

    1
    2
    jdbc访问数据库,处理事务: Connection conn; conn.commit(); conn.rollback();
    mybatis访问数据库,处理事务: SqlSession.commit(); SqlSession.rollback();
  • 3 问题中处理事务方式,有什么不足

    1. 不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理
    2. 需要掌握多种数据库中事务的处理逻辑,什么时候提交事务,什么时候回滚事务
    3. 就是多种数据库的访问技术,有不同的事务处理机制,对象,方法
  • 怎么解决不足

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

  • 处理事务,需要怎么做,做什么

    • spring 处理事务的模型,使用的步骤都是固定的,把事务使用的信息提供给spring 就可以了
    • 事务内部提交,回滚事务,使用的事务管理器对象,代替你完成commit,rollback
    • 接口: platformTransactionManager 定义了事务重要方法commit,rollback
    • 实现类: spring 把每一种数据库访问技术对应的事务处理类都创建好了
      • mybatis 访问数据库spring 创建好的DataSourceTransactionManager
      • Hibernate 访问数据库spring 创建好的HibernateTransactionManager
    • 怎么使用?怎么告诉spring
      • 只需要告诉spring,你是用那种数据库的访问技术
      • <bean id="自定义" class="...DataSourceTransactionManager">
  • 数据准备

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    use springdb;
    create table sale
    (
    id int auto_increment not null primary key,
    gid int,
    nums int
    ) default character set utf8;



    create table goods
    (
    id int not null primary key,
    name varchar(100),
    amount int comment '库存',
    price float
    ) default character set utf8;
    insert into goods values (1001,'笔记本电脑',100,5000);
    insert into goods values (1002,'手机',100,3000);

  • 小项目使用,注解方案

    • spring 框架自己用aop 实现给业务方法增加事务的功能,使用@Transaction 注解增加事务
    • @Transaction 注解是spring 框架自己的注解,放在public 方法的上面,表示当前方法具有事务,可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等
  • 使用@Transaction 的步骤

    • 需要声明事务管理器对象

      1
      2
      3
      4
      5
      6
      7
      8
      9

      <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">

      <property name="dataSource" ref="myDataSource"/>
      </bean>

      annotation-driven: http://www.springframework.org/schema/tx(必须是)
      -->
      <tx:annotation-driven transaction-manager="transactionManager"/>
    • 开启事务注解驱动,告诉spring 框架,我们要使用注解的方式管理事务

      • spring 使用aop 机制,创建@Transaction 所在类的代理对象,给方法增加事务的功能
      • spring 业务方法加入事务
        • 在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop 环绕通知
    • 在你的方法上面加入@Transaction

  • 大项目配置事务

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    <!--  大项目事务配置流程  -->
    <!-- 1. 声明事务管理器对象 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="myDataSource"/>
    </bean>

    id: 自定义名称 表示 <tx:advice> 和 </tx:advice> 之间的配置内容
    transaction-manager: 事务管理器对象的 id
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
    <tx:attributes>

    name: 方法名称:
    1. 完整的方法名称,不带有包和类
    2. 方法可以使用通配符, * 表示任意字符
    propagation: 传播行为 枚举值
    isolation: 隔离级别
    no-rollback-for: 你指定的异常类名,全限定类名 发生异常一定回滚
    buy 是一个 public 的方法(需要使用到事务控制)
    -->
    <tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
    no-rollback-for="java.lang.NullPointerException,org.example.execption.NotEnoughException"/>


    <tx:method name="add*" propagation="REQUIRES_NEW"/>
    <!-- 指定修改方法 -->
    <tx:method name="modify*"/>
    <!-- 删除方法 -->
    <tx:method name="remove*"/>
    <!-- 查询方法:query,search,find -->
    <tx:method name="*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
    </tx:advice>
    <aop:config>

    id: 切入点表达式的名称 唯一值
    expression: 切入点表达式,指定那些类要使用事务,aspectj 会创建代理对象
    * *..service..*.*(..)): 返回值类型 *目录层级下的/service/*层级目录下/*class/*(方法)(..) [* => 任意]
    -->
    <!-- 找到所有需要添加配置的方法 -->
    <aop:pointcut id="servicePointcut" expression="execution(* *..service..*.*(..))"/>
    <!-- 配置增强器: 关联 advice 和 pointcut -->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePointcut"/>
    </aop:config>
  • 分析

spring 与 web 项目

  • 需求

    Web 项目中容器对象只需要创建一次,把容器对象放入到全局作用域ServeletContext

  • 实现方案

    使用监听器,当全局作用域对象被创建时,创建容器,存入ServletContext

  • 监听器作用

    • 创建容器对象,执行ApplicationContext ac = new ClasspathXmlApplicationContext("applicationContext.xml")
    • 把容器对象放入到ServletContextServletContext.setAttribute(key,ac)
  • 具体配置

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29


    <!-- 监听器依赖 -->
    <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.5.RELEASE</version>
    </dependency>


    <!-- 2. web.xml 注册监听器
    注册监听器: ContextLoaderListener
    监听器被创建对象后,会读取 /WEB-INF/spring.xml
    为什么要读取配置文件: 因为在监听器中要创建 ApplicationContext 对象,需要加载配置文件
    /WEB-INF/applicationContext.xml 就是监听器默认读取的 spring 配置文件

    可以修改默认的文件位置,使用 context-param 重新指定文件的位置

    -->
    <context-param>
    <!-- contextConfigLocation: 表示配置文件的路径 -->
    <param-name>contextConfigLocation</param-name>

    <param-value>classpath:spring.xml</param-value>
    </context-param>
    <listener>
    <listener-class>org.springframework.web.context.ContextCleanupListener</listener-class>
    </listener>