Spring笔记

Spring笔记

第一章 Spring概述

1.1 概述

  • Spring:
    • 2002年左右诞生,降低企业级开发难度,帮助进行模块、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
      2003年传入国内,被大量使用。
      2017出现新的流行框架SpringBoot,核心思想与Spring相同。
  • 核心技术:IoC、AOP,能使模块之间、类之间解耦合。
  • 依赖:class A使用class B的属性或方法,称之为class A依赖class B。
  • 官网:spring.io

1.2 优点

  • 轻量:Spring的所需要的jar包都非常小,一般1M以下,几百kb。核心功能所需要的jar包总共3M左右。
    Spring框架运行占有资源少,运行效率高,不依赖其他jar。
  • 针对接口编程,解耦合
  • AOP 编程的支持
  • 方便集成各种优秀框架

2,3,4的优点在接下来的学习中体会。

1.3 Spring体系结构

Spring体系结构

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

第二章 IOC控制反转(重点)

IOC控制反转基本概念

  • 控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做**依赖注入(Dependency Injection,简称DI**),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。

    (摘自百度百科)

  • 控制反转(IoC,Inversion of Control),是一个概念,一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值, 依赖的管理。

    (自己总结)

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

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

      • public static void main(String args[]){
        	Student student = new Student(); // 在代码中,创建对象。--正转。
        }
        
    • 反转:把原来的开发人员管理,创建对象的权限转移给代码之外的容器实现。

      由容器代替开发人员管理对象。创建对象,给属性赋值。

    • 容器:是一个服务器软件, 一个框架(spring)

  • 为什么要使用 ioc:

    • 目的就是减少对代码的改动, 也能实现不同的功能,实现解耦合。
  • java中创建对象有哪些方式?

    • 构造方法, new
    • 反射
    • 序列化
    • 克隆
    • ioc:容器创建对象
    • 动态代理
  • ioc的体现:

    • servlet:

      • 第一步:创建类继承HttpServelt

      • 第二步:在web.xml 注册servlet,使用

        <servlet-name>myservlet</servlet-name>
        <servelt-class>com.bjpwernode.controller.MyServlet1</servlet-class>
        
      • 第三步:没有创建 Servlet对象,没有 MyServlet myservlet = new MyServlet()

    • Servlet是Tomcat服务器它能你创建的。Tomcat也称为容器。

    • Tomcat作为容器,里面存放的有Servlet对象,Listener,Filter对象。

  • IoC的技术实现:

    • 依赖查找:DL( Dependency Lookup ),容器提供回调接口和上下文环境给组件;
    • 依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
      • Spring框架使用依赖注入(DI)实现IOC。
      • DI 是ioc的技术实现。
      • DI:依赖注入,只需要在程序中提供要使用的对象名称就可以,至于对象如何在容器中创建,赋值,查找都由容器内部实现。
  • IOC底层:

    • xml解析、工厂模式、反射;
  • spring是一个容器,管理对象,给属性赋值,底层是反射创建对象。

  • Spring提供的IOC容器实现的两种方式(两个接口)

    • BeanFactory接口:IOC容器基本实现是Spring内部接口的使用接口,不提供给开发人员进行使用(加载配置文件时候不会创建对象,在获取对象时才会创建对象)
    • ApplicationContext接口:BeanFactory接口的子接口,提供更多更强大的功能,提供给开发人员使用(加载配置文件时候就会把在配置文件对象进行创建)推荐使用!

2.1 Spring的第一个程序

2.1.1 普通Spring项目创建步骤

  • 第一步:创建空项目 spring
  • 第二步:创建模板Module - Maven - Create from archetype - quickstart
    • Name:ch01-hellospring
    • GroupId:com.bjpowernode
  • 第三步:加入maven依赖
    • Spring依赖,5.3.15——Maven Repository
    • junit依赖
    • maven-compiler-plugin插件可加可不加(在指定java版本的前提下。)
  • 第四步:创建类(接口和它的实现类)
    • 类要自己写,框架只是帮你创建对象。
    • 和没有使用框架一样,就是普通的类。
  • 第五步:创建Spring需要使用的配置文件,声明类的信息,这些类由Spring创建和管理。
  • 第六步:测试spring创建的对象。

2.1.2 Maven的pom.xml

重点:单元测试依赖和Spring依赖

<?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-hellospring</artifactId>
  <version>1.0-SNAPSHOT</version>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</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.3.15</version>
    </dependency>

  </dependencies>

  <build></build>
</project>

2.1.3 创建service层

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("impl的无参构造方法");
    }
    @Override
    public void doSome() {
        System.out.println("执行了doSome方法");
    }
}

2.1.4 创建Spring配置文件beans.xml

  • 如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的servlet对象一样,在Spring中也需要有类似的配置,来自动创建刚才的SomeServiceImpl对象。

  • 在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但 Spring 建议的名称为 applicationContext.xml。 IDEA已经为我们设计好了Spring配置文件的模板;

  • 右击resources–>new–>XML configuration file–>Spring Config

  • 注意,Spring 配置文件中使用的约束文件为 xsd 文件。作用与Mybatis的sql映射文件的dtd约束文件类似,但xsd约束作用更强:
    1)定义一个 xml 文档中都有什么元素
    2)定义一个 xml 文档中都有什么属性
    3)定义一个 xml 文档中元素可以有哪些子元素,以及元素的顺序。
    4)定义一个 xml 文档中元素和属性的数据类型。

  • bean标签的属性:

    • class:类的全限定名称,不能是接口(Spring使用反射创建对象);class可以是非自定义的对象,例如”java.util.Date“,依然可以被Spring创建对象。
    • id:自定义的对象名称,要求是唯一值。 表示在Spring中的对象名称,通过这个名称可以从Spring中找到对象,获取对象
    • scope:指定bean对象的作用域(对象的存在范围和可见性)。可取值:
      • 单例:singleton,默认值,表示叫这个名称的对象在spring容器中只有一个。
      • 原型:prototype,表示每次使用getBean()都创建一个新的对象。
<?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,就是告诉Spring要创建某个类的对象-->
    <!--
        id:对象的自定义名称,唯一值。Spring通过这个名称找到对象
        class:类的全限定名称(不能是接口,因为Spring是反射机制创建对象,必须使用类。)

        通过bean,Spring完成:
			SomeService someService = new SomeServiceImpl();
        
		Spring将创建好的对象放入一个Map中,Spring框架有一个map存放对象。
            springMap.put(id的值,对象);
            例如:springMap.put("someService", new SomeSe|rviceImpl());

            一个bean标签声明一个java对象,两个bean声明两个对象。
    -->
    <bean id="someService" class="com.bjpowernode.service.impl.SomeServiceImpl"></bean>
    <bean id="someService1" class="com.bjpowernode.service.impl.SomeServiceImpl"></bean>
    <!--创建已经写好类的对象(不是自己写的)-->
    <bean id="mydate" class="java.util.Date"></bean>

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

2.1.5 创建测试类test

重点:ApplicationContext表示Spring容器。

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 {
    //测试的注解
    @Test
    public void test01(){
//        SomeService someService = new SomeService() {
//            @Override
//            public void doSome() {
//
//            }
//        }
        SomeService someService = new SomeServiceImpl();
        someService.doSome();
    }

    /**
     * spring默认创建对象的时间:在创建spring容器时,会创建配置文件中的所有对象。
     */
    @Test
    public void test02(){
        //使用spring容器创建的对象
        //1. 指定spring配置文件的名称
        String config = "beans.xml";
        //2. 创建表示spring容器的对象,ApplicationContext。
        //ApplicationContext就是表示Spring容器,通过这个容器对象就能获取对象了。
        //ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件。类路径:target/classes
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //3. 从容器中获取某个对象,你要调用对象的方法。
        //从容器Map中获取。
        SomeService someService = (SomeService) ac.getBean("someService");
        //4. 使用spring创建好的对象
        someService.doSome();
    }
    
    /**
     * 获取spring容器中java对象的信息
     */
    @Test
    public void test03(){
        //1. 获取配置文件
        String config = "beans.xml";
        //2. 加载配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //使用spring提供的方法,获取容器中定义的对象的数量。
        //definition:定义
        int nums = ac.getBeanDefinitionCount();
        System.out.println(nums);
        //容器中每个对象的名称
        String[] names = ac.getBeanDefinitionNames();
        for (String name :
                names) {
            System.out.println(name);
        }
    }
    
    
    @Test
    public void test04(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        Date mydate = (Date) ac.getBean("mydate");
        System.out.println(mydate);
    }
}

2.2 基于XML的DI

  • DI:依赖注入,表示创建对象,给属性赋值;

  • di 的实现语法有两种:

    • 在spring的配置文件中,使用标签和属性完成,叫做基于XML的di实现
    • 使用spring中的注解,完成属性赋值,叫做基于注解的di实现
  • di 的语法分类:

    • set注入(设值注入):spring调用类的set方法,在set方法中实现属性的赋值。

      百分之八十使用set注入。

      必须有setter方法,否则会报错。

    • 构造注入:spring调用类的有参数构造方法,创建对象。在构造方法中完成赋值。

