spring_day03

1.转账案例分析
先看业务层的代码:

public void transfer(int sourceId, int targetId, int money) {
      //执行操作
        //1.根据转出用户id查询转出用户
        Account source = accountDao.findById(sourceId);
        //2.根据转入用户id查询转入用户
        Account target = accountDao.findById(targetId);
        //3.给转出用户减钱
        source.setMoney(source.getMoney()-money);
        //4.给转入用户加钱
        target.setMoney(target.getMoney()+money);
        //5.更新转出用户
        accountDao.updateAccount(source);
        //手动加入异常
        int i = 1/ 0;
        //6.更新转入用户
        accountDao.updateAccount(target);
}

执行结果:转出方money减少,但因为更新转入方数据时,发生了异常,导致其money并没有增加。

why?看一下我们用的Dbutils 的核心对象QueryRunner的配置。

<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    <!--注入数据源-->
    <constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

我们发现QueryRunner是一个多例,因为实际开发中,对数据库的操作必定是多线程同时进行的,所以QueryRunner要配置成多例,所以,我们每次调用accountDao的方式,都获取了一个新的QueryRunner,每一步操作都是一个独立的事务,此时,spring时默认事务自动提交的,就出现了一致性的问题。

解决方案:手动添加一个事务对整套操作进行管理,期间使用的Connection是同一个,如何保证是同一个呢?使用ThreadLocal对象,这个对象是map存储结构,key即当前执行线程,value是Object,可以实现同一线程数据共享。

2.绑定事务改进

如何对一个功能(方法)绑定一个事务进行管理呢?如何增强一个类的功能呢?

三种方式:继承,装饰者模式,动态代理

当然使用态代理!

动态代理:在不改变源码的基础上对已有的功能进行增强,底层:预编译,反射,特点:字节码随用随创建,随用随加载,大体思想是,通过反射获取到要执行的方法对象,在方法执行(method.invoke(obj,args))前后,对其参数,或者结果集进行增强,这样预编译的程序,在这个对象真正执行方法时,就会生效。

实现:创建一个事务管理类TransactionManager,这个类提供了开启事务,提交,回滚,释放资源(注意,这里的释放资源不仅要关闭连接,还要断开连接)的方法,每个方法具体实现是由Connection对象完成的,所以,我们需要定义一个ConnectionUtils,提供获取连接的方法,从ThreadLocal对象中get()方法获取,如果是第一次获取Connection,获取不到,我们从DataSource中获取一个并通过set(Object object)方法设置到ThreadLocal中,并返回这个Connection。

好的,事务管理类就准备好了,我们可以在accountService类中对每个方法进行改进,为他们绑定事务,但是,这样做的话,会产生代码重复的问题,怎么解决呢?

我们要做的是创建一个工厂类,提供一个获取accountService代理对象的方法,方法内通过动态代理,把TransactionManager内的各个方法,增强到accountService对象的各个方法上;这样accountService的每个方法执行时都会绑定事务。

3.jdk动态代理改进

核心api:Proxy.newInstance(),提供者:JDK官方

要求被代理的类至少实现一个接口,因为jdk动态代理时基于接口的代理,即创建了一个和被代理类同级的类,和它实现同一个接口

看代码,我也不是很理解,现阶段先会用:

@Bean(name = "proxyAccountService")
public IAccountService getAccountService(){
		
	   /**	newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         */
         
    return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
            accountService.getClass().getInterfaces(), new InvocationHandler() {
            
            /**
            * 作用:执行被代理对象的任何接口方法都会经过该方法
            * 方法参数的含义
            * @param proxy   代理对象的引用
            	 stackoverflow对这个参数的解释是:
				1. 可以使用反射获取代理对象的信息(也就是proxy.getClass().getName())。
				2. 可以将代理对象返回以进行连续调用,这就是proxy存在的目的,因为this并不是代理对象。
            * @param method  当前执行的方法	
            * @param args    当前执行方法所需的参数
            * @return        和被代理对象方法有相同的返回值
            * @throws Throwable
            */
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    try {
                        //开启事务
                        transactionManager.beginTransaction();
                        //执行操作
                        Object object = method.invoke(accountService,args);
                        //提交事务
                        transactionManager.commitTransaction();
                        return object;
                    } catch (Exception e) {
                        //事务回滚
                        transactionManager.rollbackTransaction();
                        throw new RuntimeException(e);
                    }finally {
                        //释放资源
                        transactionManager.closeTransaction();
                    }
                }
            });
}

4.cglib动态代理

核心api:Enhance.create(),提供方:第三方cglib库

