Spring概述

Spring框架是什么?

Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器(框架)

Spring的优点

  • Spring是一个轻量级的框架 , 非侵入式的
  • 对事务的支持 , 对框架整合的支持
  • 针对接口编程,解耦合

Spring的体系结构

Spring 框架是一个分层架构,由 7 个定义良好的模块组成。

  • 核心容器(Spring Core):核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。BeanFactory 使用控制反转(IOC) 模式将应用程序的配置和依赖性规范与实际的应用程序代码分开。
  • Spring 上下文(Spring Context):Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web 模块:Web 上下文模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。所以,Spring 框架支持与 Jakarta Struts 的集成。Web 模块还简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC 框架:MVC 框架是一个全功能的构建 Web 应用程序的 MVC 实现。通过策略接口,MVC 框架变成为高度可配置的,MVC 容纳了大量视图技术,其中包括 JSP、Velocity、Tiles、iText 和 POI。

IOC本质

控制反转IoC(Inversion of Control),是一种设计思想,DI(依赖注入)是实现IoC的一种方法,DL(依赖查找)也是实现IOC的一种方法;

采用XML方式配置Bean的时候,Bean的定义信息是和实现分离的,而采用注解的方式可以把两者合为一体,Bean的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的。

Spring容器是一个超级大工厂,负责创建、管理所有的Java对象,这些Java对象被称为Bean。Spring 容器管理着容器中Bean之间的依赖关系,Spring 使用**DI(依赖注入)**的方式来管理Bean之间的依赖关系。使用loC实现对象之间的解耦合。

控制反转是一种通过描述(XML或注解)并通过第三方去生产或获取特定对象的方式。在Spring中实现控制反转的是IoC容器,其实现方法是依赖注入(Dependency Injection,DI)。

HelloSpring

准备

实现步骤:
1.创建maven项目
2.加入maven的依赖
        spring的依赖
        junit依赖
        lombok依赖
3.创建Student类,和没有使用框架一样,就是普通的类。
4.创建spring需要使用的配置文件
    声明类的信息。这些类由spring创建和管理
5.测试spring创建的对象。

Sprig的第一个程序

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">


    <!--告诉spring创建对象
  声明bean,告诉spring要创建某个类的对象
  id是对象的自定义名称(唯一值)。spring通过这个名称找到对象
  class:类的全限定名称(不能是接口,因为spring是反射机制)
  spring就完成Student student = new Student();
  spring是把创建好的对象放到了map中,spring框架中有一个map存放对象的。
  springMap.put(id的值,对象)
  例如 springMap.put("student",new Student());
  一个bean标签只声明一个对象
  -->
    <bean id="student" class="com.test.dyh.Student">
        <property name="id" value="1"/>
        <property name="age" value="12"/>
        <property name="name" value="段玉"/>
    </bean>


</beans>
        <!--spring的配置文件
        1.beans是根标签,spring中把Java对象成为bean
        2.spring-beans.xsd是约束文件,和mybatis指定的类似
        -->

测试

package com.test.dyh;

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

import java.text.MessageFormat;