2.2.1 set注入(设值注入,掌握)

  • set注入(设值注入):Spring调用类的set方法进行赋值。

    • 简单类型注入

      • <bean id="xx" class="yyy">
            <property name="属性名字" value="此属性的值"/>
            <!-- 一个property只能给一个属性赋值,多个属性赋值需要多个property。 -->
        </bean>
        
    • 引用类型注入

      • <bean id="xx" class="yyy">
        	<property name="属性名" ref="bean的id(对象的名称)">
        </bean>
        
  • <?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对象
            1. 注入:
                就是赋值的意思。
            2. 简单类型:
                spring规定,java基本数据类型、包装类型和字符串都是简单类型。
            3. di:给属性赋值
        -->
        <bean id="student" class="com.bjpowernode.ba01.Student">
            <!--使用set进行赋值-->
            <!--Spring不会管属性名,它是根据属性名找set方法。没有属性只有set方法,也会执行,不会报错。-->
            <!--不管什么类型,value必须放在引号中,这是xml的语法-->
            <property name="name" value="zhangsan"></property>
            <property name="age" value="20"> </property>
            <!--引用类型赋值-->
            <property name="school" ref="school"></property>
    
        </bean>
        <!--school类型对象-->
        <bean id="school" class="com.bjpowernode.ba01.School">
            <property name="address" value="北京市"></property>
            <property name="name" value="清华大学"></property>
            <property name="type" value="university"></property>
         </bean>
    
        <bean id="date" class="java.util.Date">
            <property name="time" value="4213974384938"></property><!--调用setTime方法-->
        </bean>
    </beans>
    
  • package com.bjpowernode;
    
    import com.bjpowernode.ba01.Student;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.Date;
    
    public class MyTest {
        //测试的注解
        @Test
        public void test01(){
            //由于applicationContext.xml文件在ba01目录下,因此从类的根路径开始,还要添加一层目录。
            String config = "ba01/applicationContext.xml";
            ApplicationContext ac = new ClassPathXmlApplicationContext(config);
            //从容器中获取对象
            Student student = (Student) ac.getBean("student");
            System.out.println(student);
    
            Date date = (Date) ac.getBean("date");
            System.out.println(date);
        }
    }
    
    

2.2.2 构造注入

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

    • 1. 构造注入使用<constructor-arg>标签;
      2. 一个<constructor-arg>标签代表一个参数,3个参数需要三个<constructor-arg>标签。
      3. <constructor-arg>标签属性:
      	name:表示构造方法形参名
      	index:表示构造方法参数的位置,参数从左往右位置是0, 1, 2...
      	value:构造方法的形参是简单类型,使用value
      	ref:构造方法是引用类型,使用ref。
      
  • <?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对象
                1. set注入(设值注入):spring调用类的set方法进行赋值。
                    1)简单类型的set注入
                        <bean id="xx" class="yyy">
                            <property name="属性名字" value="此属性的值"/>
                            一个property只能给一个属性赋值,
                            多个属性赋值需要多个property。
                        </bean>
                    2)引用类型的set注入
                        <bean id="xx" class="yyy">
                            <property name="属性名" ref="bean的id(对象的名称)">
                        </bean>
                2. 构造注入:spring调用类有参构造方法,在创建对象的同时,在构造方法中给属性赋值。
                    构造注入使用<constructor-arg>标签
                    一个<constructor-arg>标签代表一个参数,3个参数需要三个<constructor-arg>标签。
                    <constructor-arg>标签属性:
                        name:表示构造方法形参名
                        index:表示构造方法参数的位置,参数从左往右位置是0, 1, 2...
                        value:构造方法的形参是简单类型,使用value
                        ref:构造方法是引用类型,使用ref。
        -->
    
        <!--使用name属性实现构造注入-->
        <bean id="student1" class="com.bjpowernode.ba01.Student">
            <constructor-arg name="name" value="lisi"></constructor-arg>
            <constructor-arg name="age" value="20"></constructor-arg>
            <constructor-arg name="school" ref="school"></constructor-arg>
        </bean>
    
        <!--使用index实现构造注入-->
        <bean id="student2" class="com.bjpowernode.ba01.Student">
            <constructor-arg index="0" value="wangwu" ></constructor-arg>
            <constructor-arg index="1" value="21"></constructor-arg>
            <constructor-arg index="2" ref="school"></constructor-arg>
        </bean>
        
        <!--school类型对象-->
        <bean id="school" class="com.bjpowernode.ba01.School">
            <property name="address" value="北京市"></property>
            <property name="name" value="清华大学"></property>
            <property name="type" value="university"></property>
         </bean>
    </beans>
    
  • @Test
    public void test02(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
        Student student = (Student) ac.getBean("student");
        Student student1 = (Student) ac.getBean("student1");
        Student student2 = (Student) ac.getBean("student2");
        System.out.println(student);
        System.out.println(student1);
        System.out.println(student2);
    }
    

2.2.3 构造注入创建文件对象

<!--创建File,使用构造注入-->
<!--
	parent参数是文件夹路径
	child参数是文件夹下的文件名
-->
<bean id="myfile" class="java.io.File">
    <constructor-arg name="parent" value="E:\java-dir\Java学习截图"></constructor-arg>
    <constructor-arg name="child" value="BS架构理解.png"></constructor-arg>
</bean>
@Test
public void test03(){
    ApplicationContext ac = new ClassPathXmlApplicationContext("ba01/applicationContext.xml");
    File myfile = (File) ac.getBean("myfile");
    System.out.println(myfile);
}

2.2.4 引用类型属性自动注入

<!--
        引用类型的自动注入:spring框架根据某些规则可以给引用类型赋值。不用手动赋值了。
        使用的规则是:
            1. byName(按名称注入):
                    java类中引用数据类型的属性名和spring容器中(配置文件)<bean>的id名称一样,
                    且数据类型是一致的,这样的容器中的bean,spring能够赋值给引用类型。
                    表面上是和属性名一样,实际上反射是寻找set方法。

                语法:
                <bean id="xx" class="yyy" autowire="byName">
                简单类型属性赋值
                </bean>

            2. byType(按类型注入):
                    java类中引用数据类型和spring容器中<bean>的class属性是同源关系,这样的bean可以赋值给引用类型。
                同源就是一类的意思。
                1. java类中引用类型的数据类型和class的值是一样的。
                2. java类中引用类型的数据类型和class的值是父子类关系的。
                3. java类中引用类型的数据类型和class的值是接口和实现类关系的。

            注意:目前,在byType中,在xml配置文件中声明bean只能有一个符合条件,多余一个将会报错。
    -->

<bean name="student3" class="com.bjpowernode.ba01.Student" autowire="byName">
    <property name="name" value="zhaoliu"></property>
    <property name="age" value="21"></property>
</bean>

<bean name="student4" class="com.bjpowernode.ba01.Student" autowire="byType">
    <property name="name" value="zhaoliu"></property>
    <property name="age" value="21"></property>
</bean>

<!--school类型对象-->
<bean id="school" class="com.bjpowernode.ba01.School">
    <property name="address" value="北京市"></property>
    <property name="name" value="清华大学"></property>
    <property name="type" value="university"></property>
</bean>

2.2.5 注入空值和特殊符号

<bean id="book" class="com.atguigu.spring5.Book">
    <!--(1)null值-->
    <property name="address">
        <null/><!--属性里边添加一个null标签-->
    </property>
    
    <!--(2)特殊符号赋值-->
     <!--属性值包含特殊符号
       a 把<>进行转义 &lt; &gt;
       b 把带特殊符号内容写到CDATA
      -->
    <!--(3) CDATA语法:-->
        <property name="address">
            <value><![CDATA[<<南京>>]]></value>
        </property>
</bean>

2.2.6 内部bean

  • 内部bean可以用于:

    • 一对多关系:部门和员工
    • 一个部门有多个员工,一个员工属于一个部门(部门是一,员工是多)
  • <!--内部bean-->
        <bean id="emp" class="com.atguigu.spring5.bean.Emp">
            <!--设置两个普通属性-->
            <property name="ename" value="Andy"></property>
            <property name="gender" value=""></property>
            <!--设置对象类型属性-->
            <property name="dept">
                <bean id="dept" class="com.atguigu.spring5.bean.Dept"><!--内部bean赋值-->
                    <property name="dname" value="宣传部门"></property>
                </bean>
            </property>
        </bean>
    

2.2.7 级联赋值

方式一
  • 就是引入外部bean,使用ref。
<!--方式一:级联赋值-->
    <bean id="emp" class="com.atguigu.spring5.bean.Emp">
        <!--设置两个普通属性-->
        <property name="ename" value="Andy"></property>
        <property name="gender" value=""></property>
        <!--级联赋值-->
        <property name="dept" ref="dept"></property>
    </bean>
    <bean id="dept" class="com.atguigu.spring5.bean.Dept">
        <property name="dname" value="公关部门"></property>
    </bean>
方式二
 //方式二:生成dept的get方法(get方法必须有!!)
    public Dept getDept() {
        return dept;
    }
 <!--级联赋值-->
    <bean id="emp" class="com.atguigu.spring5.bean.Emp">
        <!--设置两个普通属性-->
        <property name="ename" value="jams"></property>
        <property name="gender" value=""></property>
        <!--级联赋值-->
        <property name="dept" ref="dept"></property>
        <property name="dept.dname" value="技术部门"></property>
    </bean>
    <bean id="dept" class="com.atguigu.spring5.bean.Dept">
    </bean>

2.2.8 注入数组或集合

2.2.8.1 注入数组
<bean id="" class="" >
    <!--注入数组类型的属性值-->
	<property name="array" >
        <!--为数组属性赋值-->
    	<array>
        	<value>1</value>
            <value>2</value>
        </array>
    </property>
</bean>
2.2.8.2 注入List集合
<bean id="" class="" >
    <!--注入List集合类型的属性值-->
	<property name="list" >
        <!--为List集合属性赋值-->
    	<list>
        	<value>1</value>
            <value>2</value>
        </list>
    </property>
