spring基础

449 阅读36分钟

1、创建bean的三种方式

1.1 方式一:

<bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl">
        在spring的配置文件中使用bean标签,仅仅以id和class属性。采用的就是默认构造函数创建bean对象。
    如果此类中没有默认构造方法,则对象无法创建。
        Failed to instantiate [com.itheima.service.impl.IAccountServiceImpl]: No default constructor found;

1.2 方式二:

使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
普通工厂:普通方法
```
    public class InstanceFactory {
        public IAccountService getAccountService(){
            return new IAccountServiceImpl();
        }
    }
```
* 看到这个类,我们可能会出现这样的困惑:  
getAccountService()方法中也使用了new,这不也是耦合了吗? 我们要知道可能存在的这种情况:这是一个模拟的工厂类,该类可能是存在jar包中的,他是一个class文件我们无法通过修改其源码的方式来提供默认构造函数。 所以既然是jar包中的类,我们没必要着眼与方法中的具体实现,是要我们不new这个类即可。我们要的是。将其中的方法的返回值return new IAccountServiceImpl()放到我们的spring容器中。
```
    <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>
```
* 分析一下这两句配置:
首先,public IAccountService getAccountService()这是一个普通的方法,需要对象调用。
1.先指定这个工厂的默认构造函数:得到instanceFactory对象
 <bean id="instanceFactory" class="com.itheima.factory.InstanceFactory"></bean>
2.怎么得到这个方法呢?
    bean id="accountService":指定要得到的对象的id——IAccountService
    factory-bean="instanceFactory":指定那个工厂bean
    factory-method="getAccountService":指定这个工厂类中我们要用到的方法

1.2 方式三:

使用静态工厂中的静态方法(使用某个类中的静态方法创建对象,并存入spring容器)    
    * 静态工厂:静态方法
    ```
    public class StaticFactory {
        public static IAccountService getAccountService(){
            return new IAccountServiceImpl();
        }
    }
    ```
    * 我们要意识到,需要调用的是静态方法,所以不用实例化那个工厂类。
      即我们直接指定对应的静态方法即可。
    ```
    <bean id="accountService" class="com.itheima.factory.StaticFactory"
        factory-method="getAccountService"></bean>
    ```
    * 分析一下这句配置
        我要用id="accountService"来取StaticFactory中的getAccountService得到的结果。
        而且我们没有创建StaticFactory对象,所以getAccountService是一个静态方法。

2 bean对象的生命周期及作用范围

    * 那么我们怎么判断要创建的对象是单例还是多例呢? 
    * bean标签的scope属性:
        作用:用于指定bean的作用范围
        取值:
            singleton:单例(默认值)
            prototype:多例
            request:作用于web应用的请求范围
            session:作用于web应用的会话范围
            global-session:作用于集群环境的会话范围(全局会话范围,当不是集群环境就只是session)
    * 相应的配置:
    ```
        <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl" scope="singleton"></bean>
    ```
    * 扩展:global-session全局session
        由于实际情况下后台的服务器是多个的。我们需要多台服务器都能共享一些数据,那么就将这些数据,在global中,这是一个全局会话,这几台服务器都能得到里面的信息。

2.1 单例对象

1.基本概念
    出生:当容器创建时,对象出生。
    活着:只要容器还在,对象一直活着
    死亡:容器销毁,对象消亡
    ```
    <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl"
                scope="prototype" init-method="init" destroy-method="destroy"></bean>
    ```
        init-method:指定类中的初始化方法名称
        destroy-method:指定类中的销毁方法名称

2. 手动关闭容器
    main是一切应用程序的入口,当main方法结束之后,当前应用中线程占据的内存全部释放。
    当然也包含创建的容器,但是此时并没有调用销毁方法destroy,就已经把内存释放了。所以说还没有来得及调用销毁,容器就已经消失了。要是想要调用销毁方法,怎么做呢?———— 手动关闭容器。
    * 注意:
        close()方法存在于ApplicationContext的子类中,ApplicationContext本类没有该方法
        前面applicationContext对象我们是通过多态的方式实例化的。
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        此时applicationContext被看成了父类的对象,那么调用的方法编译时看的是父类的方法。如果父类有这个方法,那么可以编译通过,执行的是子类的方法。
        close()方法存在于ApplicationContext的子类ClassPathXmlApplicationContext中,所以我们要想用.close()方法,实例化ClassPathXmlApplicationContext,就不要用多态
        ClassPathXmlApplicationContext cac = new ClassPathXmlApplicationContext("bean.xml");
        classApplicationContext.close();
3. 多例对象
        出生:当使用对象时,spring框架才为我们创建对象
        活着:对象在使用过程中一直活着。
        死亡:spring虽然很强大,但是它仍然无法知道我们这个对象要用到什么时候,什么时候销毁。所以它不会轻易的将这个对象销毁的。
        * 当对象长时间不用且没有别的对象引用时,由java的垃圾回收器回收

2.2 依赖注入

 * 依赖注入的方式:
        1. 使用构造方法提供
        2. 使用set方法提供
        3. 使用注解提供
        * 能注入的数据:
            基本数据类型和String
            其他的bean类型(在配置文件中/注解 配置过的bean,即在spring容器中体现过的bean)
            复杂类型/集合类型

