Spring基础知识学习

1-Spring概述

Spring概述

spring是什么
spring两大核心
spring发展历程和优势
spring的结构

程序的耦合和解耦

曾经案例的问题
工厂模式解耦

IOC概念和srping的IOC

IOC(Inverse Of Control:反转控制)
spring基于xml的ioc环境搭建

依赖注入Dependency Injection

2-基于XML的Spring

xml配置文件

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

spring基于xml配置文件的入门案例

  • 目录结构
src
    main
        java
            com.hejiale
                dao
                    impl
                        AccountDaoImpl.java
                    IAccountDao.java
                service
                    impl
                        AccountServiceImpl.java
                    IAccountService.java
                ui
                    Client.java
        resources
            bean.xml
    test
  • bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        把对象的创建交给spring来管理
        1.  bean
            1.  id  获取时的唯一标识
            2.  class  要创建对象的全限定类名
    -->
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl" ></bean>
    <bean id="accountDao" class="com.hejiale.dao.impl.AccountDaoImpl"></bean>
</beans>
  • IAccountService及其实现类,IAccountDao及其实现类
package com.hejiale.service;

/**
 * 账户业务层的接口
 */
public interface IAccountService {
    // 模拟保存账户
    void saveAccount();

}

----------------------------------
package com.hejiale.service.impl;

import com.hejiale.dao.IAccountDao;
import com.hejiale.dao.impl.AccountDaoImpl;
import com.hejiale.service.IAccountService;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao = new AccountDaoImpl();

    public void saveAccount() {
        accountDao.saveAccount();
    }
}
----------------------------------
package com.hejiale.dao;

/**
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 模拟保存账户
     */
    void saveAccount();
}

----------------------------------
package com.hejiale.dao.impl;

import com.hejiale.dao.IAccountDao;

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {
    @Override
    public void saveAccount() {
        System.out.println("保存成功");
    }
}
  • 测试类
package com.hejiale.ui;

import com.hejiale.dao.IAccountDao;
import com.hejiale.service.IAccountService;
import com.hejiale.service.impl.AccountServiceImpl;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 模拟表现层,用于调用业务层
 */
public class Client {
    /*
        获取spring的IOC核心容器,并根据id获取对象
     */
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        //2. 根据id获取bean对象

        //可以使用两种方式,一种是强制类型转换,一种是创建的时候顺便把创建对象的字节码传进去,就不用类型转换了

        for (int i = 0; i < 5; i++) {
            IAccountService as = (IAccountService) ac.getBean("accountService");
            IAccountDao ad = ac.getBean("accountDao", IAccountDao.class);
            System.out.println(as);
            System.out.println(ad);
            System.out.println("---------------");
        }
    }
}
  • 输出
com.hejiale.service.impl.AccountServiceImpl@1e66f1f5
com.hejiale.dao.impl.AccountDaoImpl@4e50c791
---------------
com.hejiale.service.impl.AccountServiceImpl@1e66f1f5
com.hejiale.dao.impl.AccountDaoImpl@4e50c791
---------------
com.hejiale.service.impl.AccountServiceImpl@1e66f1f5
com.hejiale.dao.impl.AccountDaoImpl@4e50c791
---------------
com.hejiale.service.impl.AccountServiceImpl@1e66f1f5
com.hejiale.dao.impl.AccountDaoImpl@4e50c791
---------------
com.hejiale.service.impl.AccountServiceImpl@1e66f1f5
com.hejiale.dao.impl.AccountDaoImpl@4e50c791
---------------

spring的xml涉及到的类详解

  1. ApplicationContext的三个常用实现类
    1. ClassPathXMLApplicationContext : 可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话,加载不了
    2. FileSystemXmlApplicationContext : 加载磁盘任意路径下的配置文件(必须有访问权限),所以路径必须是磁盘路径
    3. AnnotationConfigApplicationContext : 用于读取注解创建容器的,是基于注解的spring
  2. 核心容器的两个接口
    1. BeanFactory : 他在构建核心容器的时候,创建对象采用的是延迟加载的方式,也就是说,什么时候根据id获取对象,什么时候才创建对象
    2. ApplicationContext : 他在构建核心容器的时候,创建对象采用的策略是立即加载的方式,也就是说,只要读取完配置文件,马上创建配置文件中配置的对象
    3. 分析:
      1. 首先,在上面的入门案例中,我们配置文件里配置的对象,由于没有类成员,所以不存在线程安全的问题,所以创建出单例对象是可以的,那么也就是说,我们程序使用的对象,永远都是这一个,所以在这种情况下,也就是单例对象的时候,我们应该使用ApplicationContext来创建核心容器
      2. BeanFactory多例对象适用
      3. 但是需要说明,spring是一个很聪明的框架,我们可以通过修改配置文件,来修改创建对象的方式,那么考虑到,BeanFactory是一个顶层接口,功能上,可能不如ApplicationContext接口功能多,所以实际的开发中,我们一般都会适用ApplicationContext接口

spring的bean的相关细节

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--
        把对象的创建交给spring来管理
            1.  bean
                1.  id  获取时的唯一标识
                2.  class  要创建对象的全限定类名
        spring对bean对象的管理细节
            1.  创建bean的三个方式
            2.  bean的作用范围
            3.  bean的生命周期
    -->

    <!--
        创建bean的三个方式
    -->
    <!--
        第一种方式,使用默认构造函数创建
            在spring的配置文件中,使用bean标签,配以id和class顺序后,且没有其他属性和标签的时候,
            采用的就是默认构造函数创建bean对象,如果此类中没有默认构造函数,则对象无法创建
            报错:Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.hejiale.service.impl.AccountServiceImpl]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.hejiale.service.impl.AccountServiceImpl.<init>()


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

    <!--
        但是也存在一种情况,实际开发中,有可能我们会使用到别人的jar,对于jar包中的类,都是只读的,如果jar
        中有的类,恰好没有给你默认构造函数的话,用第一种方法的话,我们就无法创建出对象了

        第二种方式,使用普通工厂的普通方法创建对象(使用某个类种的方法创建对象并存入spring容器)
        <bean id="factory" class="com.hejiale.factory.InstanceFactroy"></bean>
        <bean id="accountService" factory-bean="factory" factory-method="getAccountService" ></bean>
    -->

    <!--
        第三种方式,使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象并存入spring容器)
        <bean id="accountService" class="com.hejiale.factory.StaticInstanceFactroy"
                  factory-method="getAccountService"></bean>
    -->

    <!--
        bean的作用范围调整
            首先说明:spring创建的对象,默认是单例的
        bean标签的scope属性:
            作用:用于指定bean的取值范围
            取值: 前两个常用
                1. singleton : 单例(默认值)
                2. prototype : 多例
                3. request : 作用于web应用的请求范围
                4. session : 作用于web应用的会话范围
                5. global-session : 作用于集群环境的会话范围(全局会话范围),当不是集群环境的时候,他就是session
    -->

    <!--
        bean对象的生命周期
            1. 单例对象 : 单例对象的生命周期和容器相同
                出生 : 只要解析了配置文件,bean对象就创建出来了
                活着 : 只要容器还在,对象一直在
                死亡 : 容器销毁,对象销毁
                当我们bean的作用范围是单例,也就是singleton的时候,我们可以设置初始化方法和销毁方法
                    init_method : 指定初始化方法
                    destroy_method : 指定销毁方法
                例子的输出:
                    对象创建了
                    对象初始化了...
                    service 的saveAccount方法执行了
                    对象销毁了...
                这个时候把bean的作用范围改成prototype
                例子的输出:
                    对象创建了
                    对象初始化了...
                    service 的saveAccount方法执行了
            2. 多例对象
                出生 : 当我们使用对象的时候,spring框架才为我们创建对象
                或者 : 对象在使用过程中,一直活着
                死亡 : 当对象长时间不用,且没有别的对象引用时,由java的垃圾回收机制回收
    -->
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl"  scope="prototype"
          init-method="init" destroy-method="destroy"></bean>
