Spring

94 阅读20分钟

前言

框架

多个可重用模块的集合,形成一个某个领域的整体解决方案,半成品软件,就像毛胚房一样要自己装修

Spring框架:

  • 是个容器框架,容器:可以管理所有组件(类)
  • Spring为简化企业级开发而生,使用Spring,JavaBean就可以实现很多以前要靠EJB才能实现的功能,同样的功能在EJB中要通过繁琐的配置和复杂的代码才能够实现,而在Spring中却非常的优雅和简洁
  • 核心关注IOC(DI)和AOP

Spring的优点:

  • 非侵入式:开发中的对象可以不依赖Spring的API,java代码该咋写还咋写
  • 控制反转:IOC一一Inversion of Control,翻转资源获取方向,把自己创建资源,向环境索取资源变成环境将资源准备好,我们享受资源注入,依赖注入:DI一一Dependency Injection,反转控制(IOC)最经典的实现
  • 面向切面编程:Aspect Oriented Programming一一AOP,面向切面是对面向对象的补充,面向对象是纵向继承机制,面向切面就可以将面向对象做不到的功能,如事务的管理的功能,给横向抽取出来,取到一个切面当中,在不修改源代码的基础上增强代码功能
  • 容器:Spring IOC是一个容器,因为它包含并且管理应用对象的生命周期,屏蔽了组件创建过程中的细节
  • 组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用,在Spring中可以使用XML和Java注解组合这些对象
  • 声明式:很多以前要编写代码才能实现的功能,现在只需要申明需求即可由框架实现
  • 一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方类库(实际上Spring自身也提供了表述层的SpringMVC和持久层的Spring JDBC)

Spring Framework

Spring基础框架,可以视为Spring基础设施,基本上任何其他Spring项目都是以Spring Framework为基础的

功能模块功能介绍
Core Container核心容器,在Spring环境下使用任何功能都必须基于IOC容器
AOP&Aspects面向切面编程
Testing提供了对junit或TestNG测试框架的整合
Data Access/Integration提供了对数据访问/集成的功能。
Spring MVC提供了面向Web应用程序的集成功能。

IOC

IOC:Inversion of Control,翻译过来是反转控制

IOC的思想

  • 传统资源的获取方式

    自己做饭,买菜洗菜,磨刀,切菜,炒菜全过程参与,对其中的细节自己都得掌握

  • 反转控制方式获取资源

    点外卖,吃,省时省力,不必关心饭是怎么做出来的

    反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向一一改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率,这种行为也称为查找的被动形式

DI

Dependency Injection,依赖注入,是反转控制(IOC)最经典的实现

组件以一些提前约定好的方式(例如setter方法),接收来自容器的资源注入

总结一下:IOC是一种反转控制的思想,而DI就是对这种思想的具体实现

那IOC容器在Spring中是如何实现的呢?

实际上就是一系列API

Spring的IOC容器就是IOC思想的一个落地的产品实现,IOC容器中管理的组件也叫做bean,在创建bean之前,首先需要创建IOC容器,Spring提供了IOC容器的两种实现方式

  1. BeanFactory

    这是IOC容器的基本实现,是Spring内部使用的接口,面向Spring本身,不提供给开发人员使用

  2. ApplicationContext

    BeanFactory的子接口,提供了更多的高级特性,面向Spring的使用者,几乎所有场合用的都是这个,而不是底层的BeanFactory

ApplicationContext的主要实现类

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的XML格式的配置文件创建IOC容器对象
FileSystemXmlApplicationContext通过文件系统路径读取XML格式的配置文件创建IOC容器对象
ConfigurableApplicationContextApplicationContext的子接口,包含一些扩展方法refresh()和close(),让ApplicationContext具有启动,关闭和刷新上下文的能力
WebApplicationContext专门为Web应用准备,基于Web环境创建IOC容器对象,并将对象引入存入到ServletContext域中

Spring是用来管理组件(对象)的,也叫做bean,具体如何管理呢?

  1. 基于XML管理
  2. 基于注解管理

基于XML进行管理

  1. 在maven管理模块里,添加依赖

    <dependencies>
        <!--基于Maven依赖的传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
  2. 创建一个学生类,让spring管理

    package com.pan.sp.pojo;
    
    public class Student {
        public void sayHello(){
            System.out.println("Hello");
        }
    }
    
  3. 在resources目录下创建Spring配置文件applicationContxt.xml(这个名字理论上可以随便取,但以后ssm整合后有规范)

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--bean标签:配置一个bean对象,交给IOC管理-->
        <!--id:唯一标识 class:要管理的类的全限定类名-->
        <bean id="student" class="com.pan.sp.pojo.Student"/>
    
    </beans>
    
  4. 创建测试类

    package com.pan.sp.test;
    
    import com.pan.sp.pojo.Student;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class TestStudent {
        @Test
        public void test(){
            // 获取IOC容器
            ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
            // 获取IOC容器中的Bean对象,根据bean的id获取
            Student student = (Student) ioc.getBean("student");
            student.sayHello();
        }
    }
    

获取Bean的三种方式

这里Student实现了Person接口

package com.pan.sp.test;

import com.pan.sp.pojo.Person;
import com.pan.sp.pojo.Student;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class IocByXmlTest {
    @Test
    public void testIOC(){
        // 获取IOC容器
        ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-ioc.xml");

        // 获取bean的三种方式

        // 1 根据bean的id获取
        Student student1 = (Student) ioc.getBean("student01");
        // 2 根据bean的类型获取
        Student student2 = ioc.getBean(Student.class);// 前提是xml中只有一个Student类型的bean标签,否则报错
        // 3 根据bean的id和类型获取
        Student student3 = ioc.getBean("student01", Student.class);

        System.out.println(student1);
        System.out.println(student2);
        System.out.println(student3);
        // 注意,以后第二种方式获取bean用的最多,因为xml文件里同种类型的bean一般只有一个,想多个就用scope属性设置,而不是将同种类型的bean设置多个

        // 获取bean对象后可以用接口或父类接收,面向接口编程,但前提是bean是唯一的(xml的不能有多种该接口实现类的bean)
        // 这里Student implements Person
        // 总之如果一个接口有多个实现类,这些实现类都配置了bean,根据接口类型就不可以获取bean了
        Person person = ioc.getBean(Person.class);
        System.out.println(person);
    }
}

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="student01" class="com.pan.sp.pojo.Student"/>

