03 Spring框架

本文章根据b站动力节点spring视频教程整理
视频链接:https://www.bilibili.com/video/BV1nz4y1d7uy

1. Spring 概述

1.1 什么是Spring

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

  • 主要作用:

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

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

1.2 Spring的优点

Spring 是一个框架,是一个半成品的软件。有 20 个模块组成。它是一个容器管理对象,容器是装东西的,Spring 容器不装文本,数字。装的是对象。Spring 是存储对象的容器。

  • 轻量:Spring 框架使用的 jar 都比较小,一般在 1M 以下或者几百 kb。Spring 核心功能的所需的 jar 总共在 3M 左右。Spring 框架运行占用的资源少,运行效率高。不依赖其他 jar。
  • 针对接口编程,解耦合:Spring 提供了 Ioc 控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。
  • AOP 编程的支持:通过 Spring 提供的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
  • 方便集成各种优秀框架:Spring 不排斥各种优秀的开源框架,相反 Spring 可以降低各种框架的使用难度。

1.3 体系结构

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

2. IoC 控制反转

2.1 IoC 和 DI

  • 什么是IOC ??

    IoC (Inversion of Control) : 控制反转:把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是由其它外部资源完成。
    为什么要使用 ioc : 目的就是减少对代码的改动, 也能实现不同的功能。 实现解耦合。

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

  • 什么是DI ???
    DI(Dependency Injection) : 依赖注入 : 指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序。
    为什么要使用DI: spring这个容器中,替你管理着一系列的类,前提是你需要将这些类交给spring容器进行管理,然后在你需要的时候,不是自己去定义,而是直接向spring容器索取,当spring容器知道你的需求之后,就会去它所管理的组件中进行查找,然后直接给你所需要的组件。因此实现IOC思想需要DI做支持 。 DI是IOC的技术实现

  • 常见的ioc的体现: servlet

      1: 创建类继承HttpServelt 
      2:  在web.xml 注册servlet , 使用<servlet-name> myservlet </servlet-name>
     	<servelt-class>com.bjpwernode.controller.MyServlet1</servlet-class>
     
      3. 没有创建 Servlet对象, 没有 MyServlet myservlet = new MyServlet()
     
     4. Servlet 是Tomcat服务器它能你创建的。 Tomcat也称为容器
     		Tomcat作为容器:里面存放的有Servlet对象, Listener , Filter对象
    
  • DI 依赖注入
    什么是注入:对容器中的对象进行属性赋值,初始化。

     注入模式:
      	1. 基于XML文件的DI
      	2. 基于注解的DI
      
      注入方式:
      	1. set方法注入
      	2. 构造方法注入
      
      不同类型的注入:
      	1. 对基本数据类型和String类型的注入
      	2. 对引用数据类型的注入
    

2.2 Spring的第一个使用实例(IDEA)

第一步: 创建maven项目
在这里插入图片描述
第二步: 在POM文件中添加依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>ch01-hello-spring</artifactId>
  <version>1.0</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.5</maven.compiler.source>
    <maven.compiler.target>1.5</maven.compiler.target>
  </properties>

  <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>
  </dependencies>

  <build>
  </build>
</project>

第三步:定义接口和实现类
在这里插入图片描述
接口:

package com.bjpowernode.service;

public interface SomeService {
    
    
    void doSome();
}

实现类:

package com.bjpowernode.service.impl;

import com.bjpowernode.service.SomeService;

public class SomeServiceImpl implements SomeService {
    
    
    public SomeServiceImpl() {
    
    
        System.out.println("SomeServiceImpl无参构造");
    }

    @Override
    public void doSome() {
    
    
        System.out.println("执行SomeServiceImpl的doSome()方法");

    }
}

第四步:创建 Spring 配置文件 :在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为applicationContext.xml。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--告诉spring创建对象
            声明bean , 就是告诉spring要创建某个类的对象
            id:对象的自定义名称,唯一值。 spring通过这个名称找到对象
            class:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)

            spring就完成 SomeService someService = new SomeServiceImpl();
            spring是把创建好的对象放入到map中, spring框架有一个map存放对象的。
               springMap.put(id的值, 对象);
               例如 springMap.put("someService", new SomeServiceImpl());

            一个bean标签声明一个对象。
        -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl" />
    <bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl" scope="prototype" />
    <!--
      spring能创建一个非自定义类的对象吗, 创建一个存在的某个类的对象。
   -->
    <bean id="mydate" class="java.util.Date" />

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

第五步:定义测试类:

package com.bjpowernode;
import com.bjpowernode.service.SomeService;
import com.bjpowernode.service.impl.SomeServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.Date;

public class MyTest {
    
    
    // 正转:由开发人员在代码中,使用new 构造方法创建对象, 开发人员主动管理对象。
    @Test
    public void test01() {
    
    
        SomeService service = new SomeServiceImpl();
        service.doSome();
    }

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

    /**
     * 获取spring容器中 java 对象的信息
     */
    @Test
    public void test03() {
    
    
        String config = "beans.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //使用spring提供的方法, 获取容器中定义的对象的数量
        int nums = ac.getBeanDefinitionCount();
        System.out.println("容器中定义的对象数量:" + nums);
        //容器中每个定义的对象的名称
        String names[] = ac.getBeanDefinitionNames();
        for (String name : names) {
    
    
            System.out.println(name);
        }
    }
    //获取一个非自定义的类对象
    @Test
    public void test04(){
    
    
        String config="beans.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //使用getBean();
        Date my = (Date) ac.getBean("mydate");
        System.out.println("Date:"+my);
    }
}

2.3 容器接口和实现类

BeanFactory接口
(1) spring的原始接口,针对原始接口的实现类功能较为单一
(2)BeanFactory接口实现类的容器,特点是每次在获得对象时才会创建对象

ApplicationContext接口
(1)每次容器启动时就会创建容器中配置的所有对象
(2)提供了更多功能
(3)从类路径下加载配置文件: ClassPathXmlApplicationContext
从硬盘的绝对路径下加载配置文件:FileSystemXmlApplicationContext

2.4 基于 XML 的 DI

注意:基于XML注入的方式如下:

第一种:采用set注入
	   要求 :要注入的类中必须有set方法
第二种:构造方法注入
	   要求: 要注入的类中必须有 有参构造方法

注意: 每种方式的注入都有
			简单类型注入
			引用类型注入
	其中引用类型注入有两种不同的注入方式
		方式一:手动注入
		方式二:自动注入
		   自动注入只要有byName和byType两种
		       byNmae : 通过id注入
		       byType:通过class类型推断注入

2.4.1 采用set() 注入

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

要求:set方式注入要求类中必须实现了set方法。该set方法必须有,但不考虑set方法中是否给与类赋值(即不考虑set方式是否有具体的实现细节)