2.2.1 构造方法注入

    ```
        public IAccountServiceImpl(String name, Integer age, Date birthday){
            this.name = name;
            this.age = age;
            this.birthday = birthday;
        }
    ```
    使用的标签: constructor-arg
    标签出现的位置:bean标签内部
    标签中的属性:
        type:用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
              如果有两个String属性的参数,type就无法指定哪个第一个哪个放第二个
              
        index:用于指定要注入的参数在构造函数中的索引位置。参数索引从0开始
        
        name:用于指定,给构造函数中指定名称的参数赋值(常用)
        
        * 以上三个用于指定给构造函数中哪个参数赋值
        
        value:用于提供基本类型和String类型的数据的值
        
        ref:用于指定其他的bean类型数据,它指的就是在spring的IOC核心容器中出现过的bean对象。
              就是在xml或注解中配置过这个bean,那都可以通过ref来引用。
            
              
    配置文件bean.xml代码
    ```
    <bean id="accountService" class="com.itheima.service.impl.IAccountServiceImpl">
        <constructor-arg name="age" value="18"></constructor-arg>
        <constructor-arg name="name" value="test"></constructor-arg>
        <constructor-arg name="birthday" ref="now"></constructor-arg>
    </bean>
    <bean id="now" class="java.util.Date"></bean>
    ```
    除非必要,一般不用。

2.2.2 set方法

1. 需要注入的Bean类
    ```
        private String name;
        private Integer age;
        private Date birthday;
    
        public void setQQName(String name) {
            this.name = name;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    ```
2. bean.xml配置:
    使用的标签:property
    出现的位置:bean标签的位置
    标签的属性:
        name:用于指定,给构造函数中指定名称的参数赋值(常用)
        value:用于提供基本类型和String类型的数据的值
        ref:用于指定其他bean类型数据,它指的就是在spring的IOC核心容器中出现过的bean对象。就是在xml或注解中配置过这个bean,那都可以通过ref来引用。    
3. bean.xml代码
    ```
        <bean id="accountService2" class="com.itheima.service.impl.IAccountServiceImpl2">
            <property name="age" value="21"></property>
            <property name="birthday" ref="now2"></property>
            <property name="QQName" value="撕撕撕"></property>
        </bean>
        <bean id="now2" class="java.util.Date"></bean>
    ```
4. 优势:
    很灵活,没有明确的限制,不影响使用默认构造方法

2.2.3 复杂类型注入

1.要注入的Bean类:成员变量都是数组,集合,map等。

 ```
    private String[] myStr;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String, String> myMap;
    private Properties myProps;

    public void setMyStr(String[] myStr) {
        this.myStr = myStr;
    }

    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 setMyProps(Properties myProps) {
        this.myProps = myProps;
    }
 ```
 
2. 相应的bean.xml配置
```
    <bean id="accountService3" class="com.itheima.service.impl.IAccountServiceImpl3">
        <property name="myStr" >
            <array>
                <value>AAA</value>
                <value>BBB</value>
                <value>CCC</value>
            </array>
        </property>
        <property name="myList" >
            <list>
                <value>qweq</value>
                <value>q55q</value>
                <value>888q</value>
            </list>
        </property>
        <property name="myMap">
            <map>
                <entry key="testa" value="aaaa"></entry>
                <entry key="testb">
                    <value>bbbb</value>
                </entry>
            </map>
        </property>
        <property name="mySet">
            <set>
                <value>12..as</value>
                <value>%$^.as</value>
                <value>omg</value>
            </set>
        </property>
        <property name="myProps">
            <props>
                <prop key="testC">cccc</prop>
                <prop key="testD">dddd</prop>
            </props>
        </property>
    </bean>
```

3 注解的使用

3.1 Aurowired Qualifier Resource Value

1.Autowired:
    作用:自动按照类型注入。
    只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配就可以注入成功。
*注意这里是唯一。如果没有匹配的就会报错。
2.@Qualifier
作用:在按照类中注入的基础上在按照名称注入。它在给成员注入时不能单独使用(搭配@Autowired)。
*但是在给方法参数注入时可以单独使用。 value:用于指定注入bean的id
```
@Autowired
@Qualifier("accountDao1")
private IAccountDao accountDao1 = null;
```
3.@Resource
作用:直接按照bean的id注入。它可以独立使用
属性:name用于指定bean的id
```
@Resource(name="accountDao1")
private IAccountDao accountDao1 = null;
```
***以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现,另外集合类型的注入只能通过XML来实现。
4.@Value:
作用:用于注入基本类型和String类型的数据
属性:value用于指定数据的值,它可以使用spring中的SpEl(也就是spring的el表达式)
      SpEL的写法:$(表达式)

3.2 Scope PostConstruct PreDestroy

1.用于指定ApplicationContext接口是创建单例还是多例对象的:
ApplicationContext接口可以根据我们的需要来创建单例/多例:我们怎么发出需求,
通过scope。如果配置的scope取值为singleton那么单例创建,如果配置的scope取值为
prototype那么多例创建。
属性:value
指定范围的取值,常用取值:singleton prototype(默认情况下是单例的)
2.PostConstruct:用于指定初始化方法
3.PreDestroy:用于指定销毁方法
    ---IOC容器关闭时.close()才会执行该方法。
    注意:其关闭容器的close()方法,是ApplicationContext子类的方法,@PreDestroy要想起作用。容器对象必须是ClassPathXmlApplicationContext对象。
    注意:如果注解的方法所在的类,是通过构造器多例创建的那么容器不能通过close()方法关闭,spring不负责销毁。因为它不知道你什么时候用完,所以只有单例时才可以用这个注解。