</beans>

依赖注入

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

    <!--
        spring中的依赖注入(Dependency Injection)
            我们之前学习了,将对象保存到由spring维护的容器里面,但是容器里面的对象是没有数据的,而依赖
            注入的目的其实就是,当我们使用对象的时候,注入对象所需的数据
        IOC目的 : 降低程序间的耦合(依赖)关系
        依赖关系的管理 : 以后都交给spring来维护
        依赖关系 : 在当前类需要用到其他类的对象,由spring提供,我们只需要在配置文件中说明就可以
        依赖关系的维护 : 就称之为依赖注入
        依赖注入:
            可以注入的数据有三类:
                1. 基本类型和String
                2. 其他Bean类型(在配置文件中,或者注解配置过的bean)
                3. 复杂类型(集合类型)
            注入的方式有三种
                1. 使用构造函数提供
                2. 使用set方法提供
                3. 使用注解提供


    -->

    <!--
        构造函数注入
            使用的标签 : constructor-arg
                标签出现的位置 : bean标签内
                标签中的属性 :
                    1. type : 用于指定要注入的数据类型,该数据类型,也是构造函数中某个或某些参数的类型
                    2. index : 用于指定,要注入的数据,应该把值注入给构造函数中小表为几的参数,下标从0开始
                    3. name : 用于指定,会给构造函数中指定名称的参数注入值
                    以上三个,用于指定给构造函数中哪个参数赋值,一般使用name
                    4. value : 用于给基本类型和String类型赋值
                    5. ref : 引用关联的bean对象,用于指定其他的bean类型数据,也就是在IOC容器中出现过的bean对象
                优势 : 在获取bean对象的时候,注入数据是必须的操作,否则,对象无法创建成功
                弊端 : 改变了bean对象的实例化方式,使我们在创建对象的时候,你哪怕用不到的数据也必须提交

    -->
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl"  scope="singleton">
        <constructor-arg name="name" value="test"  ></constructor-arg>
        <constructor-arg name="age" value="18"  ></constructor-arg>
        <!--但是bean(其实也可以说是bean)赋值方式比较特殊,-->
        <constructor-arg name="birthday" ref="now"  ></constructor-arg>
    </bean>
    <!--
        配置一个日期对象,这样子,spring会根据class反射创建一个对象放到IOC容器里面,然后我们根据now就可以获取这个
        对象
    -->
    <bean id="now" class="java.util.Date"></bean>


    <!--
        set方法注入(更常用)
            使用的标签 : property
            出现的位置 : bean标签内部
            标签的属性
                1. name : 用于指定,注入时调用的set方法名称
                    注意 : 比如说name对应的set方法是setUsername(),但是我们在这里应该填写的是username,也就是
                           说,和属性名称无关,只和set方法名称有关,并且去掉set,首字母小写
                2. value : 用于给基本类型和String类型赋值
                3. ref : 引用关联的bean对象,用于指定其他的bean类型数据,也就是在IOC容器中出现过的bean对象
            优势 : 解决了构造函数注入的弊端,不依赖构造函数,可以自己选择注入哪些数据
            弊端 : 获取bean对象的时候,注入数据不再是不要的步骤,如果有某个成员必须有值,则set方法无法保证获取的对象有值

    -->

    <bean id="accountService2" class="com.hejiale.service.impl.AccountServiceImpl2"  scope="singleton">
        <property name="name" value="test"></property>
        <property name="age" value="21"></property>
        <property name="birthday" ref="now"></property>
    </bean>


    <!--
        复杂类型的注入/集合类型的注入
        注意:
            给List结构注入的标签
                list array set
            给Map结构注入的标签
                map props
            结构类似,标签可以互换
    -->
    <bean id="accountService3" class="com.hejiale.service.impl.AccountServiceImpl3">
        <!-- 注入数组 -->
        <property name="myStrs">
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <!-- 注入List -->
        <property name="myList">
            <list>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </list>
        </property>
        <!-- 注入Set -->
        <property name="mySet">
            <set>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </set>
        </property>
        <!-- 注入Map -->
        <property name="myMap">
            <map>
                <entry key="1" value="何佳乐"></entry>
                <entry key="2" value="曹越"></entry>
                <entry key="3" value="吴超"></entry>
            </map>
        </property>
        <!-- 注入properties -->
        <property name="myProps">
            <props>
                <prop key="1">武文强</prop>
                <prop key="2">裴要</prop>
                <prop key="3">吕则阳</prop>
            </props>
        </property>
    </bean>
</beans>

3-基于注解的spring

基于注解的spring的xml文件

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

    <context:annotation-config/>
</beans>


spring中IOC常见注解

package com.hejiale.service.impl;

import com.hejiale.dao.IAccountDao;
import com.hejiale.dao.impl.AccountDaoImpl;
import com.hejiale.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;

