一,Spring的概述

1.Spring是什么?

Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 Java EE 企业应用开源框架。

2.Spring的优势?

  1. 方便解耦,简化开发
  2. AOP编程的支持
  3. 声明式事务的支持
  4. 方便程序的测试
  5. 方便集成各种优秀框架
  6. 降低JavaEE API 的使用难度
  7. 源码是经典的学习范例

3.Spring的体系结构图

spring架构.png

二,IOC

1.程序的耦合和解耦

耦合是影响软件复杂程度和设计质量的一个重要因素,在设计上我们应采用以下原则:如果模块间必须 存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

2.解决耦合的思路

Class.forName("com.mysql.jdbc.Driver");//此处只是一个字符串,此时的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 mysql 的驱动 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。

同时,也产生了一个新的问题,mysql 驱动的全限定类名字符串是在 java 类中写死的,一旦要改还是要修改源码。解决这个问题也很简单,使用配置文件配置。

3.工厂模式解耦

在实际开发中可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件,创建和获取三层对象的类就是工厂。

4.代码案例

4.1 AccountMapper

  1. /**
  2. * @author 二十
  3. * @since 2021/9/21 3:23 下午
  4. */
  5. public class AccountMapper {
  6. public void update(Account account) {
  7. System.out.println("调用了update()...");
  8. }
  9. public void add(Account account) {
  10. System.out.println("调用了add()...");
  11. }
  12. public void delete(Integer id) {
  13. System.out.println("调用了delete()...");
  14. }
  15. }

4.2 AccountService

/**
 * @author 二十
 * @since 2021/9/21 3:25 下午
 */
public class AccountService {

    private Account account = (Account) BeanFactory.getBean("account");
    private AccountMapper accountDao = (AccountMapper) BeanFactory.getBean("accountMapper");

    int i = 1;

    public void update(Account account) {
        accountDao.update(account);
    }

    public void add(Account account) {
        accountDao.add(account);
        System.out.println(i + 1);
    }

    public void delete(Integer id) {
        System.out.println(accountDao);
        accountDao.delete(id);
        System.out.println(account.toString());
        System.out.println(i + 1);
    }
}

4.3 BeanFactory

/**
 * @author 二十
 * @since 2021/9/21 3:27 下午
 */
public class BeanFactory {

    private static Properties prop;

    private static Map<String, Object> beans;

    private static InputStream in;

    private final static String configFile = "beans.properties";