</beans>

依赖注入之setter注入

依赖注入是IOC(被动获取)的一种实现方式

现在有一个Student类,实现了Person接口,且Student类里有sname,sid,age,gender属性和对应的getter和setter方法,构造器等

applicationContext.xml,在property标签里给Student的pojo类设置属性,通过属性的set方法赋值

<?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="student01" class="com.pan.sp.pojo.Student">
        <!--name里填属性名,value里填属性值-->
        <property name="sid" value="1001"></property>
        <property name="sname" value="张三"></property>
        <property name="age" value="13"></property>
        <!--想赋值为null用这种方式-->
        <property name="gender">
            <null></null>
        </property>
    </bean>

</beans>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person student = ioc.getBean(Person.class);// 可以用接口获取到Student,前提看上面的例子
    System.out.println(student);// Student{sid=1001, sname='张三', age=13, gender='男'}
}

注意,想赋值<property name="sname" value="<张三>"></property>会发现在xml中报错,得写成<property name="sname" value="&lt;张三&gt;"></property>,或者写成这样

<property name="sname">
    <value><![CDATA[<张三>]]></value>
</property>
<!--

<![CDATA[<这里面的内容会原样解析,是个特殊标签>]]>

-->

依赖注入之构造器注入

<?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="student01" class="com.pan.sp.pojo.Student">
        <constructor-arg value="1002"></constructor-arg>
        <constructor-arg value="李四"></constructor-arg>
        <!--也可以指定匹配构造器上的哪个属性-->
        <constructor-arg value="18" name="age"></constructor-arg>
        <constructor-arg value="女"></constructor-arg>
    </bean>

</beans>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person student = ioc.getBean(Person.class);
    System.out.println(student);// Student{sid=1002, sname='李四', age=18, gender='女'}
}

为"类类型"属性赋值

假如有个学生类,里面有个属性是Clazz类型的,这个类型是班级类,该怎么给班级属性赋值呢?注意该属性的值是个对象

班级类

package com.pan.sp.pojo;

public class Clazz {
    private Integer cid;
    private String cname;

    @Override
    public String toString() {
        return "Clazz{" +
                "cid=" + cid +
                ", cname='" + cname + '\'' +
                '}';
    }

    public Integer getCid() {
        return cid;
    }

    public void setCid(Integer cid) {
        this.cid = cid;
    }

    public String getCname() {
        return cname;
    }

    public void setCname(String cname) {
        this.cname = cname;
    }

    public Clazz() {
    }

    public Clazz(Integer cid, String cname) {
        this.cid = cid;
        this.cname = cname;
    }
}

学生类

package com.pan.sp.pojo;

public class Student implements Person {
    private Integer sid;
    private String sname;
    private Integer age;
    private String gender;

    private Clazz clazz; // 班级类型

    @Override
    public String toString() {
        return "Student{" +
                "sid=" + sid +
                ", sname='" + sname + '\'' +
                ", age=" + age +
                ", gender='" + gender + '\'' +
                ", clazz=" + clazz +
                '}';
    }

    public Clazz getClazz() {
        return clazz;
    }

    public void setClazz(Clazz clazz) {
        this.clazz = clazz;
    }

    public Integer getSid() {
        return sid;
    }

    public void setSid(Integer sid) {
        this.sid = sid;
    }

    public String getSname() {
        return sname;
    }

    public void setSname(String sname) {
        this.sname = sname;
    }

    public Integer getAge() {
        return age;
    }

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

    public String getGender() {
        return gender;
    }

    public void setGender(String gender) {
        this.gender = gender;
    }

    public Student() {
    }

    public Student(Integer sid, String sname, Integer age, String gender) {
        this.sid = sid;
        this.sname = sname;
        this.age = age;
        this.gender = gender;
    }
}

方式一

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="student01" class="com.pan.sp.pojo.Student">
        <property name="sid" value="1004"/>
        <property name="sname" value="王五"/>
        <property name="age" value="18"/>
        <property name="gender" value="男"/>
        <!--ref:引用ioc容器中的某个bean的id-->
        <property name="clazz" ref="clazz01"/>
    </bean>

    <bean id="clazz01" class="com.pan.sp.pojo.Clazz">
        <property name="cid" value="888"/>
        <property name="cname" value="高三一班"/>
    </bean>

</beans>

测试

@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person student = ioc.getBean(Person.class);
    System.out.println(student);
    //Student{sid=1004, sname='王五', age=18, gender='男', clazz=Clazz{cid=888, cname='高三一班'}}
}

方式二

级联的方式

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="age" value="18"/>
    <property name="gender" value="男"/>
    <property name="clazz" ref="clazz01"/>
    
    <!--相当于有了班级对象,采用点"."的形式修改了一下-->
    <property name="clazz.cname" value="三班"/>
    <property name="clazz.cid" value="666"/>

</bean>

<bean id="clazz01" class="com.pan.sp.pojo.Clazz">
    <property name="cid" value="888"/>
    <property name="cname" value="高三一班"/>
</bean>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person student = ioc.getBean(Person.class);
    System.out.println(student);
    //Student{sid=1004, sname='王五', age=18, gender='男', clazz=Clazz{cid=666, cname='三班'}}
}

除了上面这种也可以在Student类里new一个班级出来,总之级联的方式必须要有班级对象才行,才能用点的方式赋值

方式三

内部bean的方式,套娃

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="age" value="18"/>
    <property name="gender" value="男"/>
    <property name="clazz">
        <!--内部bean,只能在当前加bean的内部使用,不能直接通过IOC容器获取-->
        <bean id="clazzInner" class="com.pan.sp.pojo.Clazz">
            <property name="cid" value="111"/>
            <property name="cname" value="高三三班"/>
        </bean>
    </property>
</bean>
@Test
public void testDI() {
ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
Person student = ioc.getBean(Person.class);
System.out.println(student);
//Student{sid=1004, sname='王五', age=18, gender='男', clazz=Clazz{cid=111, cname='高三三班'}}
}

为数组类型的属性赋值

假如现在学生有一个爱好属性,是一个字符串数组private String[] hobby;,该怎么赋值?

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="age" value="18"/>
    <property name="gender" value="男"/>
    <property name="clazz">
        <bean id="clazzInner" class="com.pan.sp.pojo.Clazz">
            <property name="cid" value="111"/>
            <property name="cname" value="高三三班"/>
        </bean>
    </property>

    <!--给数组赋值-->
    <property name="hobby">
        <array>
            <!--这里的value标签根据类型选择,如换成ref标签可以引用其他bean-->
            <value></value>
            <value></value>
            <value></value>
        </array>
    </property>