</bean>
2.2.8.3 注入set集合
<bean id="" class="" >
    <property name="set">
            <set>
                <value>1</value>
                <value>2</value>
            </set>
    </property>
</bean>
2.2.8.4 注入Map集合
<bean id="" class="" >
    <!--注入数组类型的属性值-->
	<property name="map" >
        <!--为数组属性赋值-->
    	<map>
        	<entry key="" value="" ></entry>
            <entry key="" value="" ></entry>
        </map>
    </property>
</bean>
2.2.8.5 在对象中引入list属性
  • 注意:一定要引入util 名称空间

  • <!--第一步:在 spring 配置文件中引入名称空间 util-->
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:util="http://www.springframework.org/schema/util" <!--添加util名称空间-->
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">  <!--添加util名称空间-->
        
    <!--第二步:使用 util 标签完成 list 集合注入提取-->
    <!--把集合注入部分提取出来-->
     <!--1 提取list集合类型属性注入-->
        <util:list id="bookList">
            <value>易筋经</value>
            <value>九阴真经</value>
            <value>九阳神功</value>
        </util:list>
    
     <!--2 提取list集合类型属性注入使用-->
        <bean id="book" class="com.atguigu.spring5.collectiontype.Book" scope="prototype">
            <property name="list" ref="bookList"></property>
        </bean>
    

2.3 为什么使用多个配置文件?

  • 多个配置文件的优势

    • 第一,每个文件的大小比一个文件要小很多。效率高。
    • 第二,避免多人竞争带来的冲突。
  • 如果你的项目有多个模块(相关的功能在一起),一个模块一个配置文件。

    例如,学生考勤模块一个配置文件,学生成绩一个配置文件

  • 多文件的分配方式:

    • 按功能模块,一个模块一个配置文件
    • 按类的功能,数据库相关的配置一个文件配置文件, 做事务的功能一个配置文件, 做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文件所在的目录)(target/classes/),
                      在spring的配置文件中要指定其他文件的位置,需要使用classpath,告诉spring到哪里加载配置文件。
          -->
      
          <!--加载的是文件列表-->
      <!--    <import resource="classpath:ba02/spring-school.xml"></import>-->
      <!--    <import resource="classpath:ba02/spring-student.xml"></import>-->
      
          <!--
              在包含关系的配置文件中,可以使用通配符 * 表示任意字符。
              使用通配符的要求:
                  1. 这些文件包含在一个目录中,目录是必须的,否则无法加载.
                  2. 主文件不能匹配到通配符中.
          -->
          <!--
              后面要有.xml spring-total-total
              注意:主的配置文件名称不能包含在通配符的范围内(不能叫做spring-total.xml)
                  因为会形成死循环.
          -->
          <import resource="classpath:ba02/spring-*.xml"></import>
      </beans>
      
  • 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">
      
          <bean id="student" class="com.bjpowernode.ba02.Student" autowire="byType">
              <property name="name" value="zhangsan"></property>
              <property name="age" value="20"> </property>
          </bean>
      
          <!--两个构造,需要两个参数的有参构造,否则无法创建对象.-->
          <bean id="student1" class="com.bjpowernode.ba02.Student" >
              <constructor-arg name="name" value="lisi"></constructor-arg>
              <constructor-arg name="age" value="20"></constructor-arg>
          </bean>
      
          <!--使用index实现构造注入-->
          <bean id="student2" class="com.bjpowernode.ba02.Student" autowire="byType">
              <constructor-arg index="0" value="wangwu" ></constructor-arg>
              <constructor-arg index="1" value="21"></constructor-arg>
          </bean>
      
      </beans>
      
  • spring-school.xml

    • <?xml version="1.0" encoding="UTF-8"?>
      <beans xmlns="http://www.springframework.org/schema/beans"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <bean id="school" class="com.bjpowernode.ba02.School">
              <property name="name" value="北京大学"></property>
              <property name="type" value="university"></property>
              <property name="address" value="北京市"></property>
          </bean>
      
      </beans>
      

2.4 基于注解的DI

  • 通过注解完成Java对象的创建,属性赋值。

  • 使用注解的步骤:

    • 第一步:加入maven依赖:spring-context,在加入 spring-context 的同时,会间接加入spring-aop的依赖。

      使用注解,必须使用spring-aop依赖。

    • 第二步:在类中加入spring的注解(多个不同功能的注解)

    • 第三步:在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目的位置。

    • 第四步:使用注解创建对象,创建容器ApplicationContext

  • 学习的注解:

    • @Component
    • @Repository
    • @Service
    • @Controller
    • @Value
    • @Autowired
    • @Resource
  • 通过spring的注解完成java对象的创建,属性,代替xml文件。

2.4.1 @Component注解

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"></bean>
 *
 *  spring中和@Component 功能一致,创建对象的注解还有:
 *      1. @Repository(使用在持久层(Dao)上的注解):放在dao的实现类上面,表示创建dao对象,dao对象是能访问数据库的。
 *      2. @Service(用在业务层类(Service)的上面):放在service实现类上面,创建service对象,service对象做业务处理,可以有事务功能。
 *      3. @Controller(用在控制器(Controller)上面):放在控制器(处理器)类的上面,创建控制器对象的,控制器对象能够接收用户提交的参数,显示请求的处理结果。
 *      以上三个注解的使用语法和@Component语法一样,都能创建对象。但是这三个注解还有额外的功能。
 *      同时,以上三个注解是给项目的对象分层的。
 *
 */
//使用value,指定对象的名称。在注解中,如果只写value,value可以省略不写。
//@Component(value="student")

//省略value——最常用的语法。
//@Component("student")

//不指定对象名称,由Spring提供默认名称:类名的首字母小写。
@Component
public class Student {
    private String name;
    private Integer age;

    public Student(){
        System.out.println("无参数构造方法执行");
    }
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }

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

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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"
       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。
                xmlns:context="http://www.springframework.org/schema/context"
    -->
    <!--context前缀是为了区分component来自于哪个文件-->
    <context:component-scan base-package="com.bjpowernode.ba01"></context:component-scan>

    <!--指定多个包的三种方式-->
    <!--第一种方式:使用多次组件扫描器,指定不同的包-->
    <context:component-scan base-package="com.bjpowernode.ba01"></context:component-scan>
    <context:component-scan base-package="com.bjpowernode.ba02"></context:component-scan>

    <!--使用分隔符( ; 或 , )分隔多个包名-->
    <context:component-scan base-package="com.bjpowernode.ba01;com.bjpowernode.ba02"></context:component-scan>

    <!--第三种方式:指定父包-->
    <context:component-scan base-package="com.bjpowernode"></context:component-scan>
</beans>

2.4.3 简单类型的属性赋值

  • 简单类型的属性赋值,使用:@Value
  • 属性:
    • value:是String类型,表示简单类型的属性值。
  • 位置:
    • 在属性定义的上面,无需set方法,推荐使用。
    • 在set方法的上面。
//这个注解是必须的,为了让Spring配置文件找到这个类。然后创建对象
@Component("student")
public class Student {
    @Value("张飞")
    private String name;
    @Value(value="29")
    private Integer age;
    
    @Value("关羽")
    public void setName(String name){
        this.name = name;
    }
}

2.4.4 引用类型的属性赋值

  • 引用类型的赋值

    • byType

      • @Autowired
        
    • byName

      • @Autowired
        @Qualifier("name")
        
/**
     * 引用类型的赋值
     *  @Autowired: Spring框架提供的注解,实现引用类型的赋值。
     *              spring中通过这个注解给引用类型赋值,使用的是自动注入原理。支持byName,byType
     * 这个注解默认使用byType自动注入。
     *
     *      属性:
     *          required,一个boolean类型的属性,默认为true
     *              required = true,表示引用类型赋值失败,程序报错,并终止执行。
     *              required = false,表示引用类型若赋值失败,程序正常执行,不报错,只是属性为null。
     *          实际开发中建议使用true。
     *      位置:
     *          1. 在属性定义的上面,无需set方法,推荐使用。
     *          2. 在set方法的上面。
     *
     *  如果使用byName方式,需要做的是:
     *      1. 在属性上面加入@Autowired
     *      2. 在属性上面加入@Qualifier(value="bean的id"):表示使用指定名称的bean完成赋值。
     *      3. 两者没有先后顺序,不过建议先写@Autowired。
     */
    @Autowired
    @Qualifier("mySchool")
    private School school;

2.4.5 JDK注解Resource自动注入(自动注入只支持引用类型)

/**
     * 引用类型的赋值
     *  @Resource: 来自JDK的注解,spring框架提供了对这个注解的功能支持,可以使用它给引用类型赋值,
     *              使用的也是自动注入原理,支持byName,byType
     *              默认byName
     *          位置和@Autowired一样。
     *
     *  @Resource 只使用byName方式,需要增加一个属性: name
     *      name的值是bean的id(名称)
     */
    //原理:默认是byName,先使用byName自动注入,如果byName赋值失败,再使用byType
	//Spring更建议使用@Autowired和@Qualifier注解。
	//@Resource(name="mySchool")
    @Resource
    private School school;

2.5 xml配置文件和注解的对比

  • 经常修改使用xml配置文件
  • 不经常修改使用注解
  • 注解方便、快捷、直观
  • xml配置文件繁琐,看不了源代码,但是解耦合。