为了方便理解,创建了类Student和类School,在此不做阐述。

  • 简单类型 注入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--声明student对象
            注入:就是赋值的意思
            简单类型: spring中规定java的基本数据类型和String都是简单类型。
    
            di:给属性赋值
            1. set注入(设值注入) :spring调用类的set方法, 你可以在set方法中完成属性赋值
             1)简单类型的set注入
                <bean id="xx" class="yyy">
                   <property name="属性名字" value="此属性的值"/>
                   一个property只能给一个属性赋值
                   <property....>
                </bean>
        -->
        <bean id="myStudent" class="com.bjpowernode.ba01.Student" >
            <property name="name" value="李四lisi" /><!--setName("李四")-->
            <property name="age" value="22" /><!--setAge(21)-->
            <property name="email" value="[email protected]" /><!--setEmail("[email protected]")-->
        </bean>
    
        <bean id="mydate" class="java.util.Date">
            <property name="time" value="8364297429" /><!--setTime(8364297429)-->
        </bean>
    </beans>
    
  • 引用类型 注入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--声明student对象
    
             2) 引用类型的set注入 : spring调用类的set方法
               <bean id="xxx" class="yyy">
                  <property name="属性名称" ref="bean的id(对象的名称)" />
               </bean>
        -->
        <bean id="myStudent" class="com.bjpowernode.ba02.Student" >
            <property name="name" value="李四" />
            <property name="age" value="26" />
            <!--引用类型-->
            <property name="school" ref="mySchool" /><!--setSchool(mySchool)-->
        </bean>
    
        <!--声明School对象-->
        <bean id="mySchool" class="com.bjpowernode.ba02.School">
            <property name="name" value="北京大学"/>
            <property name="address" value="北京的海淀区" />
        </bean>
    </beans>
    

2.4.2 采用构造方法注入

注意:使用构造注入时,类中必须有 有参构造方法

  • 简单类型 注入

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--声明student对象	
            2.构造注入:spring调用类有参数构造方法,在创建对象的同时,在构造方法中给属性赋值。
              构造注入使用 <constructor-arg> 标签
              <constructor-arg> 标签:一个<constructor-arg>表示构造方法一个参数。
              <constructor-arg> 标签属性:
              构造注入有三种方式:
                1  采用name:表示构造方法的形参名
                2. 采用index:表示构造方法的参数的位置,参数从左往右位置是 0 , 1 ,2的顺序
                3. 采用value:构造方法的形参类型是简单类型的,使用value
                 ref:构造方法的形参类型是引用类型的,使用ref
        -->	
        <!--使用name属性实现构造注入-->
        <bean id="myStudent" class="com.bjpowernode.ba03.Student" >
            <constructor-arg name="myage" value="20" />
            <constructor-arg name="mySchool" ref="myXueXiao" />
            <constructor-arg name="myname" value="周良"/>
        </bean>
    
        <!--使用index属性-->
        <bean id="myStudent2" class="com.bjpowernode.ba03.Student">
            <constructor-arg index="1" value="22" />
            <constructor-arg index="0" value="李四" />
            <constructor-arg index="2" ref="myXueXiao" />
        </bean>
    
        <!--省略index-->
        <bean id="myStudent3" class="com.bjpowernode.ba03.Student">
            <constructor-arg  value="张强强" />
            <constructor-arg  value="22" />
            <constructor-arg  ref="myXueXiao" />
        </bean>
        <!--声明School对象-->
        <bean id="myXueXiao" class="com.bjpowernode.ba03.School">
            <property name="name" value="清华大学"/>
            <property name="address" value="北京的海淀区" />
        </bean>
    </beans>
    

2.4.3 引用类型的自动注入

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
          引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。·不用你在给引用类型赋值了
          使用的规则常用的是byName, byType.
          1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,
                                 且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
            语法:
            <bean id="xx" class="yyy" autowire="byName">
               简单类型属性赋值
            </bean>

          2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
                                 是同源关系的,这样的bean能够赋值给引用类型
            同源就是一类的意思:
             1.java类中引用类型的数据类型和bean的class的值是一样的。
             2.java类中引用类型的数据类型和bean的class的值父子类关系的。
             3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
            语法:
            <bean id="xx" class="yyy" autowire="byType">
               简单类型属性赋值
            </bean>
       -->
    <bean id="myStudent" class="com.bjpowernode.ba04.Student" autowire="byName">
        <property name="name" value="李四" />
        <property name="age" value="26" />
        <!--引用类型-->
<!--        <property name="school" ref="mySchool" />-->
    </bean>

    <!--声明School对象-->
    <bean id="school" class="com.bjpowernode.ba04.School">
        <property name="name" value="北京大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
          引用类型的自动注入: spring框架根据某些规则可以给引用类型赋值。·不用你在给引用类型赋值了
          使用的规则常用的是byName, byType.
          1.byName(按名称注入) : java类中引用类型的属性名和spring容器中(配置文件)<bean>的id名称一样,
                                 且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
            语法:
            <bean id="xx" class="yyy" autowire="byName">
               简单类型属性赋值
            </bean>

          2.byType(按类型注入) : java类中引用类型的数据类型和spring容器中(配置文件)<bean>的class属性
                                 是同源关系的,这样的bean能够赋值给引用类型
            同源就是一类的意思:
             1.java类中引用类型的数据类型和bean的class的值是一样的。
             2.java类中
             引用类型的数据类型和bean的class的值父子类关系的。
             3.java类中引用类型的数据类型和bean的class的值接口和实现类关系的
            语法:
            <bean id="xx" class="yyy" autowire="byType">
               简单类型属性赋值
            </bean>
       -->
    <bean id="myStudent" class="com.bjpowernode.ba05.Student"  autowire="byType">
        <property name="name" value="李四" />
        <property name="age" value="26" />
        <!--引用类型-->
<!--        <property name="school" ref="mySchool" />-->
    </bean>

    <!--声明School对象-->
<!--    <bean id="mySchool" class="com.bjpowernode.ba05.School">-->
<!--        <property name="name" value="人民大学"/>-->
<!--        <property name="address" value="北京的海淀区" />-->
<!--    </bean>-->
    <!--声明School的子类-->
        <bean id="mySchool" class="com.bjpowernode.ba05.PrimarySchool">
            <property name="name" value="北京小学"/>
            <property name="address" value="北京的海淀区" />
        </bean>


</beans>

2.4.4 如何使用多个Spring配置文件

解释说明:对于大型项目,当容器中需要放入的对象较多时,若同时存放在一个容器中,过于冗余,且可读性差。为了避免这种缺点,采用多个配置文件的方式。

多个配置优势

  • 1.每个文件的大小比一个文件要小很多。效率高
  • 2.避免多人竞争带来的冲突。

多文件的分配方式:

  1. 按功能模块,一个模块一个配置文件
  2. 按类的功能,数据库相关的配置一个文件配置文件, 做事务的功能一个配置文件, 做service功能的一个配置文件

问题:如何使用多配置文件,如下所示

第一步:定义一个主配置文件,该配置文件中只存放其他附属配置文件的地址。
第二步:按模块划分,在附属配置文件中放置类。

假设此时存在三个配置文件

主配置文件:total.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">

    <!--
          包含关系的配置文件:
          spring-total表示主配置文件 : 包含其他的配置文件的,主配置文件一般是不定义对象的。
          语法:<import resource="其他配置文件的路径" />
          关键字:"classpath:" 表示类路径(class文件所在的目录),
                在spring的配置文件中要指定其他文件的位置, 需要使用classpath,告诉spring到哪去加载读取文件。
     -->
    <!--加载的是文件列表-->

