SpringAop-接口代理-子类代理

springAop

分析经典转账案例中的问题及解决办法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VxYo5Hjd-1623836163121)(随堂笔记.assets/1583031523866.png)]
在这里插入图片描述

ThreadLocal:线程局部变量,保证同一个线程使用同一个连接保证同一个线程使用同一个连接

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LYwf9o9f-1623836163126)(随堂笔记.assets/1583033227320.png)]

import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.util.HashMap;
import java.util.Map;
//自己编写的此类,让大家明白。jdk中存在一个ThreadLocal
@Component
public class ThreadLocal {
    private static Map<Thread,Connection> map = new HashMap<Thread, Connection>();
    //以当前线程对象为key,向Map中存数据
    public void set(Connection conn){
        map.put(Thread.currentThread(),conn);
    }
    //以当前线程对象为key,从Map中获取数据
    public Connection get(){
        return map.get(Thread.currentThread());
    }
    //以当前线程对象为key,从Map中删除数据
    public void remove(){
        map.remove(Thread.currentThread());
    }
}

编写一个专门控制事务的类:事务管理器

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

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

//简易事务管理器
@Component
public class TransactionManager {
    @Autowired
    private ThreadLocal threadLocal;
    @Autowired
    private DataSource dataSource;
    //如果当前线程上有连接,就直接用;
    // 没有连接从池中获取一个连接,并绑定到当前线程上
    public Connection getConnection(){
        try {
            Connection conn = threadLocal.get();
            if(conn==null){
                conn = dataSource.getConnection();
                threadLocal.set(conn);
            }
            return conn;
        } catch (SQLException e) {
            throw new RuntimeException("从数据源获取连接失败,请检查您的数据源",e);
        }
    }
    public void startTransaction(){
        try {
            Connection connection = getConnection();
            connection.setAutoCommit(false);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public void commit(){
        try {
            Connection connection = getConnection();
            connection.commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public  void rollback(){
        try {
            Connection connection = getConnection();
            connection.rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    public  void release(){
        try {
            Connection connection = getConnection();
            connection.close();
            threadLocal.remove();// 从当前线程上解绑
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

改造转账案例:目标能控制事务

step1、修改spring配置文件
不给定数据源
step2、改造dao实现

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4GxrMFIN-1623836163129)(随堂笔记.assets/1583034285534.png)]

step3、改造业务实现

import com.zl.commons.TransactionManager;
import com.zl.dao.AccountDao;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service //交给spring容器管理
public class AccountServiceImpl implements AccountService {
    @Autowired
    private AccountDao accountDao;
    @Autowired
    private TransactionManager txManager;

    public void transfer(String sourceAccountNumber, String targetAccountNumber, Float money) {
        try{
            txManager.startTransaction();//开启事务
            //根据转出账户查询Account对象
            Account sAccount = accountDao.findAccountByNumber(sourceAccountNumber);
            //根据注入账户查询Account对象
            Account tAccount = accountDao.findAccountByNumber(targetAccountNumber);
            //转出账户对象减money
            sAccount.setBalance(sAccount.getBalance()-money);
            //转入账户对象加money
            tAccount.setBalance(tAccount.getBalance()+money);
            //持久化转出账户
            accountDao.updateAccount(sAccount);
            int i=1/0;//模拟出现异常
            //持久化转入账户
            accountDao.updateAccount(tAccount);
            txManager.commit();//提交事务
        }catch (Exception e){
            //回滚事务
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //释放资源
            txManager.release();
        }

    }
}

分析以上代码问题(AOP思想)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8BdjhEml-1623836163130)(随堂笔记.assets/1583034902385.png)]

我们最关心的就是核心业务代码,精力也应该集中在解决业务问题上。而不是天天编写重复的事务代码。

动态代理实现面向切面编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sOsLZAfN-1623836163131)(随堂笔记.assets/1583043972419.png)]

step1、修改业务代码

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kmsp55x5-1623836163131)(随堂笔记.assets/1583042570173.png)]

step2、借助动态代理实现事务代码织入到核心业务代码中(增强)