2.6 完全注解开发

  1. 创建配置类,替代 xml 配置文件;

    @Configuration //作为配置类,替代 xml 配置文件
    @ComponentScan(basePackages = {"com.atguigu"})
    public class SpringConfig {
        
    }
    
  2. 编写测试类。

    @Test
    public void testService2() {
        //加载配置类
        ApplicationContext ac
            = new AnnotationConfigApplicationContext(SpringConfig.class);
        UserService userService = context.getBean("userService", UserService.class);
        System.out.println(userService);
        userService.add();
    }
    

2.7 junit

  • junit:单元测试,一个工具类库,做测试方法使用的。

    • 单元:指定的是方法, 一个类中有很多方法,一个方法称为单元。
  • 使用单元测试

    • 第一步:需要加入junit依赖。

      • <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        
    • 第二步:创建测试作用的类,叫做测试类,在src/test/java目录中创建类

    • 第三步:创建测试方法

      • 1)public 方法
        2)没有返回值 void 
        3)方法名称自定义,建议名称是test + 你要测试方法名称
        4)方法没有参数
        5)方法的上面加入 @Test ,这样的方法是可以单独执行的。 不用使用main方法。
        

第三章 AOP面向切面编程

3.1 动态代理

  • jdk动态代理:

    • 使用 jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。

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

  • cglib动态代理:

    • 第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。

      要求目标类不能是final的, 方法也不能是final的。

  • 动态代理的作用:

    • 在目标类源代码不改变的情况下,增加功能;
    • 减少代码的重复;
    • 专注业务逻辑代码;
    • 解耦合,让你的业务功能和日志,事务非业务功能分离。
  • JDK动态代理实现步骤:

    • 第一步:创建目标类,SomeServiceImpl目标类,给它的doSome,doOther增加输出时间、事务的功能。
    • 第二步:创建InvocationHandler接口的实现类,在这个类中实现给目标方法增加功能。
      • 第三步:使用JDK中的类Proxy,创建代理对象,实现创建对象的功能。

3.1.1 创建service接口和实现类

package com.bjpowernode.service;

public interface SomeService {
    void doSome();
    void doOther();
}
package com.bjpowernode.service.impl;

import com.bjpowernode.service.SomeService;
import java.util.Date;

/**
* SomeService实现类
* 不改变该类的条件下扩展该类的方法的功能,使用动态代理
*/

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

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

3.1.2 实现InvocationHandler接口

package com.bjpowernode.handler;

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