```
public static void main(String[] args) {
    ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountService as = (IAccountService)ac.getBean("iAccountServiceImpl");
    as.saveAccount();
    ac.close();
}
@Scope(value = "singleton")
public class IAccountServiceImpl implements IAccountService {
    @Resource(name="accountDao1")
    private IAccountDao accountDao1 = null;

    @PostConstruct
    public void init(){
        System.out.println("初始化方法执行");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("销毁方法执行了");
    }
}
```

4.单表crud操作

4.1 使用xml方式

1.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="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!--通过set方法:注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!--通过set方法:注入QueryRunner-->
        <property name="runner" ref="queryRunner"></property>
    </bean>

    <!--配置注入QueryRunner对象:由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。
        注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建
        即:每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
    -->
    <bean id="queryRunner" 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/txl_spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>
```
    注意:
    1.配置AccountServiceImpl Bean对象
        2.通过set属性方法(property),注入AccountServiceImpl中的Bean类型的成员变量:AccountDaoImpl
        3.配置AccountDaoImpl Bean对象
            4.通过set属性方法(property),注入AccountDaoImpl的Bean类型的成员变量:QueryRunner
            5.配置QueryRunner Bean对象
                *6.通过构造方法(constructor-arg)注入DataSource
                    ```
                    public QueryRunner(DataSource ds) {
                        super(ds);
                    }
                    ```
                    *7注入(property)DataSource的4个属性
    2.注入QueryRunner对象:
        1.由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。(考虑到事务)
        2.注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
        ```
        <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!--注入数据源-->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
        ```
2. Test
    ```
        public void testfindAll() {
        //1.获取容器
        ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
        /**
         * 2.得到业务层对象:
         *      由于IAccountServiceImpl有IAccountDaoImpl成员属性
         *      IAccountDaoImpl有QueryRunner成员属性
         *      所以同时得到了IAccountServiceImpl对象,IAccountDaoImpl对象,QueryRunner对象
         */
        IAccountService accountService = container.getBean("accountService", IAccountService.class);
        //3.执行方法
        List<Account> accounts = accountService.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }
    ```

4.2 使用注解方式

1. bean.xml
    * 文件头要改:context
    * 告知spring:在创建容器时要扫描的包
      <context:component-scan base-package="com.itheima"></context:component-scan>
    * 只留下注入QueryRunner和数据源
    ```
    <?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/>
        <!--告知spring:在创建容器时要扫描的包-->
        <context:component-scan base-package="com.itheima"></context:component-scan>
        <!--配置注入QueryRunner对象:由于现在是单表的CRUD操作,所以可以选择传入数据源(连接池)。注意这里获取的QueryRunner默认是单例的,这样就可能出现线程问题,所以用多例模式创建
        即:每次使用QueryRunner对象都会创建一个新的QueryRunner对象。
        -->
        <bean id="queryRunner" 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/txl_spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    </beans>
    ```
2.  AccountServiceImpl和AccountDaoImpl
        * 需要注入的属性就不需要set方法了。
        * 创建Bean:
            @Service("accountService")
            @Repository("accountDao")
        * 注入(引用类型)属性
            @Resource(name="accountDao") 
            @Resource(name="queryRunner")

4.2 使用注解有哪些瓶颈

1. 我们目前学习的注解:
   *1 只能加到自己写的类上面,所以如果将其他jar包中的类也交给spring来创建或注入的话,
      只能用xml来配置,这是我们目前面临的一个问题。
      比如此例中:QueryRunner是外部jar包中的一个类,显然不能在上面加注解。
   *2 还有告知spring要扫描的包,我们也要在xml中写。

4.3 实现纯注解的步骤

想要去掉XML中的配置,全部用注解Anno来完成这个案例:
我们可以手动创建一个配置类来完成之前XML配置中剩下的内容。
SpringConfiguration类
```
package config; //在java资源文件下建立config包
@Configuration
@ComponentScan(value={"com.itheima"})
public class SpringConfiguration {
}
```
1.来看一下这两个注解
    * 1.@Configuration 
        作用:指定当前类是一个配置类 SpringConfiguration
    * 2.ComponentScan
        作用:通过该注解指定spring在创建容器时要扫描的包
        属性:value和basePackages互为别名,所以是一样的。
              都是数组,所以标准写法:
              value={"com.itheima", "xxx"}
              @ComponentScan(value={"com.itheima"}):用于指定创建容器时要扫描的包。
* 那么现在我们就解决了遗留的第一个XML配置
    ```
        <!--告知spring:在创建容器时要扫描的包-->
        <context:component-scan base-package="com.itheima"></context:component-scan>
    ```
2. 第二个问题:怎么通过注解的方式让其他jar包中的类也交给spring来创建或注入
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    发现:可以通过调用构造函数来实例化QueryRunner和DataSource,QueryRunner是有参构造,
          DataSource是无参构造。
    1 继续完善SpringConfiguration    
    我们分析一下:
        ```
        1.<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        ```
        ```
        2.public QueryRunner creatQueryRunner(DataSource dataSource){
            return new QueryRunner(dataSource);
        }
        ```
        这两者都能创建一个quertRunner对象,但是前者创建后会放到IOC容器中,而后者不会放在IOC中。
    2 新的注解:@Bean  标注在方法上,将返回值放到容器中。
        作用:用于把当前方法中的返回值bean对象存入springIOC容器中。
        属性:name用于指定bean的id。默认值:当不指定时:当前方法的名称
        细节:
            1.当我们使用注解配置时,如果方法有参数,spring框架会去容器中
              查找有没有可用的bean对象。查找的方式和AutoWired的注解方式
              是一样的:自动按照类型注入,如果有唯一的类型直接注入,如果
              有多个再按照变量名注入。
              ```
              @Bean(name="queryRunner")
              @Scope(value="prototype")   //注意QueryRunner我们需要多例创建
              public QueryRunner creatQueryRunner(DataSource dataSource){
                return new QueryRunner(dataSource);
              }
              ```
              即参数DataSource dataSource也会去容器中匹配相应的bean对象,匹配
              方式同@AutoWerid的引用类型注入。
            2.注意这里指定的@Bean(name="queryRunner"),也就是要用queryRunner取
              容器中的QueryRunner对象,所以如果其他类中要注入QueryRunner对象时,
              要注意指明name是queryRunner。
              
            3.这种也是调用构造方法创建的QueryRunner对象,而且我们没有指明是多例创建,
              所以默认也是单例创建,针对这个获取数据源连接池的QueryRunner对象多例
              创建,再加上:
              @Scope(value="prototype")   
            
            ```
                @Bean(name="queryRunner")
                @Scope(value="prototype")   //注意QueryRunner我们需要多例创建
                public QueryRunner creatQueryRunner(DataSource dataSource){
                    return new QueryRunner(dataSource);
                }
                
                @Bean("dataSource")
                public DataSource creatDataSource(){
                    ComboPooledDataSource ds = new ComboPooledDataSource();
                    try {
                        ds.setDriverClass("com.mysql.jdbc.Driver");
                        ds.setJdbcUrl("jdbc:mysql://localhost:3306/txl_spring");
                        ds.setUser("root");
                        ds.setPassword("root");
                        return ds;
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            ```
    3. 这个时候我们就完全用注解实现了这个案例:可以不用xml配置文件了
       最后:修改一下获取容器的方式
       用注解方式获容器,不用加载配置文件的方式。
    Test方法中:
       * 之前的获取方式:
        //获取容器
        ApplicationContext container = new ClassPathXmlApplicationContext("bean.xml");
       * 现在的方式   
        //注解方式:获取容器的方法也变了,参数是被@Configuration注解过的类(可以使多个)。
        ApplicationContext container  = new AnnotationConfigApplicationContext(SpringConfiguration.class);
       注意:参数是被@Configuration注解过的类(可以有多个)。

4.4 配置类的写法

@ComponentScan("com.itheima")
@Import(JdbcConfig.class)
@PropertySource(value="classpath:jdbcConfig.properties")
public class SpringConfiguration {

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

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

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="ds2")
    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="ds1")
    public DataSource createDataSource1(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

4.5 容器中有多个相同类型的参数吻合方法参数时:

同@Autowired注解规则一样。我们想到@Qualifier,
但是@Qualifier在给类成员标注时必须搭配@Autowired。我们又发现:
@Qualifier是可以标注在方法参数中的:ElementType.PARAMETER,
而且此时不需要搭配同@Autowired。所以我们为形参指定id(key):

4.6 spring和junit整合

使用抽取公共代码的方式进行改进

    1.导入spring整合junit的jar包(坐标)
        ```
            <!--spring整合junit的jar包-->
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
        ```
    2.使用JUNIT提供的一个注解把原有的main方法替换掉,替换成spring提供的
    
        @RunWith(SpringJUnit4ClassRunner.class)
        
            Class<? extends Runner> value(); 里面的值必须是一个继承Runner的字节码类。
            SpringJUnit4ClassRunner extends BlockJUnit4ClassRunner
        
            SpringJUnit4ClassRunner是Spring提供的,它一定会为我们提供容器。它还得知道
        你有的是XML配置文件还是注解。配置文件放哪了,配置类是谁?
    3. 告知Spring的运行器:
        spring的IOC创建是基于 是XML配置文件还是注解,并且说明位置。
        @ContextConfiguration
            locations:指定xml文件的位置,加上classpath关键字,表示在类路径下。
            classes:指定注解类所在的位置
            
            @ContextConfiguration(locations = "classpath:bean.xml")
            @ContextConfiguration(classes = SpringConfiguration.class)
    4. 小细节:版本要求
        当我们使用spring 5.x版本时,要求junit的jar必须是4.12及以上。这样
    在运行测试方法时,SpringJUnit4ClassRunner才会为我们创建IOC容器。
/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)//改变main方法
@ContextConfiguration(locations = "classpath:bean.xml")//创建容器
public class AccountServiceTest {
    @Autowired
    private  IAccountService as;
    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }
}
<!--以下是bean.xml的配置-->
<context:component-scan base-package="com.itheima"></context:component-scan>
<!--配置QueryRunner-->
<bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--连接数据库的必备信息-->
    <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
    <property name="jdbcUrl"  value="jdbc:mysql://localhost:3306/eesy?useSSL=false&amp;serverTimezone=GMT"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

5 事务

5.1 的示例:

    *我在业务层中加入一个方法:模拟转账
    ```
        public void transfer(String sourceName, String targetName, float money) {
        //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //3.转出账户减钱
        source.setMoney(source.getMoney() - money);
        //4.转入账户加钱
        target.setMoney(target.getMoney() + money);
        //5.更新转出账户
        accountDao.updateAccount(source);

        int i = 1/0; //模拟转账异常

        //6.更新转入账户
        accountDao.updateAccount(target);
    }
    ```
2.执行之后发现:
    由于有异常,转账失败。
    但是转出账户的钱减少了,转入账户的钱却没有增加。
    
 
3. 分析:
    1.事务管理的问题
    首先应该确定,我们的代码中是有事务提交的,因为其中的增删改方法都能够正常执行且没有回滚的。
如果没有事务,那么增删改显然是无法提交的。
    换句话说,不是没有事务造成的而是事务的管理出现了问题。

# 5.2.分析QueryRunner对象
1.前面提到每次用QueryRunner时,都会再次创建一个新的对象,同时也会从数据池中拿出一个新的连接。

2.整个过程,模拟转账代码和数据库交互了4次。那么这样就会有4个connection对象。
而每个connection也都有自己的独立事务,那么情况就是:
	第一个connection成功执行了---事务提交
	第二个connection成功执行了---事务提交
	第三个connection成功执行了---事务提交
	第四个connection没成功----------事务不提交
3.解决方式:使用ThreadLocal获取的Connection对象,来控制事务使用唯一一个Connection
*** 明确一点:之前的代码中是有事务提交的,因为其中的增删改方法都能够正常执行且没有回滚的。如果没有事务,那么增删改显然是无法提交的。换句话说,不是没有事务造成的而是事务的管理出现了问题。

5.1 手写一个事务管理器

```连接池工具类
public class ConnectionUtils {

    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired
    private DataSource dataSource;
    <!--这个set方法是为了在xml配置ConnectionUtils时可通过set方法注入-->
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     *     控制事务,是靠connection把手动提交改成自动提交
     *     再通过commit和rollback来控制事务。
     */
    public Connection getThreadConnection(){
        try{
            //1.先从ThreadLocal上获取
            Connection coon = tl.get();
            //2.判断当前线程上是否有连接
            if(coon == null){
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                coon = dataSource.getConnection();
                //4.存入ThreadLocal
                tl.set(coon);
            }
            //5.返回当前线程上的连接
            return coon;
        }catch (Exception e){
            throw  new RuntimeException(e);
        }
    }
    
    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}
```
```事务管理器代码
    public class TransactionManager {

        @Autowired
        private ConnectionUtils connectionUtils;
        //等待Spring将其注入
        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();
            }
        }
    }