<!--    <import resource="classpath:ba06/spring-school.xml" />-->
<!--    <import resource="classpath:ba06/spring-student.xml" />-->


    <!--
       在包含关系的配置文件中,可以通配符(*:表示任意字符)
       注意: 主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
    -->
    <import resource="classpath:ba06/spring-*.xml" />
</beans>

附属配置文件:spring-school.xml 和spring-student.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">

    <!--School模块所有bean的声明,School模块的配置文件-->
    <!--声明School对象-->
    <bean id="mySchool" class="com.bjpowernode.ba06.School">
        <property name="name" value="航空大学"/>
        <property name="address" value="北京的海淀区" />
    </bean>
</beans>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--student模块所有的bean声明-->
    <bean id="myStudent" class="com.bjpowernode.ba06.Student"  autowire="byType">
        <property name="name" value="王五" />
        <property name="age" value="30" />
        <!--引用类型-->
        <!--<property name="school" ref="mySchool" />-->
    </bean>
</beans>

2.5 基于 注解的 DI

2.5.1 常用注解与说明

  • @Component:用于创建对象
  • @Repository:用于创建dao对象,用来访问数据库的
  • @Service:用于Service创建对象,处理业务逻辑的,可以有事务功能
  • @Controller:用于创建控制器对象,接受请求,显示处理结果的
  • @Value:简单类型的属性赋值
  • @Autowired:Spring框架中引用类型的赋值注解,支持byName,byType
  • @Resource: jdk注解,使用自动注入给类型赋值,支持byName,byType,在使用中先使用byName,若byName查找不到则使用byType。

注意:具体描述如下:

采用注解创建对象与采用XML创建du相似
	1. 采用XML创建对象时,需要直接在Spring配置文件中,使用 <bean id="" class="" />
	   指定id和class的权限的名称
	
	2. 采用注解创建对象时,需要在指定的类上添加注解
		并在Spring配置文件中,使用<context:component-scan base-package="" />
		指定该类所在的包名

	两种方式的意义是相同的,只不过使用方式不同

2.5.2 @Component@Repository@Service@Controller的使用

此类注解的作用:通知Spring,在配置文件中查找当前包下添加此类注解的对象,并创建该类对象添加入容器中。

如下所示,对学生类(Student)的注解

package com.bjpowernode.ba01;

import org.springframework.stereotype.Component;

/**
 * @Component: 创建对象的, 等同于<bean>的功能
 *     属性:value 就是对象的名称,也就是bean的id值,
 *          value的值是唯一的,创建的对象在整个spring容器中就一个
 *     位置:在类的上面
 *
 *  @Component(value = "myStudent")等同于
 *   <bean id="myStudent" class="com.bjpowernode.ba01.Student" />
 *
 *  spring中和@Component功能一致,创建对象的注解还有:
 *  1.@Repository(用在持久层类的上面) : 放在dao的实现类上面,
 *               表示创建dao对象,dao对象是能访问数据库的。
 *  2.@Service(用在业务层类的上面):放在service的实现类上面,
 *              创建service对象,service对象是做业务处理,可以有事务等功能的。
 *  3.@Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
 *              控制器对象,能够接受用户提交的参数,显示请求的处理结果。
 *  以上三个注解的使用语法和@Component一样的。 都能创建对象,但是这三个注解还有额外的功能。
 *  @Repository,@Service,@Controller是给项目的对象分层的。
 *
 *
 */
//使用value属性,指定对象名称
//@Component(value = "myStudent")

//省略value
@Component("myStudent")
//不指定对象名称,由spring提供默认名称: 类名的首字母小写
//@Component
public class Student {
    
    
    private String name;
    private Integer age;
    public Student() {
    
    
        System.out.println("==student无参数构造方法===");
    }
    public void setName(String name) {
    
    
        this.name = name;
    }
    public void setAge(Integer age) {
    
    
        this.age = age;
    }
    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       https://www.springframework.org/schema/context/spring-context.xsd">

    <!--声明组件扫描器(component-scan),组件就是java对象
        base-package:指定注解在你的项目中的包名。
        component-scan工作方式: spring会扫描遍历base-package指定的包,
           把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象,或给属性赋值。

       加入了component-scan标签,配置文件的变化:
        1.加入一个新的约束文件spring-context.xsd
        2.给这个新的约束文件起个命名空间的名称
    -->
<!--    <context:component-scan base-package="com.bjpowernode.ba01" />-->
<!--    <context:component-scan base-package="com.bjpowernode.ba02" />-->
<!--        <context:component-scan base-package="com.bjpowernode.ba03" />-->
<!--            <context:component-scan base-package="com.bjpowernode.ba04" />-->
<!--            <context:component-scan base-package="com.bjpowernode.ba05" />-->
            <context:component-scan base-package="com.bjpowernode.ba06" />


<!--   &lt;!&ndash;-->
<!--     <bean id="myXueXiao" class="com.bjpowernode.ba03.School">-->
<!--        <property name="name" value="清华大学" />-->
<!--        <property name="address" value="北京" />-->
<!--    </bean>-->
<!--    &ndash;&gt;-->

<!--    &lt;!&ndash;加载属性配置文件&ndash;&gt;-->
<!--    <context:property-placeholder location="classpath:test.properties" />-->

</beans>

2.5.3 简单类型的赋值 采用@value注解

package com.bjpowernode.ba02;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component("myStudent")
public class Student {
    
    
    /**
     * @Value: 简单类型的属性赋值
     *   属性: value 是String类型的,表示简单类型的属性值
     *   位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *         2.在set方法的上面
     */
    @Value(value = "李四" )
//    @Value("${myname}") //使用属性配置文件中的数据
    private String name;
    @Value(value = "20")
//    @Value("${myage}")  //使用属性配置文件中的数据
    private Integer age;

