spring第三天笔记

spring第三天笔记

一、内容

介绍
1、实现转账功能

2、解决转账问题

3、动态代理回顾

4、解决转账问题

5、什么是AOP

6、AOP的xml配置

7、AOP的注解配置

8、AOP改造转账事务功能

二、转账功能

1、引入依赖
<dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
2、实体类
public class Account {
    private Integer id;
    private String name;
    private Float money;
}
3、持久层
1. 接口
public interface AcountDao {
    /**
     * 根据账户名查询账户
     */
    Account findByName(String fromName);

    /**
     * 更新账户
     */
    void update(Account fromAccount);
}

2. 实现类

@Repository
public class AcountDaoImpl implements AcountDao {
    @Autowired
    QueryRunner queryRunner;

    @Override
    public Account findByName(String name) {
        String sql = "select * from account where name = ?";
        try {
            return queryRunner.query(sql ,new BeanHandler<>(Account.class),name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void update(Account account) {
        String sql = "update account set money = ? where name = ?";
        try {
            queryRunner.update(sql ,account.getMoney(), account.getName());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4、业务层
1. 接口
public interface AccountService {
    /**
     * @param fromName 从哪个账户转出
     * @param toName   转入哪个账户
     * @param money     转多少钱
     */
    public void transfer(String fromName, String toName ,Float money);
}

2. 实现类
@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDao accountDao;

    @Override
    public void transfer(String fromName, String toName, Float money) {

        Account fromAccount = accountDao.findByName(fromName);

        Account toAccount = accountDao.findByName(toName);

        fromAccount.setMoney(fromAccount.getMoney()-money);

        toAccount.setMoney(toAccount.getMoney()+money);

        accountDao.update(fromAccount);
        accountDao.update(toAccount);
    }
}
5、配置文件
<?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:component-scan base-package="com.itheima"></context:component-scan>
    <!--创建queryRunner对象:构造方法中需要DataSource对象-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg type="javax.sql.DataSource" ref="dataSource"></constructor-arg>
    </bean>
    <!--创建dataSource对象:注入四个必要属性-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    <!--引入外部属性文件-->
    <context:property-placeholder location="classpath:jdbc.properties">
    </context:property-placeholder>
</beans>
6、测试
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {

    @Autowired
    AccountService accountService;

    @Test
    public void test(){
        //创建类对象,创建springIOC容器
//ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        accountService.transfer("aaa","bbb", 100f);
    }
}
7、发现问题
		//业务层
        accountDao.update(fromAccount);
        int i =1/0;
        accountDao.update(toAccount);

出现问题
转出的账户扣除了金额
转入的账户金额没有增加

三、解决转账问题(事务)

1、引入工具类

ConnectionUtils,java(了解)

/**
 * 一个管理连接的工具类,用于实现连接和线程的绑定
 *
 * 保证当前线程中获取的Connection是同一个
 */
@Component
public class ConnectionUtil {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();

    @Autowired
    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
    /**
     * 获取当前线程上绑定的连接
     * @return
     */
    public Connection getThreadConnection() {
        try {
            //1.先看看线程上是否绑了
            Connection conn = tl.get();
            if(conn == null) {
                //2.从数据源中获取一个连接
                conn = dataSource.getConnection();
                //3.和线程局部变量绑定
                tl.set(conn);
            }
            //4.返回线程上的连接
            return tl.get();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

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

TransactionManager.java(管理事务类)

/**
 * 事务管理器
 */
@Component
public class TransactionManager {

    @Autowired
    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    //开启事务
    public void beginTransaction() {
        //从当前线程上获取连接,实现开启事务
        try {
            connectionUtil.getThreadConnection().setAutoCommit(false);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

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


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

    //释放连接
    public void release() {
        try {
            connectionUtil.getThreadConnection().setAutoCommit(true);
            //关闭连接(还回池中)
            connectionUtil.getThreadConnection().close();
            //解绑线程:把连接和线程解绑
            connectionUtil.remove();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
2、修改业务层
/**
 *事务分为四步
 * 一个事务必须在一个Connection中完成
 *
 * ThreadLocal:线程绑定
 *   绑定Connection对象
 *   业务层和持久层需要Connection从ThreadLocal中获取
 */
@Service
public class AccountServiceImpl implements AccountService {
    @Autowired
    AcountDao acountDao;

    @Autowired
    TransactionManager txManager;

    /**
     * 假设需要事务管理
     * 事务基本结构
     */
    public void update(Account account){
        try {
            //事务1:开启事务:conn.setAutoCommit(false);
            txManager.beginTransaction();
            acountDao.update(account);
            //事务2:提交事务:conn.commit();
            txManager.commit();
        } catch (Exception e) {
            //事务3:回顾事务 conn.rollback();
            txManager.rollback();
            e.printStackTrace();
        } finally {
            //事务4:还原状态 conn.setAutoCommit(true);
            txManager.release();
        }
    }

    @Override
    public void transfer(String fromName, String toName, Float money) {
        try {
            //事务1:开启事务:conn.setAutoCommit(false);
            txManager.beginTransaction();
            
            Account fromAccount =  acountDao.findByName(fromName); 
            Account toAccount =  acountDao.findByName(toName);
            
            fromAccount.setMoney(fromAccount.getMoney() - money);
            toAccount.setMoney(toAccount.getMoney() + money);
          
            acountDao.update(fromAccount);
            //出现异常
            System.out.println(1/0);
            acountDao.update(toAccount);
            //事务2:提交事务:conn.commit();
            txManager.commit();
        } catch (Exception e) {
            //事务3:回顾事务 conn.rollback();
            txManager.rollback();
            e.printStackTrace();
        } finally {
            //事务4:还原状态 conn.setAutoCommit(true);
            txManager.release();
        }
    }
}

3、修改持久层

原来的queryRunner是从连接池拿取连接,这样不能保证获取同一个连接
要改为获取与线程绑定的连接

@Repository
public class AcountDaoImpl implements AcountDao {
    @Autowired
    QueryRunner queryRunner;

    @Autowired
    ConnectionUtil connectionUtil;

    @Override
    public Account findByName(String name) {
        String sql = "select * from account where name = ?";
        try {
            //从线程中获取一个连接对象
            Connection conn = connectionUtil.getThreadConnection();
            return queryRunner.query(conn,sql ,new BeanHandler<>(Account.class),name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void update(Account account) {
        String sql = "update account set money = ? where name = ?";
        try {
            Connection conn = connectionUtil.getThreadConnection();
            queryRunner.update(conn ,sql ,account.getMoney(), account.getName());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
4、发现新的问题
问题
1. 重复代码(每个service层的方法都要重复一遍事务代码)
2. 代码臃肿问题
3. 技术与业务整合到一起了
解决的思路
	1. 提取重复的代码
	2. 业务层中不需要技术代码
	3. 不修改业务层源码的情况下,技术增强
	4. 使用动态代理
动态代理
	特点:随用随创建,随用随加载
	不修改原来代码的基础上,对原来的代码增强

四、动态代理回顾(过渡引出AOP)

1、jdk动态代理

适用于接口+实现类的开发模式(局限性)

a. jdk动态代理: 基于接口的动态代理
b. @Test
    public void testJDKProxy(){
        //真实的对象
        NewSale newSale = new NewSaleImpl();
        //创建代理对象-- 本质上就是接口的一个实现类
            //参数1: 类加载器
            //参数2:类实现的接口
            //参数3:真实对象的增强部分:实现了InvocationHandler接口的实现类
                //匿名内部类也是该接口的实现类
        NewSale sale = (NewSale) Proxy.newProxyInstance(newSale.getClass().getClassLoader(), 
        newSale.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 {
                        //生成产品
                        ProductFactory productFactory = new ProductFactory();
                        productFactory.make();
                        //开始销售:通过反射执行真实对象的方法
                            //参数1:真实的对象
                            //参数2:方法的参数
                        method.invoke(newSale,args );
                        //判断是否挣钱了
                        //卖的价格:args[0]  假设产品的成本是 1500
                        if( (Float)args[0] > 1500){
                            //卖的价格高于成本,挣了,可卖
                            System.out.println("卖的价格高于成本");
                        }else{
                            //卖的价格低于成本,赔了,不可卖
                            System.out.println("卖的价格低于成本");
                        }
                        return null;
                    }
                });

        sale.sale(1000F);
    }
2、cglib动态代理
a. cglib动态代理: 基于类的动态代理
b. 第三方jar包: cglib-2.2.2.jar
c. 注意:代理的类不用final修饰
d. public void testCglibProxy(){
        //真实对象
        OldSale oldSale = new OldSale();
        //创建cglib代理对象
        //1. 创建增强类对象
        Enhancer enhancer = new Enhancer();
        //2. 指定代理对象的父类
        enhancer.setSuperclass(oldSale.getClass());
        //3. 指定增强的内容
        //MethodInterceptor  :接口是方法拦截器
        enhancer.setCallback(new MethodInterceptor() {
            /***
             *  增强的内容
             * @param o  代理对象,增强后的对象
             * @param method   代理的方法
             * @param objects   代理的方法的参数
             * @param methodProxy   代理方法,增强后的方法
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, 
            MethodProxy methodProxy) throws Throwable {
                //生成产品
                ProductFactory factory = new ProductFactory();
                factory.make();
                //开始销售,执行真实对象的内容
                method.invoke(oldSale, objects);
                //判断是否挣钱了
                //卖的价格:objects[0]  假设产品的成本是 1500
                if( (Float)objects[0] > 1500){
                    //卖的价格高于成本,挣了,可卖
                    System.out.println("卖的价格高于成本");
                }else{
                    //卖的价格低于成本,赔了,不可卖
                    System.out.println("卖的价格低于成本");
                }

                return null;
            }
        });

        //4. 创建代理对象:
        OldSale saleProxy  = (OldSale)enhancer.create();

        saleProxy.sale(2000f);
    }

五、动态代理解决新问题

1、jdk动态代理解决问题
@Test
    public void testJDKProxyService(){
        //创建业务层代理对象
        AccountService accountServiceProxy = 
        (AccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 
        accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) 
                    throws Throwable {
                        try {
                            //开启事务
                            txManager.beginTransaction();
                            //执行真实的对象的方法
                            method.invoke(accountService, args);
                            //提交事务
                            txManager.commit();
                        } catch (Exception e) {
                            //事务回滚
                            txManager.rollback();
                            e.printStackTrace();
                        } finally {
                            //还原状态
                            txManager.release();
                        }
                        return null;
                    }
                });

        accountServiceProxy.transfer("aaa","bbb",200f);
    }
2、cglib动态代理解决问题
 @Test
    public void testCglibProxy(){
        //增强对象
        Enhancer enhancer = new Enhancer();
        //指定代理对象的父类
        enhancer.setSuperclass(accountService.getClass());
        //增强的内容
        enhancer.setCallback(new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, 
            MethodProxy methodProxy) throws Throwable {
                try {
                    txManager.beginTransaction();
                    method.invoke(accountService, objects);
                    txManager.commit();
                } catch (Exception e) {
                    txManager.rollback();
                    e.printStackTrace();
                } finally {
                    txManager.release();
                }
                return null;
            }
        });
        //创建代理对象
        AccountService accountServiceProxy = (AccountService)enhancer.create();
        accountServiceProxy.transfer("aaa","bbb",200f);
    }

六、什么是AOP

  • 面向切面编程
  • AOP是OOP的延续(OOP:面向对象编程)即面向一片对象而不是一个个
  • 作用:在程序运行期间,不修改源码对已有方法进行增强。
  • 本质上是动态代理,底层技术是JDK和cglib动态代理技术

七、AOP的xml配置

1、依赖
<!--引入spring的核心-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--引入spring的测试包-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--引入单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--配置aop,必须引入一个包:版本必须要1.8.7以上-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
2、配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
    <!--扫描表,创建bean对象-->
    <context:component-scan base-package="com.itheima"></context:component-scan>

    <!--通知对象: 拦截到方法时,通知执行的对象-->
    <!--
        通知的类型
            前置通知: 方法之前执行
            后置通知: 方法执行完之后执行- 返回之前执行-如果有异常,则不执行
            最终通知: 方法执行完后总会执行- finally
            异常通知: 方法出现异常则执行
            环绕通知: 前置通知+后置通知+最终通知+异常通知
    -->
    <bean id="logger" class="com.itheima.log.Logger"></bean>
    <!--配置aop-->
    <aop:config>
        <!--配置切面= 切入点 + 通知
            指定通知对象是谁
         -->
        <aop:aspect ref="logger">
            <!--配置切入点
                id:唯一的标志
                expression: 表达式

                * com.itheima.service.impl.*.*(..)
                * com.itheima.service..*.*(..)
                第一个*:代表方法任意返回值类型
                第二个*: 类名任意,impl包中所有的类
                第三个*: 任意方法名
                (..) : 参数任意,个数任意,类型任意,顺序任意
            其他的配置方式
                public void com.itheima.service.impl.UserServiceImpl.findAll()
                void com.itheima.service.impl.UserServiceImpl.findAll()
                * com.itheima.service.impl.UserServiceImpl.findAll()
                * com.itheima.service..UserServiceImpl.findAll() : .. 代表的是包,及其子包
            -->
            <aop:pointcut id="pointcut" expression="execution(* com.itheima.service.impl.*.*(..))">
            </aop:pointcut>
            <!--织入: 告诉通知对象执行,具体执行哪一个方法-->
            <!--前置通知-->
            <aop:before method="before" pointcut-ref="pointcut"></aop:before>
            <!--后置通知-->
            <aop:after-returning method="afterReturning" pointcut-ref="pointcut"></aop:after-returning>
            <!--最终通知-->
            <aop:after method="after" pointcut-ref="pointcut"></aop:after>
            <!--异常通知-->
            <aop:after-throwing throwing="e" method="afterThrowing" pointcut-ref="pointcut"></aop:after-throwing>
            <!--环绕增强-->
            <aop:around method="around" pointcut-ref="pointcut"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

八、AOP的注解配置

/**
 *@Component: 创建类对象
 * @Aspect:配置该类为切面
 *  切面是:切入点 + 通知
 *
 */
@Component
@Aspect
public class Logger {
    /**
     * 配置切入点
     */
    @Pointcut("execution(* com.itheima.service.impl.*.*(..))")
    public void pointcut(){};

    /**
     * @param joinPoint  连接点-- 拦截到的方法
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){
        //被代理的对象
        Object target = joinPoint.getTarget();
        //拦截的类的名称
        String className = target.getClass().getName();
        System.out.println("拦截到的类名:" +className);
        //获取方法对象
        Signature signature = joinPoint.getSignature();
        //获取方法名
        String methodName = signature.getName();
        System.out.println("拦截到方法名:" + methodName);
        System.out.println("前置通知");
    }

    @AfterReturning("pointcut()")
    public void afterReturning(){
        System.out.println("后置增强");
    }

    @After("pointcut()")
    public void after(){
        System.out.println("最终增强");
    }

    @AfterThrowing(value = "pointcut()",throwing = "e")
    public void afterThrowing(Exception e){
        System.out.println("执行的方法的异常:"+e);
        System.out.println("异常通知");
    }

    /**
     * ProceedingJoinPoint 可以执行拦截到方法的连接点对象
     * @param joinPoint
     */
    @Around("pointcut()")
    public void around(ProceedingJoinPoint joinPoint){
        try {
            System.out.println("前置通知");
            //执行原来的方法: 可以获取方法的返回值
            Object result = joinPoint.proceed();
            System.out.println("后置增强");
        } catch (Throwable e) {
            System.out.println("异常通知");
//            e.printStackTrace();
        } finally {
            System.out.println("最终增强");
        }
    }
}

九、AOP改造转账事务功能

1、依赖

2、service(将业务与技术分开了)

@Service
public class AccountServiceImpl implements AccountService {

    @Autowired
    AccountDao accountDao;
    
    @Override
    public void transfer(String fromName, String toName, Float money) {

            Account fromAccount = accountDao.findByName(fromName);
            Account toAccount = accountDao.findByName(toName);

            fromAccount.setMoney(fromAccount.getMoney()-money);
            toAccount.setMoney(toAccount.getMoney()+money);

            accountDao.update(fromAccount);
            int i =1/0;
            accountDao.update(toAccount);

    }
}

3、dao(没有变化)

@Repository
public class AccountDaoImpl implements AccountDao {

    @Autowired
    QueryRunner queryRunner;

    @Autowired
    ConnectionUtil connectionUtil;

    public Account findByName(String name) {
        Connection conn = connectionUtil.getThreadConnection();
        
        String sql = "select * from account where name = ?";     
        try {
            return queryRunner.query(conn,sql,new BeanHandler<>(Account.class),name);
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void update(Account account) {
        Connection conn = connectionUtil.getThreadConnection();
        
        String sql = "update account set money = ? where name= ?"; 
        try {
            queryRunner.update(conn,sql,account.getMoney(),account.getName());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

4-1、xml配置aop

<context:component-scan base-package="com.itheima"></context:component-scan>

    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
    </bean>

    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="user" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

    <!--
        使用aop解决事务问题
        事务管理器类:通知对象
    -->
    <aop:config>
        <!--配置切面-->
        <aop:aspect ref="transactionManager">
            <!--切入点-->
            <aop:pointcut id="pointcut" expression="execution(* com.itheima.service.impl.*.*(..))">
            </aop:pointcut>
            <!--织入-->
            <!--前置增强:开启事务-->
            <aop:before method="beginTransaction" pointcut="execution(* com.itheima.service.impl.*.*(..))">
            </aop:before>
            <!--后置增强:提交事务-->
            <aop:after-returning method="commit" pointcut-ref="pointcut"></aop:after-returning>
            <!--异常增强:回滚事务-->
            <aop:after-throwing method="rollback" pointcut-ref="pointcut"></aop:after-throwing>
            <!--最终增强:释放资源-->
            <aop:after method="release" pointcut-ref="pointcut"></aop:after>
        </aop:aspect>
    </aop:config>

4-2、ann配置aop

    <!--开启aop自动代理注解-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
/**
 * 事务管理器
 */
@Component
@Aspect			//第一步(切面)
public class TransactionManager {

    @Autowired
    private ConnectionUtil connectionUtil;

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    @Around("execution(* com.itheima.service.impl.*.*(..))") //第二步(切入点+通知)
    public void around(ProceedingJoinPoint joinPoint){

        try {
            beginTransaction();
            joinPoint.proceed();
            commit();
        } catch (Throwable throwable) {
            rollback();
            throwable.printStackTrace();
        } finally {
            release();
        }
    }
    
//下面方法省略
发布了33 篇原创文章 · 获赞 2 · 访问量 977

猜你喜欢

转载自blog.csdn.net/Rhin0cer0s/article/details/98482199