11.Spring基于接口的动态代理(抽取事务)减少重复

1.结构图

 

2.实体类Account:

package com.domin;

import java.io.Serializable;

//账户的实体类
public class Account implements Serializable {
    private  Integer id;
    private  String name;
    private  Float money;

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

 

 

 

3.业务层接口IAccountService:

package com.service;

import com.domin.Account;

import java.util.List;

//账户的业务层接口
public interface IAccountService {
    /*查询所有*/
    List<Account> findAllAccount();
    /*查询一个*/
    Account findAccountById(Integer accountId);
    /*保存操作*/
    void saveAccount(Account account);
    /*更新操作*/
    void updateAccout(Account account);
    /*删除操作*/
    void delectAccout(Integer accountId);
    /*事务的解说*/
    /*转账操作
    *   转账源头名称
    *   转账目的名称
    *   转账金额
    * */
    /*被表现层调用,业务层应写好参数*/
    void transfer(String sourceName , String targetName , Float money);

   /*添加一个方法*/
//    void test();/*只是连接点,不是切入点,因为没有被增强*/
}
/*
* 总结:
*       业务层接口中的所有的方法都叫连接点:那是连接上面尼??是连接我们的业务,和增强方法中的那个点,是怎么样才能添加上事务,就是通过这些连接点
*       被增强的方法叫切入点
*       所以:
*           所有的切入点都是连接点(指在接口中的连接点),但所有的切入点不一定是连接点
* 此时:在接口中的所有方法并不是切入点,但是都是连接点,此时除了test都是切入点,如果想把test作为切入点,那就对他进行方法增强就可以了
*
* */

4.业务层实现类AccountServiceIpm:

package com.service.Ipm;
import com.dao.IAccountDao;
import com.domin.Account;
import com.service.IAccountService;
import com.utils.TransactionManager;
import java.util.List;

/*账户的实现类*/
public class AccountServiceIpm implements IAccountService {

   private IAccountDao accountDao;
   /*使用事务管理:那就注入实例对象,来使用*/
   /*private TransactionManager txManager;*/
    /*public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }*/

    /*注入灵魂*/
    public void setAccountDao(IAccountDao accountDao) {
        //accountDao的set方法
        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 updateAccout(Account account) {


        accountDao.updateAccout(account);

    }

    @Override
    public void delectAccout(Integer accountId) {


            accountDao.delectAccout(accountId);
    }

    /*转账*/

    @Override
    public void transfer(String sourceName, String targetName, Float money) {

        //2.1.根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2.根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3.转出账户减钱
        source.setMoney(source.getMoney()-money);
        //2.4.转入账户加钱
        target.setMoney(target.getMoney()+money);
        //int i = 1/0;
        //2.5.更新转出账户
        accountDao.updateAccout(source);
        //2.6.更新转入账户
        accountDao.updateAccout(target);

    }
}

5.持久层接口IAccountDao:

package com.dao;

import com.domin.Account;

import java.util.List;

public interface IAccountDao {
    /*查询所有*/
    List<Account> findAllAccount();
    /*查询一个*/
    Account findAccountById(Integer accountId);
    /*保存操作*/
    void saveAccount(Account account);
    /*更新操作*/
    void updateAccout(Account account);
    /*删除操作*/
    void delectAccout(Integer accountId);
    /*转账操作*/
    /*
    * 根据名称查询账户
    *       如果有唯一的一个结果就返回,如果没有结果就返回null
    *       如果结果集超过一个就抛异常
    * */
    /*被业务层调用。持久层应写好方法*/
    Account findAccountByName(String accountName);
}

6.持久层实现类AccountDaoImp:

 

package com.dao.Ipm;

import com.dao.IAccountDao;
import com.domin.Account;
import com.utils.ConnectionUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.util.List;

/*账户的持久层实现类*/
public class AccountDaoImp implements IAccountDao {
    private QueryRunner runner;
    /*让spring容器提供的set方法*/
    /*使用runner对象*/
    private ConnectionUtils connectionUtils;
    public void setRunner(QueryRunner runner) {

        this.runner = runner;
    }

    /*注入ConnectionUtils对象,脱离直接使用的dataSource,而是从绑定的线程取*/
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    @Override
    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);
        }
    }

    @Override
    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);
        }
    }

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

    @Override
    public void updateAccout(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);
        }
    }

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

    @Override
    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);
        }
    }
/*
* 此时加入数据源之后:我们的dao已经有connect连接数据源的支持,
* 同时它不再通过QueryRunner进行连接(注入)数据源
* 同时我们也通过工具类的方式将线程和连接进行绑定,并且编写了事务的管理
*
*
*
* */

}

7.事务管理类TransactionManager:

package com.utils;

/*
* 和事务管理相关的工具类,它包含了,开启事物,提交事物,回滚事物和释放连接
*
*
* */
/*怎么执行事务的整体呢??*/
/*注入连接ConnectionUtils事务的连接工具类*/

public class TransactionManager {

    /*怎么调用,连接的方法注入进来就可以使用*/
    private ConnectionUtils connectionUtils;
    /*注入并且使用*/
    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }
    /*
    * 开启事务
    */
    public void beginTransaction(){
        try{
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
        }
    }
    /*
     * 提交事务
     */
    public void commit(){
        try{
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
        }
    }
    /*
     * 回滚事务
     */
    public void rollback(){
        try{
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
        }
    }
    /*
     * 释放连接事务
     */
    public void release(){
        try{
            connectionUtils.getThreadConnection().close();
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();//在命令行打印异常信息在程序中出错的位置及原因
        }
    }
/*
* 各种池:也就是说,当服务器启动的时候,如果需要使用该线程的时候,那么程序就会从线程池当中调出一个线程出来使用,这就叫做池
* 例如:数据库连接池、线程池
*线程池:当服务器tomcat一启动的时候,会初始化一堆的线程,也就是操作系统的线程管理,然后放到一个容器当中,接下来我们每次访问时,
*       它都会从线程池中拿出一个线程提供给我们使用(如果关闭线程的时候并不是真正的关闭,而是把线程还回池中)
*
*2.事物的控制都写完了,那么就可以去改造业务层的代码,使他们都在统一的线程下执行,以免发生操作不一
*
* */
}

8.连接工具类ConnectionUtils

package com.utils;


/*
* 连接的工具类:它用于从数据源中获取一个连接,并且实现和线程的绑定(只有统一调度才不会发生改变)
*
*
* */

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

public class ConnectionUtils {

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

        /*万一没有数据源,注入一个再说*/
        private DataSource dataSource;

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

    /*
        * 获取当前线程上的连接(写的一个方法)
        *       线程上有没有,没有就从数据源中取一个存进去,并且绑定
        * */
        public Connection getThreadConnection(){
            try {
                //1.先从ThreadLocal上获取
                Connection conn = ts.get();
                //2.判断是否当前线程上有连接
                if (conn == null) {
                    //3.从数据源中获取一个连接,并且存入ThreadLocal中(也就是绑定线程)
                    conn = dataSource.getConnection();
                }
                //4.返回当前线程上的连接
                return conn;
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
        /*连接之后不用了,就进行线程的解绑,线程还回池中*/
        public void removeConnection(){
            ts.remove();
        }
}

9.代理工厂(重点)

package com.factory;

import com.domin.Account;
import com.service.IAccountService;
import com.utils.TransactionManager;

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

/*使用动态代理*/
public class beanFactory {
 private IAccountService accountService;
 private TransactionManager txManager;
 /*set注入*/

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

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }
    /*写入动态代理截取的的连接点*/
    public IAccountService getAccountService(){
       Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
               new InvocationHandler() {

                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       /*写入test方法*/
                       /*切入点,指的是那些被增强的方法,此时我没有个test绑定事物并且增强,所以他就不是切入点*/
                       if ("test".equals(method.getName())){
                           return method.invoke(accountService,args);
                       }

                       Object returnValue = null;
                       try{
                           //1.开启事务
                           txManager.beginTransaction();
                           //2.执行操作
                           returnValue = method.invoke(accountService,args);
                           //3.提交事物
                           txManager.commit();
                           //4.返回结果
                           return returnValue;
                       }catch (Exception e){
                           //5.回滚操作
                           txManager.rollback();
                           throw new RuntimeException(e);
                       }finally {
                           //6.释放连接
                           txManager.release();
                       }
                   }
               });
        return accountService;
    }
/*到此动态代理的方法已经写完,现在开始配置bean.xml*/
    /*
    * 总结:使用工厂代理的方法来配置动态接口代理
    *       1.先在beanFactory写完动态代理的配置
    *       2.并且注入事物管理和业务层接口
    *           2.1使用的是业务层接口,所以使用accountService来当做被代理对象
    *               accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
    *       3.根据参数写入要求,不要忘记有返回值
    *       4.编写配置文件bean.xml
    *           先编写beanFactory并且写入注入的内容(事物管理还有接口)
    *           然后编写动态代理的对象proxyAccountService and 工厂-bean=“beanFactory”  and工厂-方法=“getAccountService”
    *           为代理中的方法:
    *       本文介绍的是另1种模式, 在工厂方法模式中:
    *            Spring不会直接利用反射机制创建bean对象,
    *           而是会利用反射机制先找到Factory类【现在的是beanFactory类】,
    *           然后利用Factory再去生成bean对象。
    * */
}/*
哪些方法被增强过:哪些方法就是切入点
连接点是指没有被增强的
    1.Advice(通知/增强)
        所谓的通知就是指拦截到Joinpoint之后所要做的事情就是通知
        通知的类型:前置、后置、异常、最终、环绕
    2.目标对象Target
        代理的目标对象(被代理对象)【现在指的是accountService】
    3.Weaving(织入):
        这里是事务的织入,将增强应用到目标对象来创建新的代理的过程【现在指的是事务】
    4.Proxy(代理)
        一个被AOP织入增强后,就产生一个结果代理类,【现在指的是Proxy.newProxyInstance】
    5.Aspect(切面)
        切入点就叫切面【现在的是通知的方法:例如接口里面的所以以及执行增强的方法的结合就叫切面】
*/