    public Student() {
    
    
        System.out.println("==student无参数构造方法===");
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
    //@Value("30")
    public void setAge(Integer age) {
    
    
        System.out.println("setAge:"+age);
        this.age = age;
    }
    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

2.5.4 引用类型的赋值 采用@Autowrid或@Resource

package com.bjpowernode.ba05;

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


@Component("myStudent")
public class Student {
    
    

    @Value("李四" )
    private String name;
    private Integer age;

    /**
     * 引用类型
     * @Autowired: spring框架提供的注解,实现引用类型的赋值。
     * spring中通过注解给引用类型赋值,使用的是自动注入原理 ,支持byName, byType
     * @Autowired:默认使用的是byType自动注入。
     *
     *   属性:required ,是一个boolean类型的,默认true
     *       required=true:表示引用类型赋值失败,程序报错,并终止执行。
     *       required=false:引用类型如果赋值失败, 程序正常执行,引用类型是null
     * 		建议使用True避免后续的空指针异常
     *
     *  位置:1)在属性定义的上面,无需set方法, 推荐使用
     *       2)在set方法的上面
     *
     *  如果要使用byName方式,需要做的是:
     *  1.在属性上面加入@Autowired
     *  2.在属性上面加入@Qualifier(value="bean的id") :表示使用指定名称的bean完成赋值。
     */

    //byName自动注入
    @Autowired(required = false)
    @Qualifier("mySchool")
    private School school;

    public Student() {
    
    
        System.out.println("==student无参数构造方法===");
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
    @Value("30")
    public void setAge(Integer age) {
    
    
        System.out.println("setAge:"+age);
        this.age = age;
    }

    @Override
    public String toString() {
    
    
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", school=" + school +
                '}';
    }
}
package com.bjpowernode.ba07;

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

import javax.annotation.Resource;


@Component("myStudent")
public class Student {
    
    

    @Value("李四" )
    private String name;
    private Integer age;

    /**
     * 引用类型
     * @Resource: 来自jdk中的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值
     *            使用的也是自动注入原理,支持byName, byType .默认是byName
     *  位置: 1.在属性定义的上面,无需set方法,推荐使用。
     *        2.在set方法的上面
     *
     * @Resource只使用byName方式,需要增加一个属性 name
     * name的值是bean的id(名称)
     */
    //只使用byName
    @Resource(name = "mySchool")
    private School school;

    public Student() {
    
    
        System.out.println("==student无参数构造方法===");
    }

    public void setName(String name) {
    
    
        this.name = name;
    }
    @Value("30")
    public void setAge(Integer age) {
    
    
        System.out.println("setAge:"+age);
        this.age = age;
    }

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

2.5.5 使用XML和注解的区别

注解优点是:
⚫ 方便
⚫ 直观
⚫ 高效(代码少,没有配置文件的书写那么复杂)。
其弊端也显而易见:以硬编码的方式写入到 Java 代码中,修改是需要重新编译代码的。

XML 方式优点是:
⚫ 配置和代码是分离的
⚫ 在 xml 中做修改,无需编译代码,只需重启服务器即可将新的配置加载。
xml 的缺点是:编写麻烦,效率低,大型项目过于复杂。

3. AOP 面向切面编程

3.1 AOP的作用,实现方式及要求。

AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。

  • 底层实现原理

     AOP底层实现原理为动态代理:
     	1. JDK动态代理 :
     			实现方式:使用jdk反射包中的类实现创建代理对象的功能
     			要求:目标类必须实现接口
     	2. cglib动态代理
     			实现方式: 使用第三方的工具库,实现代理对象的创建
     			要求: 目标类不许能够继承,不能是final
     			原理:继承,子类就是代理类
     
     AOP可以看做是动态代理的规范化,把实现动态代理的步骤进行一个规范,定义
    
  • AOP的作用:

     1. 在目标类不被修改源代码的情况下,增强功能
     2. 减少重复的代码
     3. 专注业务功能的实现
     4. 解耦合:业务员功能和非业务功能的耦合。
    
  • 什么时候使用AOP:

     1. 当需要对某个类进行功能增强时,且不能够更改该类的源代码时采用AOP。
     2. 当给项目中的多个类,增强相同的功能是使用AOP(如对类中的每个方法都进行日志输出)
     3. 给业务方法添加事务,日志输出。
    
  • AOP的实现框架

    1. Spring实现了AOP,实现方式是接口
    2. aspectj框架实现了AOP:
    	实现方式: 1. 使用注解可以实现AOP功能
    			   2. 使用xml配置文件中的标签实现AOP功能
    

3.2 AOP编程术语

  1. aspect: 切面

    切面: 需要增强的业务方法中,所需要增强的功能是什么。
    		如: 在业务方法中,需要添加日志输出,事务机制,权限检查等。
    
  2. pointcut: 切入点

    切入点: 所需要增强的业务方法是什么,该方法在哪里,需要找到这个方法。
    	如 : 我们需要增强的业务方法时doSome()和doOther,那得在程序中声明,
    	      doSome()和doOther方法在哪里啊。
    	
    
    	注意:这个切入点可以是单个方法,也可能是多个方法的集合。
    		切入点可以通过切入点表达式表示。
    
  3. Target: 目标对象

    目标对象: 与切入点相似,在进行功能增强是,得知道增强的是哪个对象的哪个方法。
       如 : 切入点描述的是增强的那个方法,目标对象是哪个对象。
       	如:我们此时需要增强的是Student类中的set方法。
    
  4. Advice: 通知

    通知: 通知,也叫增强,表示切面执行的时间。
    
    解释说明 : 通过切面、目标对象、切入点、
    			我们知道了需要给哪个类的哪个方法增强哪些功能,
    			但是我们不知道是在业务方法的执行结束之后还是执行之前添加功能。
    通知可以确定,给业务方法增强的功能,是在业务方法执行之前,执行之后等位置。
    
  5. JoinPoint:连接点

    连接点: 连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。
     
     解释说明 : 在对业务方法添加功能时,我们如何在功能方法中执行过程中获取到业务方法的具体信息呢。
       	    如: 在执行功能时,通过切入点表达式我们知道该方法是对doSome、doOther方法的功能增强。
                 但是在功能的执行过程中,我们如何具体的知道是对doSome还是
                 doOther的方法呢。
    

3.3 切入点表达式与通知类型。

3.3.1 通知类型

通知类型:对于业务方法增强功能,所执行的时间问题。在aspectj框架中使用注解表示的。也可以使用xml配置文件中的标签

  • @Before : 前置通知
  • @AfterReturning :后置通知
  • @Around :环绕通知
  • @AfterThrowing :异常通知
  • @After :最终通知

3.3.2 切入点表达式

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

execution(modifiers-pattern? ret-type-pattern 
		  declaring-type-pattern?name-pattern(param-pattern)
          throws-pattern?)
  解释说明: 
  	modifiers-pattern : 切入点函数的访问权限类型
	ret-type-pattern :切入点函数的返回值类型
	declaring-type-pattern:切入点函数的包名类名
	name-pattern(param-pattern) :切入点函数的方法名(参数类型和参数个数)
	throws-pattern : 切入点函数的抛出异常类型
   其中 ?:表示可选的部分 即可写可不写

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

注意:以上表达式可以采用通配符表示:
	如: 
		* :表示任意字符
		..:用在方法参数中,表示任意多个参数
			用在包名后,表示当前包及其子包路径
		+ : 用在类名后,表示当前类及其子类
			 用在接口后,表示当前接口及其实现类
举例说明:
  1.  指定切入点为:任意公共方法。
		execution(public * *(..))
  2.  指定切入点为:任何一个以“set”开始的方法。
        execution(* set*(..))
  3.指定切入点为:定义在 com.xyz.service 包里的任意类的任意方法。
       execution(* com.xyz.service.*.*(..))
  4.指定切入点为:定义在 com.xyz.service 包里的Student类中的任意方法。
       execution(* com.xyz.service.Student.*(..))
  5. 指定切入点为:定义在 com.xyz.service 包里的Student类中的doSome方法
       execution(* com.xyz.service.Student.doSome(..))
  6. 指定切入点为:定义在 com.xyz.service 包里的Student类中的doSome方法,且方法参数为String
	   execution(* com.xyz.service.Student.doSome(String))

3.4 IDEA中maven使用AOP(基于注解)的小实例。

第一步: 在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>
	<!--aspectj框架依赖,在aspectj框架中实现AOP-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
  </dependencies>

第二步:定义业务接口与实现类

package com.bjpowernode.ba01;
public interface SomeService {
    
    
    void doSome(String name,Integer age);
}
package com.bjpowernode.ba01;

import org.springframework.stereotype.Component;

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

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

package com.bjpowernode.ba01;

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

import java.util.Date;

/**
 * @Aspecr:是aspectj框架中的注解
 *      作用 : 表示当前类是切面类
 *      切面类: 用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *
 *      位置: 在类定义的上面
 */

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

第四步:定义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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd">


    <!--把对象交给spring容器,由spring容器统一创建,管理对象-->
    <!--声明目标对象-->
     <bean id="someService" class="com.bjpowernode.ba01.SomeServiceImpl" />
    <!--声明切面类对象-->
     <bean id="myAspect" class="com.bjpowernode.ba01.MyAspect" />
     
    <!--声明自动代理生成器:使用aspectj框架内部的功能,创建目标对象的代理对象。
        创建代理对象是在内存中实现的, 修改目标对象的内存中的结构。 创建为代理对象
        所以目标对象就是被修改后的代理对象.
        aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
	其工作原理是,<aop:aspectj-autoproxy/>通过扫描找到@Aspect 定义的切面类,再由切
面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
    -->
    <aop:aspectj-autoproxy />
        <!--
       如果你期望目标类有接口,使用cglib代理
       proxy-target-class="true":告诉框架,要使用cglib动态代理
    -->
<!--     <aop:aspectj-autoproxy proxy-target-class="true"/> -->
</beans>

第六步:测试类中使用目标对象的 id

package com.bjpowernode;

import com.bjpowernode.ba01.SomeService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class MyTest01 {
    
    
    @Test
    public void test01(){
    
    
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        SomeService proxy = (SomeService) ctx.getBean("someService");
        //通过代理的对象执行方法,实现目标方法执行时,增强了功能
        proxy.doSome("lisi",20);
    }
}

注意:在正常情况下,Spring容器中存储的是目标类的对象,但,当使用AOP时,虽然在表面上看,我们获取的还是当前类的对象,但是Spring会在底层吧当前对象替换为当前对象的代理对象。

因此:AOP的实现原理是动态代理。

3.5 不同通知类型的具体实例。

  • @Before : 前置通知
  • @AfterReturning :后置通知
  • @Around :环绕通知
  • @AfterThrowing :异常通知
  • @After :最终通知

3.5.1 前置通知 和 JoinPoint 参数

package com.bjpowernode.ba01;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

@Aspect
public class MyAspect {
    
    
    /**
     * 指定通知方法中的参数 : JoinPoint
     * JoinPoint:业务方法,要加入切面功能的业务方法
     *    作用是:可以在通知方法中获取方法执行时的信息, 例如方法名称,方法的实参。
     *    如果你的切面功能中需要用到方法的信息,就加入JoinPoint.
     *    这个JoinPoint参数的值是由框架赋予, 必须是第一个位置的参数
     */
    @Before(value = "execution(void *..SomeServiceImpl.doSome(String,Integer))")
    public void myBefore(JoinPoint jp){
    
    
        //获取方法的完整定义
        System.out.println("方法的签名(定义)="+jp.getSignature());
        System.out.println("方法的名称="+jp.getSignature().getName());
        //获取方法的实参
        Object args [] = jp.getArgs();
        for (Object arg:args){
    
    
            System.out.println("参数="+arg);
        }
        //就是你切面要执行的功能代码
        System.out.println("2=====前置通知, 切面功能:在目标方法之前输出执行时间:"+ new Date());
    }
}

3.5.2 @AfterReturning 后置通知-注解有 returning 属性

注意 : 在后置通知中,由于我们的功能方法是最后执行的,因此我们无法确定业务方法是否有返回值,因此,一般采用Object res 获取到业务方法的返回值,但当我们执行的业务方法没有返回值时,此时也不会出错。
注意:这个返回值是可以更改的,区别在于形参和实参。

package com.bjpowernode.ba02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

import java.util.Date;

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    
    
    /**
     * 后置通知定义方法,方法是实现切面功能的。
     * 方法的定义要求:
     * 1.公共方法 public
     * 2.方法没有返回值
     * 3.方法名称自定义
     * 4.方法有参数的,推荐是Object ,参数名自定义
     */

    /**
     * @AfterReturning:后置通知
     *    属性:1.value 切入点表达式
     *         2.returning 自定义的变量,表示目标方法的返回值的。
     *          自定义变量名必须和通知方法的形参名一样。
     *    位置:在方法定义的上面
     * 特点:
     *  1。在目标方法之后执行的。
     *  2. 能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
     *      Object res = doOther();
     *  3. 可以修改这个返回值
     *
     *  后置通知的执行
     *    Object res = doOther();
     *    参数传递: 传值, 传引用
     *    myAfterReturing(res);
     *    System.out.println("res="+res)
     *
     */
    @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",
                    returning = "res")
    public void myAfterReturing(  JoinPoint jp  ,Object res ){
    
    
        // Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println("后置通知:方法的定义"+ jp.getSignature());
        System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
    }
}

3.5.3 @Around 环绕通知-增强方法有 ProceedingJoinPoint参数

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

package com.bjpowernode.ba03;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    
    
    /**
     * 环绕通知方法的定义格式
     *  1.public
     *  2.必须有一个返回值,推荐使用Object
     *  3.方法名称自定义
     *  4.方法有参数,固定的参数 ProceedingJoinPoint
     */

    /**
     * @Around: 环绕通知
     *    属性:value 切入点表达式
     *    位置:在方法的定义什么
     * 特点:
     *   1.它是功能最强的通知
     *   2.在目标方法的前和后都能增强功能。
     *   3.控制目标方法是否被调用执行
     *   4.修改原来的目标方法的执行结果。 影响最后的调用结果
     *
     *  环绕通知,等同于jdk动态代理的,InvocationHandler接口
     *
     *  参数:  ProceedingJoinPoint 就等同于 Method
     *         作用:执行目标方法的
     *  返回值: 就是目标方法的执行结果,可以被修改。
     *
     *  环绕通知: 经常做事务, 在目标方法之前开启事务,执行目标方法, 在目标方法之后提交事务
     */
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
    
    

        String name = "";
        //获取第一个参数值
        Object args [] = pjp.getArgs();
        if( args!= null && args.length > 1){
    
    
              Object arg=  args[0];
              name =(String)arg;
        }

        //实现环绕通知
        Object result = null;
        System.out.println("环绕通知:在目标方法之前,输出时间:"+ new Date());
        //1.目标方法调用
        if( "zhangsan".equals(name)){
    
    
            //符合条件,调用目标方法
            result = pjp.proceed(); //method.invoke(); Object result = doFirst();

        }

        System.out.println("环绕通知:在目标方法之后,提交事务");
        //2.在目标方法的前或者后加入功能
        //修改目标方法的执行结果, 影响方法最后的调用结果
        if( result != null){
    
    
              result = "Hello AspectJ AOP";
        }
        //返回目标方法的执行结果
        return result;
    }
}

3.5.4 @AfterThrowing 异常通知-注解中有 throwing 属性

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

package com.bjpowernode.ba04;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;

import java.util.Date;

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    
    