public class AppTest {
    /*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/
    @Test
    public void shouldAnswerWithTrue() {
        //使用spring容器创建的对象
        //1.指定spring配置文件的名称
        String config="bean.xml";
        //2.创建表示spring容器的对象,ApplicationContext
        //ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
        ApplicationContext st = new ClassPathXmlApplicationContext(config);
        //3.从容器中获取某个对象,你要调用对象的方法,
        //getBean("配置文件中的bena的id值");
        Student student = (Student) st.getBean("student");
        //使用spring创建好的对象
        System.err.println(MessageFormat.format("id={0}, age={1}, name={2}", student.getId(),
                student.getAge(), student.getName()));
                //输出:id=1, age=12, name=段玉
    }
}

IOC创建对象的方式



使用无参构造创建对象,默认!

假设我们要使用有参构造创建对象。

  • 下标赋值
<bean id="student" class="com.test.dyh.Student">
       <constructor-arg index="0" value="1"></constructor-arg>
       <constructor-arg index="1" value="14"></constructor-arg>
       <constructor-arg index="2" value="name"></constructor-arg>
   </bean>
  • 构造参数类型
<bean id="student" class="com.test.dyh.Student">
        <constructor-arg type="int" value="1"></constructor-arg>
        <constructor-arg type="java.lang.Integer" value="14"></constructor-arg>
        <constructor-arg type="java.lang.String" value="段"></constructor-arg>
    </bean>
  • 构造参数名
<bean id="student" class="com.test.dyh.Student">
        <constructor-arg name="id" value="1"></constructor-arg>
        <constructor-arg name="age" value="14"></constructor-arg>
        <constructor-arg name="name" value="段"></constructor-arg>
    </bean>

总结:在配置文件加载的时候,容器中管理的对象就已经初始化了

Spring配置

别名

<!--设置别名:在获取Bean的时候可以使用别名获取-->
    <alias name="student" alias="student2"></alias>

Bean的配置

<!--bean就是java对象,由Spring创建和管理-->

<!--
   id : bean的标识符,要唯一,如果没有配置id,name就是默认标识符
   如果配置id,又配置了name,那么name是别名
   name可以设置多个别名,可以用逗号,分号,空格隔开
   如果不配置id和name,可以根据applicationContext.getBean(.class)获取对象;

   class : bean的全限定名=包名+类名
-->
<bean id="hello" name="hello2 h2,h3;h4" class="com.kuang.pojo.Hello">
   <property name="name" value="Spring"/>
</bean>

import

这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个;

假设,现在项目中有多个人开发,这三个人复制不同的类开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!

<import resource="{path}/beans.xml"/>

依赖注入

基于XML的DI

构造器注入

前面已经说过了

Set方式注入 【重点】

依赖注入:Set注入

  • 依赖:bean对象的创建依赖于容器
  • 注入:bean对象中的所有属性,由容器来注入,spring调用类的set方法,在set方法可以实现属性的赋值

2个实体类

package com.test.dyh;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author dyh
 * @date 2021/6/1 - 16:41
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private Integer age;
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

}
package com.test.dyh;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author dyh
 * @date 2021/6/1 - 17:52
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Address {
    private String address;
}

配置 applicationContext.xml

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



    <bean id="address" class="com.test.dyh.Address">
        <property name="address" value="广东深圳"></property>
    </bean>
      <!--声明student对象
    注入:就是赋值的意思
    简单类型:spring中规定Java的基本数据类型和string类型都是简单类型。
    di:给属性赋值
    1.set注入(设置注入):spring调用类的set方法,你可以在set方法中完成属性赋值
        利用property

    -->
    <bean id="student" class="com.test.dyh.Student">
        <!--第一种,普通值注入,value-->
        <property name="id" value="1"></property>
        <property name="age" value="14"></property>认准set方法
        <property name="name" value="断浪"></property><!--调用setName...-->
        <!--第二种,Bean注入-->
        <property name="address" ref="address"></property>
        <!--数组-->
        <property name="books">
            <array>
                <value>水浒传</value>
                <value>西游记</value>
            </array>
        </property>
        <!--List-->
        <property name="hobbys">
            <list>
                <value>music</value>
                <value>swimming</value>
                <value>coding</value>
            </list>
        </property>
        <!--Map-->
        <property name="card">
            <map>
                <entry key="身份证" value="12312121212"/>
                <entry key="银行卡" value="678112121111000"/>
            </map>
        </property>
        <!--Set-->
        <property name="games">
            <set>
                <value>CF</value>
                <value>LOL</value>
                <value>GTA</value>
            </set>
        </property>
        <!--null-->
        <property name="wife">
            <null/>
        </property>
        <!--Properties-->
        <property name="info">
            <props>
                <prop key="学号">20190526</prop>
                <prop key="username">root</prop>
                <prop key="password">root</prop>
            </props>
        </property>
    </bean>
</beans>

测试

package com.test.dyh;

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


public class AppTest {
    /*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/
    @Test
    public void shouldAnswerWithTrue() {
        //使用spring容器创建的对象
        //1.指定spring配置文件的名称
        String config = "bean.xml";
        //2.创建表示spring容器的对象,ApplicationContext
        //ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
        ApplicationContext st = new ClassPathXmlApplicationContext(config);
        //3.从容器中获取某个对象,你要调用对象的方法,
        //getBean("配置文件中的bena的id值");
        Student student = (Student) st.getBean("student");
        //使用spring创建好的对象
        System.err.println(student);
         //输出:Student(id=1, age=14, name=断浪, address=Address(address=广东深圳), 
         // books=[水浒传, 西游记], hobbys=[music, swimming, coding], 
         // card={身份证=12312121212, 银行卡=678112121111000}, games=[CF, LOL, GTA],
         // wife=null, info={password=root, 学号=20190526, username=root})

    }
}

拓展方式注入

我们可以使用p命名空间和c命名空间进行注入

<?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:p="http://www.springframework.org/schema/p"
       xmlns:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--p命名空间注入,可以直接注入属性的值:property-->
    <bean id="student" class="com.test.dyh.Student" p:name="张三" p:age="18"/>

    <!--c命名空间注入,通过构造器注入:construt-args-->
    <bean id="student2" class="com.test.dyh.Student" c:name="李四" c:age="11"/>

</beans>

注意点:p命名和c命名空间不能直接使用,需要导入xml约束!

xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"

基于注解的DI

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

使用注解的步骤

  1. 加入maven的依赖spring-context,在你加入spring-context的同时,间接加入spring-aop的依赖。
    使用注解必须使用spring-aop依赖
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.14.RELEASE</version>
        </dependency>
  1. 导入约束(xmlns:context)
  2. 在类中加入spring的注解(多个不同功能的注解)
  3. 在spring的配置文件中,加入一个组件扫描器的标签,说明注解在你的项目中的位置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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对象,所以就是找Java对象
       base-package:指定注解在你的项目中的包名
       component-scan工作方式:spring会扫描遍历base-package指定的包
       把包中和子包中的所有类,找到类中的注解,按照注解的功能创建对象和给属性赋值。
       -->

  <context:component-scan base-package="com.test.dyh"></context:component-scan>
</beans>

1.@Component 2.@Repository 3.@Service 4.@Controller 5.@Value 6.@Autowired 7.@Resource

package com.test.dyh;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

/**
 * @author dyh
 * @date 2021/6/1 - 16:41
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
/*
 * @Component:创建对象的,等同于<bean>的功能
 * 属性value: 就是对象的名称,也就是bean的id值,value值是唯一的,创建的对象在整个spring容器中就一个
 * 位置:在类的上面写注解
 * */
//等同于<bean id="myStudent" class="com.test.dyh.Student"/>
//@Component(value = "myStudent")
//省略value
//@Component("myStudent")
//不指定对象名称,由spring提供默认名称(首字母小写类名)
@Component
public class Student {
    private int id;
    private Integer age;
    //相当于 <property name="name" value="凯斯"></property>
    @Value("凯斯")
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

}

测试

package com.test.dyh;

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


public class AppTest {