/*
    账户的业务层实现类

    基于xml的时候的配置
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl"
       scope="singleton" init-method="" destroy-method="">
       <property name="" value="">

       </property>
    </bean>

    1. 用于创建对象的注解 : 作用和bean标签一样
        @Component
            1. 作用 : 用于把当前类对象存入spring容器中
            2. 属性 :
                value : 用于指定bean的id,当我们不写的时候,默认值是当前类名,且首字母小写
        以下三个注解,作用和属性都和@Component一样,仅仅是命名不同罢了,他们三个是spring框架为我们提供的明确的三层使用注解
        使我们的三层对象更加清晰
        @Controller : 表现层
        @Service : 业务层
        @Repository : 持久层

    2. 用于注入数据的注解 : 作用和bean标签的property标签一样
        1. @Autowired : 我们之前已经将as和ad保存到IOC容器里面了,那么当扫描到Autowired注解的时候,就会看他注解
        的成员变量或方法是否可以和IOC容器里的对象对应上,如果对应上,就会自动进行类型注入,如果IOC容器没有
        如何bean的类型和要注入的变量类型匹配,则报错
            1. 作用 : 自动按照类型注入,只要容器中有唯一的一个bean对象和要注入的变量类型匹配,就可以注入成功
            2. 书写位置 : 可以是变量上,也可以是方法上
            3. 细节 : 在使用注解注入的时候,set方法就不是必须的了
            4. 匹配过程 :
                IOC容器其实是一个Map<String,Object>,那么首先,先按照Object的数据类型进行匹配,若有多个匹配,则再按照
                注解了的变量的名称和Key(String)匹配
                举个例子:
                    @Repository("accountDao")
                    public class AccountDaoImpl implements IAccountDao
                    那么存储到IOC中,形式应该是
                    <accountDao,IAccountDao>
                    --------------------------------------
                    @Repository("accountDao2")
                    public class AccountDaoImpl implements IAccountDao
                    那么存储到IOC中,形式应该是
                    <accountDao2,IAccountDao>
        2. @Qualifier
            1. 作用 : 在按照类中注入的基础上,再按名称注入,他在给类成员注入的时候,不可以单独使用,要和
            Autowired一起使用,但是在给方法参数注入的时候,可以单独使用
            2. 属性 :
                value : 用于指定注入bean的id
            3. 例子 :
                -----------------------------------------
                @Autowired
                @Qualifier("accountDao2")
                private IAccountDao accountDao;
                -----------------------------------------
                在这个例子里面,如果只有@Autowired,那么就是按照类注入,如果类有多个匹配的话,再按照属性名称
                那么最终的结果匹配的结果应该是AccountDaoImpl
                但是这里加了@Qualifier("accountDao2"),那么我们按找类匹配上AccountDaoImpl,AccountDaoImpl2
                之后,我们就会按名称注入,也就是注入AccountDaoImpl2
        3. @Resource
            1. 作用 : 直接按照bean的id注入,他可以独立使用
            2. 属性 :
                name : 用于指定bean的id
            3. 注意 : @Resource注解需要额外导入maven
                ----------------------------------------------
                <dependency>
                    <groupId>javax.annotation</groupId>
                    <artifactId>javax.annotation-api</artifactId>
                    <version>1.3.2</version>
                </dependency>
                ----------------------------------------------
        4. 注意的是,上述的三个注解,都仅可以注入bean类型的数据,而基本类型和String类型无法使用上述注解
           另外,集合类型的注入,只可以通过xml实现
        5.  @Value
            1. 作用 : 用于注入基本类型和String类型的数据
            2. 属性 :
                value : 指定注入数据的值,它可以使用spring中的SPel也就是spring的el表达式
                    SPel的写法 : ${表达式}
    3. 用于改变作用范围的注解 : 作用和bean标签的scope属性一样
        1.  @Scope
            1.  作用 : 用于指定bean的作用范围,
            2.  属性 : value : 指定范围的取值,常用取值
                1.  singleton   单例(默认)
                2.  prototype   多例
    4. 和生命周期相关的注解(了解即可) : 作用和bean标签的init-method 和 destroy-method属性一样
        1.  @PreDestroy
            1.  作用 : 用于指定销毁的方法
        2.  @PostConstruct
            1.  作用 : 用于指定初始化的方法

*/
@Service("accountService")
@Scope("singleton")
public class AccountServiceImpl implements IAccountService {

//    @Autowired
//    @Qualifier("accountDao2")
    @Resource(name = "accountDao2")
    private IAccountDao accountDao;

    public AccountServiceImpl() {
    }

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

    @PostConstruct
    public void init(){
        System.out.println("对象初始化...");
    }

    @PreDestroy
    public void destroy(){
        System.out.println("对象销毁...");
    }
}

案例:使用xml方式和注解方式实现单表的CRUD操作

持久层使用dbutils

改造基于注解的IOC案例,使用纯注解方式实现

  • 这个项目主要解决的是两个知识点

    1. 如何完全取消bean.xml配置文件–>SpringConfiguration.java
    2. 在JdbcConfig.java中,我们将数据库的配置写死了,这个时候,我们希望和以前一样,把数据库的配置信息写在一个
      .properties文件中
    3. 如何消除junit测试类中的大部分冗余代码,因为每个测试单元,都需要创建容器,获取对象这两个重复步骤–>AccountServiceTest
  • 当我们添加了jdbcConfig.properties后,我们会发现运行测试类出错,这是因为,我们的target中还没有jdbcConfig.properties
    这个时候我们需要右键项目,然后重新编译

  • SpringConfiguration.java

package config;

import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.*;

import javax.sql.DataSource;