</bean>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Person student = ioc.getBean(Person.class);
    System.out.println(student);
    //Student{sid=1004, sname='王五', age=18, gender='男', clazz=Clazz{cid=111, cname='高三三班'}, hobby=[吃, 喝, 睡]}
}

给list集合类型的属性赋值

假如Clazz类里有个属性stus是个学生Student类型的集合,该怎么赋值?

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="age" value="18"/>
    <property name="gender" value="男"/>
    <property name="clazz">
        <bean id="clazzInner" class="com.pan.sp.pojo.Clazz">
            <property name="cid" value="111"/>
            <property name="cname" value="高三三班"/>
        </bean>
    </property>

    <property name="hobby">
        <array>
            <value></value>
            <value></value>
            <value></value>
        </array>
    </property>
</bean>

<!--为list类型的属性赋值-->
<bean id="clazz01" class="com.pan.sp.pojo.Clazz">
    <property name="cid" value="67676"/>
    <property name="cname" value="学前班1"/>
    <property name="stus">
        <list>
            <ref bean="student01"/>
        </list>
    </property>
</bean>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Clazz clazz = ioc.getBean(Clazz.class);
    System.out.println(clazz);
    //Clazz{cid=67676, cname='学前班1', stus=[Student{sid=1004, sname='王五', age=18, gender='男',
    //      clazz=Clazz{cid=111, cname='高三三班', stus=null}, hobby=[吃, 喝, 睡]}]}
}

除了将集合写在property标签内,还可以写一个集合类型的bean,在property上引用

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="age" value="18"/>
    <property name="gender" value="男"/>
    <property name="clazz">
        <bean id="clazzInner" class="com.pan.sp.pojo.Clazz">
            <property name="cid" value="111"/>
            <property name="cname" value="高三三班"/>
        </bean>
    </property>

    <property name="hobby">
        <array>
            <value></value>
            <value></value>
            <value></value>
        </array>
    </property>
</bean>

<bean id="clazz01" class="com.pan.sp.pojo.Clazz">
    <property name="cid" value="67676"/>
    <property name="cname" value="学前班1"/>
    <property name="stus" ref="stuList"/>
</bean>

<!--集合类型的bean,需要使用util的约束-->
<util:list id="stuList">
    <ref bean="student01"/>
</util:list>

测试结果和上面的一样

给map集合类型的属性赋值

假如现在学生类里还有个属性,是个map集合类型的private Map<String,Teacher> teacherMap;,如何给map集合赋值

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="teacherMap">
        <map>
            <entry key="语文老师" value-ref="teacher01"/>
        </map>
    </property>
</bean>

<bean name="teacher01" class="com.pan.sp.pojo.Teacher">
    <property name="tid" value="999"/>
    <property name="tname" value="王老师"/>
</bean>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Clazz clazz = ioc.getBean(Clazz.class);
    System.out.println(clazz);
    //Clazz{cid=67676, cname='学前班1', stus=[Student{sid=1004, sname='王五', age=null, gender='null',
    // teacherMap={语文老师=Teacher{tid=999, tname='王老师'}},
    // clazz=null, hobby=null}]}
}

同list一样,也可以设置map类型的bean

<bean id="student01" class="com.pan.sp.pojo.Student">
    <property name="sid" value="1004"/>
    <property name="sname" value="王五"/>
    <property name="teacherMap" ref="teacherMap"/>
</bean>

<util:map id="teacherMap">
    <entry key="语文老师" value-ref="teacher01"/>
</util:map>

<bean name="teacher01" class="com.pan.sp.pojo.Teacher">
    <property name="tid" value="999"/>
    <property name="tname" value="王老师"/>
</bean>

p命名空间

除了标签套标签的方式,也可以用p:来设置属性,注意输入p:后要导入约束

<bean id="student02" class="com.pan.sp.pojo.Student" p:sid="98765" p:sname="tom"/>
@Test
public void testDI() {
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student student = ioc.getBean("student02", Student.class);
    System.out.println(student);
    //Student{sid=98765, sname='tom', age=null, gender='null', teacherMap=null, clazz=null, hobby=null}
}

用Spring管理一个数据源对象

  1. 先在pom.xml引入依赖

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
    
        <groupId>com.pan.spring</groupId>
        <artifactId>Spring01</artifactId>
        <version>1.0-SNAPSHOT</version>
        <packaging>jar</packaging>
    
        <dependencies>
            <!--基于Maven依赖的传递性,导入spring-context依赖即可导入当前所需所有jar包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.3.23</version>
            </dependency>
            <!--单元测试-->
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.13.2</version>
                <scope>test</scope>
            </dependency>
            <!--德鲁伊连接池-->
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid</artifactId>
                <version>1.0.31</version>
            </dependency>
            <!--jdbc驱动-->
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <version>8.0.30</version>
            </dependency>
        </dependencies>
    
    </project>
    
  2. 写一个jdbc.properties配置文件

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=root
    
  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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--bean标签:配置一个bean对象,交给IOC管理-->
        <!--id:唯一标识 class:要管理的类的全限定类名-->
    
        <!--获取JDBC配置文件-->
        <bean id="property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <property name="location" value="jdbc.properties"/>
        </bean>
    
        <!--现在想让ioc容器管理数据源对象-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
    
    </beans>
    
  4. 测试

    package com.pan.sp.test;
    
    import com.alibaba.druid.pool.DruidDataSource;
    import org.junit.Test;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.sql.SQLException;
    
    public class TestDataSource {
        @Test
        public void testDS() throws SQLException {
            ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
            DruidDataSource dataSource = ioc.getBean(DruidDataSource.class);
            System.out.println(dataSource.getConnection());
            // 输出com.mysql.cj.jdbc.ConnectionImpl@6ab7a896,成功
        }
    }
    

Bean的作用域

默认情况下,配置的bean标签搞出来的实例都是单例的,多数情况使用的都是单例

但是也可以通过配置bean标签的scope属性来指定bean的作用范围

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

在WebApplicationContext环境下还有另外两个作用域(不常用):

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

Bean的生命周期