public class MyInvocationHandler implements InvocationHandler {
    /**
     * 目标对象
     */
    private Object target;
    public MyInvocationHandler(Object target){
        this.target = target;
    }
    /**
     * 通过代理对象执行方法时,会调用invoke方法。
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //返回值
        Object res = null;
        //只让doOther方法执行额外功能
        String methodName = method.getName();
        if("doOther".equals(methodName)){
            System.out.println(new Date());
            //执行目标类的方法,通过method实现。
            res = method.invoke(target, args);
            System.out.println("执行事务");
        }else{
            res = method.invoke(target, args);
        }
        //返回目标方法的结果
        return res;
    }
}

3.1.3 测试

package com.bjpowernode.test;

import com.bjpowernode.handler.MyInvocationHandler;
import com.bjpowernode.service.SomeService;
import com.bjpowernode.service.impl.SomeServiceImpl;

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

public class MyApp {
    public static void main(String[] args) {
        //使用jdk的Proxy创建代理对象。
        //1. 创建目标对象
        SomeService target = new SomeServiceImpl();
        //2. 创建InvocationHandler
        InvocationHandler handler = new MyInvocationHandler(target);
        //3. 创建代理
        SomeService proxy = (SomeService) Proxy.newProxyInstance(
            target.getClass().getClassLoader(), 
            target.getClass().getInterfaces(), 
            handler);
        //4. 通过代理执行方法,会调用handler中的invoke方法
        //doSome和doOther会被传为method
        proxy.doSome();
        System.out.println("================");
        proxy.doOther();
    }
}

3.2 AOP简介

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

    面向切面编程是从动态角度考虑程序运行过程;AOP 底层,就是采用动态代理模式实现的

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

    • Aspect:
      • 切面,给你的目标类增加额外的功能,就是切面。像上面用的日志、事务都是切面。
      • 切面的特点:一般都是非业务逻辑,可以独立使用。
    • Orient:
      • 面向,对着
    • Programming:
      • 编程
  • OOP:面向对象编程。

  • 怎么理解面向切面编程?

    • 在分析项目功能时,找出切面;
    • 合理的安排切面的执行时间(在目标方法前,还是在目标方法后);
    • 合理的安排切面执行的位置,在哪个类,哪个方法执行增强功能。
  • AOP采用了两种代理:

    • JDK 的动态代理
    • CGLIB 的动态代理
  • AOP可以看作动态代理的规范化与标准化。

  • 面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志、缓存等。 若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。

  • 例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但它们的代码量所占比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大大干扰了主业务逻辑—转账。

3.2 AOP术语

  • 切面(Aspect)
    • 切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。
    • 把通知应用到切入点的过程。
  • 连接点(JoinPoint)
    • 连接业务方法和切面的位置;类里面哪些方法可以被增强,这些方法就成为连接点。
    • 简而言之:某类中的业务方法;
    • 连接点一般是1个方法。
  • 切入点(Pointcut)
    • 切入点指声明的一个或多个连接点的集合,实际上被真正增强的方法称为切入点。
    • 通过切入点指定一组方法。
    • 被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
  • 目标对象(Target)
    • 目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。
    • 上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不 被增强,也就无所谓目标不目标了。
  • 通知(Advice)
    • 通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。Advice 也叫增强。上例中的 MyInvocationHandler 就可以理解为是一种通知。
    • 换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
    • 简言之:切面功能执行的时间点。

切入点定义切入的位置,通知定义切入的时间。

3.3 切面的关键要素

  • 切面的功能代码,切面干什么
  • 切面的执行位置,使用Pointcut表示切面执行的位置
  • 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

3.4 AOP的实现

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

  • aop的技术实现框架:

    • spring:
      • spring在内部实现了aop规范,能完成面向切面编程(AOP);
      • spring主要在事务处理时使用aop;
      • 我们项目开发中很少使用spring的aop实现,因为spring的aop比较笨重。
  • aspectJ:

    • 一个开源的专门做aop的框架。
    • spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。
    • aspectJ框架实现aop有两种方式:
      • 使用xml的配置文件 : 配置全局事务
      • 使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解。

3. 5 AspectJ对AOP的实现

3.5.1 AspectJ的通知类型/执行时间

  • AspectJ 中常用的通知有五种类型,体现在五个不同的添加在切面的注解:
    (1)前置通知
    (2)后置通知
    (3)环绕通知
    (4)异常通知
    (5)最终通知

  • 切面的执行时间:

    • 这个执行时间在规范中叫做Advice(通知,增强)
    • 在aspectj框架中使用注解表示,也可以使用xml配置文件中的标签
      • @Before
      • @AfterReturning
      • @Around
      • @AfterThrowing
      • @After
  • 切面执行的位置:

    • 切入点表达式

3.5.2 切入点表达式

切入点表达式原型:

execution(modifiers-pattern? ret-type-pattern
		  declaring-type-pattern? name-pattern(param-pattern)
		  throws-pattern? )
问号前面的变量表示是可选的,可以没有。
modifiers-pattern:修饰符(public/private)
ret-type-pattern:返回值类型
declaring-type-pattern:包名、类名
name-pattern:方法名
param-pattern:参数
throws-pattern:抛出异常

execution(访问权限 **方法返回值** **方法声明(参数)** 异常类型)
(1)切入点表达式作用:知道对哪个类里面的哪个方法进行增强 
(2)语法结构: execution([权限修饰符] [返回类型] [类全路径] [方法名称]([参数列表]) )
(3)例子如下:
    例 1:对 com.atguigu.dao.BookDao 类里面的 add 进行增强
		execution(* com.atguigu.dao.BookDao.add(..))
 	例 2:对 com.atguigu.dao.BookDao 类里面的所有的方法进行增强
		execution(* com.atguigu.dao.BookDao.* (..))
    例 3:对 com.atguigu.dao 包里面所有类,类里面所有方法进行增强
		execution(* com.atguigu.dao.*.* (..))

  • *:0至多个字符

  • …:用在方法的参数中,表示任意多个参数;用在包名后,表示当前包及其子包路径。

  • +:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类。

    execution(* …service..*(…))
    可以表示:
    com.service.impl
    com.bjpowrnode.service.impl
    cn.crm.bjpowernode.service

3.5.3 AspectJ项目创建步骤

  • 为什么使用AOP?

    • 目的是在不改变原来的类的代码的前提下,要完成对该类功能的扩展。
  • 使用AspectJ框架实现AOP的步骤:

    • 第一步:新建maven项目

    • 第二步:加入依赖:

      • spring依赖
      • aspectJ依赖
      • junit依赖
    • 第三步:创建目标类:接口和它的实现类

      要做的是给类中的方法增加功能。

    • 第四步:创建切面类:普通类。

      • 在类的上面加入@Aspect

      • 在类中定义方法,方法就是切面要执行的功能代码

        在方法的上面加入aspectj中的通知注解,例如@Before

        有需要指定切入点表达式execution()。

    • 第五步:创建spring的配置文件,声明对象,把对象交给容器统一管理。

      声明对象可以使用注解或者xml配置文件。

      • 声明目标对象
      • 声明切面类对象
      • 声明aspectj框架中的自动代理生成器标签。
        • 自动代理生成器:用来完成代理对象的自动创建功能。
    • 第六步:创建测试类,从Spring容器中获取目标对象(实际上是代理对象)

      通过代理执行方法,实现aop的功能增强。

3.5.4 @Aspect和前置通知@Before

  • @Aspect:是aspectj框架中的注解。

    • 作用:表示当前的类是切面类。
  • 切面类:用来给业务方法增加功能的类,在这个类中,有切面的功能代码。

    • 可以在Aspect类中定义方法,方法是实现切面功能的。
  • @Before:前置通知注解

    • 属性:value,是切入点表达式,表示切面的功能执行的位置。

    • 位置:在方法的上面添加注解。

    • 特点:

      • 在目标方法之前先执行
      • 不会改变目标方法的执行结果
      • 不会影响目标方法的执行。
    • 前置通知方法的定义要求:

      • 公共方法public

      • 方法没有返回值(void)

      • 方法名称自定义

      • 方法可以有参数,也可以没有参数。

        如果有参数,参数不是自定义的,有几个参数类型可以使用。

  • SomeService接口

    public interface SomeService{
        void doSome(String name, Integer age);
    }
    
  • SomeServiceImpl实现类

    public class SomeServiceImpl implements SomeService{
        void doSome(Sring name, Integer age){
            System.out.println("doSome方法执行了!");
        }
    }
    
  • Aspect切面类

    @Aspect
    public class MyAspect{
        //当execution找不到时,不会生成代理对象,也不会功能增强。
        
        //完整的@Before
        //@Before表示时间,value表示位置,方法体表示功能。
    	//@Before
        //(value = "execution(public void com.bjpowernode.ba01.SomeServiceImpl.doSome(String, Integer))")
        
        //除去访问权限
        //@Before(value = "execution(void com.bjpowernode.ba01.SomeServiceImpl.doSome(String, Integer))")
        
        //@Before(value = "execution(void *..SomeServiceImpl.doSome(String, Integer))")
        
        //@Before(value = "execution(* *..SomeServiceImpl.do*(..))")
        
        //do开头的所有方法
        @Before(value = "execution(* do*(..))")
        public void myBefore(){
            //就是切面要执行的功能代码
            System.out.println("前置通知:在目标方法之前输出执行时间:" + new Date());
        }
    }
    
  • applicationContext.xml配置文件

    • <beans>
          <!--把对象交给spring容器,由spring容器统一创建,管理对象-->
          <!--声明目标对象-->
          <bean id="someService" class="com.bjpowernode.ba01.SomeServiceImpl"></bean>
          <!--声明切面类对象-->
          <bean id="myAspect" class="com.bjpowernode.ba01.MyAspect"></bean>
          <!--
      		声明自动代理生成器:
      			使用aspectj框架内部的功能,创建目标对象的代理对象。
      		创建代理对象是在内存中实现的,修改目标对象的内存中的结构,创建为代理对象。
      		所以目标对象就是被修改后的代理对象。
      
      		aspectj-autoproxy:会把spring容器中的所有的目标对象,一次性都生成代理对象。
      	-->
      	<aop:aspectj-autoproxy />
      </beans>
      
  • MyTest01测试类

    • public class MyTest01{
          @Test
          public void test01(){
              String config = "applicationContext.xml";
              Application ac = new ClassPathXmlApplicationContext(config);
              //从容器中获取目标对象(经过aspectj框架的处理,实际上已经是代理对象了。)
              SomeService proxy = (SomeService) ac.getBean("someService");
              //通过代理对象执行方法,增强功能
              proxy.doSome("lisi", 20);
          }
      }
      

3.5.5 JoinPoint

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

3.5.6 @AfterReturning 后置通知-注解有Returning属性

  • @AfterReturning 后置通知:

    • 后置通知方法的定义要求:

      • 公共方法public
      • 方法没有返回值(void)
      • 方法名称自定义
      • 方法有参数,推荐使用Object,参数名自定义
    • 属性:

      • value:切入点表达式
      • returning:自定义变量,表示目标方法的返回值。自定义变量名必须和通知方法的形参名相同。
    • 位置:在方法定义的上面。

    • 特点:

      • 在目标方法之后执行。

      • 能够获取到目标方法的返回值,可以根据返回值做不同的处理功能。

        • Object res = doOther();
          
      • 可以修改返回值。

    • 后置通知的执行:

      • Object res = doOther();
        myAfterReturning(res);
        
    • @Aspect
      public class MyAspect{
          @AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))", returning = "res")
          //JoinPoint必须在第一位。
          public void myAfterReturning(JoinPoint jp, Object res){
             //Object res:是目标方法执行后的返回值,根据返回值做你的切面的功能处理。
              System.out.println("后置通知,方法的定义:" + jp.getSignature());
              System.out.println("后置通知,在目标方法之后执行,获取的返回值是:" + res);
              if("xxx".equals(res)){
                  //做一些功能
              }else{
                  //做其他功能。
              }
              
              //修改目标方法的返回值
              if(res != null){
                  res = "yyy";//不会变
                  //如果是引用类型,可以改变引用类型的内容。
              }
          }
      }
      

3.5.7 @Around 环绕通知

  • @Around环绕通知
    • 环绕通知方法定义格式:
      • public
      • 必须有一个返回值,推荐使用Object
      • 方法名称自定义
      • 方法有参数,并且是固定的——ProceedingJoinPoint
    • 属性:
      • value:切入点表达式
    • 位置:在方法定义的上方。
    • 特点:
      • 它是功能最强的通知。
      • 在目标方法的前和后都能增强功能。
      • 能控制目标方法是否被调用执行
      • 修改原来的目标方法的执行结果,影响最后的调用结果。
    • 环绕通知,等同于JDK的动态代理,InvocationHandler接口
    • 参数:
      • ProceedingJoinPoint 就等同于JDK动态代理中的method
      • 作用:执行目标方法
      • ProceedingJoinPoint接口继承了JoinPoint接口,可以获得参数,从而控制方法的执行。
    • 返回值:就是目标方法的执行结果,可以被修改。
    • 环绕通知经常做事务,在目标方法开启事务,执行目标方法,在目标方法之后提交事务。
@Aspect
public class MyAspect{
    @Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
    public Object myAround(ProceedingJoinPoint pjp) throws Throwable{
        //获取第一个参数
        Object[] args = pjp.getArgs();
        String name = "";
        if(args != null && args.length > 1){
            name = (String)args[0];
        }
        //实现环绕通知的功能
        Object res = null;
        //目标方法执行之前加入功能
        System.out.println("环绕通知:目标方法执行之前加入功能");
        
        //1. 目标方法调用
        //该方法会有异常,要上抛
        if("zhangsan".equals(name)){
            res = pjp.proceed();//相当于method.invoke();	相当于Object res = doFirst();
        }
        //目标方法执行之后加入功能
        System.out.println("环绕通知:目标方法执行之后加入功能");
        //2. 在目标方法的前或后加入功能
        
        //修改目标方法的执行结果,影响方法最后的调用结果
        if(res != null){
            res = "xxx";
        }
        //3. 返回目标方法的执行结果
        return res;
    }
       
}

3.5 9 @AfterThrowing异常通知(了解)

  • AfterThrowing 异常通知

    • 异常通知方法的定义要求:

      • 公共方法public

      • 方法没有返回值(void)

      • 方法名称自定义

      • 方法可以有参数,也可以没有参数。可以添加一个Exception类型的参数

      • @AfterThrowing(value = "...", throwing = "ex")
        public void myAfterThrowing(Exception ex){
            //输出异常信息
            System.out.println(ex.getMessage());
        }
        
      • 如果有参数,参数不是自定义的,有几个参数类型可以使用。

    • 在目标方法抛出异常后执行。

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

    • 在效果上相当于一个try…catch语句。目标方法的方法体在try语句块中,而切面方法的方法体放在了catch子句中。

3.5.9 @After最终通知

  • @After最终通知
    • 最终通知方法的定义要求:
      • 公共方法public
      • 方法没有返回值(void)
      • 方法名称自定义
      • 方法可以有参数,也可以没有参数。如果有,就是JoinPoint。
    • 属性:value 切入点表达式
    • 特点:
      • 总是会执行,即使发生异常了也会执行。相当于try…catch中的finally。
      • 在目标方法之后执行。
      • 一般做资源清理工作。

3.5.10 @Pointcut

  • @Pointcut

    • 不是通知注解,只是一个辅助注解。

    • 定义和管理切入点。

    • 属性:value 切入点表达式

    • 位置:在自定义方法的上面。

    • 特点:

      • 当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名。
      • 其他的通知中,value属性就可以使用这个方法名称,代替切入点表达式。
    • 如果在一个Aspect类中有很多通知,并且很多通知都有一个重复的切入点表达式,就可以对切入点表达式进行定义。

      • @After(value = "mypt()")
           public void myAfter(){
               System.out.println("最终通知,总是会被执行的,可以做程序最后要做的工作,例如资源回收,内存释放");
           }
         
           /**
            * @Pointcut: 定义和管理切入点
            *     属性:value 切入点表达式
            *     位置:在自定义的方法上面。
            * 作用:@Pointcut定义在方法的上面, 这个方法的名称就是切入点的别名
            * 其他的通知注解的value属性可以使用方法名称,表示切入点。
            */
           @Pointcut(value = "execution(* *..SomeServiceImpl.doThird(..))")
           private void mypt(){
               //无需代码
           }
        

3.5.11 指定cglib动态代理

<!--有接口,spring使用jdk动态代理-->
<!--没有接口,spring会自动使用cglib动态代理-->
<!--cglib动态代理效率稍微高一点-->
<!--如果有接口,还是想用cglib动态代理,可以使用proxy-target-class="true"告诉框架,使用cglib动态代理。-->
<aop:aspectj-autoproxy proxy-target-class="true" />

第四章 Spring集成MyBatis

4.1 概述

  • Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。

  • 使用的技术:ioc

  • 主要有以下对象:

    • 数据源dataSource。就是保存数据库连接信息的对象。

      在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;

    • 生成sqlSession对象的sqlSessionFactory;

    • Dao接口的实现类对象。

  • MyBatis使用步骤:

    • 第一步:定义dao接口 ,StudentDao

    • 第二步:定义mapper文件 StudentDao.xml

    • 第三步:定义mybatis的主配置文件 mybatis.xml

    • 第四步:创建dao的代理对象

      • StudentDao dao = SqlSession.getMapper(StudentDao.class);
        List<Student> students  = dao.selectStudents();
        
  • 想要使用dao对象,需要使用getMapper()方法,怎么能使用getMapper()方法呢?需要哪些条件?

    • 获取SqlSession对象, 需要使用SqlSessionFactory的openSession()方法。
    • 创建SqlSessionFactory对象。 通过读取mybatis的主配置文件,能创建SqlSessionFactory对象。
  • SqlSessionFactory-------->SqlSession-------->dao
    Factory的创建需要读取主配置文件

4.2 Spring集成MyBatis创建项目的流程

  • 第一步:新建Maven项目
  • 第二步:加入Maven依赖
    • spring依赖
    • mybatis依赖
    • mysql依赖
    • spring的事务依赖
    • mybatis和spring集成的依赖。 mybatis官方提供,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象的。
  • 第三步:创建实体类
  • 第四步:创建dao接口和mapper文件
  • 第五步:创建mybatis主配置文件
  • 第六步:创建Service接口和实现类,属性是dao
  • 第七步:创建spring的配置文件,声明mybatis的对象交给spring创建
    • 数据源:dataSource
    • SqlSessionFactory
    • Dao对象
    • 声明自定义的service
  • 第八步:创建测试类,获取Service对象,通过service调用dao完成对数据库的访问。

4.2.1 添加maven依赖

<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.3.15</version>
    </dependency>
    <!--spring事务依赖-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.3.15</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.3.15</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.9</version>
    </dependency>
    <!--mybatis和spring集成依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>2.0.7</version>
    </dependency>
    <!--mysql依赖-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.28</version>
    </dependency>
    <!--阿里公司的数据库连接池druid依赖-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.2.8</version>
    </dependency>
  </dependencies>

将src/main/java目录下的xml,properties文件也会被扫描到classes文件夹中

<build>
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在目录-->
        <includes><!--包括目录下的.properties,.xml文件都会被扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
    <!--可以添加指定jdk版本的插件-->
  </build>

曾经的mybatis-config模板

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

    <!--引入外部独立的资源文件-->
    <properties resource="jdbc.properties"/>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">

                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>

            </dataSource>
        </environment>
    </environments>

    <!--  关联xml配置的文件  -->
    <mappers>
        <mapper resource="SqlMapper.xml"></mapper>
    </mappers>
</configuration>

mybatis-mapper模板

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

</mapper>

4.2.2 新建实体类Student

package com.bjpowernode.bean;

public class Student {
    private Integer id;
    private Integer age;
    private String name;
    private String email;


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

    public Student() {
    }

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

    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;
    }
}

4.2.3 新建Dao接口和sql映射文件(Mapper)

Dao接口

package com.bjpowernode.dao;

import com.bjpowernode.bean.Student;

import java.util.List;

public interface StudentDao {
    int insertStudent(Student student);
    List<Student> selectStudents();
}

Sql映射文集:(Mapper)

<?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" parameterType="Student">
        insert into t_student values(#{id}, #{age}, #{name}, #{email});
    </insert>
    <select id="selectStudents" resultType="Student">
        select * from t_student order by id desc;
    </select>

</mapper>

4.2.4 新建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>
    <!--设置mybatis输出日志-->
    <!--settings标签必须在最上面-->
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

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

    <!--  关联xml配置的文件  -->
    <mappers>
        <!--name:包名,这个包中的所有mapper.xml一次都能加载-->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

4.2.5 新建Service接口和实现类

StudentService interface

package com.bjpowernode.service;

import com.bjpowernode.bean.Student;

import java.util.List;

public interface StudentService {
    int addStudent(Student student);
    List<Student> queryStudent();
}

实现类

package com.bjpowernode.service.impl;

import com.bjpowernode.bean.Student;
import com.bjpowernode.dao.StudentDao;
import com.bjpowernode.service.StudentService;

import java.util.List;

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

    @Override
    public int addStudent(Student student) {
        return studentDao.insertStudent(student);
    }

    @Override
    public List<Student> queryStudent() {
        return studentDao.selectStudents();
    }
}

4.2.6 新建spring配置文件

数据源DataSource
	<!--声明数据源DataSource,作用是连接数据库,代替mybatis-config中的连接数据库信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库的信息-->
        <!--
            使用属性配置文件中的数据,语法${}
        -->
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>
SqlSessionFactoryBean类
	<!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--将数据库连接池dataSource通过set注入赋值给了SqlSessionFactoryBean类的dataSource属性-->
        <property name="dataSource" ref="dataSource"></property>
        <!--mybatis主配置文件的位置-->
        <!--configLocation是Resource类型,读取配置文件-->
        <!--它的赋值,使用value,指定文件的路径,在spring配置文件中使用classpath:表示其他配置文件的位置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>
创建dao对象
<!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)-->
    <!--这个类在内部调用getMapper()生成每个dao接口的代理对象-->
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
       <!--指定SqlSessionFactory对象的id-->
       <!--String类型使用value-->
       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
       <!--指定包名,包名是dao接口所在的包名-->
       <!--MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次getMapper()方法,得到每个接口的dao对象-->
       <!--创建好的dao对象放入到spring容器中-->
       <!--value可以分隔多个包-->
       <!--dao对象的默认名称是:接口名首字母小写-->
       <!--String简单类型使用value-->
       <property name="basePackage" value="com.bjpowernode.dao"></property>
   </bean>
service对象
	<!--声明service-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
        <!--studentDao是引用类型对象,是上面创建好的-->
        <property name="studentDao" ref="studentDao"></property>
    </bean>
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"></context:property-placeholder>

    <!--声明数据源DataSource,作用是连接数据库,代替mybatis-config中的连接数据库信息-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
          init-method="init" destroy-method="close">
        <!--set注入给DruidDataSource提供连接数据库的信息-->
        <!--
            使用属性配置文件中的数据,语法${}
        -->
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>

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

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)-->
    <!--这个类在内部调用getMapper()生成每个dao接口的代理对象-->
   <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
       <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
       <!--指定包名,包名是dao接口所在的包名-->
       <!--MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行一次getMapper()方法,得到每个接口的dao对象-->
       <!--创建好的dao对象放入到spring容器中-->
       <!--value可以分隔多个包-->
       <!--dao对象的默认名称是:接口名首字母小写-->
       <property name="basePackage" value="com.bjpowernode.dao"></property>
   </bean>

    <!--声明service-->
    <bean id="studentService" class="com.bjpowernode.service.impl.StudentServiceImpl">
        <!--studentDao是引用类型对象,是上面创建好的-->
        <property name="studentDao" ref="studentDao"></property>
    </bean>
</beans>

4.2.7 jdbc.properties

jdbc.url=jdbc:mysql://localhost:3306/exercise?serverTimezone=UTC
jdbc.username=root
jdbc.password=******
jdbc.maxActive=20

4.2.8 新建测试类

package com.bjpowernode;

import com.bjpowernode.bean.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 Test01 {
    @Test
    public void testInsertStudent(){
        //mybatis和spring整合在一起时,事务是自动提交的,无需执行SqlSession.commit()
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) ac.getBean("studentService");
        Student student = new Student(2, 24, "李四", "456@qq.com");
        int num = studentService.addStudent(student);
        System.out.println(num);
    }
    @Test
    public void testSelectStudents(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentService studentService = (StudentService) ac.getBean("studentService");
        List<Student> students = studentService.queryStudent();
        for (Student student:
             students) {
            System.out.println(student);
        }
    }
}

第五章 Spring事务

5.1 关于事务的几个问题

5.1.1 什么是事务?

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

5.1.2 在什么时候想到使用事务?

  • 当程序员的操作涉及多张表,或者是多个sql语句执行,需要保证这些语句都是成功才能完成我的功能,或者都失败,保证操作是符合要求的。

5.1.3 事务应该放到哪里?

在java代码中写程序,控制事务,此时事务应该放在那里呢?

  • 事务应该放在service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。

5.1.4 常规事务怎么处理?

  • 通常使用JDBC或MyBatis访问数据库怎么处理事务?
    • jdbc访问数据库,处理事务 Connection conn ; conn.commit(); conn.rollback();
    • mybatis访问数据库,处理事务, SqlSession.commit(); SqlSession.rollback();
    • hibernate访问数据库,处理事务, Session.commit(); Session.rollback();

5.1.5 常规方法处理事务不足之处

  • 第三个问题处理事务方式有何不足?
    • 不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理。
    • 掌握多种数据库中事务的处理逻辑。什么时候提交事务,什么时候回滚事务。
    • 处理事务的多种方法。
    • 总结:
      • 不同的数据库,有不同的事务处理的机制,对象,方法。
      • 需要学习很多东西,很杂乱。