```
``` service层代码
public void transfer(String sourceName, String targetName, float money) {
    try {
        //1.开启事务
        txManager.beginTransaction();
        //2.执行操作
            //1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
            //2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
            //3.转出账户减钱
        source.setMoney(source.getMoney() - money);
            //4.转入账户加钱
        target.setMoney(target.getMoney() + money);
            //5.更新转出账户
        accountDao.updateAccount(source);

        int i = 1/0; //模拟转账异常

        //6.更新转入账户
        accountDao.updateAccount(target);
        //3.提交事务
        txManager.commit();

    } catch (Exception e){
        //4.回滚操作
        txManager.rollback();
        e.printStackTrace();
    }finally {
        //5.释放连接
        txManager.release();
    }
}
```dao层代码
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);
    }
}

5.2 改进service层的代码 通过动态代理

1.AccountServiceImpl       
```
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    public void setAccountDao(IAccountDao accountDao){
        this.accountDao = accountDao;
    }
    public List<Account> findAllAccount() {
        return  accountDao.findAllAccount();
    }
    public void transfer(String sourceName, String targetName, float money) {
        System.out.println("transfer...");
                //1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
                //2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
                //3.转出账户减钱
            source.setMoney(source.getMoney() - money);
                //4.转入账户加钱
            target.setMoney(target.getMoney() + money);
                //5.更新转出账户
            accountDao.updateAccount(source);

            //int i = 1/0; //模拟转账异常

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

    }
}
```

2. 创建AccountServiceImpl的代理对象的工厂: BeanFactory
```
public class BeanFactory {
    private IAccountService accountService;
    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }
    private TransactionManager txManager;
    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

    public IAccountService getAccountService(){
        IAccountService proxyAccountService = 
        (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),accountService.getClass().getInterfaces(),
                new InvocationHandler() {

                    /**
                     * 添加事务的支持
                     * @param proxy
                     * @param method
                     * @param args
                     * @return
                     * @throws Throwable
                     */
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        try {
                            //1.开启事务
                            txManager.beginTransaction();
                            //2.执行操作
                            rtValue = method.invoke(accountService, args);
                            //3.提交事务
                            txManager.commit();
                            //4.返回结果
                            return rtValue;

                        } catch (Exception e){
                            //5.回滚操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        }finally {
                            //6.释放连接
                            txManager.release();
                        }
                    }
                });

        return proxyAccountService;
    }
}
```

6 spring之aop

6.1基本术语

1.Joinpoint(连接点):
    所谓连接点是指那些被拦截到的点,在spring中,这些点指的是方法,因为
spring只支持方法类型的连接点。
2.Pointout(切入点)
    所谓切入点是指我们要对哪些Joinpoint进行增强。
    我们可以指定Service层哪些方法被增强,哪些方法不被增强。
    ```
    if("test".equals(method.getName())){
        // 如果是test函数:按照真实对象中的方法执行:没有事务支持
        // 调用method对象的invoke方法,并且指明该method是accountService中的method,且传递的参数args也不改变
        return method.invoke(accountService, args);
    }
    ```
3. Advice(通知/增强)
    所谓通知是指拦截到的Joinpoint之后要做的事情就是通知。
    通知的类型:
        前置通知
        后置通知
        异常通知
        最终通知
        环绕通知

4.Introduction(引介)
    引介是一种特殊的通知,在不修改类代码的前提下,Introduction可以在运行期为
类动态地添加一些方法或Field
5.Target(目标对象)
    代理的目标对象:被代理对象
6.Weaving(织入)
    是指把增强应用到目标对象来创建新的代理对象的过程。
7. Proxy(代理)
    一个类被AOP织入增强后,就产生的一个结果代理类。
8.Aspect(切面):
    是切入点和通知(引介)的结合。
    例如:
        被增强的方法:transfer()
        通知:就是那些提供了公共代码的类中的方法。如之前写的transactionManager中的开启事务,提交事务,回滚事务,释放连接的方法
    切面:建立切入点方法和通知方法在执行调用的先后关系

6.2 Spring基于XML的AOP配置步骤

* 要用AOP的配置,所以要在bean.xml中将AOP的约束放进来。ctrl+F : 搜索xmlns:aop
* 导入aspectjweaver依赖
    aspectj是一个软件联盟:能解析出我们在bean.xml中配置的切入点表达式。
1.把通知类(Logger)bean也交给spring来管理
2.使用aop:config标签表明开始AOP的配置
3.使用aop:aspect标签开始配置切面
    id属性:给切面提供一个唯一标识 随便取:logAdvice()表明是,log通知
    ref属性:使用指定通知类(Logger)的bean的id。
4.在aop:aspect标签的内部使用对应标签来配置通知的类型
    我们现在的示例是让通知类(Logger)中的printLog方法在切入点之前执行,所以是前置通知。
    aop:before:表示配置前置通知
        method属性:用于指定Logger类中的哪个方法是前置通知
5.pointcut属性:用于指定对业务层中哪些方法增强,
    切入点表达式的写法:
    关键字:execution(表达式)
    表达式:访问修饰符 返回值  包名.包名.包名... 类名.方法名(参数列表)
    标准的表达式写法  public void com.itheima.service.impl.AccountServiceImpl.saveAccount()

6.3 四种通知类型

1. aop:before :配置前置通知的类型
        在切入点方法之前执行
2. aop:after-returning :配置后置通知的类型----代表着切入点方法成功执行。
        在切入点方法成功的执行之后才会执行后置通知
3. after-throwing:配置异常通知的类型
        切入点方法出现异常后,会执行异常通知
4. aop:after :配置最终通知的类型
        在finally()里面的通知,无论切入点方法是否正常之心,一定会执行。
最多执行三个:
    后置通知和异常通知只有一个能执行成功。

6.4 环绕通知

如下代码没有明确的切入点调用

我们也要在环绕通知里调用切入点方法
    Spring框架为我们提供了一个接口:ProceedingJoinPoint。
    该接口有一个方法proceed()
    此方法就相当于明确调用切入点方法。该接口可以作为环绕通知的方法参数,
    在程序执行时,spring框架会为我们提供该接口的实现类供我们使用。
    简单来说:拿去用就行了,其他的spring都给你弄好了。
    修改原来的环绕通知方法:aroundPrintLog()

    * 传入参数 ProceedingJoinPoint  pjp
    * 调用其中的方法:pjp.proceed()
    * 增强方法:通知
        System.out.println("Logger类中的环绕通知aroundPrintLog()开始记录日志了");
        写在pjp.proceed()之前就是前置
        写在pjp.proceed()之后就是后置
        写在catch里面就是异常
        写在finally里面就是最终
        最后这整个aroundPrintLog()就变成了环绕通知。
    *注意:注意:此处异常必须写Throwable t ; Exception拦不住它

6.5 spring中基于XML和注解的AOP配置

1.改成IOC的注解:改成context的名称空间---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">
```
2.bean.xml的其他配置
    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.itheima"></context:component-scan>
    <!--配置springAOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>    