bean的对象通过反射加工厂模式创造,反射创建对象的时候用的是无参构造器

  1. bean的对象的创建(调用无参构造器)
  2. 给bean的对象设置属性(依赖注入,其实就是在调用setXxx方法)
  3. bean对象初始化前的操作(由bean的后置处理器负责)
  4. bean对象初始化(需要在配置bean时指定初始化方法)
  5. bean对象初始化后的操作(由bean的后置处理器负责)
  6. bean对象就绪
  7. bean对象销毁(需要在配置bean时指定销毁方法)
  8. IOC关闭

注意,3和4的后置处理器怎么用:先创建一个类,实现BeanPostProcessor接口(可以实现接口里的两个方法),分别是bean对象初始化前后的操作,方法返回值是个bean对象,写完这个类之后就可以在配置文件里写个bean标签将该类配置进去,此时这个后置处理器就作用于该IOC容器中的所有Bean了

怎么指定初始化和销毁方法?需要在bean标签上写destroy-method="" init-method=""两个属性,还要在该类里写两个方法,将方法名写到属性值上,这两个方法就成了初始化和销毁方法了,在初始化和销毁时被调用

注意,ApplicationContext接口没有提供close方法,是在其子接口ConfigurableApplicationContext中提供的关闭方法,所以ioc要想手动关闭,ioc被创建时就得用有关闭方法的接口接收,调用ioc的close方法后发现bean的销毁方法被执行了,即IOC容器关闭前被销毁

  1. 假如bean是默认的单例,就没有必要在Person student = ioc.getBean(Person.class)的时候创建了,所以单例的bean会提前在创建IOC容器的时候就会被创建ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");,此时bean对象创建、依赖注入、对象初始化都会被执行
  2. 假如将bean标签的scope属性设置为prototype多例时,这时候就只能在Person student = ioc.getBean(Person.class)获取bean的时候创建了,此时bean对象创建、依赖注入、对象初始化都会被执行,多例时销毁的方法就不由IOC容器管理了,即ioc.close()后发现bean的销毁的方法没有被执行

FactoryBean

工厂bean,FactoryBean是一个接口,需要创建一个类实现该接口,其中有三个方法

  1. getObject 通过一个对象交给IOC容器管理
  2. getObjectType 设置所提供对象的类型
  3. isSingleton 是否单例

当把FactoryBean的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理

FactoryBean的实现类配置为bean,比普通工厂更快捷,配置成bean后能直接获取产出的对象

有个学生类Student

package com.pan.sp.pojo;

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

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", 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;
    }

    public Student() {
    }

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

创建一个StudentFactoryBean类

package com.pan.sp.factory;

import com.pan.sp.pojo.Student;
import org.springframework.beans.factory.FactoryBean;

public class StudentFactoryBean implements FactoryBean<Student> {
    @Override
    public Student getObject() throws Exception {
        return new Student();
    }

    @Override
    public Class<?> getObjectType() {
        return Student.class;
    }
    
    @Override
    public boolean isSingleton() {
        return true;
    }
}

给IOC配置文件写的是学生工厂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">

    <!--bean标签:配置一个bean对象,交给IOC管理-->
    <!--id:唯一标识 class:要管理的类的全限定类名-->
    <bean id="studentFactory" class="com.pan.sp.factory.StudentFactoryBean"/>

</beans>

测试

@Test
public void test(){
    ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
    Student stu = ioc.getBean(Student.class);
    System.out.println(stu);// Student{name='null', age=null}
}

基于xml的自动装配

自动装配: 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类型属性赋值

假如现在有三层架构,controller调用service,service调用dao,里面的各层的对象只申明不创建,交给ioc容器管理

package com.pan.sp.controller;

import com.pan.sp.service.StudentService;

public class StudentController {
    private StudentService studentService;// 只申明不创建,要交给ioc容器管理

    public StudentService getStudentService() {
        return studentService;
    }

    public void setStudentService(StudentService studentService) {
        this.studentService = studentService;
    }

    // 调用保存学生信息方法
    public void saveStudent() {
        studentService.saveStudent();
    }
}

// 三层架构每层的代码都差不多,不一一列举了
<?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="studentController" class="com.pan.sp.controller.StudentController">
        <property name="studentService" ref="studentServiceImpl"/>
    </bean>
    <bean id="studentServiceImpl" class="com.pan.sp.service.impl.StudentServiceImpl">
        <property name="studentDao" ref="studentDaoImpl"/>
    </bean>
    <bean id="studentDaoImpl" class="com.pan.sp.dao.impl.StudentDaoImpl"/>

</beans>
package com.pan.sp.test;

import com.pan.sp.controller.StudentController;
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestStudent {
    @Test
    public void test(){
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        StudentController studentController = ioc.getBean(StudentController.class);
        studentController.saveStudent();
    }
}

会发现在controller里可以一层一层成功调用到dao层的方法

现在如何设置成自动装配呢?

可以通过bean标签中的autowire属性设置自动装配的策略