5.1.6 怎么解决不足?

  • spring提供了一种处理事务的统一模型, 能使用统一步骤或方式完成多种不同数据库访问技术的事务处理。
  • 使用spring的事务处理机制,可以完成mybatis访问数据库的事务处理。
  • 使用spring的事务处理机制,可以完成hibernate访问数据库的事务处理。

5.2 Spring处理事务概述

  • spring处理事务的模型,使用的步骤都是固定的。把事务使用的信息提供给spring就可以了。
    • 事务内部提交,回滚事务,使用的是事务管理器对象,代替你完成commit,rollback。
    • 事务管理器是一个接口和他的众多实现类。
    • 事务管理器接口:PlatformTransactionManager,定义了事务重要方法 commit,rollback;
      实现类:spring把每一种数据库访问技术对应的事务处理类都创建好了。
    • mybatis访问数据库—spring创建好的是DataSourceTransactionManager
      hibernate访问数据库----spring创建的是HibernateTransactionManager

5.2.1 怎么使用Spring处理事务?

  • 你需要告诉spring 你用是哪种数据库的访问技术。

  • 怎么告诉spring程序员所使用的技术呢?

    • 声明数据库访问技术对应的事务管理器实现类,在spring的配置文件中使用“bean”标签声明就可以了
      例如,你要使用mybatis访问数据库,你应该在xml配置文件中配置:

      <bean id=“xxx" class="...DataSourceTransactionManager"></bean>
      

5.2.2 怎么说明需要事务的类型?

5.2.2.1 事务的隔离级别:
  • DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
  • READ_UNCOMMITTED:读未提交。未解决任何并发问题。
  • READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
  • REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
  • SERIALIZABLE:串行化。不存在并发问题。
5.2.2.2 事务的超时时间(一般不配置,因为影响的因素太多):
  • 表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚。
  • 单位是秒,整数值,默认是 -1。(-1表示没有最长时间,这个方法能执行多长时间,就执行多长时间)
5.2.2.3 事务的传播行为(重要)
  • 控制业务方法是不是有事务,是什么样的事务。

  • 7个传播行为,表示你的业务方法调用时,事务在方法之间是如何使用的:

    • PROPAGATION_REQUIRED

      指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。

      这种传播行为是最常见的选择,也是Spring默认的事务传播行为。

    • PROPAGATION_SUPPORTS

      指定的方法支持当前事务,但若当前没有事务,也可以以非事务方法执行。

      (比如查询操作,有没有事务没有都可以)

    • PROPAGATION_REQUIRES_NEW

      总是创建一个新事务,若当前存在事务,就将当前事务挂起(暂停),直到新事务执行完毕。

    • PROPAGATION_MANDATORY

    • PROPAGATION_NESTED

    • PROPAGATION_NEVER

    • PROPAGATION_NOT_SUPPORTED

5.2.3 提交事务,回滚事务的时机?

  • 当你的业务方法执行成功,没有异常抛出,那么当方法执行完毕时,spring会在方法执行后提交事务。

    • 调用事务管理器的commit方法。
  • 当你的业务方法抛出非受检异常(运行时异常)或ERROR,spring执行回滚。

    • 调用事务管理器的rollback方法。

    • 非受检异常(运行时异常)的定义:

      • RuntimeException 和它的子类都是运行时异常;例如:NullPointException , NumberFormatException。
  • 当你的业务方法抛出非运行时异常,主要是受检异常(编译时异常)时,提交事务。

    • 受检异常:在你写代码中,必须处理的异常。例如IOException, SQLException。

5.2.4 总结Spring事务

  • 管理事务的是事务管理和它的实现类
  • spring的事务是一个统一模型。
    • 指定要使用的事务管理器实现类,使用“bean”标签;
    • 指定哪些类,哪些方法需要加入事务的功能;
    • 指定方法需要的隔离级别,传播行为,超时。
  • 你需要告诉spring,你的项目中类信息,方法的名称,方法的事务传播行为。

5.3 Spring框架处理事务

5.3.1 注解(适合中小项目)

  • spring框架自己用aop实现给业务方法增加事务的功能,使用@Transactional注解增加事务。

  • @Transactional注解是spring框架自己的注解,放在public方法的上面,表示当前方法具有事务。

  • 可以给注解的属性赋值,表示具体的隔离级别,传播行为,异常信息等等。

  • @Transactional 的所有可选属性如下所示:

    • propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为 Propagation.REQUIRED。
    • isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为 Isolation.DEFAULT。
    • readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值 为 false。
    • timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为 -1,即没有时限。在实际业务开发中一般不设置。
    • rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有 一个异常类时,可以不使用数组。
    • rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。 当然,若只有一个异常类时,可以不使用数组。
    • noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若 只有一个异常类时,可以不使用数组。
    • noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空 数组。当然,若只有一个异常类时,可以不使用数组。
  • 需要注意的是,@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。 若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。

  • 使用@Transactional的步骤:

    • 第一步:需要声明事务管理器对象;

      • <bean id="xx" class="DataSourceTransactionManager">
        
    • 第二步:开启事务注解驱动, 告诉spring框架,我要使用注解的方式管理事务。spring将使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。

      • 第三步:在你的方法上加入@Transactional。
  • spring给业务方法加入事务:

    spring通过使用aop的环绕通知,在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务。

    @Around("你要增加的事务功能的业务方法名称")
    Object myAround(){
    	开启事务,spring给你开启
    	try{
    		buy(1001,10);
    		spring的事务管理器.commit();
    	}catch(Exception e){
    		spring的事务管理器.rollback();
    	}	 
    }
    

5.3.2 xml配置文件(适合大型项目)

5.4 程序举例

  • 做事务的环境项目。
  • 实现步骤:
    • 第一步:新建Maven项目
    • 第二步:加入Maven依赖
      • spring依赖
        • spring-context
        • spring-tx
        • spring-jdbc
      • mybatis依赖
        • mybatis
        • mybatis-spring
      • mysql依赖
        • mysql-connector-java
      • spring的事务依赖
      • mybatis和spring集成的依赖。 mybatis官方提供,用来在spring项目中创建mybatis的SqlSessionFactory,dao对象的。
    • 第三步:创建实体类
      • Sale,Goods
    • 第四步:创建dao接口和mapper文件
      • SaleDao接口,GoodsDao接口
      • SaleDao.xml,GoodsDao.xml
    • 第五步:创建mybatis主配置文件
    • 第六步:创建Service接口和实现类,属性是saleDao,goodsDao
    • 第七步:创建spring的配置文件,声明mybatis的对象交给spring创建
      • 数据源:dataSource
      • SqlSessionFactory
      • Dao对象
      • 声明自定义的service
    • 第八步:创建测试类,获取Service对象,通过service调用dao完成对数据库的访问。

5.3.1 创建数据库表

  • t_sale表:
    • id,销售id
    • gid,goods的id
    • nums,销售的goods数量
  • t_goods表:
    • id,goods的id
    • name,goods的name
    • amount,goods的库存
    • price,goods的价格
create table t_sale(
id int primary key auto_increment,
gid int,
nums int,
foreign key(gid) references t_goods(id)
);
create table t_goods(
id int primary key auto_increment,
name varchar(100),
amount int,
price double(8,2)
);
insert into t_goods(id, name, amount, price) values(1001, '笔记本电脑', 100, 5000);
insert into t_goods(id, name, amount, price) values(1002, '手机', 50, 3000);

5.3.2 添加maven依赖

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

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

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.3.15</version>
    </dependency>

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.15</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.9</version>
    </dependency>

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.7</version>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.28</version>
    </dependency>

    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.8</version>
    </dependency>
</dependencies>
<build>
   <resources>
     <resource>
       <directory>src/main/java</directory>
       <includes>
         <include>**/*.properties</include>
         <include>**/*.xml</include>
       </includes>
       <filtering>false</filtering>
     </resource>
   </resources>
 </build>

5.3.3 创建实体类

Sale类:

package com.bjpowernode.bean;

public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;

    @Override
    public String toString() {
        return "Sale{" +
                "id=" + id +
                ", gid=" + gid +
                ", nums=" + nums +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public Integer getGid() {
        return gid;
    }

    public void setGid(Integer gid) {
        this.gid = gid;
    }

    public Integer getNums() {
        return nums;
    }

    public void setNums(Integer nums) {
        this.nums = nums;
    }
}

Goods类:

package com.bjpowernode.bean;

public class Goods {
    private Integer id;
    private String name;
    private Integer amount;
    private Float price;

    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", amount=" + amount +
                ", price=" + price +
                '}';
    }

    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 Integer getAmount() {
        return amount;
    }

    public void setAmount(Integer amount) {
        this.amount = amount;
    }

    public Float getPrice() {
        return price;
    }

    public void setPrice(Float price) {
        this.price = price;
    }
}

5.3.4 创建Dao接口和mapper文件

GoodsDao:

package com.bjpowernode.dao;

import com.bjpowernode.bean.Goods;
import com.bjpowernode.bean.Sale;

public interface GoodsDao {
    /**
     * 更新商品库存
     * @param sale 销售记录
     * @return 更新条数
     */
    int updateGoods(Sale sale);

    /**
     * 根据id查询商品信息
     * @param id 商品的id
     * @return 商品信息
     */
    Goods selectGoods(Integer id);
}

GoodsDao.xml