/*
    该类是一个配置类,他的作用和bean.xml是一样的

    spring中的新注解
    1.  configuration
        1.  作用 : 指定当前类是一个配置类
        2.  细节 : 当配置类作为AnnotationConfigAApplicationContext对象创建的参数时,该注解可以不写
                   AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
                   因为我们创建容器的时候,传参的时候就使用了这个类了
                   接下来,讲一个不写@Configuration注解是出错,从而必须写的例子
                       但是接下来,我们将dataSource的相关内容单独放到另一个配置类JdbcConfig里面,那么这个时候
                       就出错了,因为SpringConfiguration是无法正确配置的,因为QueryRunner的创建需要DataSource,
                       接下来,在Component注解中再添加一个扫描的包:config,即使这样子,还是会出错,这个时候,就需要
                       说明,即使我们配置了扫描config包下的类,但是我们是无法扫描到类中的任何注解的,因为扫描
                       出来的前提就是,必须先扫描到@Configuration注解,扫描到这个注解,我们才会认为这是一个配置类
                       才会继续扫描下去,所以,当我们的配置类需要别的配置类的配置的时候,对应的配置类必须加上
                       @Configuration,或者在创建容器的时候,讲两个类都加载进去
                       AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class, JdbcConfig.class);
           当然,如果你就是只想写一个Configuration注解,同时,还要写多个关联的配置类的时候,同时,你还不想扫描那么多的包,你可
           以使用@Import注解(见4)
    2.  ComponentScan
        1.  作用 : 用于通过注解指定spring在创建容器的时候,要扫描的包,使用此注解,就等同于在xml中配置了
            <context:component-scan base-package="com.hejiale"></context:component-scan>
        2.  属性 : basePackages和value,二者等同
    3.  Bean
        1.  作用 : 用于把当前方法的返回值作为bean对象存入spring的IOC容器中
        2.  属性 :
            name : 用于指定bean的id,当不写的时候,默认值是当前方法的名称
            也即是说,默认情况的话
            key : createQueryRunner     Value : QueryRunner
        3.  细节 : 当我们使用注解配置方法的时候,如果方法有参数,spring会去容器中查找有没有可用的bean对象
                   查找的方式和autowired注解是一样的,也就是先按类型找,如果有多个可以匹配的,就根据方法参数
                   名称找id,如果还没有就会报错
                   比如:
                       public QueryRunner createQueryRunner(DataSource dataSource)
                       那么根据@Import(JdbcConfig.class)
                       spring会去容器中找有没有DataSource的对象,结果发现有两个
                       DataSource : datasource
                       DataSource : datasource2
                       那么这个时候,类型无法匹配到唯一值,就会去根据方法参数名称匹配,也就是dataSource
                   如果另一种情况,方法参数名称是dataSource,但是spring容器根据类型也匹配到两个
                        DataSource : ds
                        DataSource : ds2
                        那么会编译报错,无法匹配上

    4.  Import
        1.  作用 : 导入其他的配置类
        2.  属性 :
            value : 用于指定其他配置类的字节码文件
        3.  细节 : 当我们使用import注解后,有import注解的类,也被称作主配置类,而导入的配置类,也被称为子配置类
    5.  PropertySource
        1.  作用 : 用于指定properties文件的位置
        2.  属性
                value : 指定文件的名称和路径
                    关键字 : classpath : 表示类路径下
    6.  Qualifier
        1.  作用 : 在按照类中注入的基础上,再按名称注入,他在给类成员注入的时候,不可以单独使用,要和
            Autowired一起使用,但是在给方法参数注入的时候,可以单独使用
            2.  属性 :
                value : 用于指定注入bean的id
*/
@Configuration
@ComponentScan(basePackages = "com.hejiale")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {

    //用于创建一个queryRunner对象
    @Bean(name = "runner")
    @Scope("prototype") //创建多例对象
    public QueryRunner createQueryRunner(@Qualifier("dataSource2") DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /*
        创建数据源对象
     */

//    @Bean(name = "ds")
//    public DataSource createDataSource(){
//        try {
//            ComboPooledDataSource ds = new ComboPooledDataSource();
//            ds.setDriverClass("com.mysql.jdbc.Driver");
//            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day18");
//            ds.setUser("root");
//            ds.setPassword("root");
//            return ds;
//        }catch (Exception e){
//            throw new RuntimeException(e);
//        }
//    }
}

  • JdbcConfig.java
package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;

import javax.sql.DataSource;

/*
    和spring连接数据库相关的配置类
 */
public class JdbcConfig {

    @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 = "dataSource")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Bean(name = "dataSource2")
    public DataSource createDataSource2(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/day18_copy");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

  • JdbcConfig.properties
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/day18
jdbc.username = root
jdbc.password = root
  • AccountServiceTest\
package com.hejiale;

import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import config.SpringConfiguration;
import org.apache.commons.dbutils.QueryRunner;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * 使用Junit单元测试:测试我们的配置
 * spring整合junit配置
 *      1.  导入spring整合junit的坐标
 *      -------------------------------------
 *      <dependency>
 *       <groupId>org.springframework</groupId>
 *       <artifactId>spring-test</artifactId>
 *       <version>5.3.5</version>
 *     </dependency>
 *      -------------------------------------
 *      2.  使用junit提供的一个注解,把原有的main方法替换了,替换成spring提供的
 *          @Runwith
 *      3.  告知spring的运行器,spring和ioc创建是基于xml还是注解,并且说明位置
 *          @ContextConfiguration
 *              locations : 指定xml文件的位置
 *              classes   : 指定注解类所在位置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public void testFindAll() {

        //1. 获取容器
        //ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //1. 获取容器,但是和之前不一样,这个应该使用AnnotationConfigApplicationContext,因为我们使用的是配置类而不是xml配置文件
        //AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2. 得到业务层对象
        //IAccountService as = ac.getBean("accountService", IAccountService.class);

        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindById() {

        //3.执行方法
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {

        Account account = new Account();
        account.setName("test2");
        account.setMoney(12345f);
        //3.执行方法
        as.saveAccount(account);

    }

    @Test
    public void testUpdate() {

        //3.执行方法
        Account account = as.findAccountById(4);
        account.setMoney(23456f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {

        //3.执行方法
        as.deleteAccount(4);
    }

    @Test
    public void testQueryRunner(){
        AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        QueryRunner runner1 = (QueryRunner) ac.getBean("runner");
        QueryRunner runner2 = (QueryRunner) ac.getBean("runner");
        System.out.println(runner1==runner2);
    }
}

4-AOP前言

account银行转帐案例

package com.hejiale.service.impl;

import com.hejiale.dao.IAccountDao;
import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import com.hejiale.utils.TransactionManager;

import java.util.List;

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

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

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

    }

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

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

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

    @Override
    public void deleteAccount(Integer accountId) {
        accountDao.deleteAccount(accountId);
    }

    // 转账
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        //2.1. 根据名称查询转出账户
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        //2.2. 根据名称查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);
        //2.3. 转出账户减钱
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        //2.4. 转入账户加钱
        targetAccount.setMoney(targetAccount.getMoney() + money);
        //2.5. 更新转出账户
        accountDao.updateAccount(sourceAccount);
        //int i =1/0;
        //2.6. 更新转入账户
        accountDao.updateAccount(targetAccount);

    }
}

分析案例中的问题

存在线程不安全的问题,比如说,在2.5. 更新转出账户之后我们加一行代码

int i =1/0;

然后再执行2.6. 更新转入账户,这个时候就会出问题,会出现转出账户少钱了但是转入账户没多钱,所以这个时候,我们需要实现事务管理

银行转账案例优化,实现事务管理

package com.hejiale.service.impl;

import com.hejiale.dao.IAccountDao;
import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import com.hejiale.utils.TransactionManager;

import java.util.List;

/**
 * 账户的业务层实现类,实现了对事务的管理,但是依然存在一些问题
 * 1.   每个方法都有重复代码
 * 2.   存在方法间的依赖,比如说TransactionManager.beginTransaction()改名为TransactionManager.beginTransaction1()
 *      那么在AccountServiceImpl_OLD中,所有调用了该方法的地方都要改名
 */
public class AccountServiceImpl_OLD implements IAccountService {

    private IAccountDao accountDao;
    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

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

    @Override
    public List<Account> findAllAccount() {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3. 提交事务
            transactionManager.commit();
            //4. 返回结果
            return accounts;
        } catch (Exception e) {
            //5. 回滚操作
            transactionManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6. 释放连接
            transactionManager.release();
        }

    }

    @Override
    public Account findAccountById(Integer accountId) {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            Account accountById = accountDao.findAccountById(1);
            //3. 提交事务
            transactionManager.commit();
            //4. 返回结果
            return accountById;
        } catch (Exception e) {
            //5. 回滚操作
            transactionManager.rollback();
            throw new RuntimeException(e);
        } finally {
            //6. 释放连接
            transactionManager.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            accountDao.saveAccount(account);
            //3. 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            //5. 回滚操作
            transactionManager.rollback();
        } finally {
            //6. 释放连接
            transactionManager.release();
        }
    }

    @Override
    public void updateAccount(Account account) {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            accountDao.updateAccount(account);
            //3. 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            //5. 回滚操作
            transactionManager.rollback();
        } finally {
            //6. 释放连接
            transactionManager.release();
        }
    }

    @Override
    public void deleteAccount(Integer accountId) {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            accountDao.deleteAccount(accountId);
            //3. 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            //5. 回滚操作
            transactionManager.rollback();
        } finally {
            //6. 释放连接
            transactionManager.release();
        }
    }

    // 转账
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1. 开启事务
            transactionManager.beginTransaction();
            //2. 执行操作
            //2.1. 根据名称查询转出账户
            Account sourceAccount = accountDao.findAccountByName(sourceName);
            //2.2. 根据名称查询转入账户
            Account targetAccount = accountDao.findAccountByName(targetName);
            //2.3. 转出账户减钱
            sourceAccount.setMoney(sourceAccount.getMoney() - money);
            //2.4. 转入账户加钱
            targetAccount.setMoney(targetAccount.getMoney() + money);
            //2.5. 更新转出账户
            accountDao.updateAccount(sourceAccount);
            //int i =1/0;
            //2.6. 更新转入账户
            accountDao.updateAccount(targetAccount);
            //3. 提交事务
            transactionManager.commit();
        } catch (Exception e) {
            //4. 回滚操作
            transactionManager.rollback();
            e.printStackTrace();
        } finally {
            //5. 释放连接
            transactionManager.release();
        }

    }
}