autowire属性的值:

  1. default
  2. no 和default一样,表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值
  3. byType 根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值(用的最多
    • 若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值
    • 若通过类型找到了多个类型匹配的bean,此时会抛出异常:NoUniqueBeanDefinitionException
    • 总结:当使用byType 实现自动装配时,IOC容器中有且只有一个类型匹配的bean能够为属性赋值
  4. byName 将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值
    • 当类型匹配的bean有多个时,此时可以使用byName实现自动装配

byType的方式改一下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="studentController" class="com.pan.sp.controller.StudentController" autowire="byType"/>
    <bean id="studentServiceImpl" class="com.pan.sp.service.impl.StudentServiceImpl" autowire="byType"/>
    <bean id="studentDaoImpl" class="com.pan.sp.dao.impl.StudentDaoImpl"/>

</beans>

byName 的方式改一下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="studentController" class="com.pan.sp.controller.StudentController" autowire="byName"/>
    <bean id="studentService" class="com.pan.sp.service.impl.StudentServiceImpl" autowire="byName"/>
    <bean id="studentDao" class="com.pan.sp.dao.impl.StudentDaoImpl"/>

</beans>

基于注解管理Bean

注解和xml两种管理方式都会用到,注解可以标记在自己写的类上面,但是第三方jar包里的类就只能用xml的形式了,二者都有用处

注解与扫描

和XML配置文件一样,注解本身并不执行,注解本身仅仅只是做一个标记,具体的功能是框架检测到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

Spring为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注解进行后续操作。

标识组件的常用注解

  • @Component:将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Repository:将类标识为持久层组件

通过查看源码我们得知,@Controller、@Service、@Repository这三个注解只是在@Component注解的基础上起了三个新的名字。对于Spring使用lOC容器管理这些组件来说没有区别。所以@Controller、@Service、@Repository这三个注解只是给开发人员看的,让我们能够便于分辨组件的作用 注意:虽然它们本质上一样,但是为了代码的可读性,为了程序结构严谨我们肯定不能随便胡乱标记。

注意,加了注解后需要在xml配置文件中对组件进行扫描

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

    <!--扫描组件-->
    <context:component-scan base-package="com.pan.sp"/>

</beans>

扫描组件标签里可以写排除扫描的内容(为啥排除,因为后面springmvc管理控制层,spring管理控制层以外的组件)

<context:component-scan base-package="com.pan.sp">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

context:exclude-filter:排除扫描 type:设置排除扫描的方式 type="annotation或assignable"

annotation根据注解排除,expression里写注解的全类名

assignable根据类的类型排除,expression里写类的全类名

<context:component-scan base-package="com.pan.sp" use-default-filters="false">
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Repository"/>
</context:component-scan>

context:include-filter:包含扫描

需要在context:component-scan标签里设置use-default-filters="false",所设置的包下所有的类都不需要扫描,此时才可以使用排除扫描

注解Bean的ID

通过注解加扫描配置的bean的id该怎么设置?

通过注解+扫描所配置bean的id,默认值为类的小驼峰,即类名的首字母为小写的结果

想自定义的话写成这样@Service("s1")

基于注解的自动装配

添加**@Autowired**即可自动装配

较于上面的基于xml的自动装配的例子,连set方法也不用写了,只需要加一个Autowired注解

package com.pan.sp.controller;

import com.pan.sp.service.StudentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class StudentController {
    @Autowired
    private StudentService studentService;

    // 调用保存学生信息方法
    public void saveStudent() {
        studentService.saveStudent();
    }
}
// // 三层架构每层的代码都差不多,不一一列举了

别忘了扫描注解

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

    <!--扫描组件-->
    <context:component-scan base-package="com.pan.sp"/>

</beans>

测试结果正常

那注解可以加到哪里呢?

  • 标识在成员变量上,此时不需要设置成员变量的set方法(这种最简单,推荐)
  • 标识在set方法上
  • 标识在为当前成员变量赋值的有参构造上

@Autowired注解的原理

  • 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
  • 若有多个类型匹配的Bean,此时会自动转换为byName的方式实现自动装配的效果,即,将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值
  • 若部byType和byName的方式都无法实现自动装配,即IOC容器中有多个类型匹配的bean,且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NoUniqueBeanDefinitionException,此时可以在要赋值的属性上,添加一个注解 @Qualifier,通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值,@Qualifier("Xxx")

AOP

先引入一个需求,假如现在有一个计算接口,里面有加减乘除4个抽象方法,你写一个计算器类实现了这四个方法,现在要求是想在计算器功能上加一个日志功能,即负责输出计算的那行代码前后的数值,那你只能添加几行print语句输出计算前后的数值,这种解决方式对核心业务功能有干扰,导致程序员在开发核心业务功能时分散了精力,附加功能分散在各个业务功能方法中,不利于统一维护

那该怎么办?只能想着将日志代码抽取出来到父类身上,但解决问题的困难:要抽取的负责日志的代码在方法内部,靠以前把子类中的重复代码抽取到父类的方式没法解决,因为面向对象时纵向继承机制。所以需要引入新的技术。

代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑的代码从目标方法中剥离出来一一解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

相当于在原来的方法上裹了一层代理层,将乱七八糟的不属于核心方法的功能代码放到代理层,将核心代码放到最里面的核心层,假如想调用核心代码可以通过代理层中转一下调用

实际上代理层也是一个类,实现了和目标类一样的接口,不过代理类在类里new了一个目标类对象出来,以后访问的方法都是经过代理类方法里再调用目标类的方法访问,而且在代理类里也可以写附加的代码,也可以写try-catch-finally

静态代理

一对一,一个代理类对应一个目标类,当前这个代理类只能作为目标类的代理,专属的

静态代理确实实现了解耦,但是由于代码都写死了,完全不具备任何的灵活性。就拿日志功能来说,将来其他地方也需要附加日志,那还得再声明更多个静态代理类,那就产生了大量重复的代码,日志功能还是分散的,没有统一管理。

提出进一步的需求:将日志功能集中到一个代理类中,将来有任何日志需求,都通过这一个代理类来实现。 这就需要使用动态代理技术了。

动态代理

会动态生成目标类所对应的代理类

动态代理有两种:

  1. jdk动态代理,要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,生成的类名叫$proxy222
  2. cglib动态代理,最终生成的代理类会继承目标类,并且和目标类在相同的包下

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术。

相关术语

  1. 横切关注点

    从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。

  2. 通知

    每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

    • 前置通知:在被代理的目标方法前执行
    • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
    • 异常通知:在被代理的目标方法异常结束后执行(死于非命)
    • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
    • 环绕通知:使用try-catch-finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
  3. 切面

    封装通知方法的类

  4. 目标

    被代理的目标对象

  5. 代理

    向目标对象应用通知之后创建的代理对象

  6. 连接点

    这也是一个纯逻辑概念,不是语法定义的。把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连 接点。

  7. 切入点

    定位连接点的方式。 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说) 如果把连接点看作数据库中的记录,那么切入点就是查询记录的SQL语句 Spring的AOP技术可以通过切入点定位到特定的连接点 切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件

AOP的作用

简化代码:把方法中固定位置的重复的代码抽取出来,让被取的方法更专注于自己的核心功能,提高内聚性。 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上,被套用了切面逻辑的方法就被切面给增强了。

基于注解的AOP

  • 动态代理(InvocationHandler):JDK原生的实现方式,需要被代理的目标类必须实现接口。因为这个技术要求代理对象和目标对象实现同样的接口(兄弟两个拜把子模式)
  • cglib:通过继承被代理的目标类(认干爹模式)实现代理,所以不需要目标类实现接口
  • AspectJ:本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,所以最终效果是动态的。weaver就是织入器。Spring只是借用了AspectJ中的注解(AspectJ是AOP思想的重要实现)

