Spring框架学习第三天

转账案例基础

转账基础功能

public interface AccountDao {

    //转出
    void outUser(String outUser, Double money);

    //转入
    void inUser(String inUser, Double money);
}
@Repository
public class AccountDaoImpl implements AccountDao {

    //依赖queryRunner
    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void outUser(String outUser, Double money) {

        String sql = "update account set money=money - ? where name = ?";
        //执行sql
        try {
            queryRunner.update(sql, money, outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void inUser(String inUser, Double money) {
        try {
            // 1.编写sql
            String sql = "update account set money = money + ?  where name = ?";
            // 2.执行sql
            queryRunner.update(sql, money, inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
public interface AccountService {
    //转账
    void transfer(String outUser, String inUser, Double money);

}
@Service
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao
    @Autowired
    private AccountDao accountDao;


    @Override
    public void transfer(String outUser, String inUser, Double money) {
        //业务代码
        //转出

        accountDao.outUser(outUser, money);
        //转入

        accountDao.inUser(inUser, money);
   
@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {

    @Autowired
    private AccountService accountService;

    //测试转账案例
    @Test
    public void test01() throws Exception {
        accountService.transfer("tom", "jerry", 100d);
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        http://www.springframework.org/schema/context
		http://www.springframework.org/schema/context/spring-context.xsd">



    <!--开启组件扫描-->
    <context:component-scan base-package="cn.itcast"></context:component-scan>
    <!--加载第三方配置文件-->
    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--druid连接交给ioc容器-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--queryRunner交给ioc容器-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

</beans>

事务控制

上述方法存在问题的解决方案:
将service方法的多个dao层代码,看做一个事务,要么都成功,要么都失败!!!!

修改AccountService

@Service
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao
    @Autowired
    private AccountDao accountDao;
    //依赖dataSource
    @Autowired
    private DataSource dataSource;


    @Override
    public void transfer(String outUser, String inUser, Double money) {
        Connection connection = null;

        //获取conn手动控制事务
        try {
            connection = dataSource.getConnection();
            connection.setAutoCommit(false);
            //业务代码
            //转出

            accountDao.outUser(connection,outUser, money);

            //机器故障

            int i = 1 / 0;

            //转入
            accountDao.inUser(connection,inUser, money);

            //提交事务

            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //进行事务回滚
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            // 释放资源
            try {
                // 再改为自动提交
                connection.setAutoCommit(true);

                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}

修改AccountDao

@Repository
public class AccountDaoImpl implements AccountDao {

    //依赖queryRunner
    @Autowired
    private QueryRunner queryRunner;

    @Override
    public void outUser(Connection connection, String outUser, Double money) {
        try {
            String sql = "update account set money=money - ? where name = ?";
            //执行sql(使用service连接对象)
            queryRunner.update(connection, sql, money, outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void inUser(Connection connection,String inUser, Double money) {
        try {
            // 1.编写sql
            String sql = "update account set money = money + ?  where name = ?";
            // 2.执行sql(使用service连接对象)
            queryRunner.update(connection,sql, money, inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
   }

问题:我们不应该将service的conn对象传递到dao层,这种方式,就产生了dao层与service的耦合性问题

ThreadLocal

ThreadLocal是一个线程的局部变量

@Component//交给ioc容器
public class ConnectionUtils {

    @Autowired
    private DataSource dataSource;

    //储物柜,线程隔离
    private static final ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    //从当前线程内绑定并获取conn对象
    public Connection getThreadConnection() {
        Connection connection = tl.get();
        if (connection == null) {
            try {
                connection = dataSource.getConnection();
                tl.set(connection);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        return connection;
    }

    //移出当前线程的conn对象
    public void removeThreadConnection() {
        tl.remove();
    }


}
@Service
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao
    @Autowired
    private AccountDao accountDao;

    依赖dataSource
    //@Autowired
    //private DataSource dataSource;

    @Autowired
    private ConnectionUtils connectionUtils;


    @Override
    public void transfer(String outUser, String inUser, Double money) {
        Connection connection = null;

        //获取conn手动控制事务
        try {
            //connection = dataSource.getConnection();
            connection = connectionUtils.getThreadConnection();
            connection.setAutoCommit(false);
            //业务代码
            //转出

            accountDao.outUser(outUser, money);

            //机器故障

            int i = 1 / 0;

            //转入
            accountDao.inUser(inUser, money);

            //提交事务

            connection.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //进行事务回滚
                connection.rollback();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
        } finally {
            // 释放资源
            try {
                // 再改为自动提交
                connection.setAutoCommit(true);

                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

    }
}
@Repository
public class AccountDaoImpl implements AccountDao {

    //依赖queryRunner
    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private ConnectionUtils connectionUtils;

    @Override
    public void outUser(String outUser, Double money) {
        try {
            String sql = "update account set money=money - ? where name = ?";
            //获取当前线程内的conn
            final Connection threadConnection = connectionUtils.getThreadConnection();
            //执行sql(使用service连接对象)
            queryRunner.update(threadConnection, sql, money, outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void inUser( String inUser, Double money) {
        try {
            // 1.编写sql
            String sql = "update account set money = money + ?  where name = ?";
            // 获取当前线程内的 conn
            Connection threadConnection = connectionUtils.getThreadConnection();
            // 2.执行sql(使用service连接对象)

            queryRunner.update(threadConnection, sql, money, inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

问题:在企业开发时,我们基础每一个业务层方法都需要进行事务的控制,这部分代码属于公共业务且重复的,出现了大量的代码冗余

事务管理器(TransactionManager)

这时候我们可以把这部分代码抽取到工具类(事务管理器)

@Component
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;

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

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

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

    //释放资源
    public void release() {
        try {
            //回复自动提交事务
            connectionUtils.getThreadConnection().setAutoCommit(true);
            //归还到连接池
            connectionUtils.getThreadConnection().close();
            //将connection对象从当前线程移出
            connectionUtils.removeThreadConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
@Service
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao
    @Autowired
    private AccountDao accountDao;

    依赖dataSource
    //@Autowired
    //private DataSource dataSource;

    //@Autowired
    //private ConnectionUtils connectionUtils;

    @Autowired
    private TransactionManager transactionManager;


    @Override
    public void transfer(String outUser, String inUser, Double money) {
        // Connection connection = null;

        //获取conn手动控制事务
        try {
            //connection = dataSource.getConnection();
            //connection = connectionUtils.getThreadConnection();
            //connection.setAutoCommit(false);
            //开启事务
            transactionManager.beginTransaction();
            //业务代码
            //转出

            accountDao.outUser(outUser, money);

            //机器故障

            int i = 1 / 0;

            //转入
            accountDao.inUser(inUser, money);

            //提交事务

            //connection.commit();
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            try {
                //进行事务回滚
                //connection.rollback();
                transactionManager.rollback();
            } finally {
                transactionManager.release();
            }
            // 释放资源
            //try {
            //    // 再改为自动提交
            //    connection.setAutoCommit(true);
            //
            //    connection.close();
            //} catch (SQLException e) {
            //    e.printStackTrace();
            //}
        }

    }
}
@Repository
public class AccountDaoImpl implements AccountDao {

    //依赖queryRunner
    @Autowired
    private QueryRunner queryRunner;
    @Autowired
    private ConnectionUtils connectionUtils;

    @Override
    public void outUser(String outUser, Double money) {
        try {
            String sql = "update account set money=money - ? where name = ?";
            //获取当前线程内的conn
            final Connection threadConnection = connectionUtils.getThreadConnection();
            //执行sql(使用service连接对象)
            queryRunner.update(threadConnection, sql, money, outUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void inUser( String inUser, Double money) {
        try {
            // 1.编写sql
            String sql = "update account set money = money + ?  where name = ?";
            // 获取当前线程内的 conn
            Connection threadConnection = connectionUtils.getThreadConnection();
            // 2.执行sql(使用service连接对象)

            queryRunner.update(threadConnection, sql, money, inUser);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

问题:几乎所有的方法都需要使用事务管理器,进行事务控制,且事务管理器属于通用业务,与我们核心业务代码产生耦合

转账案例进阶

我们可以将业务代码和事务代码进行拆分,通过动态代理的方式,对业务方法进行事务的增强。这样就不会对业务层产生影响,解决了耦合性的问题啦!
常用的动态代理技术

JDK 代理 : 基于接口的动态代理技术

CGLIB代理:基于父类的动态代理技术

Spring框架学习第三天_第1张图片
在企业进行开发时,只有核心业务成代码,不会出现事务相关代码,开发者只需要编写核心业务即可

public void transfer(String outUser, String inUser, Double money) {
    // 业务代码-------- start
    // 转出
    accountDao.outUser(outUser, money);

    // 模拟故障
    int i = 1/0;

    // 转入
    accountDao.inUser(inUser, money);
    // 业务代码-------- end
}

JDK动态代理

目标对象:AccountService
编写JdkProxyFactory

//基于JDK,实现对目标对象的事务增强
@Component
public class JdkProxyFactory {
    @Autowired
    private TransactionManager transactionManager;

    public Object createJdkProxyTx(final Object target) {
        Object proxy = null;
        //使用SUN公司提供的jdk代理工具类
        //目标对象类加载器
        final ClassLoader classLoader = target.getClass().getClassLoader();
        //目标对象接口数组
        final Class<?>[] interfaces = target.getClass().getInterfaces();
        //实现增强的业务逻辑(匿名内部类、Lambda)
        proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            //invoke方法是代理对象的入口
            //proxy:jdk工具类生成的代理对象
            //method:当前用户执行的某个具体方法
            //args:当前用户执行的某个具体方法传递的实际参数列表

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object result = null;
                try {
                    //开启事务
                    transactionManager.beginTransaction();
                    //执行目标对象原有的功能
                    result = method.invoke(target, args);
                    //提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //回滚事务
                    transactionManager.rollback();
                } finally { //释放资源
                    transactionManager.release();
                }
                return result;
            }

        });
        //返回代理对象(增强后的)
        return proxy;
    }
}

@RunWith(SpringRunner.class)
@ContextConfiguration({"classpath:applicationContext.xml"})
public class AccountTest {

    @Autowired
    private AccountService accountService;//目标对象

    @Autowired
    private JdkProxyFactory jdkProxyFactory;// jdk生产代理对象工厂

    //测试转账案例
    @Test
    public void test01() throws Exception {
        accountService.transfer("tom", "jerry", 100d);
    }

    @Test
    public void test02() throws Exception {
        //accountService.transfer("tom", "jerry", 100d);
        //使用jdk对目标对象进行事务增强
        final AccountService jdkProxyTx = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
        jdkProxyTx.transfer("tom", "jerry", 100d);
    }
}

CGLIB动态代理

存在问题:进入公司,可能有些代码没有接口,但又需要动态代理进行增强,这时候sun公司提供jdk工具就无法实现了…
基于CGLIB技术,就可以对普通的java类型实现代理增强,需要导入jar包(Spring框架内置包含了此架包)
目标对象:AccountService
编写CglibProxyFactory

//基于JDK,实现对目标对象的事务增强
@Component
public class JdkProxyFactory {
    @Autowired
    private TransactionManager transactionManager;

    public Object createJdkProxyTx(final Object target) {
        Object proxy = null;
        //使用SUN公司提供的jdk代理工具类
        //目标对象类加载器
        final ClassLoader classLoader = target.getClass().getClassLoader();
        //目标对象接口数组
        final Class<?>[] interfaces = target.getClass().getInterfaces();
        //实现增强的业务逻辑(匿名内部类、Lambda)
        proxy = Proxy.newProxyInstance(classLoader, interfaces, new InvocationHandler() {
            //invoke方法是代理对象的入口
            //proxy:jdk工具类生成的代理对象
            //method:当前用户执行的某个具体方法
            //args:当前用户执行的某个具体方法传递的实际参数列表

            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                Object result = null;
                try {
                    //开启事务
                    transactionManager.beginTransaction();
                    //执行目标对象原有的功能
                    result = method.invoke(target, args);
                    //提交事务
                    transactionManager.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    //回滚事务
                    transactionManager.rollback();
                } finally { //释放资源
                    transactionManager.release();
                }
                return result;
            }

        });
        //返回代理对象(增强后的)
        return proxy;
    }
}
    @Test
    public void test02() throws Exception {
        //accountService.transfer("tom", "jerry", 100d);

        //使用jdk对目标对象进行事务增强
        //final AccountService jdkProxyTx = (AccountService) jdkProxyFactory.createJdkProxyTx(accountService);
        //jdkProxyTx.transfer("tom", "jerry", 100d);

        // 使用cglib对目标对象事务增强
        AccountService cglibProxy = (AccountService) cglibProxyFactory.createCglibProxyTx(accountService);
        cglibProxy.transfer("tom", "jerry", 100d);
    }
}

SpringAOP简介

概述

AOP( 面向切面编程 )是一种思想, 它的目的就是在不修改源代码的基础上,对原有功能进行增强。

Spring AOP是对AOP思想的一种实现, Spring底层同时支持jdk和cglib动态代理。

这样做的好处是

  1. 在程序运行期间,在不修改源码的情况下对方法进行功能增强
  2. 逻辑清晰,开发核心业务的时候,不必关注增强业务的代码
  3. 减少重复代码,提高开发效率,便于后期维护

Spring会根据被代理的类是否有接口自动选择代理方式:

  • 如果有接口,就采用jdk动态代理( 当然,也可以强制使用cglib )
  • 没有接口就采用cglib的方式

相关术语

  • Target:目标对象
    service层的核心业务(AccountServiceImpl)

  • JoinPoint:连接点
    目标对象中的所有方法
    transfer()
    findAll()

  • Pointcut:切点
    目标对象需要增强的方法
    transfer()

  • Advice:通知(增强)
    实现增强的功能的(TransactionManager)
    beginTransaction()
    commit()
    rollback()
    release()

  • Weaving:织入
    将通知和切点进行织入(动作)

  • Aspect:切面(spring术语)
    通知 + 切点 = 切面

  • Proxy:代理对象(底层实现)
    通知 + 切点 = 代理对象

基于XML的AOP开发

快速入门

创建maven的java模块,导入坐标

Spring框架学习第三天_第2张图片

编写目标类

public interface AccountService {
    void transfer();

    void save();
}

编写通知类(增强)

//通知类(增强)
public class MyAdvice {

    //在对目标对象方法执行之前进行增强
    public void before(){
        System.out.println("前置通知");
    }
}

配置Spring的AOP(切点+通知)

Spring框架学习第三天_第3张图片

测试

@RunWith(SpringRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class AccountTest {
    @Autowired
    private AccountService accountService; // 配置了aop之后,就成为了代理对象

    @Test
    public void test01() throws Exception {
        accountService.save();//保存了

        accountService.transfer();//前置通知,转账了
    }
}

你可能感兴趣的:(Spring框架学习)