    @Test
    public void shouldAnswerWithTrue() {
        ApplicationContext ac=new ClassPathXmlApplicationContext ("bean.xml");
        //从容器中获取对象
        Student student=(Student) ac.getBean ("student");
        System.out.println (student.getName());
        //输出:Student(id=0, age=null, name=凯斯, 
        //address=null, books=null, hobbys=null, 
        //card=null, games=null, wife=null, info=null)


    }
}
* spring中和@Component功能一致,创建对象的注解还有:
* @Repository(用在持久层上):放在dao的实现类上面,
* 表示创建dao对象,dao对象是能访问数据库的。(持久层注解)
*
* @Service(用在业务层类的上面):放在service的实现类上面,
* 创建service对象,service对象是做业务处理的,可以有事物等功能的。
*
* @Controller(用在控制器的上面):放在控制器(处理器)类的上面,创建控制器对象的,
* 控制器对象可以接收用户提交的参数和显示请求的处理结果。
*
* 以上三个注解的使用语法和@Component是一样的,都能够创建对象,但是这三个注解还有额外的功能----给项目分层

指定多个包的三种方式

使用多次组件扫描器标签,指定不同的包
使用分隔符(分号或者逗号)分隔多个包名
指定父包

Bean的自动装配

Bean的自动装配(XML)

  • 自动装配是Spring满足bean依赖的一种方式
  • Spring会在上下文中自动寻找,并自动给bean装配属性

byName与byTpye自动装配

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address" class="com.test.dyh.Address">
        <property name="address" value="深圳"></property>
    </bean>
    <!--
    byName(按名称注入): 会自动在容器上下文中查找,和自己对象set方法参数名对应的bean_id
    且数据类型一致,这样的容器中的bean,spring能够自动注入
     byType(按类型注入) : 会自动在容器上下文中查找,和自己对象属性类型相同的bean
-->
    <bean id="student" class="com.test.dyh.Student" autowire="byName">
        <property name="id" value="1"></property>
        <property name="age" value="14"></property>
    </bean>
</beans>

注意

byName的时候,需要保证所有bean的id唯一,并且这个bean需要和自动注入的属性的set方法的值一致
byType的时候,需要保证所有bean的class唯一,并且这个bean需要和自动注入的属性的类型一致

测试

package com.test.dyh;

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


public class AppTest {
    /*spring默认创建对象的时间:在创建spring的容器时,他会创建配置文件中的所有对象*/

    @Test
    public void shouldAnswerWithTrue() {
        //使用spring容器创建的对象
        //1.指定spring配置文件的名称
        String config = "bean.xml";
        //2.创建表示spring容器的对象,ApplicationContext
        //ClassPathXmlApplicationContext;表示从类路径中加载spring的配置文件
        ApplicationContext st = new ClassPathXmlApplicationContext(config);
        //3.从容器中获取某个对象,你要调用对象的方法,
        //getBean("配置文件中的bena的id值");
        Student student = (Student) st.getBean("student");
        //使用spring创建好的对象
        System.err.println(student.getAddress().getAddress());
        //输出:深圳
    }
}

Bean的自动装配(注解)

前提

要使用注解须知:

  • 导入约束(xmlns:context)
  • 配置注解的支持(context:annotation-config)
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    <!--  比较少用的扫描标签,它只会扫描属性(即变量)
	<context:annotation-config></context:annotation-config>
	仅作了解即可。  -->
    <context:annotation-config></context:annotation-config>
    
    <bean id="address" class="com.test.dyh.Address">
        <property name="address" value="深圳"></property>
    </bean>
    