现在有个案例,给计算器的方法前后加上日志功能

  1. 在IOC所依赖的基础上再加一个spring-aspects即可

    <dependencies>
        <!--基于Maven依赖的传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--spring-aspects会帮我们传递过来aspectjweaver-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
    </dependencies>
    
  2. 有一个计算接口,有一个类实现了他

    package com.pan.sp.aop.annotation;
    
    public interface Calculator {
        int add(int i, int j);
    
        int sub(int i, int j);
    
        int mul(int i, int j);
    
        int div(int i, int j);
    }
    
    package com.pan.sp.aop.annotation;
    
    @Component
    public class CalculatorImpl implements Calculator {
    
        @Override
        public int add(int i, int j) {
            int result = i + j;
            System.out.println("方法内部的结果:" + result);
            return result;
        }
    
        @Override
        public int sub(int i, int j) {
            int result = i - j;
            System.out.println("方法内部的结果:" + result);
            return result;
        }
    
        @Override
        public int mul(int i, int j) {
            int result = i * j;
            System.out.println("方法内部的结果:" + result);
            return result;
        }
    
        @Override
        public int div(int i, int j) {
            int result = i / j;
            System.out.println("方法内部的结果:" + result);
            return result;
        }
    }
    
  3. 创建切面类并配置(注意要标识@Aspect注解,标识为切面)

    package com.pan.sp.aop.annotation;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class LoggerAspect {
    
    }
    
  4. 将计算接口的实现类(目标类)和切面类都要交给IOC管理(注解的方式),所以配置一下IOC扫描

    注意还需要<aop:aspectj-autoproxy/>用于开启基于注解的AOP

    <?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">
    
        <!--AOP的注意事顶:切面类和目标类都需要交给IOC容器管理-->
        <context:component-scan base-package="com.pan.sp.aop.annotation"/>
        
        <!--开启基于注解的AOP-->
        <aop:aspectj-autoproxy/>
    
    </beans>
    
  5. 准备工作完成了,接着就可以在切面类里写通知方法了

    给add方法添加@Before注解,添加前置通知

    package com.pan.sp.aop.annotation;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class LoggerAspect {
        @Before("execution(public int com.pan.sp.aop.annotation.CalculatorImpl.add(int,int))")
        public void beforeAdviceMethod() {
            System.out.println("前置通知");
        }
    }
    
  6. 测试一下

    @org.junit.Test
        public void test() {
        ClassPathXmlApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
        Calculator calculator = ioc.getBean(Calculator.class);// 这里用接口获取的是代理对象,一旦被代理,就只能通过代理调用方法了
        calculator.add(1, 2);
        // 前置通知
        // 方法内部的结果:3
    }
    

    发现add方法一旦执行,会在输出结果前多了一句话,成功模拟了日志功能

  7. 但是这时候只有add方法有日志功能,怎样给四个方法都加上日志功能呢?

    可以加上切入点表达式

    package com.pan.sp.aop.annotation;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    @Component
    @Aspect
    public class LoggerAspect {
        @Before("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
        public void beforeAdviceMethod() {
            System.out.println("前置通知");
        }
    }
    
    // @Before("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
    // 第一个*表示任意的访问修饰符和返回值类型
    // 第二个*表示类中任意的方法
    // ..表示任意的参数列表
    // 类的地方也可以使用*,表示包下所有的类
    

    这时再测试发现四个方法都有日志功能了

  8. 获取连接点的信息(连接点所对应方法的信息)

    package com.pan.sp.aop.annotation;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    @Component
    @Aspect
    public class LoggerAspect {
        @Before("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
        public void beforeAdviceMethod(JoinPoint joinPoint) {
            // getSignature获取连接点所对应方法的签名信息,getSignature.getName获取方法名
            Signature signature = joinPoint.getSignature();
            // 获取连接点所对应方法的参数
            Object[] args = joinPoint.getArgs();
            System.out.println("前置通知" + signature.getName() + Arrays.toString(args));// sub [1, 2]
        }
    }
    
  9. 假如好多通知都要用到注解,要写好多遍,可以写到一个@Pointcut注解里,详见代码

    package com.pan.sp.aop.annotation;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.Signature;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.stereotype.Component;
    
    import java.util.Arrays;
    
    @Component
    @Aspect
    public class LoggerAspect {
    
        @Pointcut("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
        public void pointCut() {
        }
    
        @Before("pointCut()")
        public void beforeAdviceMethod(JoinPoint joinPoint) {
            // getSignature获取连接点所对应方法的签名信息,getSignature.getName获取方法名
            Signature signature = joinPoint.getSignature();
            // 获取连接点所对应方法的参数
            Object[] args = joinPoint.getArgs();
            System.out.println("前置通知" + signature.getName() + Arrays.toString(args));// sub [1, 2]
        }
    
        @After("pointCut()")
        public void afterAdviceMethod() {
            System.out.println("后置");
        }
    }
    

几种通知

@Before 前置通知 在代理类的try块里执行的,后面紧跟着就是目标方法

@After 后置通知 实际上是在代理类的finally块里执行的,所以一般在后置通知里关闭资源

@AfterReturning 返回通知 在目标方法返回值之后执行,假如目标方法有异常发生,就不会有返回值了,直接走catch块了,所以这时返回通知也就不会执行了

@Pointcut("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
public void pointCut() {
}
// 在返回通知中若要获取目标对象方法的返回值
// 只需要通过@AfterReturning注解的returning属性
// 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
// 这样配置就能接收目标方法的返回值了
@AfterReturning(value = "pointCut()", returning = "result")
public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
    System.out.println(result);
}

@AfterThrowing 异常通知 在代理类的catch块中执行

@Pointcut("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
public void pointCut() {
}

// 只需要通过@AfterThrowing注解的throwing属性
// 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
@AfterThrowing(value = "pointCut()",throwing = "e")
public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable e){
    System.out.println(e.getStackTrace());
}

@Around 环绕通知 这种就和手写代理一样,靠自己写try-catch-finally不同地方的代码

@Pointcut("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
public void pointCut() {
}

@Around("pointCut()")
public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
    Object result = null;
    try {
        System.out.println("前置通知");
        // 表示目标方法的执行
        result = joinPoint.proceed();
        System.out.println("返回通知");
    } catch (Throwable e) {
        e.printStackTrace();
        System.out.println("异常通知");
    } finally {
        System.out.println("后置通知");
    }
    return result;
}

// 注意:环绕通知的方法的一定要和目标对象方法的返回值一致

各种通知的执行顺序:

  • Spring版本5.3.x以前:
    • 前置通知
    • 目标操作
    • 后置通知
    • 返回通知或异常通知
  • Spring版本5.3.x以后:
    • 前置通知
    • 目标操作
    • 返回通知或异常通知
    • 后置通知

各种切面的优先级

假如现在给计算器类相加日志功能,还想加校验功能,就需要两个切面类分别实现,那么优先级该怎样设置呢?

可以设置@Order注解,值越小,优先级越高,默认值是Integer的最大值(很大很大)

package com.pan.sp.aop.annotation;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Aspect
@Order(123) // int类型
public class LoggerAspect {

    @Pointcut("execution(* com.pan.sp.aop.annotation.CalculatorImpl.*(..))")
    public void pointCut() {
    }

    @Around("pointCut()")
    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            System.out.println("前置通知");
            // 表示目标方法的执行
            result = joinPoint.proceed();
            System.out.println("返回通知");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知");
        } finally {
            System.out.println("后置通知");
        }
        return result;
    }
}

基于XML的AOP

这种了解就行

切面类

package com.pan.sp.aop.xml;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.stereotype.Component;

@Component
public class LoggerAspect {
    public void beforeMethod() {
        System.out.println("前置通知(xml)");
    }

    public void afterMethod() {
        System.out.println("前置通知(xml)");
    }

    public void afterReturningMethod(Object result) {
        System.out.println("返回通知(xml)");
    }

    public void afterThrowingMethod(Throwable e) {
        System.out.println(e);
        System.out.println("异常通知(xml)");
    }

    public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
        Object result = null;
        try {
            System.out.println("前置通知(around-xml)");
            // 表示目标方法的执行
            result = joinPoint.proceed();
            System.out.println("返回通知(around-xml)");
        } catch (Throwable e) {
            e.printStackTrace();
            System.out.println("异常通知(around-xml)");
        } finally {
            System.out.println("后置通知(around-xml)");
        }
        return result;
    }
}