3. 也可以把一点XML都不写:写一个配置类 SpringConfiguration

4. 注解通知类
@Component("logger")
@Aspect//表示当前类是一个切面类
public class Logger {
@Around("pt1()")
public Object aroundPringLog(ProceedingJoinPoint pjp){
    Object rtValue = null;
    try{
        Object[] args = pjp.getArgs();//得到方法执行所需的参数

        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。前置");

        rtValue = pjp.proceed(args);//明确调用业务层方法(切入点方法)

        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。后置");

        return rtValue;
    }catch (Throwable t){
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。异常");
        throw new RuntimeException(t);
    }finally {
        System.out.println("Logger类中的aroundPringLog方法开始记录日志了。。。最终");
    }
}

@Pointcut("execution(* com.itheima.service.impl.*.*(..))")
private void pt1(){}

/**
 * 前置通知
 */
@Before(value = "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方法开始记录日志了。。。");
}
**顺序错乱问题:如图

7 spring中基于XML和注解的AOP配置

7.1 纯配置实现事务控制

1.实体类: Account
```
@Data
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
    ...
}
```
2.账户的业务层实现类:AccountServiceImpl
```
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;
    public void setAccountDao(IAccountDao accountDao){
        this.accountDao = accountDao;
    }
    public void transfer(String sourceName, String targetName, float money) {
        System.out.println("transfer...");
                //1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
                //2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
                //3.转出账户减钱
            source.setMoney(source.getMoney() - money);
                //4.转入账户加钱
            target.setMoney(target.getMoney() + money);
                //5.更新转出账户
            accountDao.updateAccount(source);
            //int i = 1/0; //模拟转账异常
                //6.更新转入账户
            accountDao.updateAccount(target);
    }
}
```
3.账户的持久层实现类
```
public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner;
    private ConnectionUtils connectionUtils;
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    public void setRunner(QueryRunner runner){
        this.runner = runner;
    }
        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 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);
        }
    }
}
```
4.连接工具类
  从数据源中获取一个连接,并且实现和线程的绑定