    <bean id="student" class="com.test.dyh.Student"></bean>
</beans>

@Autowired与@Resource

package com.test.dyh;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.*;

/**
 * @author dyh
 * @date 2021/6/1 - 16:41
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor

public class Student {
    private int id;
    private Integer age;
    private String name;
    //如果显示定义了Autowired的required属性为false,说明这个对象可以为Null,否则不允许为空
    //@Autowired(required = false)
    //如果@Autowired自动装配的环境比较复杂,
    //自动装配无法通过一个注解【@Autowired】完成的时候,
    //我们可以使用==@Qualifier(value = “xxx”)==去
    //配合@Autowire的使用,指定一个唯一的bean对象注入!
    //@Autowired
    //@Qualifier(value = "address")
    
    @Resource( name = "address")
    private Address address;
    private String[] books;
    private List<String> hobbys;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;

}

测试

package com.test.dyh;

import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class AppTest {

    @Test
    public void shouldAnswerWithTrue() {
        ApplicationContext ac=new ClassPathXmlApplicationContext ("bean.xml");
        //从容器中获取对象
        Student student=(Student) ac.getBean ("student");
        System.out.println (student.getAddress().getAddress());
		//输出:深圳
    }
}

小结

@Resource和@Autowired的区别:

  • 都是用来自动转配的,都可以放在属性字段上
  • @Autowired 是通过byType的方式实现,而且必须要求这个对象存在!【常用】
  • @Resource 默认通过byName的方式实现,如果找不到名字,则通过byType实现!如果两个都找不到的情况下,就报错!【常用】
  • 执行顺序不同: @Autowired 通过byType的方式实现。@Resource默认通过byName的方式实现。

使用Java的方式实现Spring

不再使用spring的xml配置,全权交给java
JavaConfig是Spring的一个子项目
实体类

package com.test.dyh;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.beans.factory.annotation.Value;

/**
 * @author dyh
 * @date 2021/6/1 - 16:41
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private int id;
    private Integer age;
    @Value("李四")
    private String name;
}

JavaConfig类

package com.test.dyh;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

/**
 * @author dyh
 * @date 2021/6/2 - 17:32
 * @description:
 */
//这个也会被Spring容器托管,注册到容器中,因为它本身就是一个@Component
//@Configuration代表这是一个配置类。就跟之前bean.xml是一样的
@Configuration
@ComponentScan("com.test.dyh")
@Import(JavaConfig2.class)
public class JavaConfig {

    //注册一个bean,就相当于一个bean标签
    //这个方法的名字,就相当于bean标签的id属性
    //这个方法的返回值,就相当于bean标签的class属性
    @Bean
    public Student student() {
        return new Student();
    }

}

测试类

package com.test.dyh;

import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;


public class AppTest {


    @Test
    public void shouldAnswerWithTrue() {
        //如果完全使用配置类做,就只能通过AnnotationConfig上下文来获取容器,通过配置类的class对象加载
        ApplicationContext ac = new AnnotationConfigApplicationContext(JavaConfig.class);
        //从容器中获取对象
        Student student = (Student) ac.getBean("student");
        System.out.println(student.getName());
        //输出李四

    }
}

AOP面向切面编程

代理模式的分类:

  • 动态代理
  • 静态代理

动态代理

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

实现方式

jdk动态代理, 使用jdk中的Proxy, Method, InvocationHanderl创建代理对象。jdk动态代理要求目标类必须实现接口。
cglib动态代理:第三方的工具库,创建代理对象,原理是继承。通过继承目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能是final的。

动态代理的作用

  • 在目标类源代码不变的情况下,增强功能
  • 减少代码的重复
  • 专注业务逻辑代码
  • 解耦合,让你的业务功能和日志,事务非事务功能分离

AOP简介

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

AOP底层,就是采用动态代理模式实现的。采用了两种代理: JDK的动态代理,与CGLIB的动态代理

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,可通过运行期动态代理实现程序功能的统一维护的一种技术。AOP是Spring框架中的一个重要内容。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(动态代理的规范化,把动态代理的实现步骤,方式都定义好了,让开发人员用一种统一的方法,使用动态代理)。

切面:给你的目标类增加的功能,就是切面,什么日志等(切面的特点:一般都是非业务方法,独立使用的。)

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

如何理解面向切面编程?

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

AOP编程术语

  • Aspect:切面,表示增强的功能,就是一堆代码,完成某个功能。(非业务功能,可独立执行。如:日志,统计信息,权限验证等)
  • JoinPoint:连接点,连接你的业务方法和切面位置。其实就是某个类中的业务方法。
  • Pointcut:切入点,指多个连接点方法的集合。
  • 目标对象:给哪个类的方法增加功能,这个类就是目标对象。
  • Advice:通知,通知表示切面功能执行的时间。

一个切面有三个关键的要素