    /**
     * 异常通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法有个一个Exception, 如果还有是JoinPoint,
     */

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

}

3.5.5 @After 最终通知

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

package com.bjpowernode.ba05;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    
    
    /**
     * 最终通知方法的定义格式
     *  1.public
     *  2.没有返回值
     *  3.方法名称自定义
     *  4.方法没有参数,  如果还有是JoinPoint,
     */

    /**
     * @After :最终通知
     *    属性: value 切入点表达式
     *    位置: 在方法的上面
     * 特点:
     *  1.总是会执行
     *  2.在目标方法之后执行的
     *
     *  try{
     *      SomeServiceImpl.doThird(..)
     *  }catch(Exception e){
     *
     *  }finally{
     *      myAfter()
     *  }
     *
     */
    @After(value = "execution(* *..SomeServiceImpl.doThird(..))")
    public  void  myAfter(){
    
    
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

}

3.5.6 @Pointcut 定义切入点

当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。

其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的 value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。

package com.bjpowernode.ba06;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;

/**
 *  @Aspect : 是aspectj框架中的注解。
 *     作用:表示当前类是切面类。
 *     切面类:是用来给业务方法增加功能的类,在这个类中有切面的功能代码
 *     位置:在类定义的上面
 */
@Aspect
public class MyAspect {
    
    