xml

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

    <!--扫描组件-->
    <context:component-scan base-package="com.pan.sp.aop.xml"/>

    <!--开启基于xml的AOP-->
    <aop:config>
        <!--设置一个公共的切入点表达式-->
        <aop:pointcut id="pointCut" expression="execution(* com.pan.sp.aop.xml.CalculatorImpl.*(..))"/>
        <!--将IOC容器中的某个bean设置为切面-->
        <aop:aspect ref="loggerAspect">
            <aop:before method="beforeMethod" pointcut-ref="pointCut"/>
            <aop:after method="afterMethod" pointcut-ref="pointCut"/>
            <aop:after-returning method="afterReturningMethod" pointcut-ref="pointCut" returning="result"/>
            <aop:after-throwing method="afterThrowingMethod" pointcut-ref="pointCut" throwing="e"/>
            <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>

</beans>

声明式事务

先说说JdbcTemplate,Spring框架对JDBC进行封装,使用JdbcTemplate方便实现对数据库操作

试一下怎么用JdbcTemplate来对数据库操作

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.pan</groupId>
    <artifactId>Spring07</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencies>
        <!--基于Maven依赖的传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--spring-aspects会帮我们传递过来aspectjweaver-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring持久化层支持的jar包-->
        <!--Spring在执行持久化层操作,与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包-->
        <!--导入orm包就可以通过Maven的依赖传递性把其他两个也导入-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring相关测试-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--MySQL驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>
</project>

jdbc.properties配置文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=root

spring配置文件

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

    <!--获取JDBC配置文件-->
    <bean id="property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="jdbc.properties"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    <bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

</beans>

pojo类Act

package com.pan.sp.pojo;

public class Act {
    private Integer id;
    private String actno;
    private Double balance;

    @Override
    public String toString() {
        return "Act{" + "id=" + id + ", actno='" + actno + '\'' + ", balance=" + balance + '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public Double getBalance() {
        return balance;
    }

    public void setBalance(Double balance) {
        this.balance = balance;
    }

    public Act() {
    }

    public Act(Integer id, String actno, Double balance) {
        this.id = id;
        this.actno = actno;
        this.balance = balance;
    }
}

查询

package com.pan.sp.test;

import com.pan.sp.pojo.Act;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)// 指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@ContextConfiguration("classpath:spring-jdbc.xml")// 设置Spring测试环境的配置文件
public class TestJdbcTemplate {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    // 插入
    @Test
    public void testInsert() {
        String sql = "insert into t_act values (null,?,?)";
        jdbcTemplate.update(sql, "act003", "999");
    }

    // 查一条
    @Test
    public void testSelectById() {
        String sql = "select * from t_act where id = ?";
        Act act = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Act.class), 1);
        System.out.printf(act.toString());
    }

    // 查多条
    @Test
    public void testSelectAll() {
        String sql = "select * from t_act";
        List<Act> query = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Act.class));
        query.forEach(System.out::println);
    }

    // 查总数
    @Test
    public void testSelectCount() {
        String sql = "select count(*) from t_act";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(count);
    }
}

编程式事务

Connection connection = null;
try {
    // 开启事务:关闭事务的自动提交
    connection.setAutoCommit(false);

    // 核心代码

    // 提交事务
    connection.commit();
} catch (Exception e) {
    // 回滚事务
    connection.rollback();
} finally {
    // 释放连接
    connection.close();
}

编程式的实现方式存在缺陷: 细节没有被屏蔽:具体操作过程中,所有细节都需要程序员自己来完成,比较繁琐。 代码复用性不高:如果没有有效抽取出来,每次实现功能都需要自己编写代码,代码就没有得到复用。

声明式事务

既然事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。封装起来后,我们只需要在配置文件中进行简单的配置即可完成操作。

  • 好处1:提高开发效率
  • 好处2:消除了冗余的代码
  • 好处3:框架会综合考虑相关领域中在实际开发环境下有可能遇到的各种问题,进行了健壮性、性能等各个方面的优化

总之,编程式:自己写代码实现功能;声明式:通过配置让框架实现功能