  • 切面的功能代码,切面干什么
  • 切面的执行位置,使用Pointcut表示切面执行位置
  • 切面执行时间,使用advice表示时间。

AspectJ对AOP的实现

aop的实现

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

aop的技术实现框架:

spring:spring在内部实现了aop规范,能做aop的工作。

aspectJ:一个开源的专门做aop的框架。spring框架中集成了aspectJ框架,通过spring就可以使用aspectJ的功能了。

aspectJ框架实现有两种方式:1.使用xml的配置文件(配置全局事务) 2.使用注解,一般都用注解

学习AspectJ框架的使用

如何表示切面的执行时间?
注解表示:1.@Before 2.@AfterReturning 3.@Around 4.@AfterThrowing 5.@After

如何表示切面执行的位置?
使用的是切入点表达式。

表达式原型为:

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

execution(<修饰符模式>?<返回类型模式><方法名模式>(<参数模式>)<异常模式>?)

解释:

modifiers-pattern 访问权限类型

ret-type-pattern 返回值类型

declaring-type-pattern 包名类名

name-pattern(param-pattern) 方法名(参数类型和参数个数)

throws-pattern 抛出异常类型

?表示可选部分

使用Spring实现Aop(使用AspectJ实现Aop)

使用aspectJ框架实现aop
使用aop:目的是给已经存在的一些类和方法,增加额外的功能,前提是不改变原来的类的代码。

框架的使用步骤:
1.新建maven项目
2.加入依赖
    spring依赖 aspectJ依赖 junit单元测试
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
3.创建目标类:接口和他的实现类
    要做的是给类中的方法去增加功能
4.创建切面类:普通类
        1.在类的上面加入@Aspect
        2.在类中去定义方法---要执行的功能代码
        3.在方法的上面加入aspectJ中的通知注解,例如@Before
          还需要指定切入点表达式execution()
5.创建spring的配置文件,在文件中声明对象,把对象交给容器统一管理
   声明对象可以使用注解或者xml配置文件
   声明目标对象
   声明切面类对象
   声明aspectJ框架中的自动代理生成器标签(用来完成代理对象的自动创建功能的)
6.创建测试类,从spring容器中获取目标对象(实际上是代理对象)。
    通过代理执行方法,实现aop的功能增强

@Before前置通知

接口

package cqutlc.ba01;

public interface SomeSevice {

    void doSome(String name,Integer age);
}

目标类

package cqutlc.ba01;
//目标类
public class SomeSeviceImpl implements SomeSevice {

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

切面类

package cqutlc.ba01;

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

import java.util.Date;

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

}

Spring配置文件

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

    <!--把对象交给spring容器,由容器统一创建和管理对象-->
    <!--声明目标对象-->
    <bean id="someService" class="cqutlc.ba01.SomeSeviceImpl"/>
    <!--声明切面类对象-->
    <bean id="myAspect" class="cqutlc.ba01.MyAspect"/>
    <!--声明自动代理生成器:使用aspectJ框架内部的功能,创建目标对象的代理对象
    创建代理对象是在内存中实现的,修改目标对象的内存中的结构,创建为代理对象
    所以目标对象就是被修改后的代理对象

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

测试类

package cqutlc.ba01;

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

public class test1 {

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

JoinPoint

指定通知方法中的参数

joinpoint:业务方法,要加入切面功能的业务方法

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

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

这个joinpoint参数的值是由框架赋予,必须是第一个位置的参数。

@Aspect
public class MyAspect {
    
    @Before (value = "execution(public void *..SomeSeviceImpl.doSome(..))")
    public void myBefore(JoinPoint jp){
        //获取方法的完整定义
        System.out.println("方法的定义:"+jp.getSignature());
        System.out.println("方法的名称:"+jp.getSignature().getName());
        //获取方法的实参
        Object[] args= jp.getArgs();
        for(Object arg: args){
            System.out.println("参数="+arg);
        }
        //功能代码
        System.out.println ("时间:"+new Date ());
    }

}

spring 上下文设置session spring上下文的作用_配置文件

@AfterReturning后置通知

package cqutlc.ba02;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import java.util.Date;
/*
*
@Aspect:aspectJ框架中的注解,用来表示当前类是切面类
* 位置:类定义的上面
* */
@Aspect
public class MyAspect {
    /*
    * 后置通知定义方法:实现切面功能的、
    * 方法的定义要求:
    * 1.公共方法
    * 2.没有返回值3.方法名自定义4.方法有参数,推荐使用object,参数名自定义
    * */
    /*
    * @AfterReturning
    * 属性:
    * 1.value:切入点表达式
    * 2.returning:自定义的变量,用来表示目标方法的返回值,自定义变量名必须和通知方法的形参名一样
    * 特点:
    * 1.在目标方法之后执行的
    * 2.能够获得目标方法的返回值,可以根据这个返回值做不同的处理功能
    *   Object res = doOther();
    * 3.可以修改这个返回值。
    * */
    @AfterReturning(value = "execution(* *..SomeSeviceImpl.doOther(..))",
            returning = "res")
    public void myAfterReturing(Object res){
        //Object res :是目标方法执行后的返回值,根据返回值做你的切面的功能处理
        System.out.println ("后置通知,获取的返回值是"+res);
        if (res.equals ("abc")){
            res="hi";
        }else
        {
        }
    }
}

@Around环绕通知

增强方法有ProceedingJoinPoint参数

package cqutlc.ba03;

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

import java.util.Date;

/*
*
@Aspect:aspectJ框架中的注解,用来表示当前类是切面类
* 位置:类定义的上面
* */
@Aspect
public class MyAspect {
   /*
   * 环绕通知定义方法的格式
   * 1.public
   * 2.有一个返回值 object
   * 3.方法名称自定义
   * 4.方法有参数,固定的 ProceedingJoinPoint
   *
   * */
   /*@Around
   特点:
   1.功能最强的通知
   2.在目标方法前和后都可以加入功能
   3.控制目标方法是否被调用实行
   4.修改原来的目标方法的执行结果,影响最后的调用结果
   环绕通知等同于jdk动态代理,InvocationHandler接口
   参数:ProceedingJoinPoint 就等同于Method
   作用:执行目标方法的执行结果,可以被修改。

   环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务
   * */
   @Around (value = "execution(* *..SomeServiceImpl.doFirst(..))")
   public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
       String name="";
       Object[] args =pjp.getArgs ();
       if (args!=null&&args.length>1){
           Object arg= args[0];
           name=(String)arg;
       }
       //在目标方法前或者后加功能
       //实现环绕通知
       Object result=null;
       System.out.println ("环绕通知在目标方法之前"+new Date ());
       if ("zhangsan".equals (name)){
           //1.实现目标方法的调用
           result= pjp.proceed ();//method.invoke,object result=doFirst();
       }