10.表现类TestCode:

package com.itheima;

import com.domin.Account;
import com.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 javax.annotation.Resource;
import java.util.List;

@RunWith(SpringJUnit4ClassRunner.class)
/*说明位置*/
@ContextConfiguration(locations ="classpath:bean.xml")
/*pom.xml加上spring-test依赖*/

public class TestCode {
    //@Autowired
    /*在这个时候:就有两个AccountService,通过@Qualifier来指定名称*/
//    @Qualifier("proxyAccountService")

    @Resource(name="proxyAccountService")
    /*这时候当然也可以用@Resource替换上面两个,但是需要导入相关依赖,否则会报空指针异常*/
    private IAccountService as;
    @Test
    public void testTransfer(){
        as.transfer("qi","hello",1000f);
        System.out.println("执行成功");
    }

}

11.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">

    <!--配置动态代理工厂类proxyBeanFactory配置beanFactory,还有方法,在beanFactory写入对应的注入实例-->
    <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

    <!--配置实例化动态工厂注入对应的实例-->
      <bean id="beanFactory" class="com.factory.beanFactory">
          <property name="accountService" ref="accountService"></property>
          <property name="txManager" ref="txManager"></property>
      </bean>



<!--        配置IAccountService的实例-->
    <bean id="accountService" class="com.service.Ipm.AccountServiceIpm">
        <!--注入类的实例accountDao,so创建持久层类的实例就不用写了-->
        <!--这时候还没有accountDao的创建实体类,这时候他会报错,所以引入一下创建accountDao对象,然后这里注入的地方就是实现了调用-->
        <property name="accountDao" ref="accountDao"></property>
        <!--注入是位于管理器txManager 它使用了了-->
        <!--<property name="txManager" ref="txManager"></property>-->
    </bean>
    <bean id="accountDao" class="com.dao.Ipm.AccountDaoImp">
        <property name="runner" ref="runner"></property>
        <!--因为他在使用-->
        <!--注入connectionUtils工具类-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源ds-->
       <!-- <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://127.0.0.1:3306/eesy"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--配置工具类ConnectionUtils-->
    <!--在连接事务中注入dataSource,以方便有时不用,没有时拿出一个使用-->
    <bean id="connectionUtils" class="com.utils.ConnectionUtils">
       <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--配置管理事物的工具类-->
    <bean id="txManager" class="com.utils.TransactionManager">
        <!--里面使用到了connectionUtils-->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

</beans>

12.依赖pom.xml

<!--依赖添加-->
<dependencies>
  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
  </dependency>

  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.0.2.RELEASE</version>
  </dependency>

  <dependency>
    <groupId>commons-dbutils</groupId>
    <artifactId>commons-dbutils</artifactId>
    <version>1.6</version>
  </dependency>

  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
  </dependency>

  <dependency>
    <groupId>c3p0</groupId>
    <artifactId>c3p0</artifactId>
    <version>0.9.1.2</version>
  </dependency>

  <dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
  </dependency>
  <!--测试类-->
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
    <scope>test</scope>
  </dependency>
  <!--注释依赖-->
  <dependency>
    <groupId>javax.annotation</groupId>
    <artifactId>javax.annotation-api</artifactId>
    <version>1.3.2</version>
  </dependency>
</dependencies>

总结:

基于接口的动态代理,抽取事务,减少像案例10那样的代码重复,只要通过拦截该接口,接口里的方法都是会绑定上事务的,所以这样就可以把事务添加到每一个方法当中,但是如果只在接口添加一个方法例如test(),并且在代理处把它错开了【

if ("test".equals(method.getName())){
    return method.invoke(accountService,args);
}

】,那它[test]就不能被事务绑定,错开。总的来说,代理的机制就是拦截并且增强(改变)。

注:有错误的还得多多指出,一起进步,谢谢

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值