  • 基于接口的动态代理(JDK代理:Proxy):被代理类和代理类实现相同的接口。即被代理类必须实现至少一个接口。
import com.zl.commons.TransactionManager;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

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

//产生代理对象的工厂
@Component
public class ProxyBeanFactory {

    @Autowired
    private TransactionManager txManager;

    @Autowired
    @Qualifier("accountServiceImpl") //由于spring容器中会存在多个AccountService类型的对象,此处最好用名称查找
    private AccountService accountService;//原对象,被代理对象
    //基于接口的动态代理:代理类和被代理类都实现了AccountService接口
    @Bean  //将方法的返回对象存到spring容器中,默认名字是getInterfaceProxyObject
    public AccountService getInterfaceProxyObject(){
        return (AccountService) Proxy.newProxyInstance(
                accountService.getClass().getClassLoader(),
                accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        if("transfer".equals(method.getName())) {//如果当前执行的方法是转账,才控制事务
                            try {
                                txManager.startTransaction();//开启事务
                                rtValue = method.invoke(accountService, args);//代表原核心业务代码
                                txManager.commit();
                            }catch (Exception e){
                                txManager.rollback();
                                throw new RuntimeException(e);
                            }finally {
                                txManager.release();
                            }
                        }else{
                            rtValue = method.invoke(accountService,args);//其他发放不增强
                        }
                        return rtValue;
                    }
                }
        );
    }
}

  • 基于子类的动态代理:被代理类没有实现任何接口,代理类是被代理类的子类。借助第三方的开发包(CGLIB,不需要单独导入,spring内置了)
import com.zl.commons.TransactionManager;
import com.zl.domain.Account;
import com.zl.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cglib.proxy.Callback;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

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

//产生代理对象的工厂
@Component
public class ProxyBeanFactory {

    @Autowired
    private TransactionManager txManager;

    @Autowired
    @Qualifier("accountServiceImpl") //由于spring容器中会存在多个AccountService类型的对象,此处最好用名称查找
    private AccountService accountService;//原对象,被代理对象

    //利用CGLIB实现基于子类的动态代理。代理类是被代理类的子类
    @Bean 将方法的返回对象存到spring容器中,默认名字是getSubClassProxyObject
    public AccountService getSubClassProxyObject(){
        return (AccountService) Enhancer.create(
                accountService.getClass(),//  代理类的父类是什么类型
                new org.springframework.cglib.proxy.InvocationHandler() {//和JDK中的InocationHandler雷同
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object rtValue = null;
                        if("transfer".equals(method.getName())) {//如果当前执行的方法是转账,才控制事务
                            try {
                                txManager.startTransaction();//开启事务
                                rtValue = method.invoke(accountService, args);//代表原核心业务代码
                                txManager.commit();
                            }catch (Exception e){
                                txManager.rollback();
                                throw new RuntimeException(e);
                            }finally {
                                txManager.release();
                            }
                        }else{
                            rtValue = method.invoke(accountService,args);//其他发放不增强
                        }
                        return rtValue;
                    }
                }
        );
    }

注意:如果使用spring的AOP,我们不需要自己编写代理代码。spring会自动生成代理对象,如果被代理类实现了某个或某些接口,spring将使用JDK的动态代理;如果被代理类没有实现任何接口,spring将采用CGLIB基于子类的动态代理方式。而这些是不需要我们关心的。

AOP面向切面编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5k38Y6rY-1623836163133)(随堂笔记.assets/1583035319486.png)]

编码阶段要做到:我们应该做到的

1、核心代码单独编写

2、非核心代码(切面代码)单独编写

运行阶段要做到:交给spring完成(配置),我们自己不需要写代理

核心代码和非核心代码能一起运行,完成需要的功能

spring framework中的AOP

常用概念

1、连接点(joinpoint):指代码中的所有方法,都做连接点。