<?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.GoodsDao">
    <update id="updateGoods" parameterType="com.bjpowernode.bean.Sale">
        update t_goods set amount = amount - #{nums} where id = #{gid};
    </update>

    <select id="selectGoods" resultType="com.bjpowernode.bean.Goods">
        select id, name, amount, price from t_goods where id = #{id};
    </select>
</mapper>

SaleDao接口:

package com.bjpowernode.dao;

import com.bjpowernode.bean.Sale;

public interface SaleDao {
    /**
     * 增加销售记录
     * @param sale 销售记录
     * @return 增加条数
     */
    int insertSale(Sale sale);
}

SaleDao.xml

<?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.SaleDao">
    <insert id="insertSale" parameterType="com.bjpowernode.bean.Sale">
        insert into t_sale(gid, nums) values(#{gid}, #{nums});
    </insert>
</mapper>

5.3.5 创建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>
        <!--别名:实体类所在包名-->
        <package name="com.bjpowernode.bean"/>
    </typeAliases>

    <!--  关联xml配置的文件  -->
    <mappers>
        <!--name:包名,这个包中的所有mapper.xml一次都能加载-->
        <package name="com.bjpowernode.dao"/>
    </mappers>
</configuration>

5.3.6 创建Service接口和实现类

package com.bjpowernode.service;

import com.bjpowernode.bean.Goods;
import com.bjpowernode.bean.Sale;

public interface SaleService {
    /**
     * 购买商品
     * @param sale 商品种类(同时添加到销售记录,并且更新商品库存)
     */
    void buy(Sale sale);

    /**
     * 展示商品信息
     * @param id 商品id
     * @return 商品信息
     */
    Goods selectGoods(Integer id);

}
package com.bjpowernode.service.impl;

import ...;

public class SaleServiceImpl implements SaleService {
    SaleDao saleDao = null;
    GoodsDao goodsDao = null;

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

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

    /**
     * 事务管理
     * propagation:事务传播类型
     * isolation:事务隔离级别
     * readOnly:是否只读
     * rollbackFor:对于指定异常一定回滚,是一个数组。
     *      处理逻辑:spring框架会首先检查方法抛出的异常是不是在rollback的属性值中,
     *              如果在,那么不管是什么类型的异常,一定会回滚,不管类型。
     *              如果不在,spring会判断异常是不是RuntimeException,如果是,一定回滚。
     *
     * @param sale 商品种类(同时添加到销售记录,并且更新商品库存)
     */
//    @Transactional(
//            propagation = Propagation.REQUIRED,
//            isolation = Isolation.DEFAULT,
//            readOnly = false,
//            rollbackFor = {
//                    NotEnoughException.class
//            }
//    )

    //使用的是事务控制的默认值,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT,默认抛出运行时异常,回滚事务。
    @Transactional
    @Override
    public void buy(Sale sale) {
        //为了表现回滚异常,添加操作放前面。
        //添加销售记录
        saleDao.insertSale(sale);
        Goods goods = goodsDao.selectGoods(sale.getGid());
        if(goods.getAmount() - sale.getNums() < 0){
            throw new NotEnoughException("商品库存不足");
        }
        //当出现异常后,下列代码无法执行,会导致出现销售记录错误。
        //更新库存
        goodsDao.updateGoods(sale);
        System.out.println("销售成功");
    }

    @Override
    public Goods selectGoods(Integer id) {
        return goodsDao.selectGoods(id);
    }
}

5.3.7 创建spring主配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">

    <!--
        把数据库的配置信息,写在一个独立的文件,便于修改数据库的配置信息
        spring知道jdbc.properties文件的位置
    -->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--声明DataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"
    init-method="init" destroy-method="close">
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="maxActive" value="${jdbc.maxActive}"></property>
    </bean>

    <!--声明SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="configLocation" value="classpath:mybatis-config.xml"></property>
    </bean>

    <!--获得dao对象,内部会创建dao对象,并自动声明变量名,不需要id-->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.bjpowernode.dao"></property>
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
    </bean>

    <!--创建service-->
    <bean id="service" class="com.bjpowernode.service.impl.SaleServiceImpl">
        <property name="goodsDao" ref="goodsDao"></property>
        <property name="saleDao" ref="saleDao"></property>
    </bean>

    <!--使用Spring的事务处理-->
    <!--1. 声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库——指定数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 开启事务注解驱动,告诉spring使用注解管理事务,创建代理对象-->
    <!--transaction-manager:事务管理器的id-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

5.3.8 创建测试类

package com.bjpowernode.test;

import com.bjpowernode.bean.Sale;
import com.bjpowernode.service.SaleService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test01 {
    @Test
    public void testService(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        SaleService service = (SaleService) ac.getBean("service");
        Sale sale = new Sale();
        sale.setGid(1001);
        sale.setNums(100);
        service.buy(sale);
    }
}

5.3.9 自定义异常

package com.bjpowernode.exception;

public class NotEnoughException extends RuntimeException{
    public NotEnoughException(String s){
        System.out.println(s);
    }
}

第六章 使用AspectJ的AOP配置管理事务

  • 适合大型项目,有很多类、方法,需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类、方法需要的事务。这种方式业务方法和事务配置完全分离。

  • 实现步骤:都是在xml配置文件中完成的。

    • 第一步:要使用的是aspectj框架,需要加入依赖

      <!--aspectj依赖-->
      <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.3.15</version>
      </dependency>
      
    • 第二步:声明事务管理器对象

      <bean id="xx" class="DataSourceTransactionManager">
      
    • 第三步:声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)

    • 第四步:配置aop:指定哪些哪类要创建代理。

6.1 添加依赖(pom.xml)

<!--aspectj依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.3.15</version>
</dependency>

6.2 applicationContext.xml

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

<!--2. 声明业务方法的事务属性(隔离级别,传播行为,超时时间-->
<!--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.exception.NotEnoughException">					</tx:method>
        <!--使用通配符,指定很多方法,属性不写表示是默认值-->
        <tx:method name="add*" />
        <!--指定修改方法-->
        <tx:method name="modify*" />
        <!--指定查询方法,有search,query等等-->
        <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="servicePointcut" expression="execution(* *..service..*.*(..))" />
    <!--配置增强器:关联advice和pointcut
		advice-ref:通知,上面tx:advice那里的配置
		pointcut-ref:切入点表达式的id
	-->
    <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePointcut" />
</aop:config>

第七章 web项目中使用容器对象

  1. 普通测试项目做的是javase项目有main方法的,执行代码是执行main方法的,在main里面创建的容器对象

    ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
    
  2. web项目是在tomcat服务器上运行的。 tomcat一起动,项目一直运行的。

    需求:
    web项目中容器对象只需要创建一次, 把容器对象放入到全局作用域ServletContext中。

  3. 怎么实现?
    使用监听器:当全局作用域对象被创建时,创建容器,存入ServletContext。

  4. 监听器的作用:
    1)创建容器对象,执行 ApplicationContext ctx = new ClassPathXmlApplicationContext(“applicationContext.xml”);
    2)把容器对象放入到ServletContext, ServletContext.setAttribute(key,ctx)

    监听器可以自己创建,也可以使用框架中提供好的ContextLoaderListener

  5. 实现:

    • 为了使用监听器对象,需要在pom.xml中添加依赖:

      • <dependency>
        	<groupId>org.springframework</groupId>
            <artifactId>spring-web</artifactId>
            <version>5.3.15</version>
        </dependency>
        
    • 在web.xml文件中配置监听器

      • <!--注册监听器ContextLoaderListener
        	监听器对象被创建后,会读取/WEB-INF/applicationContext.xml
        	为什么读取文件:因为在监听器中要创建ApplicationContext对象,需要加载配置文件
        	/WEB-INF/applicationContext.xml就是监听器默认读取的spring配置文件路径。
        	这样就需要路径固定,名称固定。
        	所以一般不使用默认配置。
        
        	可以修改默认路径,使用context-param重新指定文件的位置。
        
        	配置监听器:目的是创建容器对象,创建了容器对象,就能把applicationContext.xml配置文件中的所有对象都创建好。
        	用户发起请求就可以直接使用对象了。
        -->
        <context-param>
        	<!--contextConfigLocation:表示配置文件的路径-->
            <param-name>contextConfigLocation</param-name>
            <!--自定义配置文件的路径-->
            <param-value>classpath:applicationContext.xml</param-value>
        </context-param>
        <listener>
        	<listener-class>...ContextLoaderListener</listener-class>
        </listener>
        
    • 在servlet中:

      • public class RegisterServlet extends HttpServlet{
            protected void doPost(HttpServletRequest request, HttpServletResponse response){
                String ... = request.getParameter("...");
                ...
                //创建spring的容器对象,不使用ApplicationContext类,使用WebApplicationContext类
                WebApplicationContext wac = null;
                //获取ServletContext中的容器对象,创建好的容器对象,拿来就用。
                //String key = WebApplicationContext....
                /*
                Object attr = getServletContext().getAttribute(key);
                if(attr != null){
                    wac = (WebApplicationContext)wac;
                }*/
                //使用框架中的方法,获取容器对象
                ServletContext context = getServletContext();
                wac = WebApplicationContextUtils.getRequiredWebApplicationContext(context);
                
                //合并
                WebApplicationContext applicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext);
            }
        }
        
  6. WebApplication和Application的关系:

    • private WebApplicationContext context;
      public interface WebApplicationContext extends ApplicationContext;
      
    • ApplicationContext:javase项目中使用的容器对象
      WebApplicationContext:web项目中的使用的容器对象
      
      把创建的容器对象,放入到全局作用域
       key: WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
             WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
       value:this.context
      
       servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
      
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值