    @After(value = "mypt()")
    public  void  myAfter(){
    
    
        System.out.println("执行最终通知,总是会被执行的代码");
        //一般做资源清除工作的。
     }

    @Before(value = "mypt()")
    public  void  myBefore(){
    
    
        System.out.println("前置通知,在目标方法之前先执行的");
    }

    /**
     * @Pointcut: 定义和管理切入点, 如果你的项目中有多个切入点表达式是重复的,可以复用的。
     *            可以使用@Pointcut
     *    属性:value 切入点表达式
     *    位置:在自定义的方法上面
     * 特点:
     *   当使用@Pointcut定义在一个方法的上面 ,此时这个方法的名称就是切入点表达式的别名。
     *   其它的通知中,value属性就可以使用这个方法名称,代替切入点表达式了
     */
    @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))" )
    private void mypt(){
    
    
        //无需代码,
    }
}

4. Spring整合MyBatis框架

4.1 为什么要使用Spring整合Mybatis框架

将 MyBatis 与 Spring 进行整合,主要解决的问题就是将 SqlSessionFactory 对象交由 Spring来管理。所以,该整合,只需要将 SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。

理由如下;

  • 在Mybatis框架中,若要对数据库中数据执行CRUD要如下步骤:(这里假设数据库表为Student,且具有属性,id、name、email、age)

    1.  创建maven项目,并加入依赖(mybatis依赖和mysql依赖)
    2.  在数据库中创建表Student
    3.  创建entity包,并创建实体类对象Student.(一表一个实体类)
    4.  创建dao包,并创建接口(StudentDao,并创建CRUD方法)和mapper映射文件(创建sql执行语句)。
    5.  创建mybatis主配置文件,在主配置文件中指定连接数据库的数据源DataSource,并指定mapper映射文件地址。
    6.  创建service包,并创建接口StudentService(这个接口是用来管理dao的,在实际过程中,
          并不是直接访问dao,而是通过service访问dao,进而访问数据库。),在接口创建CRUD方法。
          在service包下,创建包impl,并实现接口中CRUD的具体实现方法。
          (注意,该实现方式是通过调用dao包下的StudentDao接口实现类中的
             方法(具体是mybatis通过getMapper()方法获得StudentDao的dao代理类))
    7. 创建测试类,在测试类中,指定MyBatis主配置文件地址,具体执行步骤如下:
         1.  读取mybatis主配置文件
         2.  创建SqlSessionFactoryBuilder对象
         3.  创建SqlSessionFactory对象
         4.  获取 SqlSession对象,从SqlSessionFactory中会获取SqlSession
         5.  获取dao代理对象,并执行sql语句
    
  • 如上所示:在Mybatis,我们需要手动获取到SqlSessionFactory对象,获取dao代理对象,因此,当我们的dao代理对象较多时,会存在较多麻烦,因此可以采用Spring框架帮我们获取到SqlSessionFactory对象和dao代理对象。

  • 除此之外:在Mybatis主配置文件中,我们需要指定数据源DataSources,此时采用的数据库连接池是Mybatis的,该数据库连接池无法满足实际开发需求,所以采用Spring指定数据库连接池。

4.2 整合案例

主要过程如下所示:

步骤:

1.新建maven项目
2.加入maven的依赖
	  1)spring依赖
	  2)mybatis依赖
	  3)mysql驱动
	  4)spring的事务的依赖
	  5)mybatis和spring集成的依赖: mybatis官方体用的,用来在spring项目中创建mybatis
	     的SqlSesissonFactory,dao对象的
3.创建实体类
4.创建dao接口和mapper文件
5.创建mybatis主配置文件
6.创建Service接口和实现类,属性是dao。
7.创建spring的配置文件:声明mybatis的对象交给spring创建
	 1)数据源DataSource
	 2)SqlSessionFactory
	 3) Dao对象
	 4)声明自定义的service

8.创建测试类,获取Service对象,通过service调用dao完成数据库的访问

4.2.1 创建maven项目,并添加所需依赖

依赖描述:

  • Spring依赖
  • mybatis依赖
  • mysql驱动
  • spring的事务的依赖
  • mybatis和spring集成的依赖

在POM文件中如下:

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>ch07-spring-mybatis</artifactId>
  <version>1.0</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.5</maven.compiler.source>
    <maven.compiler.target>1.5</maven.compiler.target>
  </properties>

  <dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    
    <!--Spring核心IOC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    
    <!--Spring事务依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--spring集成mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.23</version>
    </dependency>
    <!--阿里公司数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

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

4.2.2 创建实体类