2、切入点(pointcut):指代码中需要增强的方法,叫做切入点。

3、被代理对象:只包含核心业务代码的对象,需要增强的对象。

4、代理对象:由spring产生的代理对象,他对被代理对象进行了一定的功能增强。

5、通知(advice):即增强对象(比如案例中TransactionManager事务管理器,那些切面代码)。

6、切面(Aspect):通知(Advice)+切入点(Pointcut)=切面

7、织入(Weaving):将通知的代码运行期间织入到被代理对象中,这是一个动作。

切入点表达式

单独定义表达式:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aCuC1Pao-1623836163134)(随堂笔记.assets/1583047430338.png)]

表达式的写法:

在这里插入图片描述

五大通知

四个基本通知

重点:明确不同的通知的执行位置

前置通知(before):核心代码执行前执行。

后置通知(afterReturning):核心代码没有任何问题,正常返回后执行。

异常通知(afterThrowing):核心代码运行期间发生了异常,此时catch中的代码执行,异常通知就认为在异常代码中。

最终通知(after):最后都必须执行的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UlkfYNk-1623837558260)(随堂笔记.assets/1583113003313.png)]
注意:后置通知和异常通知互斥的

环绕通知

不要被名字的直接意思给误会了。

所谓环绕通知,实际上是spring框架给程序提供的一种==“自由”==编写通知代码执行位置的方式。写在前面就是前置通知,写在后面就是后置通知,写在异常中异常通知,写在finally中就是最终通知。即环绕通知有四个基本通知的所有功能且可以自由定制。(买衣服:四个基本通知买的成品,选择适合自己身材的号码;环绕通知相当于量身定做)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ULmJ1yDc-1623837558264)(随堂笔记.assets/1583114380031.png)]

代码演示:

 //环绕通知:自定义通知的方式
    //ProceedingJoinPoint:正在处理的连接点(切入点)
        //该对象spring框架会自动传入
        //如果该对象作为方法参数时,必须位于第一个
        //spring框架会封装正在执行的核心代码到该对象中
    public void around(ProceedingJoinPoint pjp){
//        Object objs[] = pjp.getArgs();//获取当前执行的方法中的参数
//        pjp.getSignature().getName();//获取当前执行的方法的名字
        try {
            System.out.println("before......你的前置通知");
            pjp.proceed();//代表核心业务方法
            System.out.println("after.....你的后置通知");
        } catch (Throwable throwable) {//抓的级别更高的Throwable
            System.out.println("异常.....你的后置通知");
            throwable.printStackTrace();
        }finally {
            System.out.println("最终......你的最终通知");
        }
    }

Spring的基于注解的AOP配置

a、四个基本通知

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4jDeuVxR-1623837558268)(随堂笔记.assets/1583116620964.png)]

测试效果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SylBlPq6-1623837558269)(随堂笔记.assets/1583116700683.png)]

b、抽取切入点表达式:给人感觉比较怪异记住这种格式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GcL8dq54-1623837558269)(随堂笔记.assets/1583116922433.png)]

c、配置环绕通知注解(注解方式建议使用环绕,不会有顺序错乱)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z5ow19A7-1623837558270)(随堂笔记.assets/1583117079617.png)]

小结:spring的aop的注解配置

step1、必须在spring配置文件中打开开关

step2、在通知类上使用@Aspect

step3、配置切入点:@Pointcut

step4、四个基本通知注解:@Before@AfterReturning@AfterThrowing@After,注意:有可能存在执行顺序不正确的问题

step5、环绕通知:@Around

Spring的基于类的AOP配置

目标:用类的配置替换xml(no xml)
step1、建立一个配置类:开启AOP的注解支持


import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@ComponentScan("com.zl")
@EnableAspectJAutoProxy //开启AOP的注解支持
public class SpringConfig {

}

step2、修改测试类
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3RdUbiTO-1623837558271)(随堂笔记.assets/1583117652041.png)]

案例:利用spring的aop(注解方式)改写转账案例

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值