它是基于子类的动态代理,新生成的代理对象是被代理对象的子类

看代码吧,这个我也不是很理解

   create方法的参数:
   *      Class:字节码
   *          它是用于指定被代理对象的字节码。
   *
   *      Callback:用于提供增强的代码
   *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
   *          此接口的实现类都是谁用谁写。
   *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
   */
  Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
      /**
       * 执行被代理对象的任何方法都会经过该方法
       * @param proxy
       * @param method
       * @param args
       *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
       * @param methodProxy :当前执行方法的代理对象
       * @return
       * @throws Throwable
       */
      @Override
      public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
          try {
                    //开启事务
                    transactionManager.beginTransaction();
                    //执行操作
                    Object object = method.invoke(accountService, objects);
                    //提交事务
                    transactionManager.commitTransaction();
                    return object;
                } catch (Exception e) {
                    //事务回滚
                    transactionManager.rollbackTransaction();
                    throw new RuntimeException(e);
                } finally {
                    //释放资源
                    transactionManager.closeTransaction();
                }
  }

5.aop入门

aop: 面向切面编程

对已有方法进行 无代码侵入增强,底层是动态代理

学几个专业名词:

	连接点:要增强的类中的所有方法

	切入点:被增强的方法

	通知:增强的功能 		----->前置通知,后置通知,异常通知,最终通知

	切面:切入点和通知的结合

啊哈?四个吧,其他感觉没必要记啊,见名知意,

	目标对象:被增强的对象

	代理对象:增强后产生的代理对象

	织入:把通知应用到目标对象中产生代理对象的过程就是织入

	引介:不知道 - -,算了算了,补上吧,就是给类中添加字段或者方法

在编写aop我们需要做的事(这个老师总结的太好了):

	a. 编写业务代码

	b.编写增强代码

	c.配置业务代码和增强代码的关系,即切面

maven依赖:aspectjweaver

约束:xmlns:aop

配置切面:这个是一步一步的:

	<aop:config>

		<!--共性抽取配置切入点表达式-->

		<aop:pointcut id="pt" 	value="execution(* cn.itcast.spring.service.impl.*.*(..))"></aop:pointcut>		

		<!--配置切面-->

		<aop:aspect id="txManager" ref="transactionManager">

			<aop:before method="前置通知" pointcut-ref="pt"></aop:before>

		       <aop:after-returning method="后置通知" pointcut-ref="pt"></aop:after-returning>

			<aop:after-throwing method="异常通知" pointcut-ref="pt"></aop:after-throwing>

			<aop:after method="最终通知" pointcut-ref="pt"></aop:after>

		</aop:aspect>

	</aop:config>

6.aop注解开发

a. 开启注解包扫描

		<context:component-san base-package="cn.itcast.spring"></conext:component-scan>

b. 开启aop注解支持

		<aop:aspectj-autoproxy></aop:aspectj-autoproxy>

c. 在增强类上声明切面注解

		@Aspect

d. 在增强类定义切入点表达式

		@Pointcunt("excution(* cn.itcast.spring.service.impl..(..))")	

		public void pt(){}

		不要问我为什么这里写了个方法,我猜是因为切入点本身就是个方法,这个抽取出的切入点表达式,也要定义成一个方法

c. 配置通知

		@Before("pt()")

		@AfterReturning("pt()")

		@AfterThrowing("pt()")

		@After("pt()")	

问题来了,spring的aop注解方式通知的执行顺序是:前置,最终,后置/异常

所以我们使用环绕通知:

		@Around("pt()")

环绕通知是将所有的增强代码放到一个方法里,我们自己定义其执行顺序,以方法执行为相对位置,原方法执行用到一个api:ProceedingJoinPoint 中的getArgs()/proceeding()方法。

看不懂把,来段代码:

@Around("gt()")
public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint){
    try {
    	//前置通知:开启事务
        connectionUtils.getConnection().setAutoCommit(false);
		
		//获取参数,并执行原方法
        Object[] args = proceedingJoinPoint.getArgs();
        Object object = proceedingJoinPoint.proceed(args);
		
		//后置通知:提交事务
        connectionUtils.getConnection().commit();
        
        //返回结果
        return object;
    } catch (Throwable throwable) {
        try {	
        	//异常通知:回滚事务
            connectionUtils.getConnection().rollback();
        } catch (SQLException e1) {
            e1.printStackTrace();
        }
        throwable.printStackTrace();
    }finally {
        try {
        	//最终通知:释放资源
            connectionUtils.getConnection().close();//还回连接池中
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    return null;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值