package com.hejiale.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 connection = tl.get();
            //2. 判断当前线程上是否有连接
            if (connection == null){
                //3. 从数据源中获取一个连接,并且和线程绑定(存入threadlocal中)
                connection = dataSource.getConnection();
                //4. 把connection存入tl
                tl.set(connection);
            }
            //5. 返回当前线程上的连接
            return connection;
        }catch (Exception e){
            throw new RuntimeException(e);
        }

    }

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

}

package com.hejiale.utils;

import java.sql.SQLException;

/**
 * 和事务管理相关的工具类
 * 包含:开启事务  ,提交事务,  回滚事务,   释放连接
 */
public class TransactionManager {

    private ConnectionUtils connectionUtils;

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

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

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

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

    /**
     * 释放连接
     */
    public void release(){
        try {
            connectionUtils.getThreadConnection().close();//还会连接池中,而不是关闭
            connectionUtils.removeConnection(); // 将线程和连接解绑
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

}

依然存在的问题

账户的业务层实现类,实现了对事务的管理,但是依然存在一些问题

  1. 每个方法都有重复代码
  2. 存在方法间的依赖,比如说TransactionManager.beginTransaction()改名为TransactionManager.beginTransaction1()
    那么在AccountServiceImpl_OLD中,所有调用了该方法的地方都要改名

回顾之前讲过的一个技术 : 动态代理

  • 本项目
    1. 基于接口的动态代理,但是要求被代理对象必须实现一个接口
      proxy.Client.java
    2. 基于子类的动态代理—>cglib.Client.java
  • 项目结构
com.hejiale
    cglib
        Client
        Producer
    proxy
        Client
        Producer
        IProducer

基于接口的动态代理

package com.hejiale.proxy;

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

/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /*
        动态代理
        特点:
            字节码,随用随创建,随用随加载
        作用:
            不修改源码的基础上对方法增强
        分类:
            1.  基于接口的动态代理 : 被代理类最少实现一个接口,如果没有,则不能使用
            2.  基于子类的动态代理
        基于接口的动态代理
            涉及的类 : Proxy
            提供者 : JDK官方
        如何创建代理对象:
            使用Proxy类中的newProxyInstance方法
        创建代理类的要求:
            被代理类最少实现一个接口,如果没有,则不能使用
        newProxyInstance方法的参数
            1.  ClassLoader : 类加载器
                他是用于加载代理对象字节码的,写的是被代理对象的加载器,和被代理对象使用的是相同的类加载器
            2.  Class[] interfaces
                用于让代理对象和被代理对象有相同的方法
            3.  InvocationHandler
                用于提供增强的代码
                他是让我们写,如何代理,我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的
                次接口的实现类,都是谁用谁写

        */
        IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
                producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**
                     * 作用 : 执行被代理对象的任何接口方法,都会经过该方法
                     * @param proxy  代理对象的引用
                     * @param method    当前执行的方法
                     * @param args  当前执行方法所需的参数
                     * @return      和被代理对象有相同的返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强的方法
                        Object returnValue = null;
                        //1. 获取方法执行的参数
                        Float money = (Float) args[0];
                        //2. 判断要增强的是哪个方法
                        if("saleProducer".equals(method.getName())){
                            System.out.println("欢迎来到联想电脑经销商");
                            /**
                             * 两个参数
                             *      第一个要传入一个对象,这个对象就是被代理对象
                             *      第二个要传入被代理对象的,要被加强的方法的参数
                             */
                            method.invoke(producer, money*0.8f);
                            System.out.println("购物愉快");
                        }
                        if("afterService".equals(method.getName())){
                            System.out.println("您好,欢迎来到售后");
                            method.invoke(producer,money*1.2f);
                            System.out.println("祝你使用愉快");
                        }
                        return null;
                    }
                });
        proxyProducer.saleProducer(10000f);
        proxyProducer.afterService(200f);
    }
}
package com.hejiale.proxy;

/**
 * 对生产厂家要求的接口
 */
public interface IProducer {
    public void saleProducer(float money);

    public void afterService(float money);
}
package com.hejiale.proxy;

/**
 * 生产者
 */
public class Producer implements IProducer{
    /**
     * 销售
     * @param money
     */
    public void saleProducer(float money) {
        System.out.println("销售产品,并拿到钱" + money);
    }

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

基于子类的动态代理

package com.hejiale.cglib;

import com.hejiale.proxy.IProducer;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

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

/**
 * 模拟一个消费者
 */
public class Client {
    public static void main(String[] args) {
        final Producer producer = new Producer();
        /*
        动态代理
        特点:
            字节码,随用随创建,随用随加载
        作用:
            不修改源码的基础上对方法增强
        分类:
            1.  基于接口的动态代理 : 被代理类最少实现一个接口,如果没有,则不能使用
            2.  基于子类的动态代理
        基于子类的动态代理
            涉及的类 : Enhancer
            提供者 : 第三方-->cglib库
        如何创建代理对象:
            1. 首先,使用基于类的动态代理,必须先导包
            ----------------------------------------------------------
            <dependency>
                <groupId>cglib</groupId>
                <artifactId>cglib</artifactId>
                <version>2.1_3</version>
            </dependency>
            ----------------------------------------------------------
            2. 使用Enhancer类中的create方法
        创建代理类的要求:
            被代理类不可以是最终类
        create方法的参数
            1.  Class type : 被代理对象的字节码
            2.  Callback callback : 用于提供增强的代码,我们一般写的都是该接口的子接口是实现类:MethodInterceptor(方法拦截)


        */
        Producer producer1 = (Producer) Enhancer.create(Producer.class, new MethodInterceptor() {
            /**
             * 执行被代理对象的任何方法,都会调用该方法,也就是不论你调用了被代理对象的哪个方法,都会经过该方法
             * @param o 其实就是proxy 也就是代理对象
             * @param method 调用的被代理对象的哪个方法
             * @param objects   调用被代理对象的方法的参数
             *      以上三个参数,和基于接口的动态代理中,InvocationHandler接口的invoke方法的参数是一样的
             * @param methodProxy : 当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                Object returnValue = null;
                Float money = (Float)objects[0];
                if("saleProducer".equals(method.getName())){
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        producer1.saleProducer(10000f);

    }
}

package com.hejiale.cglib;

import com.hejiale.proxy.IProducer;

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

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

利用动态代理解决案例中的问题

package com.hejiale.factory;

import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import com.hejiale.utils.TransactionManager;

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

/**
 * 用于创建service的代理对象的工厂方法
 */
public class BeanFactory {

    private TransactionManager transactionManager;
    private IAccountService accountService;

    /**
     * 获取service的代理对象
     *
     * @return
     */
    public IAccountService getAccountService() {
        IAccountService as = (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        try {
                            //1. 开启事务
                            transactionManager.beginTransaction();
                            //2. 执行操作
                            method.invoke(accountService, args);
                            //3. 提交事务
                            transactionManager.commit();
                        } catch (Exception e) {
                            //4. 回滚操作
                            transactionManager.rollback();
                            e.printStackTrace();
                        } finally {
                            //5. 释放连接
                            transactionManager.release();
                        }
                        return null;
                    }
                });

        return as;
    }

    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
}
  • 测试类
package com.hejiale;

import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations ="classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    @Qualifier("proxyAccountService")
    IAccountService as;//使用代理对象执行方法,那么这种情况下,我们并没有修改AccountService中的转账方法的代码,依然实现了对方法的增强(增强了事务控制)

    @Test
    public void TestTransfer(){
        as.transfer("aaa", "bbb", 100f);
    }
}

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