package com.bjpowernode.domain;

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

    public Student() {
    
    
    }

    public Student(Integer id, String name, String email, Integer age) {
    
    
        this.id = id;
        this.name = name;
        this.email = email;
        this.age = age;
    }

    public Integer getId() {
    
    
        return id;
    }

    public void setId(Integer id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

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

    public String getEmail() {
    
    
        return email;
    }

    public void setEmail(String email) {
    
    
        this.email = email;
    }

    public Integer getAge() {
    
    
        return age;
    }

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

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

4.2.3 创建dao接口和mapper映射文件

package com.bjpowernode.dao;

import com.bjpowernode.domain.Student;

import java.util.List;

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

4.2.4 创建mybatis主配置文件

注意:在没有使用spring整合之前,mybatis主配置文件的作用是:
1. 指定DataSource
2. 指定mapper映射文件地址
3. 其他辅助功能

因此,在使用Springh整合之后,,mybatis主配置文件的作用是:
1. 指定mapper映射文件地址
2. 其他辅助功能

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--settings:控制mybatis全局行为的-->
    <settings>
        <!--设置mybatis输出日志-->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!--设置别名-->
    <typeAliases>
        <!--name: 实体类所在包名-->
        <package name="com.bjpowernode.domain"/>
    </typeAliases>

    <!--sql映射文件的地址-->
    <mappers>
        <!--name : 是包名,这个包中所有mapper文件一次都能加载-->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

4.2.5 创建Service接口和实现类,属性是dao。

注意:虽然在使用过程中,我们是可以直接dao代理对象直接对数据库进行访问的,但是在实际开发过程中,我们需要采用Service接口和实现类来对dao进行访问,间接的访问数据库。

这么做的目的是:
1. 代码清晰
2. 容易做功能扩展
3. 解耦合
4. 对数据库进行访问,有可能访问失败,那访问失败该怎么办,我们需要对访问失败做处理,因此采用service分离,让dao只做访问数据库,让service做访问处理。

package com.bjpowernode.service;

import com.bjpowernode.domain.Student;

import java.util.List;

public interface StudentService {
    
    
    int addStudent(Student student);
    List<Student> queryStudents();
}
package com.bjpowernode.service.impl;
import com.bjpowernode.dao.StudentDao;
import com.bjpowernode.domain.Student;
import com.bjpowernode.service.StudentService;
import java.util.List;

public class StudentServiceImpl implements StudentService {
    
    
    //引用类型
    private StudentDao studentDao;
    //目的是为了使用set注入赋值
    public void setStudentDao(StudentDao studentDao) {
    
    
        this.studentDao = studentDao;
    }

    public int addStudent(Student student) {
    
    
        int nums = studentDao.insertStudent(student);
        return nums;
    }

    public List<Student> queryStudents() {
    
    
        List<Student> students = studentDao.selectStudents();
        return students;
    }
}

4.2.6 (整合重点)创建spring的配置文件:声明mybatis的对象交给spring创建

分析主要过程,该过程为固定编码过程即框架编码格式。

  1. 首先指定数据源DataSources
  2. 声明SqlSessionFactory对象,让Spring容器创建
  3. 声明Dao代理对象,让Spring容器创建
  4. 声明自定义的service

注意:在上述过程中,1,2,3是固定编码格式(死记硬背)

具体Spring配置文件内容如下所示:

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

    <!--
       把数据库的配置信息,写在一个独立的文件,编译修改数据库的配置内容
       spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties" />
    <!--第一步:声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入-->
        <!--    使用属性配置文件中的数据,语法 ${key} -->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

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

    <!--第三步:创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。

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

    <!--第四步: 声明service-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
    	<!--此时的Dao对象已经由Spring创建过了,创建的名字默认为接口名首字母小写-->
        <property name="studentDao" ref="studentDao" />
    </bean>
</beans>

4.2.7 创建测试类,获取Service对象,通过service调用dao完成数据库的访问

package com.bjpowernode;

import com.bjpowernode.dao.StudentDao;
import com.bjpowernode.domain.Student;
import com.bjpowernode.service.StudentService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class MyTest {
    
    
    @Test
    public void TestDaoInsert(){
    
    
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        StudentDao studentDao = (StudentDao) ctx.getBean("studentDao");
        //spring整合Mybatis框架中事务是自动提交的
        Student student = new Student(1006,"妲己","[email protected]",10);
        studentDao.insertStudent(student);
    }

    @Test
    public void TestServiceInsert(){
    
    
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        StudentService studentService = (StudentService) ctx.getBean("studentService");
        Student student = new Student(1007,"盖伦","[email protected]",20);
        studentService.addStudent(student);
    }

    @Test
    public void TestServiceSelect(){
    
    
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        StudentService studentService = (StudentService) ctx.getBean("studentService");
        List<Student> students = studentService.queryStudents();
        for (Student student:students) {
    
    
            System.out.println(student);
        }
    }
}

5. Spring 事务

注意:Spring事务实现的底层原理采用的是**@Around环绕通知**的方式,为业务类创建代理类。

5.1 基本概念

5.1.1 常见问题描述

  • 什么是事务:在一组sql语句的执行过程中,此次执行的sql语句要么同时执行成功,要么同时执行失败。即,该组sql语句的执行是一个整体的执行。

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

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

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

     jdbc访问数据库,处理事务  Connection conn ; conn.commit(); conn.rollback();
     mybatis访问数据库,处理事务, SqlSession.commit();  SqlSession.rollback();
     hibernate访问数据库,处理事务, Session.commit(); Session.rollback();
    
  • 上述事务的处理方式有什么不足

    1)不同的数据库访问技术,处理事务的对象,方法不同,
        需要了解不同数据库访问技术使用事务的原理
    2)掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回顾事务
    3)处理事务的多种方法。
    
    总结: 就是多种数据库的访问技术,有不同的事务处理的机制,对象,方法。
           开发人员所需要学习的事务方式较多。不方便。
    
  • 怎么解决不足: spring提供一种处理事务的统一模型, 能使用统一步骤,方式完成多种不同数据库访问技术的事务处理。

5.1.2 Spring实现事务的方式

在 Spring 中通常可以通过以下两种方式来实现对事务的管理:
(1)使用 Spring 的事务注解管理事务
(2)使用 AspectJ 的 AOP 配置管理事务

5.1.3 Spring 事务管理 API

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

     常用的两个实现类
       PlatformTransactionManager 接口有两个常用的实现类:
        ➢ DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
        ➢ HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
    
  • Spring 的回滚方式:Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交。不过,对于受查异常,程序员也可以手工设置其回滚方式。

5.1.4 Spring 事务定义接口

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

  • 事务隔离级别:

     ➢ DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
     
     	➢ READ_UNCOMMITTED:读未提交。未解决任何并发问题。
     	➢ READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
     	➢ REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
     	➢ SERIALIZABLE:串行化。不存在并发问题。
    
  • 事务传播行为:所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。事务传播行为是加在方法上的。

     事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。-
     
     7种事务传播行为如下所示:
     		PROPAGATION_REQUIRED
            PROPAGATION_REQUIRES_NEW
     		PROPAGATION_SUPPORTS
     		
     		PROPAGATION_MANDATORY 
     		PROPAGATION_NESTED
     		PROPAGATION_NEVER
     		PROPAGATION_NOT_SUPPORTED
    
    注意:主要理解上述三种就行
    
       1. PROPAGATION_REQUIRED:指定的方法必须在事务内执行。若当前存在事务,
               就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播
               行为是最常见的选择,也是 Spring 默认的事务传播行为。
       2. PROPAGATION_SUPPORTS:指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
       3. PROPAGATION_REQUIRES_NEW:总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
    
  • 事务超时时限:常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。注意,事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。所以,该值一般就使用默认值即可。

5.2 Spring使用事务的实例环境搭建

第一步:创建数据库表:销售表sale 和 商品表goods
在这里插入图片描述