       System.out.println ("环绕通知在目标方法之后,提交事务");

       return  result;

   }

}

测试

package cqutlc.ba03;

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

public class test1 {

    @Test
    public void test1(){
        String config="applicationContext.xml";
        ApplicationContext ac=new ClassPathXmlApplicationContext (config);
        SomeService proxy=(SomeService)ac.getBean ("someService");
        String str=proxy.doFirst ("lc",19);


    }
}

@Pointcut定义切入点

当较多的通知增强方法使用相同的execution切入点表达式时,编写、维护均较为麻烦。
AspectJ提供了@Pointcut注解,用于定义execution切入点表达式。其用法是,将@Pointcut注解在一个方法之上,以后所有的execution的value属性值均可使用该方法名作为切入点。代表的就是@Pointcut定义的切入点。这个使用@Pointcut注解的方法一般使用private 的标识方法,即没有实际作用的方法。

属性 :value切入点表达式

位置:自定义的方法的上面

特点:当使用@Pointcut定义在一个方法的上面,此时这个方法的名称就是切入点表达式的别名,其他的通知中,value属性就可以使用这个方法名称代替切入点表达式。

@Pointcut(value="execution(* *..SomeServiceImpl.doOther(..))")
private void mypt(){
	//无需代码
}

有接口也可以使用cglib代理

在xml中配置<aop:aspectj-autoproxy proxy-target-class=“true”/>表示告诉框架,你要使用cglib代理。JDK(默认 <aop:aspectj-autoproxy proxy-target-class=“false”/>)

Spring集成MyBatis

spring和mybatis集成
步骤
1.新建maven项目
2.加入依赖  spring依赖 mybatis依赖 mysql驱动 spring的事务依赖 mybatis和spring集成的依赖
3.创建实体类
4.创建mybatis主配置文件
5.创建接口和mapper文件
6.创建测试类

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.test.dyh</groupId>
    <artifactId>spring</artifactId>
    <version>1.0-SNAPSHOT</version>

    <name>spring</name>
    <url>http://www.example.com</url>
    <properties>

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

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.11</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>

        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.5</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.1</version>
        </dependency>

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

    <build>


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

        </resources>


        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>

    </build>
</project>

实体类

package com.test.dyh.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author dyh
 * @date 2021/6/3 - 15:55
 * @description:
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}

mybatis.xml

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

    <!--设置别名-->
    <typeAliases>
        <!--name所在包的路径-->
        <package name="com.test.dyh.pojo"/>
    </typeAliases>

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

            </transactionManager>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis
            ?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <!--sql mapper映射文件的位置-->
    <mappers>
       <mapper class="com.test.dyh.mapper.UserMapper"></mapper>

    </mappers>
</configuration>

接口

package com.test.dyh.mapper;

import com.test.dyh.pojo.User;

import java.util.List;

/**
 * @author dyh
 * @date 2021/6/3 - 16:02
 * @description:
 */
public interface UserMapper {
    public List<User> listUser();

}

mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.test.dyh.mapper.UserMapper">
    <select id="listUser" resultType="user">
select * from mybatis.user;

    </select>
</mapper>

测试类

package com.test.dyh;

import com.test.dyh.mapper.UserMapper;
import com.test.dyh.pojo.User;
import lombok.val;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

public class AppTest {
    @Test
    public void shouldAnswerWithTrue() throws IOException {
        String resources = "mybatis.xml";
        InputStream resourceAsStream = Resources.getResourceAsStream(resources);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = build.openSession(true);
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        List<User> users = mapper.listUser();
        for (User u : users) {
            System.err.println(u);
        }

    }
}
User(id=1, name=张三, pwd=124)

Spring事务

  • 声明式事务
  • 编程式事务

编程式和声明式事务的区别

Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。
简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

事务的四个特性

事务有四个特性:ACID

  • 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
  • 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
  • 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
  • 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

事务的传播行为

事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。Spring定义了七种传播行为:

传播行为

含义

PROPAGATION_REQUIRED

表示当前方法必须运行在事务中。如果当前事务存在,方法将会在该事务中运行。否则,会启动一个新的事务

PROPAGATION_SUPPORTS

表示当前方法不需要事务上下文,但是如果存在当前事务的话,那么该方法会在这个事务中运行