```
public class ConnectionUtils {
    //1.ThreadLocal对象,用泛型指定管理对象是java.sql.Connection
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired
    private DataSource dataSource;
    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * 获取当前线程上的连接
     *     控制事务,是靠connection把手动提交改成自动提交
     *     再通过commit和rollback来控制事务。
     * @return
     */
    public Connection getThreadConnection(){
        try{
            //1.先从ThreadLocal上获取
            Connection coon = tl.get();
            /**
             * 2.判断当前线程上是否有连接:
             */
            if(coon == null){
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                coon = dataSource.getConnection();
                //4.存入ThreadLocal
                tl.set(coon);
            }
            //5.返回当前线程上的连接
            return coon;
        }catch (Exception e){
            throw  new RuntimeException(e);
        }
    }
    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}
```
5.事务管理类
```
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    //等待Spring将其注入
    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();
        }
    }
}
```
6.配置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">
        
    <!--配置service对象-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!--配置QueryRunner对象-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    </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/txl_spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--配置Connection的工具类:ConnectionUtils-->
    <bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置事务管理类:TransactionManager
        也就是提供了重复代码的通知类,将其交给spring来管理。
    -->
    <bean id="transactionManager" class="com.itheima.utils.TransactionManager">
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!--配置AOP-->
    <aop:config>
        <!--配置一个通用的切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"/>
        <aop:aspect id="txAdvice" 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>