    <!--配置代理的service对象-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

    <!--配置beanFactory-->
    <bean id="beanFactory" class="com.hejiale.factory.BeanFactory">
        <property name="accountService" ref="accountService"></property>
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>
    <!-- 配置Service -->
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.hejiale.dao.impl.AccountDaoImpl">
        <!-- 注入QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!--注入connectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置QueryRunner-->
    <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.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/day18"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--配置connection工具类-->
    <bean id="connectionUtils" class="com.hejiale.utils.ConnectionUtils">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--配置事务管理器-->
    <bean id="transactionManager" class="com.hejiale.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

5-Spring中的AOP

AOP的概念

  • 在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。简单的说,就是把我们程序的重复代码抽取出来,再需要执行的时候,使用动态代理技术,在不修改源码的基础上,对我们已有的方法进行增强
  • AOP的作用
    • 在程序运行期间,不修改源代码的方式对已有方法增强
  • 优势
    • 减少重复代码
    • 提高开发效率
    • 维护方便
  • AOP实现方式
    • 动态代理技术
  • Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。比如业务层(AccountService)中的所有方法都是连接点,因为他们都有资格进行方法增强
  • Pointcut(切入点):所谓切入点是指我们要对哪些Joinpoint进行拦截的定义。通俗的说,AccountService中的所有方法都是连接点,但是不一定都是切入点,连接点是有资格,而切入点是有没有做,如果对AccountService的test方法,我们没有对其做了事务管理的增强,而其他方法都做了,那么其他方法都是切入点,而test方法只是接入点,而不是切入点
  • Advice(通知/增强):所谓通知是指拦截到Joinpoint之后所要做的事情就是通知。通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //1. 开启事务
        transactionManager.beginTransaction();//前置通知
        //2. 执行操作
        method.invoke(accountService, args);
        /*
            整个invoke方法在执行过程中属于环绕通知
            环绕通知中有明确的切入点方法调用
        */
        //3. 提交事务
        transactionManager.commit();//后置通知
    } catch (Exception e) {
        //4. 回滚操作
        transactionManager.rollback();//异常通知
        e.printStackTrace();
    } finally {
        //5. 释放连接
        transactionManager.release();//最终通知
    }
    return null;
}
  • Introduction(引介):引介是一种特殊的通知在不修改类代码的前提下, Introduction可以在运行期为类动态地添加一些方法或Field。
  • Target(目标对象):代理的目标对象,也就是被代理对象
  • Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程。spring采用动态代理织入,而AspectJ采用编译期织入和类装载期织入。
  • Proxy(代理):一个类被AOP织入增强后,就产生一个结果代理类。也就是代理对象
  • Aspect(切面):是切入点和通知(引介)的结合
  • 学习spring中的AOP要明确的事:
    • 开发阶段(我们做的)
      1. 编写核心业务代码(开发主线):大部分程序员来做,要求熟悉业务需求。
      2. 把公用代码抽取出来,制作成通知。(开发阶段最后再做):AOP编程人员来做。
      3. 在配置文件中,声明切入点与通知间的关系,即切面。:AOP编程人员来做
    • 运行阶段(Spring框架完成的)
      1. Spring框架监控切入点方法的执行。一旦监控到切入点方法被运行,使用代理机制,动态创建目标对象的代理对象,根据通知类别,在代理对象的对应位置,将通知对应的功能织入,完成完整的代码逻辑运行

Spring中基于xml和注解的AOP配置

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

    1. 首先,确定切入类,也就是被增强的类,然后将其存入IOC容器
    2. 确定切面类,也就是可以增强切入类的类,也将其存入IOC容器,在切面类中,我们最好将前置通知,后置通知,异常
      通知,最终通知,环绕通知分别确定好,方便我们xml文件的编写
    3. 在bean.xml文件中进行配置
    <!--配置aop-->
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.hejiale.service.impl.AccountServiceImpl.transfer(..))"/>
        <!--配置切面类-->
        <aop:aspect id="TM" ref="transactionManager">
            <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
            <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
            <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
            <aop:after method="release" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>
    
  • 切面类(也就是可以对切入类增强的类)Logger.java

package com.hejiale.utils;

import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 用于记录日志的工具类,里面提供了公共的代码
 */
public class Logger {
    //用于打印日志:并且计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)

    /**
     * 前置通知
     */
    public void beforePrintLog() {
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了");
    }

    /**
     * 后置通知
     */
    public void afterReturningPrintLog() {
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了");
    }

    /**
     * 异常通知
     */
    public void afterThrowingPrintLog() {
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了");
    }

    /**
     * 最终通知
     */
    public void afterPrintLog() {
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了");
        System.out.println("------------------------------------------------");
    }

    /*
        环绕通知:
        输出:
            环绕通知:aroundPrintLog
            执行更新1
            执行删除
        问题 : 切入点方法没有执行,但是通知方法执行了
        分析 :
            出现这种情况的原因:通过对比动态代理中的环绕通知的代码,发现动态代理中的环绕通知,有明确的切入点
            方法调用invoke,但是我们的代码中没有
        解决方法 : spring框架为我们提供了一个接口:ProceedingJoinPoint,该接口有一个方法proceed(),此方法就
                相当于明确调用切入点方法,该接口可以作为环绕通知的方法参数,在程序执行的时,spring框架会为我们
                提供该接口的实现类供我们使用

    */
    public void aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
        Object returnValue = null;
        try {
            //得到方法执行所需的参数
            Object[] args = proceedingJoinPoint.getArgs();
            System.out.println("前置通知");
            returnValue =  proceedingJoinPoint.proceed(args);//明确调用业务层方法(切入点方法)\
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("最终通知");
        }

    }
}

  • 切入类
package com.hejiale.service.impl;

import com.hejiale.service.IAccountService;