    static {
        try {
            in = BeanFactory.class.getClassLoader().getResourceAsStream(configFile);
            prop = new Properties();
            prop.load(in);
            beans = new HashMap<>();
            Enumeration<Object> keys = prop.keys();
            while (keys.hasMoreElements()) {
                String key = keys.nextElement().toString();
                String path = prop.getProperty(key);
                Object value = Class.forName(path).newInstance();
                beans.put(key, value);
            }
        } catch (Exception e) {

        } finally {
            try {
                assert in != null;
                in.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public static Object getBean(String name) {
        assert name != null;
        return beans.get(name);
    }
}

4.4 beans.properties

accountService=com.es.service.AccountService
accountMapper=com.es.mapper.AccountMapper
account=com.es.domain.Account

4.5 单元测试

@Test
public void test(){
//AccountService service=new AccountServiceImpl();
AccountService service = (AccountService) BeanFactory.getBean("accountService");
service.delete(1);
}

5.控制反转

  1. 存哪去?

    分析:由于我们是很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
    到底选 Map 还是 List 就看我们有没有查找需求。有查找需求,选 Map。
    所以我们的答案就是在应用加载时,创建一个 Map,用于存放三层对象。我们把这个 map 称之为容器。

  2. 还是没解释什么是工厂?

    工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变。
    原来:我们在获取对象时,都是采用 new 的方式。是主动的。
    现在:我们获取对象时,同时跟工厂要,有工厂为我们查找或者创建对象。是被动的。 这种被动接收的方式获取对象的思想就是控制反转,它是 spring 框架的核心之一。

  3. 明确 ioc 的作用:

    削减计算机程序的耦合(解除我们代码中的依赖关系)。

三,使用IOC解决程序的耦合

1.基于xml形式的装配

1.1 步骤

  1. 创建maven工程,导入相关依赖
  2. 创建spring的配置文件,applicationContext.xml配置文件
  3. 让spring管理资源,在spring的配置文件中配置service和mapper

1.2 代码

/**
 * 测试基于xml形式的spring ioc获取对象
 *
 */
@Test
public void test3(){
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user= (User) ioc.getBean("user");//在此处打断点验证对象是什么时候被创建的。
    user.show();
}
<!-- 基于xml形式装配bean -->
<bean id="user" class="com.es.domain.User"></bean>

2.细节

2.1 BeanFactory和ApplicationContext的区别

BeanFactory 才是 Spring 容器中的顶层接口。

ApplicationContext 是它的子接口。

BeanFactory 和 ApplicationContext 的区别:创建对象的时间点不一样。

  • ApplicationContext:只要一读取配置文件,默认情况下就会创建对象。
  • BeanFactory:什么使用什么时候创建对象

2.2 ApplicationContext接口的实现类

ClasspathXmlApplicationContext:从类的根路径下加载配置文件,推荐使用这种方式。

FileSystemXmlApplicationContext:从磁盘路径上加载配置文件,可以指定在任意位置。

AnnotationConfigApplicationContext:当使用注解配置容器或者对象的时候,需要使用此类来创建 spring 容器,用来读取注解。

2.3 bean标签

作用:用于配置对象让 spring 来创建的。默认情况下它调用的是类中的无参构造函数。如果没有无参构造函数则不能创建成功。

属性:

  • id:给对象在容器中提供一个唯一标识。用于获取对象。
  • class:指定类的全限定类名。用于反射创建对象。默认情况下调用无参构造函数。
  • scope:指定对象的作用范围。
    • singleton :默认值,单例的.
    • prototype :多例的.
    • request :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中.
    • session :WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中.
    • global session :WEB 项目中,应用在 Portlet 环境.如果没有 Portlet 环境那么globalSession 相当于 session.
  • init-method:指定类中的初始化方法名称。
  • destroy-method:指定类中销毁方法名称。

2.4 bean的生命周期和作用范围

单例对象scope="singleton" 一个应用只有一个对象的实例。它的作用范围就是整个引用。

生命周期:

  • 对象出生:当应用加载,创建容器时,对象就被创建了。
  • 对象活着:只要容器在,对象一直活着。
  • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。

多例对象scope="prototype" 每次访问对象时,都会重新创建对象实例。

生命周期:

  • 对象出生:当使用对象时,创建新的对象实例。
  • 对象活着:只要对象在使用中,就一直活着。
  • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。

2.5 实例化bean的三种方式

2.5.1.使用无参构造器

默认情况下:它会根据默认无参构造器来创建类对象。如果bean中没有默认无参构造器,将会创建失败。

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"/>

2.5.2.静态工厂

此种方式是使用StaticFactory类中的静态方法创建对象,并存入Spring容器。

id属性:指定bean的id,用于从容器中获取。

class属性:指定静态工厂的全限定类名。

factory-method属性:指定生产对象的静态方法。

案例:

/**
* 模拟一个静态工厂,创建业务层实现类
*/
public class StaticFactory {
  public static IAccountService createAccountService(){
    return new AccountServiceImpl();
  } 
}
<bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="createAccountService"></bean>

2.5.3 实例工厂

此种方式先把工厂的创建交给spring容器来管理。然后再使用工厂的bean来调用里面的方法。

factory-bean属性:用于指定实例工厂bean的id。

factory-method属性:用于指定实例工厂中创建对象的方法。

案例:

/**
* 模拟一个实例工厂,创建业务层实现类
* 此工厂创建对象,必须现有工厂实例对象,再调用方法
*/
public class InstanceFactory {
  public IAccountService createAccountService(){
    return new AccountServiceImpl();
  } 
}
<bean id="instancFactory" class="com.itheima.factory.InstanceFactory"></bean> 
<bean id="accountService"factory-bean="instancFactory"factory- method="createAccountService"></bean>

3.依赖注入

依赖注入(Dependency Injection)他是spring框架的核心,ioc的具体实现。

编写程序的时候,通过控制反转,把对象的创建交给spring容器,但是代码中不可能出现没有依赖的情况。

ioc解耦只是降低他们的依赖关系,但是不会消除。比如我们的业务层仍然会调用持久层的方法。

那么这种业务层和持久层的依赖关系,在使用spring框架之后,就让spring来维护了。

简单地说,就是坐等框架把持久层的对象传入业务层,不需要开发人员手动去获取。

3.1 构造函数注入

就是使用类中的构造函数给成员变量赋值。

注意:赋值的操作不是我们自己做的,而是通过配置的方式,让spring框架来为我们注入。

3.2 set()注入

在类中提供需要注入的成员的set方法。

3.3 注入集合属性

给类中的集合成员传递值,他用的也是set方法的注入方式,只不过变量的数据类型都是集合。

3.4 案例

3.4.1 User

public class User {
    private String name;
    private Integer age;
    private Date birth;

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

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

    public void setBirth(Date birth) {
        this.birth = birth;
    }

    public User(){
        System.out.println("我被创建了...");
    }
    public void show(){
        System.out.println("user中的show方法背调用了。。。");
    }

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

3.4.2 Person

public class Person {
    private String name;
    private int age;

    public Person() {
    }

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

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

3.4.3 CollectionDemo

public class CollectionDemo {
    private  String [] arr;
    private  List<String> myList;
    private  Set<String> mySet;
    private  Map<String,String> myMap;
    private Properties myProp;

    public void setArr(String[] arr) {
        this.arr = arr;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProp(Properties myProp) {
        this.myProp = myProp;
    }

    public String[] getArr() {
        return arr;
    }

    public List<String> getMyList() {
        return myList;
    }

    public Set<String> getMySet() {
        return mySet;
    }

    public Map<String, String> getMyMap() {
        return myMap;
    }

    public Properties getMyProp() {
        return myProp;
    }
}

3.4.4配置文件

<!-- 基于xml形式装配bean -->
<bean id="user" class="com.es.java1.User"></bean>
<!--使用get方法创建bean-->
<bean id="user2" class="com.es.java1.User">
    <property name="name" value="张"></property>
    <property name="age">
        <value>20</value>
    </property>
    <property name="birth" ref="now"></property>
</bean>
<bean id="now" class="java.util.Date"></bean>
<!--集合和数组类型的依赖注入-->
<bean id="demo" class="com.es.java1.CollectionDemo">
    <property name="arr">
        <array>
            <value>111</value>
            <value>222</value>
            <value>333</value>
        </array>
    </property>
    <property name="myList">
        <list>
            <value>111</value>
            <value>222</value>
            <value>333</value>
        </list>
    </property>
    <property name="mySet">
        <set>
            <value>111</value>
            <value>222</value>
            <value>333</value>
        </set>
    </property>
    <property name="myMap">
        <map>
            <entry key="aaa" value="aaa"></entry>
            <entry key="bbb" value="bbb"></entry>
            <entry key="ccc" value="ccc"></entry>
        </map>
    </property>
    <property name="myProp">
        <props>
            <prop key="aaa">aaa</prop>
            <prop key="bbb">bbb</prop>
            <prop key="ccc">ccc</prop>
        </props>
    </property>
</bean>
<!--使用默认构造器创建bean-->
<bean id="person" class="com.es.java1.Person">
    <constructor-arg name="name" value="张"></constructor-arg>
    <constructor-arg name="age" value="20"></constructor-arg>
</bean>

3.4.5 测试类

/**
 * 测试基于xml形式的spring ioc获取对象
 *
 */
@Test
public void test3(){
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user= (User) ioc.getBean("user");//在此处打断点验证对象是什么时候被创建的。
    user.show();
}
/**
 * 采用默认构造器的形式创建bean对象
 */
@Test
public void test(){
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    Person p= (Person) ioc.getBean("person");
    Person p2= (Person) ioc.getBean("person");
    System.out.println(p.toString());
}
/**
 * 使用get方法进行依赖注入
 */
@Test
public void test4(){
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    User user= (User) ioc.getBean("user2");//在此处打断点验证对象是什么时候被创建的。
    System.out.println(user.toString());
}

/**
 * 集合和数组的依赖注入
 */
@Test
public void test5(){
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    CollectionDemo demo= (CollectionDemo) ioc.getBean("demo");
    System.out.println(Arrays.toString(demo.getArr()));
    System.out.println(demo.getMyList());
    System.out.println(demo.getMySet());
    System.out.println(demo.getMyMap());
    System.out.println(demo.getMyProp());
}

四,使用ioc实现账户CRUD

1.基于xml形式

1.1 引用外部属性文件

<!-- 引用外部属性文件 -->
    <context:property-placeholder location="classpath:druid.properties"/>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="username" value="${jdbc.username}"></property>
    <property name="url" value="${jdbc.url}"></property>
    <property name="password" value="${jdbc.password}"></property>
    <property name="driverClassName" value="${driverClassName}"></property>
    <property name="initialSize" value="${initialSize}"></property>
    <property name="maxActive" value="${maxActive}"></property>
    </bean>

1.2 SPEL表达式

1.2.1 简介

Spring Expression Language,Spring表达式语言,简称SpEL。支持运行时查询并可以操作对象图。

和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,SpEL根据JavaBean风格的getXxx()、setXxx()方法定义的属性访问对象图,完全符合我们熟悉的操作习惯。

1.2.2 基本语法

SpEL使用#{…}作为定界符,所有在大框号中的字符都将被认为是SpEL表达式。

1.2.3 使用字面量

●整数:<property name="count" value="#{5}"/><br />    ●小数:<property name="frequency" value="#{89.7}"/><br />    ●科学计数法:<property name="capacity" value="#{1e4}"/><br />    ●String类型的字面量可以使用单引号或者双引号作为字符串的定界符号<br />        <property name=”name” value="#{'Chuck'}"/><br />        <property name='name' value='#{"Chuck"}'/><br />    ●Boolean:<property name="enabled" value="#{false}"/>

1.2.4 引用其他bean

<bean id="emp04" class="com.es.parent.bean.Employee">
  <property name="empId" value="1003"/>
  <property name="empName" value="Kate"/>
  <property name="age" value="21"/>
  <property name="detp" value="#{dept}"/>
</bean>

1.2.5 引用其他bean的属性值作为自己某个属性的值

<bean id="emp05" class="com.es.parent.bean.Employee">
  <property name="empId" value="1003"/>
  <property name="empName" value="Kate"/>
  <property name="age" value="21"/>
  <property name="deptName" value="#{dept.deptName}"/>
</bean>

1.2.6调用非静态方法

<!-- 创建一个对象,在SpEL表达式中调用这个对象的方法 -->
<bean id="salaryGenerator" class="com.es.spel.bean.SalaryGenerator"/>

<bean id="employee" class="com.es.spel.bean.Employee">
  <!-- 通过对象方法的返回值为属性赋值 -->
  <property name="salayOfYear" value="#{salaryGenerator.getSalaryOfYear(5000)}"/>
</bean>

1.2.7调用静态方法

<bean id="employee" class="com.es.spel.bean.Employee">
  <!-- 在SpEL表达式中调用类的静态方法 -->
  <property name="circle" value="#{T(java.lang.Math).PI*20}"/>
</bean>

1.2.8 运算符

①算术运算符:+、-、*、/、%、^
②字符串连接:+
③比较运算符:<、>、==、<=、>=、lt、gt、eq、le、ge
④逻辑运算符:and, or, not, |
⑤三目运算符:判断条件?判断结果为true时的取值:判断结果为false时的取值
⑥正则表达式:matches

1.3 Spring的#和$的区别

${key名称}

  1. 用户获取外部文件中指定key的值;

  2. 可以出现在xml配置文件中,也可以出现在注解@Value中;

  3. 一般用户获取数据库配置文件的内容信息等。

#{表达式}

  1. SpEL表达式的格式,详情(https://blog.csdn.net/xingfei_work/article/details/76058178));

  2. 可以出现在xml配置文件中,也可以出现在注解@Value中

  3. 可以任意表达式,支持运算符等。

1.4 案例

1.4.1 配置文件

<?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <!--设置自动扫描的包-->
    <context:component-scan base-package="com.es"></context:component-scan>
    <bean id="accountDao" class="com.es.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"></property>
    </bean>
    <bean id="accountService" class="com.es.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <bean id="account" class="com.es.domain.Account"></bean>
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="yhd666"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>
</beans>

1.4.2 持久层

package com.es.dao.impl;


import com.es.dao.IAccountDao;
import com.es.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 账户的持久层实现类
 */

public class AccountDaoImpl implements IAccountDao {

    private QueryRunner runner;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }


    public List<Account> findAllAccount() {
        try{
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public Account findAccountById(Integer accountId) {
        try{
            return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void saveAccount(Account account) {
        try{
            runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void updateAccount(Account account) {
        try{
            runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

1.4.3 业务层

package com.es.service.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;

import com.es.domain.Account;
import com.es.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 账户的业务层实现类
 */

public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }


    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }


    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }


    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }


    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }
}

1.4.4 测试类

public class Test1 {
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test1(){
        IAccountService service= (IAccountService) ioc.getBean("accountService");
        service.deleteAccount(2);
    }
}

2.xml和注解混搭

2.1 用于创建对象

他们的作用就和在XML配置文件中编写一个标签实现的功能是一样的。

  • Component:

    作用:用于把当前类对象存入spring容器中
    属性:
    value:用于指定bean的id。当我们不写时,它的默认值是当前类名,且首字母改小写。

  • Controller:一般用在表现层

  • Service:一般用在业务层
  • Repository:一般用在持久层

以上个注解他们的作用和属性与Component是一模一样。

他们是spring框架为我们提供明确的层使用的注解,使我们的层对象更加清晰。

2.2 用于注入数据

他们的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的。

  • Autowired:

    作用:自动照类型注入。只要容器中唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功
    如果ioc容器中没任何bean的类型和要注入的变量类型匹配,则报错。
    如果Ioc容器中多个类型匹配时:
    出现位置:
    可以是变量上,也可以是方法上
    细节:
    在使用注解注入时,set方法就不是必须的了。

  • Qualifier:

    作用:在照类型注入的基础之上再照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
    属性:
    value:用于指定注入bean的id。

  • Resource

    作用:直接照bean的id注入。它可以独立使用
    属性:
    name:用于指定bean的id。
    以上个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
    另外,集合类型的注入只能通过XML来实现。

  • Value

    作用:用于注入基本类型和String类型的数据
    属性:
    value:用于指定数据的值。它可以使用spring中SpEL(也就是spring的el表达式,SpEL的写法:${表达式}

2.3.用于改变作用范围

他们的作用就和在bean标签中使用scope属性实现的功能是一样的。

  • Scope

    作用:用于指定bean的作用范围
    属性:
    value:指定范围的取值。常用取值:singleton prototype

2.4 和生命周期相关

他们的作用就和在bean标签中使用init-method和destroy-methode的作用是一样的。

  • PreDestroy

    作用:用于指定销毁方法

  • PostConstruct

    作用:用于指定初始化方法

2.5 案例

2.5.1 配置文件

<?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"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <!--设置自动扫描的包-->
    <context:component-scan base-package="com.es"></context:component-scan>
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="username" value="root"></property>
        <property name="password" value="yhd666"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>
</beans>

2.5.2 持久层

package com.es.dao.impl;


import com.es.dao.IAccountDao;
import com.es.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 账户的持久层实现类
 */
@Repository(value = "accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner runner;




    public List<Account> findAllAccount() {
        try{
            return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public Account findAccountById(Integer accountId) {
        try{
            return runner.query("select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void saveAccount(Account account) {
        try{
            runner.update("insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void updateAccount(Account account) {
        try{
            runner.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void deleteAccount(Integer accountId) {
        try{
            runner.update("delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

2.5.3 业务层

package com.es.service.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;

import com.es.domain.Account;
import com.es.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 账户的业务层实现类
 */
@Service("accountService")
public class AccountServiceImpl implements IAccountService{
    @Autowired
    private IAccountDao accountDao;

    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }


    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);
    }


    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }


    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }


    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }
}

2.5.4 测试类

import com.es.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author yinhuidong
 * @createTime 2020-03-01-11:03
 */
public class Test1 {
    ApplicationContext ioc=new ClassPathXmlApplicationContext("applicationContext.xml");
    @Test
    public void test1(){
        IAccountService service= (IAccountService) ioc.getBean("accountService");
        service.deleteAccount(2);
    }
}

3.纯注解配置

3.1 注解

  • Configuration

作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。

  • ComponentScan

作用:用于通过注解指定spring在创建容器时要扫描的包
属性:
value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用此注解就等同于在xml中配置了:
<context:component-scan base-package="com.itheima"></context:component-scan>

  • Bean

作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:
当我们使用注解配置方法时,如果有方法参数,spring框架会去容器中查找没可用的bean对象。
查找的方式和Autowired注解的作用是一样的

  • Import

    作用:用于导入其他的配置类<br />      属性:<br />          value:用于指定其他配置类的字节码。<br />                  当我们使用Import的注解之后,Import注解的类就父配置类,而导入的都是子配置类
    
  • PropertySource

    作用:用于指定properties文件的位置<br />       属性:<br />         value:指定文件的名称和路径。<br />                  关键字:classpath,表示类路径下
    

3.2 spring整合junit4

/**

1、应用程序的入口
    main方法
2、junit单元测试中,没有main方法也能执行
    junit集成了一个main方法
    该方法就会判断当前测试类中哪些方法有 @Test注解
    junit就让有Test注解的方法执行
3、junit不会管我们是否采用spring框架
    在执行测试方法时,junit根本不知道我们是不是使用了spring框架
    所以也就不会为我们读取配置文件/配置类创建spring核心容器
4、由以上三点可知
    当测试方法执行时,没有Ioc容器,就算写了Autowired注解,也无法实现注入

-------------------------------------------------------------------------
 * 使用Junit单元测试:测试我们的配置
 * Spring整合junit的配置
 *      1、导入spring整合junit的jar(坐标)
 *      2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
 *             @Runwith(SpringJUnit4ClassRunner.class)
 *      3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
 *          @ContextConfiguration
 *                  locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
 *                  classes:指定注解类所在地位置
 *
 *   当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
 */

3.3 案例

3.3.1 配置类

/**
 * @author yinhuidong
 * @createTime 2020-03-01-16:43
 */
@Configuration
@ComponentScan("com.es")
@Import(JdbcConfig.class)
@PropertySource("classpath:c3p0.properties")
public class SpringConfig {

}

3.3.2 配置子类

/**
 * @author yinhuidong
 * @createTime 2020-03-01-16:56
 */

public class JdbcConfig {
    @Bean(name="runner")
    @Scope(value = "prototype")
    public QueryRunner getRunner(@Qualifier("ds1") DataSource dataSource) {
        QueryRunner runner = new QueryRunner(dataSource);
        return runner;
    }

    private static DataSource dataSource = null;

    @Bean(name="ds1")
    public DataSource getDataSource() {
        try {
            Properties prop = new Properties();
            InputStream is = JdbcConfig.class.getClassLoader().getResourceAsStream("jdbc.properties");
            prop.load(is);
            dataSource = DruidDataSourceFactory.createDataSource(prop);
            return dataSource;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    @Bean(name="ds2")
    public DataSource getDataSource2(){
        try {
            ComboPooledDataSource dataSource=new ComboPooledDataSource();
            dataSource.setDriverClass(driver);
            dataSource.setJdbcUrl(url);
            dataSource.setUser(username);
            dataSource.setPassword(password);
            return dataSource;
        } catch (PropertyVetoException e) {
            e.printStackTrace();
        }
        return null;
    }
}

3.3.3 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = com.es.java1.SpringConfig.class)
public class Test1 {
    @Autowired
    private IAccountService service = null;

    @Test
    public void test1() {
        service.deleteAccount(5);
    }
}

五,AOP面向切面编程

1.动态代理

1.1 基于接口的动态代理

public class Test1 {
    @Test
    public void test1(){
        //被代理类对象要声明为最终的
        final Producer producer=new Producer();
        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于接口的动态代理:
         *      涉及的类:Proxy
         *      提供者:JDK官方
         *  如何创建代理对象:
         *      使用Proxy类中的newProxyInstance方法
         *  创建代理对象的要求:
         *      被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         */
        //代理对象和被代理类对象要实现同一个接口
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用:执行被代理对象的任何接口方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy   代理对象的引用
                     * 1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName()。
                     * 2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
                     * @param method  当前执行的方法
                     * @param args    当前执行方法所需的参数
                     * @return        和被代理对象方法相同的返回值
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object value=null;
                        //获取方法执行的参数

                        //判断当前方法是不是销售
                        if ("saleProduct".equals(method.getName())){
                            Float money= (Float) args[0];
                            //两个参数:被代理类对象,方法增强的参数
                            value=method.invoke(producer,money*0.8f);
                        }
                        return value;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}
package com.es.java1;

/**
 * 一个生产者
 */
public class Producer implements IProducer{

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}


-------------------------------------------------------------------
/**
 * 对生产厂家要求的接口
 */
public interface IProducer {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money);

    /**
     * 售后
     * @param money
     */
    public void afterService(float money);
}

1.2 基于子类的动态代理

/**
 * @author yinhuidong
 * @createTime 2020-03-02-1:08
 */
public class Test4 {
    @Test
    public void test() {
        final Producer producer = new Producer();
        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于子类的动态代理:
         *      涉及的类:Enhancer
         *      提供者:第方cglib库
         *  如何创建代理对象:
         *      使用Enhancer类中的create方法
         *  创建代理对象的要求:
         *      被代理类是最终类
         *  create方法的参数:
         *      Class:字节码
         *          它是用于指定被代理对象的字节码。
         *
         *      Callback:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
         */
        Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;
                //1.获取方法执行的参数
                Float money = (Float) args[0];
                //2.判断当前方法是不是销售
                if ("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money * 0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}
package com.es.java2;

/**
 * 一个生产者
 */
public class Producer {

    /**
     * 销售
     * @param money
     */
    public void saleProduct(float money){
        System.out.println("销售产品,并拿到钱:"+money);
    }

    /**
     * 售后
     * @param money
     */
    public void afterService(float money){
        System.out.println("提供售后服务,并拿到钱:"+money);
    }
}

1.3 使用动态代理对spring进行方法增强

接口和实现类

public interface MyInterface {
    public int add(int a,int b);
    public int del(int a,int b);
    public int che(int a,int b);
    public int div(int a,int b);
}

----------------------------------------------------------
public class java1 implements MyInterface{
    public int add(int a,int b){
        return a+b;
    }
    public int del(int a,int b){
        return a-b;
    }
    public int che(int a,int b){
        return a*b;
    }
    public int div(int a,int b){
        return a/b;
    }
}

BeanFactory

public class BeanFactory {
    private  java1 java;

    public void setJava(java1 java) {
        this.java = java;
    }

    public MyInterface getBean(){
        MyInterface proxyJava = (MyInterface) Proxy.newProxyInstance(
                java.getClass().getClassLoader(),
                java.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object value = null;
                        System.out.println("方法执行前....");
                        value = method.invoke(java, args);
                        System.out.println("方法执行之后....");
                        return value;
                    }
                }
        );
        return proxyJava;
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    @Qualifier("proxyJava")
    private MyInterface myInterface;
    @Test
    public void test1(){
        System.out.println(myInterface.add(1, 2));
    }
    @Test
    public void test2(){
        System.out.println(myInterface.del(1, 2));
    }
    @Test
    public void test3(){
        System.out.println(myInterface.che(1, 2));
    }
    @Test
    public void test4(){
        System.out.println(myInterface.div(1, 2));
    }
}

配置文件

<bean id="factory" class="com.es.factory.BeanFactory">
    <property name="java" ref="java"></property>
</bean>

<bean id="java" class="com.es.java1.java1"></bean>

<bean id="proxyJava" factory-bean="factory" factory-method="getBean"></bean>

2.AOP

2.1 AOP相关概念

AOP:全称是 Aspect Oriented Programming即:面向切面编程。就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态代理的技术,在不修改源码的基础上,对我们的已有方法进行增强。

  • 作用:

      在程序运行期间,不修改源码对已有方法进行增强。
    
  • 优势:

      减少重复代码<br />        提高开发效率<br />        维护方便<br />**AOP 相关术语**
    
  • Joinpoint(连接点):

      所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
    
  • Pointcut(切入点):

      所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
    
  • Advice(通知/增强):

      所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。<br />    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
    
  • Introduction(引介):

      引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
    
  • Target(目标对象):被代理对象

      代理的目标对象。
    
  • Weaving(织入):

      是指把增强应用到目标对象来创建新的代理对象的过程。<br />        spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入。
    
  • Proxy(代理:

      一个类被 AOP 织入增强后,就产生一个结果代理类。
    
  • Aspect(切面):

      是切入点和通知(引介的结合
    

Spring 框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行。

2.2 基于xml形式的配置

2.2.1 配置步骤

<!--spring中基于XML的AOP配置步骤
    1、把通知Bean也交给spring来管理
    2、使用aop:config标签表明开始AOP的配置
    3、使用aop:aspect标签表明配置切面
            id属性:是给切面提供一个唯一标识
            ref属性:是指定通知类bean的Id。
    4、在aop:aspect标签的内部使用对应标签来配置通知的类型
           我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知
           aop:before:表示配置前置通知
                method属性:用于指定Logger类中哪个方法是前置通知
                pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
        切入点表达式的写法:
            关键字:execution(表达式)
            表达式:
                访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
            标准的表达式写法:
                public void com.itheima.service.impl.AccountServiceImpl.saveAccount()
            访问修饰符可以省略
                void com.itheima.service.impl.AccountServiceImpl.saveAccount()
            返回值可以使用通配符,表示任意返回值
                * com.itheima.service.impl.AccountServiceImpl.saveAccount()
            包名可以使用通配符,表示任意包。但是几级包,就需要写几个*.
                * *.*.*.*.AccountServiceImpl.saveAccount())
            包名可以使用..表示当前包及其子包
                * *..AccountServiceImpl.saveAccount()
            类名和方法名都可以使用*来实现通配
                * *..*.*()
            参数列表:
                可以直接写数据类型:
                    基本类型直接写名称           int
                    引用类型写包名.类名的方式   java.lang.String
                可以使用通配符表示任意类型,但是必须参数
                可以使用..表示无参数均可,参数可以是任意类型
            全通配写法:
                * *..*.*(..)

            实际开发中切入点表达式的通常写法:
                切到业务层实现类下的所方法
                    * com.itheima.service.impl.*.*(..)
-->

配置文件

<!--配置bean-->
<bean id="java" class="com.es.java1.java1"></bean>
<bean id="logging" class="com.es.java2.Logging"></bean>
<!-- 配置aop -->
<aop:config>
    <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容
          此标签写在aop:aspect标签内部只能当前切面使用。
          它还可以写在aop:aspect外面,此时就变成了所切面可用
      -->
    <aop:pointcut id="a" expression="execution(public int com.es.java1.java1.*(int,int))"></aop:pointcut>
    <!--配置切面-->
    <aop:aspect id="log" ref="logging">
        <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联-->
        <!-- 配置前置通知:在切入点方法执行之前执行 -->
        <aop:before method="before"
                    pointcut="execution(public int com.es.java1.java1.*(int,int))"></aop:before>
        <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个-->
        <aop:after-returning method="afterReturn" pointcut-ref="a"></aop:after-returning>
        <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个-->
        <aop:after-throwing method="afterThrowing" pointcut-ref="a"></aop:after-throwing>
        <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 -->
        <aop:after method="after" pointcut-ref="a"></aop:after>
        <!-- 配置环绕通知 详细的注释 :Logger类中-->
        <aop:around method="around" pointcut-ref="a"></aop:around>
    </aop:aspect>
</aop:config>

实现类

public class java1 implements MyInterface{
    public int add(int a,int b){
        return a+b;
    }
    public int del(int a,int b){
        return a-b;
    }
    public int che(int a,int b){
        return a*b;
    }
    public int div(int a,int b){
        return a/b;
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    @Qualifier("java")
    private MyInterface java;
    @Test
    public void test1(){
        java.div(1, 1);
    }
}

日志类

public class Logging {
    public void before(){
        System.out.println("★★★前置★★★");
    }
    public void afterReturn(){
        System.out.println("★★★后置★★★");
    }
    public void afterThrowing(){
            System.out.println("★★★异常★★★");
    }
    public void after(){
        System.out.println("★★★最终★★★");
    }
    /**
     * 环绕通知
     * 问题:
     *      当我们配置了环绕通知之后,切入点方法没执行,而通知方法执行了。
     * 分析:
     *      通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知明确的切入点方法调用,而我们的代码中没。
     * 解决:
     *      Spring框架为我们提供了一个接口:ProceedingJoinPoint。该接口一个方法proceed(),此方法就相当于明确调用切入点方法。
     *      该接口可以作为环绕通知的方法参数,在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
     *
     * spring中的环绕通知:
     *      它是spring框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object around(ProceedingJoinPoint pjp){
        Object value=null;
        //获取方法执行的参数
        Object []args=pjp.getArgs();
        //获取方法名
        String methodName = pjp.getSignature().getName();
        try {
            System.out.println("★★★前置★★★"+methodName+"..."+Arrays.toString(args));
            value=pjp.proceed(args);
            System.out.println("★★★后置★★★"+methodName+"..."+Arrays.toString(args)+"..."+value);
            return value;
        } catch (Throwable throwable) {
            System.out.println("★★★异常★★★"+methodName+"..."+Arrays.toString(args));
            throw new RuntimeException(throwable);
        }finally {
            System.out.println("★★★最终★★★"+methodName+"..."+Arrays.toString(args)+"..."+value);
        }
    }
}

2.3 基于注解的配置

2.3.1 步骤

  1. 首先在配置文件里开启声明式aop注解支持

    <!--开启声明式事务注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
  2. 在logging类上声明其为一个切面类

    @Aspect//声明一个切面
    
  3. 在类中声明一个方法作为切入点表达式 ```java @Pointcut(“execution(public com.es.java1.java1.(..))”) public void pointcut(){

}


4. 在各个方法上添加注解
4. 设置切面优先级
```java
@Order(2)//通过@Order(2)注解指定切面优先级,value值越小,优先级越高,默认是int最大值。

2.3.2 案例

配置文件

 <!--开启声明式事务注解-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy>
        <!--设置自动扫描的包-->
<context:component-scan base-package="com.es"></context:component-scan>

日志类

package com.es.utils;

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

import java.util.Arrays;

/**
 * @author yinhuidong
 * @createTime 2020-03-04-16:37
 */
@Component("logging")
@Aspect
public class Logging {
    @Before(value="execution(* com.es.java1.*.*(..))")
    public void before(JoinPoint jp){
        Object[] args = jp.getArgs();
        String name = jp.getSignature().getName();
        System.out.println("before..."+name+"..."+Arrays.toString(args));
    }
    @AfterReturning(value="execution(* com.es.java1.*.*(..))",returning = "result")
    public void afterReturning(JoinPoint jp,Object result){
        Object[] args = jp.getArgs();
        String name = jp.getSignature().getName();
        System.out.println("afterReturning..."+name+"..."+Arrays.toString(args)+"..."+result);
    }
    @AfterThrowing(value="execution(* com.es.java1.*.*(..))",throwing = "e")
    public void afterThrowing(JoinPoint jp,Exception e){
        Object[] args = jp.getArgs();
        String name = jp.getSignature().getName();
        System.out.println("afterThrowing..."+name+"..."+Arrays.toString(args)+"..."+e);
    }
    @After("execution(* com.es.java1.*.*(..))")
    public void after(JoinPoint jp){
        Object[] args = jp.getArgs();
        String name = jp.getSignature().getName();
        System.out.println("after..."+name+"..."+Arrays.toString(args));
    }
}

实现类

@Component("add")
public class Add {
    public void add(int a ,int b){
        System.out.println(a+b);
    }
    public int del(int a, int b){
        return a-b;
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    private Add add;
    @Test
    public void test1(){
        add.add(1,1);
        System.out.println(add.del(1, 1));
    }
}

补充:

    //配置通用的切入点表达式
    @Pointcut(value = "execution(* com.es.dao.impl.ComputerDaoImpl.*(..))")
    public void pointCut(){

    }
    //引用通用的切入点表达式
    @Before("pointCut()")
    public void before(JoinPoint point){
        System.out.println("前置通知-->"+"方法名:"+point.getSignature().getName()+"参数列表:"+ Arrays.asList(point.getArgs()));
    }

六,spring的事务

1.基于AOP的事务处理模拟

1.1 步骤

  1. 创建一个链接工具类,负责从线程获取连接,并实现与线程的绑定。
  2. 创建和事务管理相关的工具类,负责处理事务操作
  3. 利用aop实现事务处理

1.2 案例

配置文件

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

    <!--配置dao-->
    <bean id="accountDao" class="com.es.dao.impl.AccountDaoImpl">
        <property name="runner" ref="runner"></property>
        <property name="connectionUtils" ref="connection"></property>
    </bean>
    <!--配置service-->
    <bean id="accountService" class="com.es.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置queryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <!--<constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    </bean>
    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/eesy"></property>
        <property name="password" value="yhd666"></property>
        <property name="username" value="root"></property>
    </bean>
    <!--配置transactionManager-->
    <bean id="transactionManager" class="com.es.utils.TransactionManager">
        <property name="connectionUtils" ref="connection"></property>
    </bean>
    <!--配置ConnectionUtils-->
    <bean id="connection" class="com.es.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置aop-->
    <aop:config>
        <!--配置切入点表达式-->
        <aop:pointcut id="txAdvice" expression="execution(* com.es.service.impl.*.*(..))"></aop:pointcut>
        <aop:aspect id="txA" ref="transactionManager">
            <aop:before method="beginTransaction" pointcut-ref="txAdvice"></aop:before>
            <aop:after-returning method="commit" pointcut-ref="txAdvice"></aop:after-returning>
            <aop:after-throwing method="rollback" pointcut-ref="txAdvice"></aop:after-throwing>
            <aop:after method="release" pointcut-ref="txAdvice"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>

持久层

package com.es.dao.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;
import com.es.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;

import java.util.List;

/**
 * 账户的持久层实现类
 */

public class AccountDaoImpl  implements IAccountDao {


    private QueryRunner runner;
    private ConnectionUtils connectionUtils;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }


    public List<Account> findAllAccount() {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public Account findAccountById(Integer accountId) {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account where id = ? ",new BeanHandler<Account>(Account.class),accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void saveAccount(Account account) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"insert into account(name,money)values(?,?)",account.getName(),account.getMoney());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void updateAccount(Account account) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public void deleteAccount(Integer accountId) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }


    public Account findAccountByName(String accountName) {
        try{
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(Account.class),accountName);
            if(accounts == null || accounts.size() == 0){
                return null;
            }
            if(accounts.size() > 1){
                throw new RuntimeException("结果集不唯一,数据有问题");
            }
            return accounts.get(0);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

服务层

package com.es.service.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;
import com.es.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */

public class AccountServiceImpl implements IAccountService{


    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }


    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

            int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}

连接工具类

package com.es.utils;

import javax.sql.DataSource;
import java.sql.Connection;

/**
 * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
 */
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            Connection conn = tl.get();
            //2.判断当前线程上是否连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

事务处理类

package com.es.utils;

import org.springframework.stereotype.Component;

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */

public class TransactionManager {

    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }


    /**
     * 释放连接
     */
    public  void release(){
        try {
            connectionUtils.getThreadConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2.spring的事务配置

2.1 spring中基于xml的事务配置

2.2.1 配置步骤

spring中基于XML的声明式事务控制配置步骤

  1. 配置事务管理器
  2. 配置事务的通知

     此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的<br />        使用tx:advice标签配置事务通知<br />            属性:<br />                id:给事务通知起一个唯一标识<br />                transaction-manager:给事务通知提供一个事务管理器引用
    
  3. 配置AOP中的通用切入点表达式

  4. 建立事务通知和切入点表达式的对应关系
  5. 配置事务的属性

    是在事务的通知tx:advice标签的内部
    

配置事务的属性

  1. isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
  2. propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
  3. read-only:用于指定事务是否只读。只查询方法才能设置为true。默认值是false,表示读写。
  4. timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
  5. rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
  6. no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。

2.2.2 案例

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <context:component-scan base-package="com.es"></context:component-scan>
    <bean id="accountDao" class="com.es.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <bean id="account" class="com.es.domain.Account"></bean>
    <bean id="accountService" class="com.es.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="yhd666"></property>
        <property name="url" value="jdbc:mysql:///eesy"></property>
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
    </bean>
    <!-- spring中基于XML的声明式事务控制配置步骤
        1、配置事务管理器
        2、配置事务的通知
                此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的
                使用tx:advice标签配置事务通知
                    属性:
                        id:给事务通知起一个唯一标识
                        transaction-manager:给事务通知提供一个事务管理器引用
        3、配置AOP中的通用切入点表达式
        4、建立事务通知和切入点表达式的对应关系
        5、配置事务的属性
               是在事务的通知tx:advice标签的内部

    -->
    <!--配置事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务的通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!-- 配置事务的属性
                       isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
                       propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
                       read-only:用于指定事务是否只读。只查询方法才能设置为true。默认值是false,表示读写。
                       timeout:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
                       rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
                       no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
               -->
        <tx:attributes>
            <tx:method name="*" isolation="DEFAULT" propagation="REQUIRED" timeout="-1" read-only="false"/>
            <tx:method name="find*" isolation="DEFAULT" propagation="SUPPORTS" read-only="true" timeout="-1"/>
        </tx:attributes>
    </tx:advice>
    <aop:config>
        <aop:pointcut id="pointCut" expression="execution(* com.es.service.impl.*.*(..))"></aop:pointcut>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pointCut"></aop:advisor>
    </aop:config>
</beans>

持久层

package com.es.dao.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

import java.util.List;

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {


    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }


    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }


    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

服务层

package com.es.service.impl;

import com.es.dao.IAccountDao;
import com.es.domain.Account;
import com.es.service.IAccountService;

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl implements IAccountService{

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }




    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

           // int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}

测试类

import com.es.service.IAccountService;
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;

/**
 * @author yinhuidong
 * @createTime 2020-03-03-12:03
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    private IAccountService service;
    @Test
    public void test1(){
        service.transfer("aaa","bbb",1000f);
    }
}

2.2 spring中基于注解的事务配置

2.2.1 配置步骤

  1. 在配置文件配置事务管理器
  2. 开始声明式事务的支持
  3. 在对应的方法上加@Transcational注解,事务声明注解:该注解可以添加到类或者方法上

属性:
propagation:用来设置事务的传播行为:一个方法运行在一个开启了事务的方法中,当前方法是使用原有的事务还是开启新事物
required:如果有事务就使用,没有就开启一个新的(默认)
required_new:必须开启新事物
supports:如果有事务就运行,否则也不开启新的事务
no_supports:即使有事务也不用

2.2.2 案例

配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jdbc="http://www.springframework.org/schema/jdbc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-4.2.xsd
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd
        http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd
        http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.2.xsd">
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <context:property-placeholder location="classpath:druid.properties"></context:property-placeholder>
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${driverClassName}"></property>
        <property name="url" value="${url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!-- spring中基于注解 的声明式事务控制配置步骤
        1、配置事务管理器
        2、开启spring对注解事务的支持
        3、在需要事务支持的地方使用@Transactional注解
     -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 开启spring对注解事务的支持-->
    <tx:annotation-driven></tx:annotation-driven>
    <!--组件扫描-->
    <context:component-scan base-package="com.es"></context:component-scan>
</beans>

持久层

/**
 * 账户的持久层实现类
 */
@Repository("accountDao")
public class AccountDaoImpl  implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }


    public Account findAccountByName(String accountName) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }


    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

服务层

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
@Service("accountService")
@Transactional(readOnly = false,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService{
    @Autowired
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }


    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }



    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

            int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:applicationContext.xml")
public class Test1 {
    @Autowired
    private IAccountService service;
    @Test
    public void test1(){
        service.transfer("aaa","bbb",1000f);
    }
}