```

7.2 配置和注解实现事务控制

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.itheima"></context:component-scan>
    <!--配置QueryRunner对象-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    </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/txl_spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--开启spring对注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>
```
3.事务管理类
```
@Component("transactionManager")
@Aspect
public class TransactionManager {
    @Resource(name="connectionUtils")
    private ConnectionUtils connectionUtils;
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    private void pt1(){
    }
    /**
     * 开启事务
     */
    @Before("pt1()")
    public void beginTransaction(){
        try{
            connectionUtils.getThreadConnection().setAutoCommit(false);
        } catch (Exception e){
            e.printStackTrace();
        }

    }
    /**
     * 提交事务
     */
    @AfterReturning("pt1()")
    public void commit(){
        try{
            connectionUtils.getThreadConnection().commit();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
        /**
     * 回滚事务
     */
    @AfterThrowing("pt1()")
    public void rollback(){
        try{
            connectionUtils.getThreadConnection().rollback();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    @After("pt1()")
    public void release(){
        try{
            connectionUtils.getThreadConnection().close();
            //解绑
            connectionUtils.removeConnection();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
}
```
4.连接工具类
```
@Component("connectionUtils")
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired
    private DataSource dataSource;
    public Connection getThreadConnection(){
        try{
            //1.先从ThreadLocal上获取
            Connection coon = tl.get();
            if(coon == null){
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                coon = dataSource.getConnection();
                coon.setAutoCommit(false);
                //4.存入ThreadLocal
                tl.set(coon);
            }
            //5.返回当前线程上的连接
            return coon;
        }catch (Exception e){
            throw  new RuntimeException(e);
        }
    }
    public void removeConnection(){
        tl.remove();
    }
}
```
5.业务层
```
@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 accountId) {
        accountDao.deleteAccount(accountId);
    }
    public void transfer(String sourceName, String targetName, float money) {
        System.out.println("transfer...");
                //1.根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
                //2.根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
                //3.转出账户减钱
            source.setMoney(source.getMoney() - money);
                //4.转入账户加钱
            target.setMoney(target.getMoney() + money);
                //5.更新转出账户
            accountDao.updateAccount(source);
            //int i = 1/0; //模拟转账异常
                //6.更新转入账户
            accountDao.updateAccount(target);
    }
}
```
6.持久层...
7. 分析:
    先回想一下,注解实现AOP的一个小问题:先调用最终通知,再调用异常通知/后置通知。
    所以先调用最终通知就会将coon关闭了,再将coon与当前线程解绑。
    ```
    @After("pt1()")
    public void release(){
        try{
            connectionUtils.getThreadConnection().close();
            //解绑
            connectionUtils.removeConnection();
        } catch (Exception e){
            e.printStackTrace();
        }
    }
    ```
    那么再调用后置通知时:会先调用connectionUtils.getThreadConnection().commit();
发现没有连接了,它会再从数据源中拿一个新的coon然后绑上去。
    为什么可以拿新的coon,因为我们在ConnectionUtils类中写的方法:
    connectionUtils.getThreadConnection()
    前置通知执行完了(因为前置通知会将autocommit置false),才从数据源中拿一个新的
coon绑上去。注意从数据池中得到的新的coon的autocommit=true,所以再提交commit就会报错:
    Can't call commit when autocommit=true
    那我们在ConnectionUtils.getThreadConnection()获取新的coon时,将其AutoCommit设为false。
设置成false之后,那么可以提交了,但是数据库中的数据会改吗?
    因为刚才操作的那个coon被先关闭了,而现在这个新的coon虽然你提交,没有异常产生了。但是 新的coon里面没有任何的执行操作,什么都提交不了。 
8. 所以这就是Spring基于注解的配置AOP时:通知的执行调用是有问题的,
  如果非要用注解:我们用环绕通知。

7.3 Spring提供的工具来进行事务控制