PROPAGATION_MANDATORY

表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常

PROPAGATION_REQUIRED_NEW

表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

PROPAGATION_NOT_SUPPORTED

表示该方法不应该运行在事务中。如果存在当前事务,在该方法运行期间,当前事务将被挂起。如果使用JTATransactionManager的话,则需要访问TransactionManager

PROPAGATION_NEVER

表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常

PROPAGATION_NESTED

表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持是有所差异的。可以参考资源管理器的文档来确认它们是否支持嵌套事务

事务的隔离级别

隔离级别

含义

ISOLATION_DEFAULT

使用后端数据库默认的隔离级别

ISOLATION_READ_UNCOMMITTED

最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

ISOLATION_READ_COMMITTED

允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

ISOLATION_REPEATABLE_READ

对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

ISOLATION_SERIALIZABLE

最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

编程式事务

如何实现编程式事务?

Spring提供两种方式的编程式事务管理,分别是:使用TransactionTemplate和直接使用PlatformTransactionManager

使用TransactionTemplate

采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:

TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
            }  
    }); // 执行execute方法进行事务管理

使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。

使用PlatformTransactionManager

示例代码如下:

DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager(); //定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
    dataSourceTransactionManager.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
    TransactionStatus status = dataSourceTransactionManager.getTransaction(transDef); // 获得事务状态
    try {
        // 数据库操作
        dataSourceTransactionManager.commit(status);// 提交
    } catch (Exception e) {
        dataSourceTransactionManager.rollback(status);// 回滚
    }

声明式事务

配置方式

根据代理机制的不同,总结了五种Spring事务的配置方式,配置文件如下:

(1)每个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"
    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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 
           <!-- 配置事务管理器 --> 
           <property name="transactionManager" ref="transactionManager" />    
        <property name="target" ref="userDaoTarget" /> 
         <property name="proxyInterfaces" value="com.bluesky.spring.dao.GeneratorDao" />
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop>
            </props> 
        </property> 
    </bean> 
</beans>
(2)所有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"
    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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="transactionBase" 
            class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean" 
            lazy-init="true" abstract="true"> 
        <!-- 配置事务管理器 --> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>   

    <!-- 配置DAO -->
    <bean id="userDaoTarget" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <bean id="userDao" parent="transactionBase" > 
        <property name="target" ref="userDaoTarget" />  
    </bean>
</beans>
(3)使用拦截器
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    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-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd">

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean> 

    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionInterceptor"> 
        <property name="transactionManager" ref="transactionManager" /> 
        <!-- 配置事务属性 --> 
        <property name="transactionAttributes"> 
            <props> 
                <prop key="*">PROPAGATION_REQUIRED</prop> 
            </props> 
        </property> 
    </bean>

    <bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"> 
        <property name="beanNames"> 
            <list> 
                <value>*Dao</value>
            </list> 
        </property> 
        <property name="interceptorNames"> 
            <list> 
                <value>transactionInterceptor</value> 
            </list> 
        </property> 
    </bean> 

    <!-- 配置DAO -->
    <bean id="userDao" class="com.bluesky.spring.dao.UserDaoImpl">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
</beans>
(4)使用tx标签配置的拦截器
<?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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <aop:pointcut id="interceptorPointCuts"
            expression="execution(* com.bluesky.spring.dao.*.*(..))" />
        <aop:advisor advice-ref="txAdvice"
            pointcut-ref="interceptorPointCuts" />       
    </aop:config>     
</beans>
(5)全注解
<?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"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context-2.5.xsd
           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <context:annotation-config />
    <context:component-scan base-package="com.bluesky" />

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="sessionFactory" 
            class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> 
        <property name="configLocation" value="classpath:hibernate.cfg.xml" /> 
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration" />
    </bean> 

    <!-- 定义事务管理器(声明式的事务) --> 
    <bean id="transactionManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

</beans>

此时在DAO上需加上@Transactional注解,如下:

package com.bluesky.spring.dao;

import java.util.List;

import org.hibernate.SessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.stereotype.Component;

import com.bluesky.spring.domain.User;

@Transactional
@Component("userDao")
public class UserDaoImpl extends HibernateDaoSupport implements UserDao {

    public List<User> listUsers() {
        return this.getSession().createQuery("from User").list();
    }  
}

一个声明式事务的实例

首先是数据库表
book(isbn, book_name, price)
account(username, balance)
book_stock(isbn, stock)

然后是XML配置

<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"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd">

    <import resource="applicationContext-db.xml" />

    <context:component-scan
        base-package="com.springinaction.transaction">
    </context:component-scan>

    <tx:annotation-driven transaction-manager="txManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

</beans>

使用的类
BookShopDao

package com.springinaction.transaction;

public interface BookShopDao {
    // 根据书号获取书的单价
    public int findBookPriceByIsbn(String isbn);
    // 更新书的库存,使书号对应的库存-1
    public void updateBookStock(String isbn);
    // 更新用户的账户余额:account的balance-price
    public void updateUserAccount(String username, int price);
}

BookShopDaoImpl