基于注解式声明事务

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.pan</groupId>
    <artifactId>Spring07</artifactId>
    <version>1.0-SNAPSHOT</version>

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

    <dependencies>
        <!--基于Maven依赖的传递性,导入spring-context依赖即可导入当前所需所有jar包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.23</version>
        </dependency>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--spring-aspects会帮我们传递过来aspectjweaver-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring持久化层支持的jar包-->
        <!--Spring在执行持久化层操作,与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包-->
        <!--导入orm包就可以通过Maven的依赖传递性把其他两个也导入-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--Spring相关测试-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.1</version>
        </dependency>
        <!--MySQL驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
        <!--数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.31</version>
        </dependency>
    </dependencies>
</project>

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:component-scan base-package="com.pan.sp"/>

    <bean id="property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="jdbc.properties"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--
        开启事务的注解驱动,和配置事务管理器用id关联起来,transaction-manager属性设置事务管理器的id,
        若事务管理器bean的id默认为transactionManager,则该属性可以不写
    -->
    <!--将使用Transactional注解所标识的方法或类中所有的方法使用事务进行管理-->
    <tx:annotation-driven transaction-manager="transactionManager"/>
</beans>

jdbc配置

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC
jdbc.username=root
jdbc.password=root

控制层

package com.pan.sp.controller;

import com.pan.sp.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BookController {
    @Autowired
    private BookService bookService;

    public void buyBook(Integer userId, Integer bookId) {
        bookService.buyBook(userId, bookId);
    }
}

持久层

package com.pan.sp.dao;

public interface BookDao {
    void updateBalance(Integer userId, Integer price);

    void updateStock(Integer bookId);

    Integer getPriceByBookId(Integer bookId);
}
package com.pan.sp.dao;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

@Repository
public class BookDaoImpl implements BookDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql, price, userId);
    }

    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    @Override
    public Integer getPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
    }
}

业务层

package com.pan.sp.service;

public interface BookService {
    void buyBook(Integer userId, Integer bookId);
}
package com.pan.sp.service;

import com.pan.sp.dao.BookDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BookServiceImpl implements BookService {
    @Autowired
    private BookDao bookDao;

    @Override
    @Transactional
    public void buyBook(Integer userId, Integer bookId) {
        // 查图书价格
        Integer price = bookDao.getPriceByBookId(bookId);
        // 更新图书库存
        bookDao.updateStock(bookId);
        // 更新用户余额
        bookDao.updateBalance(userId, price);
    }
}

测试类

package com.pan.test;

import com.pan.sp.controller.BookController;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)// 指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@ContextConfiguration("classpath:spring-transaction.xml")// 设置Spring测试环境的配置文件
public class Test01 {
    @Autowired
    private BookController bookController;

    @Test
    public void testBuyBook() {
        bookController.buyBook(1, 1);
    }
}

声明式事务的配置步骤:

  1. 在Spring的配置文件中配置事务管理器

  2. 开启事务的注解驱动

  3. 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理

    @Transactional注解标识的位置:

    • 标识在方法上
    • 标识在类上,则类中所有的方法都会被事务管理

事务的属性

  1. 只读

    对一个查询操作来说,如果我们把它设置成只读,就能够明确告诉数据库,这个操作不涉及写操作。这样数据库就能够针对查询操作来进行优化。

    注意,对增删改操作设置只读会抛出异常

    @Transactional(
        readOnly = true
    )
    public void buyBook(Integer userId, Integer bookId) {
        // 查图书价格
        Integer price = bookDao.getPriceByBookId(bookId);
        // 更新图书库存
        bookDao.updateStock(bookId);
        // 更新用户余额
        bookDao.updateBalance(userId, price);
    }
    
  2. 超时

    事务在执行过程中,有可能因为遇到某些问题,导致程序卡住,从而长时间占用数据库资源。而长时间占用资源,大概率是因为程序运行出现了问题(可能是Java程序或MySQL数据库或网络连接等等)此时这个很可能出问题的程序应该被回滚,撤销它已做的操作,事务结束,把资源让出来,让其他正常程序可以执 行。概括来说就是一句话:超时回滚,释放资源。超时后强制回滚

    @Transactional(
        timeout = 3 // 3秒
    )
    
  3. 回滚策略

    声明式事务默认只针对运行时异常回滚,编译时异常不回滚。可以通过@Transactional中相关属性设置回滚策略

    • rollbackFor属性:需要设置一个Class类型的对象
    • rollbackForClassName属性:需要设置一个字符串类型的全类名
    • noRollbackFor属性:需要设置一个Class类型的对象
    • noRollbackForClassName属性:需要设置一个字符串类型的全类名
    @Transactional(
        noRollbackFor = ArithmeticException.class
        // noRollbackForClassName = "java.lang.ArithmeticException"
    )
    
    // 设置不因xxx异常而回滚,两种方式都行,第二种是全类名
    
  4. 隔离级别

    数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。 隔离级别一共有四种:(具体可以看看MySQL的笔记)

    • 读未提交:READ UNCOMMITTED 允许Transaction01读取Transaction02未提交的修改。
    • 读已提交:READ COMMITTED 要求Transaction01只能读取Transaction02已提交的修改。
    • 可重复读:REPEATABLE READ 确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
    • 串行化:SERIALIZABLE 确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

    MySQL对四种隔离级别都支持,默认为可重复读:REPEATABLE READ

    Oracle对读已提交:READ COMMITTED和串行化:SERIALIZABLE两种级别支持,默认是读已提交

    @Transactional(
        isolation = Isolation.SERIALIZABLE
    )
    // 像这样,设置隔离级别,一般用默认DEFAULT,不需要设置
    
  5. 事务的传播行为

    假如现在有两个方法都设置了事务注解,就像结账方法checkout里需要执行好多次buyBook方法,假如其中一本书买失败了,那结账方法还能提交吗,默认情况下要有一本书买失败,整个结账就失败了

    如何解决?只需要在结账的方法事务注解里加属性propagation

    @Transactional(
        propagation = Propagation.REQUIRES_NEW
    )
    

基于XML的声明事务

用的不多

将上面注解式中的@Transactional注解删掉,然后将Spring的xml文件修改成下面这样即可

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

    <bean id="property" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" value="jdbc.properties"/>
    </bean>

    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="url" value="${jdbc.url}"/>
        <property name="username" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>

    <bean id="jdbc" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--配置事务通知-->
    <tx:advice id="tx" transaction-manager="transactionManager">
        <tx:attributes>
            <!--可以设置各种属性-->
            <tx:method name="buyBook"/>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:advisor advice-ref="tx" pointcut="execution(* com.pan.sp.service.impl.*.*(..))"/>
    </aop:config>

</beans>