/*
    业务层实现类

    如果这个时候,我们提出要求,就是在业务层方法执行之前开始记录日志,你们如果不适用配置的话,要我们自己写,
    那步骤就是首先创建代理对象,然后代理对象中,在业务层方法执行之前调用Logger的printLog方法就可以,但是
    我们这里希望用spring配置的方式,而不是自己手写动态代理
*/
public class AccountServiceImpl implements IAccountService {
    @Override
    public void saveAccount() {
        System.out.println("保存成功");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行更新" + i);
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行删除");
        return 0;
    }
}

  • bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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
        http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--配置spring的IOC,把service对象配置进去-->
    <bean id="accountService" class="com.hejiale.service.impl.AccountServiceImpl"></bean>

    <!--
        spring中基于xml的AOP配置步骤
            1.  把通知的bean也交给spring管理
            2.  使用aop:config标签表明,开始aop的配置
            3.  使用aop:aspect标签表明开始配置切面
                属性:
                    1.  id : 给切面提供一个唯一标志
                    2.  ref : 指定通知类bean的id
            4.  在aop:aspect标签内部使用对应的标签来配置通知的类型
                我们现在的示例,是让logger的printLog方法在切入点方法执行前执行,所以是前置通知
                aop:before  表示配置前置通知
            5.  aop:before  表示配置前置通知
                属性:
                    1.  method : 用于指定Logger类中的哪个方法是前置通知
                    2.  pointcut : 用于指定切入点表达式,该表达式的含义是指对业务层中的哪些方法增强
            6.  切入点表达式的写法 :
                首先,我们写的切入点表达式如果想被解析,需要导包
                ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                <dependency>
                      <groupId>org.aspectj</groupId>
                      <artifactId>aspectjweaver</artifactId>
                      <version>1.9.6</version>
                </dependency>

                ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
                关键字 : execution(表达式)
                表达式 :
                    1.  标准写法:
                        访问修饰符 返回值 包名.包名....包名.类名.方法名(参数列表)
                        例如 :
                        public void com.hejiale.service.impl.AccountServiceImpl.saveAccount()
                    2.  访问修饰符可以省略
                        void com.hejiale.service.impl.AccountServiceImpl.saveAccount()
                    3.  返回值可以使用通配符表示任意返回值
                        * com.hejiale.service.impl.AccountServiceImpl.saveAccount()
                    4.  包名可以使用通配符,表示任意包,但是有几级包,就应该写几个*.
                        * *.*.*.*.AccountServiceImpl.saveAccount()
                    5.  包名可以使用..表示当前包及其子包
                        * *..AccountServiceImpl.saveAccount()
                    6.  类名和方法名都可以使用*来实现通配
                        * *..*.saveAccount()
                        * *..*.*() 这种情况下,saveAccount和deleteAccount会被增强
                    7.  参数列表,
                        可以直接写参数类型
                            1.  基本类型 : 直接写名称
                            2.  引用类型 : 包名.类名的方式 比如 : java.lang.String
                        可以使用通配符 * *..*.*(*) 那么这种情况下,所有有参数的方法都会增强,没有参数的则不会
                        可以使用..表示有无参数均可,那么就是全通配写法
                    8.  全通配写法:
                        * *..*.*(..)
                        实际开发少写,因为这种情况下,所有类的所有方法都满足条件
                    9.  实际开发中切入点表达式的常见写法
                        切到业务层实现类下的所有方法
                        * com.hejiale.service.impl.*.*(..)

    -->
    <!--1. 配置Logger类-->
    <bean id="logger" class="com.hejiale.utils.Logger"></bean>

    <!--2. 配置aop-->
    <aop:config>
        <!--
                配置切入点表达式: 此标签写于aop:aspect标签内部,只可以用作于当前切面,他也可以写于aop:aspect外面,此时
                就变成了所有切面可用,但是这种情况下,必须写在aop:aspect之前,写到后面会报错
                id : 属性用于指定表达式的唯一标识
                expression : 用于指定表达式内容
            -->
        <aop:pointcut id="pt1" expression="execution(* com.hejiale.service.impl.*.saveAccount(..))"/>

        <!--配置切面-->
        <aop:aspect id="logAdvice" ref="logger">
            <!--配置通知的类型且建立和切入点方法的联系-->
            <!-- 前置通知 : 在切入点方法执行之前执行 -->
            <!--
            <aop:before method="beforePrintLog" pointcut-ref="pt1"></aop:before>

            &lt;!&ndash; 后置通知 : 在切入点方法正常执行之后执行,和异常通知永远只会执行一个 &ndash;&gt;
            <aop:after-returning method="afterReturningPrintLog" pointcut-ref="pt1"></aop:after-returning>

            &lt;!&ndash; 异常通知 : 在切入点方法执行产生异常后执行,和后置通知永远只会执行一个 &ndash;&gt;
            <aop:after-throwing method="afterThrowingPrintLog" pointcut-ref="pt1"></aop:after-throwing>

            &lt;!&ndash; 最终通知 : 无论切入点方法是否正常执行,都会执行 &ndash;&gt;
            <aop:after method="afterPrintLog" pointcut-ref="pt1"></aop:after>

            -->

            <!-- 配置环绕通知 ,详细注释在Logger类中-->
            <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around>
            </aop:aspect>
    </aop:config>
</beans>

Spring基于注解的AOP

  • 开发步骤
    1. 首先,写好切面类和切入类,切面类上记得加@Aspect注解
    2. 将切入类和切面类都加入IOC容器
    3. 在切面类上的各种前置通知后置通知上加上相对应的注解
    4. 在配置类中加上@EnableAspectJAutoProxy表示开启注解AOP
  • 切面类
package com.hejiale.utils;

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

/**
 * 用于记录日志的工具类,里面提供了公共的代码
 */
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
    //用于打印日志:并且计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)

    @Pointcut("execution(* com.hejiale.service.impl.*.saveAccount(..))")
    private void pt1(){ }
    /**
     * 前置通知
     */
    @Before("pt1()")
    public void beforePrintLog() {
        System.out.println("前置通知Logger类中的beforePrintLog方法开始记录日志了");
    }

    /**
     * 后置通知
     */
    @AfterReturning("pt1()")
    public void afterReturningPrintLog() {
        System.out.println("后置通知Logger类中的afterReturningPrintLog方法开始记录日志了");
    }

    /**
     * 异常通知
     */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog() {
        System.out.println("异常通知Logger类中的afterThrowingPrintLog方法开始记录日志了");
    }

    /**
     * 最终通知
     */
    @After("pt1()")
    public void afterPrintLog() {
        System.out.println("最终通知Logger类中的afterPrintLog方法开始记录日志了");
        System.out.println("------------------------------------------------");
    }

    /**
     * 环绕通知
     * @param proceedingJoinPoint
     */
    //@Around("pt1()")
    public void aroundPrintLog(ProceedingJoinPoint proceedingJoinPoint) {
        Object returnValue = null;
        try {
            //得到方法执行所需的参数
            Object[] args = proceedingJoinPoint.getArgs();
            System.out.println("前置通知");
            returnValue =  proceedingJoinPoint.proceed(args);//明确调用业务层方法(切入点方法)\
            System.out.println("后置通知");
        } catch (Throwable throwable) {
            System.out.println("异常通知");
            throwable.printStackTrace();
        } finally {
            System.out.println("最终通知");
        }
    }
}

  • 切入类