package com.springinaction.transaction;

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

@Repository("bookShopDao")
public class BookShopDaoImpl implements BookShopDao {

    @Autowired
    private JdbcTemplate JdbcTemplate;

    @Override
    public int findBookPriceByIsbn(String isbn) {
        String sql = "SELECT price FROM book WHERE isbn = ?";

        return JdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    @Override
    public void updateBookStock(String isbn) {
        //检查书的库存是否足够,若不够,则抛出异常
        String sql2 = "SELECT stock FROM book_stock WHERE isbn = ?";
        int stock = JdbcTemplate.queryForObject(sql2, Integer.class, isbn);
        if (stock == 0) {
            throw new BookStockException("库存不足!");
        }
        String sql = "UPDATE book_stock SET stock = stock - 1 WHERE isbn = ?";
        JdbcTemplate.update(sql, isbn);
    }

    @Override
    public void updateUserAccount(String username, int price) {
        //检查余额是否不足,若不足,则抛出异常
        String sql2 = "SELECT balance FROM account WHERE username = ?";
        int balance = JdbcTemplate.queryForObject(sql2, Integer.class, username);
        if (balance < price) {
            throw new UserAccountException("余额不足!");
        }       
        String sql = "UPDATE account SET balance = balance - ? WHERE username = ?";
        JdbcTemplate.update(sql, price, username);
    }

}

BookShopService

package com.springinaction.transaction;
public interface BookShopService {
     public void purchase(String username, String isbn);
}

BookShopServiceImpl

package com.springinaction.transaction;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service("bookShopService")
public class BookShopServiceImpl implements BookShopService {

    @Autowired
    private BookShopDao bookShopDao;

    /**
     * 1.添加事务注解
     * 使用propagation 指定事务的传播行为,即当前的事务方法被另外一个事务方法调用时如何使用事务。
     * 默认取值为REQUIRED,即使用调用方法的事务
     * REQUIRES_NEW:使用自己的事务,调用的事务方法的事务被挂起。
     *
     * 2.使用isolation 指定事务的隔离级别,最常用的取值为READ_COMMITTED
     * 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚,也可以通过对应的属性进行设置。通常情况下,默认值即可。
     * 4.使用readOnly 指定事务是否为只读。 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。若真的是一个只读取数据库值得方法,应设置readOnly=true
     * 5.使用timeOut 指定强制回滚之前事务可以占用的时间。
     */
    @Transactional(propagation=Propagation.REQUIRES_NEW,
            isolation=Isolation.READ_COMMITTED,
            noRollbackFor={UserAccountException.class},
            readOnly=true, timeout=3)
    @Override
    public void purchase(String username, String isbn) {
        //1.获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2.更新书的库存
        bookShopDao.updateBookStock(isbn);
        //3.更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }
}

Cashier

package com.springinaction.transaction;
import java.util.List;
public interface Cashier {
    public void checkout(String username, List<String>isbns);
}

CashierImpl:CashierImpl.checkout和bookShopService.purchase联合测试了事务的传播行为

package com.springinaction.transaction;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service("cashier")
public class CashierImpl implements Cashier {
    @Autowired
    private BookShopService bookShopService;

    @Transactional
    @Override
    public void checkout(String username, List<String> isbns) {
        for(String isbn : isbns) {
            bookShopService.purchase(username, isbn);
        }
    }
}

BookStockException

package com.springinaction.transaction;
public class BookStockException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public BookStockException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public BookStockException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

UserAccountException

package com.springinaction.transaction;
public class UserAccountException extends RuntimeException {

    private static final long serialVersionUID = 1L;

    public UserAccountException() {
        super();
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1, boolean arg2,
            boolean arg3) {
        super(arg0, arg1, arg2, arg3);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0, Throwable arg1) {
        super(arg0, arg1);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(String arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }

    public UserAccountException(Throwable arg0) {
        super(arg0);
        // TODO Auto-generated constructor stub
    }
}

测试类

package com.springinaction.transaction;

import java.util.Arrays;

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

public class SpringTransitionTest {

    private ApplicationContext ctx = null;
    private BookShopDao bookShopDao = null;
    private BookShopService bookShopService = null;
    private Cashier cashier = null;
    {
        ctx = new ClassPathXmlApplicationContext("config/transaction.xml");
        bookShopDao = ctx.getBean(BookShopDao.class);
        bookShopService = ctx.getBean(BookShopService.class);
        cashier = ctx.getBean(Cashier.class);
    }

    @Test
    public void testBookShopDaoFindPriceByIsbn() {
        System.out.println(bookShopDao.findBookPriceByIsbn("1001"));
    }

    @Test
    public void testBookShopDaoUpdateBookStock(){
        bookShopDao.updateBookStock("1001");
    }

    @Test
    public void testBookShopDaoUpdateUserAccount(){
        bookShopDao.updateUserAccount("AA", 100);
    }
    @Test
    public void testBookShopService(){
        bookShopService.purchase("AA", "1001");
    }

    @Test
    public void testTransactionPropagation(){
        cashier.checkout("AA", Arrays.asList("1001", "1002"));
    }
}