*概述
 1. JavaEE体系进行分层开发,事务处理位于业务层,Spring提供了分层设计业务层的事务处理解决方案。
 2. spring框架为我们提供了一组事务控制的接口。具体在后面的第二小节介绍。这组接口是在
    spring-tx-5.0.2.RELEASE.jar中。
    所以要先导入依赖
 3. spring的事务控制都是基于AOP的,它既可以使用编程的方式实现,也可以使用配置的方式实现。
 4. 注意:我们学习的重点是使用配置的方式实现。

7.3.1 Spring基于XML的事务控制

步骤: 1. 配置spring的事务管理器 这个里面有提交和回滚等方法。然后在第2步,指明其是通知。 2. 配置事务的通知 此时我们要导入事务的约束: tx的名称空间和约束,同时也需要aop的约束 xmlns:tx 使用tx:advice配置事务通知 属性: id:给事务通知起一个唯一标志 transaction-manager:给事务通知提供一个事务管理器引用 3. 配置AOP中的通用切入点表达式 4. 建立事务通知和切入点表达式的对应关系 5. 配置事务的属性
是在事务的通知tx:advice标签的内部 明确:因为用的是spring自带的事务管理器,里面有提交和回滚等方法。所以我们只要指定管理器和切入点方法的联系即可。不用我们在设置里面具体的执行步骤。 <!--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"> <tx:attributes> <!--4.为service中的所有方法,配置事务的属性 isolation="":指定事务的隔离级别,默认值DEFAULT表示使用数据库的默认隔离级别 no-rollback-for="":用于指定一个异常,产生该异常时不回滚,其他异常会滚。没有默认值,表示任何异常都会滚。 propagation="":指定事务的传播级别:默认REQUIRED:没有就新建一个,有就添加进去。适合增删改。SUPPORTS:没事务就用,没有就不用。适合查询 read-only="":事务是否只读:查询方法可以设为只读true,增删改设为读写。 rollback-for="":用于指定一个异常,产生该异常时回滚,其他异常不会滚。没有默认值,表示任何异常都会滚。 timeout="" :指定事务的超时时间:,默认-1,永不超时。指定数值以秒为单位。 --> <tx:method name="*" propagation="REQUIRED" read-only="false"/> <!--为service中的findxxx()方法,配置事务的属性 这个优先级是高于上面的全通配的 --> <tx:method name="find*" propagation="SUPPORTS" read-only="true"/> </tx:attributes> </tx:advice> <!--3.配置AOP--> <aop:config> <!--3.1配置通用点表达式--> <aop:pointcut id="pt1" expression="execution(*com.itheima.service.impl.*.*(..))"/> <!--3.2建立切入点表达式和事务通知的对应关系--> <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor> </aop:config>

7.3.2 Spring基于注解和XML结合的事务控制

1.配置事务管理器
```
<!--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>
```
3.在需要事务支持的地方使用@Transactional注解

不仅仅在类上,可以指定到具体的方法

7.3.2 Spring基于注解的事务控制 (没有bean.xml)

  1.账户的持久层实现类
```
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
//1.按方法名查询一个
public Account findAccountByName(String accountName) {
}   
//2.更新账户
public void updateAccount(Account account) {
}
```
2.账户的业务层实现类:让事务控制和业务层的方法分离
  在需要事务支持的地方使用@Transactional注解
```
@Service("accountService")
@Transactional                  //事务的属性全在里面:read-only/timeout/isolation...
public class AccountServiceImpl implements IAccountService {
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public void transfer(String sourceName, String targetName, Float money) {
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);
        accountDao.updateAccount(source);
        //int i = 1/0; //模拟转账异常
        accountDao.updateAccount(target); 
    }
}
```
3. 连接资源配置文件:jdbcConfig.properties
```
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/txl_spring
jdbc.username=root
jdbc.password=root
```
4. 3个配置类
    主配置类:public class SpringConfiguration,引入子配置类
    子配置类1:public class JdbcConfig,和连接数据库相关的配置类
    子配置类2:public class TransactionConfig,和事务相关的配置类
    4.1: 子配置类1:public class JdbcConfig,和连接数据库相关的配置类
    ```
    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对象
        @Bean(name="jdbcTemplate")
        public JdbcTemplate createJdbcTemplate(DataSource dataSource){
            return new JdbcTemplate(dataSource);
        }
        //创建数据源对象
        @Bean(name="dataSource")
        public DataSource createDataSource(){
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setPassword(password);
            ds.setUsername(username);
            return ds;
        }
    }
    ```
    4.2: 子配置类2:public class JdbcConfig,public class TransactionConfig,和事务相关的配置类
    ```
    public class TransactionConfig {
        /**
         * 创建管理器对象
         * PlatformTransactionManager  接口
         * new DataSourceTransactionManager(dataSource);    返回它的子类
         * @param dataSource
         * @return
         */
        @Bean(name="transactionManager")
        public PlatformTransactionManager createTransactionManager(DataSource dataSource){
            return new DataSourceTransactionManager(dataSource);
        }
    }
    ```
    4.3 : 主配置类:public class SpringConfiguration
    ```
    /**
     * spring的配置类,相当于bean.xml
     */
    @Configuration  //这个注解可写可不写:因为可以把这个类当参数传递给测试方法
    @ComponentScan("com.itheima")
    @Import({JdbcConfig.class,TransactionConfig.class})
    @PropertySource("classpath:jdbcConfig.properties")  //指明连接资源
    @EnableTransactionManagement   //开启注解支持
    public class SpringConfiguration {
    
    }
    ```
5. 测试方法---spring整合的junit
```
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {
    @Autowired
    private IAccountService accountService;
    @Test
    public void testTransfer(){
        accountService.transfer("bbb", "ccc", 100f);
    }
}
```