package com.hejiale.service.impl;

import com.hejiale.service.IAccountService;
import org.springframework.stereotype.Service;

/*
    业务层实现类

    如果这个时候,我们提出要求,就是在业务层方法执行之前开始记录日志,你们如果不适用配置的话,要我们自己写,
    那步骤就是首先创建代理对象,然后代理对象中,在业务层方法执行之前调用Logger的printLog方法就可以,但是
    我们这里希望用spring配置的方式,而不是自己手写动态代理
*/
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Override
    public void saveAccount() {
        System.out.println("保存成功");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("执行更新" + i);
    }

    @Override
    public int deleteAccount() {
        System.out.println("执行删除");
        return 0;
    }
}
  • bean.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.hejiale"></context:component-scan>

    <!--
        配置spring开启注解AOP的支持
    -->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>

利用XML配置AOP优化银行转账案例

Spring_13_AOP_09Better_XML

利用注解AOP优化银行转账案例

Spring_14_AOP_09Better_Anno

6-Spring的JdbcTemplate

概述

  • 它是spring框架中提供的一个对象,是对原始Jdbc API对象的简单封装。spring框架为我们提供了很多的操作模板类。操作关系型数据的:JdbcTemplateHibernateTemplate操作nosql数据库的:RedisTemplate操作消息队列的:JmsTemplate我们今天的主角在spring-jdbc-5.0.2.RELEASE.jar中,我们在导包的时候,除了要导入这个jar包外,还需要导入一个spring-tx-5.0.2.RELEASE.jar(它是和事务相关的)。
  • 作用:和数据库交互,实现对表的CURD操作
  • 如何创建对象
  • 对象中的常用方法

7-Spring中基于XML的声明式事务控制

XML配置文件头

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

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <context:component-scan base-package="com.hejiale"></context:component-scan>


    <!--配置账户的持久层-->
    <bean id="accountDao" class="com.hejiale.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/day18"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--
        Spring中基于XML的声明式事务控制配置步骤
            1.  配置事务管理器
            2.  配置事务通知
                此时我们需要导入事务的约束 tx的名称空间和约束,同时也需要aop的
                使用tx:advice标签配置事务通知
                    属性
                        1.  id 给事务通知起一个唯一表示
                        2.  transaction-manager 给事务通知提供一个事务管理器应用
            3.  配置AOP的通用切入点表达式
            4.  建立事务通知和切入点表达式的对应关系
            5.  配置事务的属性
                是在事务的通知tx:advice标签的内部
                1.  name="transfer"
                2.  isolation : 配置事务的隔离级别   默认值是DEFAULT,表示使用数据库的默认隔离级别
                3.  no-rollback-for :用于指定一个异常,当产生该异常时,事务不回滚,当产生其他异常的时候,事务回滚
                                    ,没有默认值,表示任何异常都回滚
                4.  propagation : 用于指定事务的传播行为
                    1.  默认值是REQUIRED,表示一定会有事务 ,用于增删改的事务
                    2.  SUPPORTS,表示有就有,没有就没有,用于查询事务
                5.  read-only : 用于指定事务是否只读,只有查询方法才可以设置为true,默认值:false,表示读写
                6.  rollback-for : 用于指定一个异常,当产生该异常时,事务回滚,当产生其他异常的时候,事务不回滚
                                    没有默认值,表示任何异常都回滚
                7.  timeout : 用于指定事务的超时时间,默认值是-1,表示永不超时,如果指定了数值,以秒为单位
    -->
    <!--1. 配置事务管理器,里面由通知和回滚方法-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 配置事务通知  -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--5. 配置事务的属性-->
        <tx:attributes>
            <!--半通配比全通配优先级高-->
            <!--
                给哪些方法增强     *表示全部方法
                                  find*表示以find开头的方法
                REQUIRED 用于增删改方法
                SUPPORTS  用于查询方法
            -->
            <tx:method name="*" propagation="REQUIRED" read-only="false" />
            <tx:method name="find*" propagation="SUPPORTS" read-only="true" />
        </tx:attributes>
    </tx:advice>

    <!--配置AOP-->
    <aop:config>
        <!--3. 切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.hejiale.service.impl.AccountServiceImpl.*(..))"/>
        <!--4. 建立切入点表达式和事务通知的对应关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>
</beans>

8-Spring中基于注解的事务控制

bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--开启注解-->
    <context:annotation-config/>
    <context:component-scan base-package="com.hejiale"></context:component-scan>

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/day18"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--
        Spring中基于XML的声明式事务控制配置步骤
            1.  配置事务管理器
            2.  开启Spring对注解事务的支持
            3.  在需要事务支持的地方使用@Transactional注解
    -->
    <!--1. 配置事务管理器,里面由通知和回滚方法-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2. 开启Spring对注解事务的支持-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>

AccountServiceImpl.java

package com.hejiale.service.impl;

import com.hejiale.dao.IAccountDao;
import com.hejiale.domain.Account;
import com.hejiale.service.IAccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

/**
 * 账户的业务层实现类
 */
@Service("accountService")
@Transactional(propagation = Propagation.SUPPORTS,readOnly = true)//只读型事务配置
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;


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


    // 转账
    @Override
    @Transactional(propagation = Propagation.REQUIRED,readOnly = false)//读写型事务配置
    public void transfer(String sourceName, String targetName, Float money) {
        //2.1. 根据名称查询转出账户
        Account sourceAccount = accountDao.findAccountByName(sourceName);
        //2.2. 根据名称查询转入账户
        Account targetAccount = accountDao.findAccountByName(targetName);
        //2.3. 转出账户减钱
        sourceAccount.setMoney(sourceAccount.getMoney() - money);
        //2.4. 转入账户加钱
        targetAccount.setMoney(targetAccount.getMoney() + money);
        //2.5. 更新转出账户
        accountDao.updateAccount(sourceAccount);
        //int i =1/0;
        //2.6. 更新转入账户
        accountDao.updateAccount(targetAccount);

    }
}

9-Spring中基于纯注解的事务控制

  • 注意:
    @Value("${jdbc.driver}")的美元符号和大括号不要忘记写
  • 各个配置类
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = "com.hejiale")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("classpath:jdbcConfig.properties")
@EnableTransactionManagement//开启注解支持
public class SpringConfiguration {
}

package config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

import javax.sql.DataSource;

/**
 * 和连接数据库相关的配置类
 */
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 创建jdbcTemplate对象
     *
     * @param dataSource
     * @return
     */
    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建一个数据源对象
     * @return
     */
    @Bean("dataSource")
    public DataSource createDateSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

/**
 * 和事务相关的配置类,配置事务管理器
 */
public class TransactionConfig {

    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

XML完整文件头

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值