第二步: 创建maven项目,并加入依赖

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>com.bjpowernode</groupId>
  <artifactId>ch09-spring-trans-anno</artifactId>
  <version>1.0</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.5</maven.compiler.source>
    <maven.compiler.target>1.5</maven.compiler.target>
  </properties>

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

    <!--Spring核心IOC-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <!--Spring事务依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--spring集成mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.23</version>
    </dependency>
    <!--阿里公司数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

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

第三步: 创建实体类
在这里插入图片描述
第四步:定义 dao 接口
在这里插入图片描述
第五步:定义 dao 接口对应的 sql 映射文件
SaleDao.xml
在这里插入图片描述
第六步:定义异常类
在这里插入图片描述
第七步:定义 Service 接口
在这里插入图片描述
第八步:定义 service 的实现类
在这里插入图片描述
第九步:定义 mybatis主配置文件

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

    <!--设置别名-->
    <typeAliases>
        <!--name: 实体类所在包名-->
        <package name="com.bjpowernode.domain"/>
    </typeAliases>

    <!--sql映射文件的地址-->
    <mappers>
        <!--name : 是包名,这个包中所有mapper文件一次都能加载-->
        <package name="com.bjpowernode.dao"/>
    </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">

    <context:property-placeholder location="classpath:jdbc.properties" />
    <!--第一步:声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--第二步: 声明的是mybatis中提供的SqlSessionFactoryBean类-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">  
        <property name="dataSource" ref="myDataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--第三步:创建dao对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> 
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--第四步: 声明service-->
    <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao" />
        <property name="saleDao" ref="saleDao"/>
    </bean>
</beans>

第十一步:定义测试类

package com.bjpowernode;
import com.bjpowernode.service.BuyGoodsService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
    
    
    @Test
    public void Test01(){
    
    
        String config = "applicationContext.xml";
        ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
        BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");
        service.buy(1001,100);
    }
}

注意:以上代码是在没有事务的情况下执行,上述代码的主要目的是操作两个数据库表。

下面将阐述如何在spring中使用事务。

5.3 使用注解管理事务

该种方法适应于中小型项目,因为注解的方式注解是写在业务方法上的,当使用大型项目时业务方法较多,这种方式有局限性。

具体步骤:在spring配置文件中

第一步:声明事务管理器
在这里插入图片描述
第二步:开启注解驱动
在这里插入图片描述
第三步:业务层 public 方法加入事务属性
在这里插入图片描述

具体代码如下:

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

    <context:property-placeholder location="classpath:jdbc.properties" />
    <!--第一步:声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入-->
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--第二步: 声明的是mybatis中提供的SqlSessionFactoryBean类-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--第三步:创建dao对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--第四步: 声明service-->
    <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao" />
        <property name="saleDao" ref="saleDao"/>
    </bean>

    <!--使用事务管理-->
    <!--第一步:声明事务管理器-->
    <bean id="TransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
    <!--第二步:开启事务注解驱动,告诉Spring使用注解管理事务,创建代理对象
        TransactionManager : 事务管理器对象的id
    -->
    <tx:annotation-driven transaction-manager="TransactionManager"/>
</beans>
package com.bjpowernode.service.impl;

import com.bjpowernode.dao.GoodsDao;
import com.bjpowernode.dao.SaleDao;
import com.bjpowernode.domain.Goods;
import com.bjpowernode.domain.Sale;
import com.bjpowernode.excep.NotEnoughException;
import com.bjpowernode.service.BuyGoodsService;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

public class BuyGoodsServiceImpl implements BuyGoodsService {
    
    
    private GoodsDao goodsDao;
    private SaleDao saleDao;

    /**
     *rollbackFor : 表示发生指定的异常,一定回滚
     */
    @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = {
    
    
                    NullPointerException.class,NotEnoughException.class
            }
    )
    @Override
    public void buy(Integer goodsId, Integer nums) {
    
    
        System.out.println("buy方法的开始");
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        // 1. 记录销售的信息,向Sala表添加记录
        saleDao.insertSale(sale);
        // 2. 更新库存,更新Goods表
        Goods goods = goodsDao.selectGoods(goodsId);

        if (goods == null) {
    
    
            //商品不存在
            throw new NullPointerException("编号是 : " + goodsId+ "商品不存在");
        }else if (goods.getAmount() < nums){
    
    
            //商品库存不足
            throw new NotEnoughException("编号是 : " + goodsId+ "商品库存不足");
        }

        //修改库存
        Goods buyGoods = new Goods();
        buyGoods.setId(goodsId);
        buyGoods.setAmount(nums);
        goodsDao.updateGoods(buyGoods);
        System.out.println("buy方法的结束");
    }

    public void setGoodsDao(GoodsDao goodsDao) {
    
    
        this.goodsDao = goodsDao;
    }

    public void setSaleDao(SaleDao saleDao) {
    
    
        this.saleDao = saleDao;
    }
}

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

大型项目时使用,因为此中方式,为业务方法添加事务是在XML中指定的,可以通过通配符的方式一次性对多个方法添加事务。

具体实现步骤如下:

第一步: 在POM文件中添加AspectJ 依赖

  <!--aspectj依赖,利用aspectj框架实现spring的AOP-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aspects</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>

第二步: 添加事务管理器
在这里插入图片描述
第三步: 配置事务通知:为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
在这里插入图片描述
第四步: 配置增强器:指定将配置好的事务通知,织入给谁
在这里插入图片描述
具体代码如下:

<?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="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}" /><!--setUrl()-->
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.passwd}" />
        <property name="maxActive" value="${jdbc.max}" />
    </bean>

    <!--第二步: 声明的是mybatis中提供的SqlSessionFactoryBean类-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="myDataSource" />
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--第三步:创建dao对象-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <property name="basePackage" value="com.bjpowernode.dao"/>
    </bean>

    <!--第四步: 声明service-->
    <bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
        <property name="goodsDao" ref="goodsDao" />
        <property name="saleDao" ref="saleDao"/>
    </bean>

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

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

            <!--使用通配符,指定很多的方法-->
<!--            <tx:method name="add*" propagation="REQUIRES_NEW" />-->
<!--            &lt;!&ndash;指定修改方法&ndash;&gt;-->
<!--            <tx:method name="modify*" />-->
<!--            &lt;!&ndash;删除方法&ndash;&gt;-->
<!--            <tx:method name="remove*" />-->
<!--            &lt;!&ndash;查询方法,query,search,find&ndash;&gt;-->
<!--            <tx:method name="*" propagation="SUPPORTS" read-only="true" />-->
        </tx:attributes>
    </tx:advice>

    <!--配置aop 配置增强器-->
    <aop:config>
        <!--配置切入点表达式:指定哪些包中类,要使用事务
            id:切入点表达式的名称,唯一值
            expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象

            com.bjpowernode.service
            com.crm.service
            com.service
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>

        <!--配置增强器:关联adivce和pointcut
           advice-ref:通知,上面tx:advice哪里的配置
           pointcut-ref:切入点表达式的id
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt" />
    </aop:config>
</beans>

猜你喜欢

转载自blog.csdn.net/qq_28384023/article/details/116499882