Spring AOP 概念及动态代理模式

Spring AOP 概念及动态代理模式


1 AOP 的概念及相关术语

Spring 官方解释:https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop

  面向切面编程(Aspect-oriented Programming ,AOP)通过提供另一种思考程序设计结构的方式来补充面向对象程序设计(Object-oriented Programming,OOP)。面向对象编程中,模块化的关键单元是类,而在面向切面编程中,模块化的单元是方面。方面支持对关注点的模块化(比如事务管理)跨多个类型和对象。(这种关注在 AOP 文献中通常被称为 “跨领域”关注)

  AOP 是 Spring 的一个关键组件,尽管 Spring IoC 容器不依赖于 AOP(意味着你不需要用AOP的话,可以不使用),但 AOP 是对 Spring IoC 的补充,提供了功能强大的中间件解决方案。

也可以去看看:百度百科

相关术语:

术语描述
连接点(Join point)指的是那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点。
切入点(Point Cut)指的是我们要对哪些 Join point 进行拦截的定义。被增强的连接点就叫做切入点,切入点一定是连接点,连接点不一定是切入点。
通知 / 增强(Advice)指拦截到 Join Point 之后要执行的操作。类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
引介(Introduction)是一种特殊的通知,可以为类动态地添加一些方法 或 Filed。
目标对象(Target object)代理的目标对象。
织入(Weaving)指把增强应用到的目标对象来创建新的代理对象的过程。 spring 采用 动态织入,而 AspectJ 采用编译期织入和类装载期织入。
AOP 代理(AOP proxy)一个类被 AOP 织入增强后,就产生了一个结果代理类。
切面(Aspect)是切入点和通知 / 引介 的结合。建立切入点方法和通知方法在调用执行时的对应关系就是一个切面

2 AOP 作用

  AOP 可以对业务逻辑得到各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

通俗描述:不修改源代码的条件下,在主干功能中添加新功能。

案例解释:
  现在有一个账户服务类( AccountService ),类中有一个转账的方法( transferAccounts )。顾名思义,转账操作就是要将一个账户的钱转给另一个账户,这个过程需要查询两次,修改余额两次。如果一个账户在转账的过程中出现异常,导致转账人的钱减少了,而收款人的钱没有到账的情况。这种情况可以通过添加事务来解决,可以在源代码上直接做处理,但如果有很多个类似的方法呢?每个方法都加上事务管理,代码无疑会变得十分冗余,这时就可以使用一个代理类(AccountProxy),将 AccountService 类中所有需要事务管理的方法都拦截放到添加事务管理的方法中,这样就可以做的不修改源代码的条件下,增强功能。这就是 AOP 的过程。

在 Spring 中用 AOP 术语匹配以上案例:

  • 切面(Aspect):代理类就是一个切面,里面有代理增强的方法。
  • 连接点(Join point):被代理对象 AccountService 中被拦截到的方法就叫做连接点。
  • 切入点(Point Cut):被代理对象 AccountService 中被拦截且增强的方法就叫做切入点。(切入点一定是连接点,连接点不一定是切入点。)
  • 通知 / 增强(Advice):添加的事务管理功能就叫做通知或增强。
  • 目标对象(Target object) :指得就是被代理的对象 AccountService 。
  • 织入(Weaving):在代理类 AccountProxy 中为被代理对象 AccountService 添加事务管理功能的过程就叫做织入。
  • AOP 代理(AOP proxy):指的就是 AccountService 的代理对象 AccountProxy。

3 AOP 原理概述

AOP 底层使用到了动态代理模式:

  • 特点:字节码文件随用随创建,随用随加载。
  • 作用:不修改源码的基础上对方法增强。
  • 分类:基于接口的动态代理和基于子类的动态代理。
    • 第一种,基于接口的动态代理,使用 JDK 实现动态代理。
    • 第二种,基于子类的动态代理,使用第三方 CGLIB 库实现动态代理。

菜鸟教程-代理模式

简单理解代理模式:用户购买电脑不是到生产厂家处购买,而是到经销商处购买,经销商又从生产厂家取货。倘若用户电脑出问题了,找的是经销商的售后,经销商售后要么自己处理,要么返还给生产厂家处理,这个过程就叫做代理。


3.1 JDK 动态代理(Proxy)

这是基于接口的动态代理方式,是由 JDK 提供。

使用类和方法:java.lang.reflect.Proxy.newProxyInstance

要求被代理对象必须实现至少一个接口,否则出现以下异常:

java.lang.ClassCastException: com.sun.proxy.$Proxy0 cannot be cast to xxx

可参考:Proxy API介绍


newProxyInstance参数解读:

  • ClassLoader loader :代理类的类加载器,使用 xxx.class.getClassLoader() 获取类加载器,xxx表示代理对象。
  • Class<?>[] interfaces : 被代理对象的所有接口,使用 xxx.class.getInterfaces() 获取被代理对象的所有接口,xxx 表示被代理对象。目的是让代理对象与被代理对象有相同的方法。
  • InvocationHandler h : 调用代理对象方法的控制器接口,可以在这个接口的实现类中写增强方法的代码。里面有一个唯一的方法 invoke() 需要被重写。通常是匿名内部类的形式。

nvocationHandler 接口中的 invoke 方法:用于控制代理实例上的方法调用和返回结果。


invoke 方法参数解读:

  • Object proxy: 代理对象,方法被调用的地方。
  • Method method: 被代理对象中的方法,可以使用 method.getName() 方法获取当前执行方法的名称。
  • Object[] args: 被代理对象中方法的参数列表,可使用索引的方式获取参数,如args[0] 表示当前执行方法的第一个参数。

代理过程演示:

1 创建接口,定义相关方法

//被代理对象,模拟播放人类从匍匐到奔跑的过程的视频
public interface Person {
    public void walk();
}

2 创建接口实现类并实现方法

public class PersonImpl implements Person{
    public void walk(){
        System.out.println("视频播放:人类行走过程。。。");
        // int a = 1/0;        //手动制造异常,默认在观看人类行走时产生疑惑
    }
}

3 使用 Proxy 类创建接口代理对象。

public class PersonProxy {
	// 创建被代理对象
    PersonImpl personImpl = new PersonImpl();
	// 返回代理对象的方法
    public Object personProxy() {
        return Proxy.newProxyInstance(
                PersonProxy.class.getClassLoader(),
                PersonImpl.class.getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println(proxy.getClass());
                        try {
                            Object obj = null;
                            System.out.println("视频播放:人类匍匐过程。。。");     // 被增强方法执行前的动作
                            obj = method.invoke(personImpl, args);          // 被增强方法被明确调用,保证原有的功能
                            System.out.println("视频播放:人类奔跑奔跑。。。");     // 被增强方法执行后执行的动作
                            return obj;
                        } catch (Exception e) {
                            System.out.println("观众产生疑问。。。");            //被增强方法执行过程中出现异常执行的动作
                            throw new RuntimeException(e);
                        } finally {
                            System.out.println("视频播放结束。。。");         // 无论方法是否正常执行,都会执行的动作
                        }
                    }
                });
    }
}

4 测试方法

public static void main(String[] args){
    PersonProxy pp = new PersonProxy();
    Person p = (Person) pp.personProxy();
    p.walk();
}

打印结果:

class com.sun.proxy.$Proxy0
视频播放:人类匍匐过程。。。
视频播放:人类行走过程。。。
视频播放:人类奔跑奔跑。。。
视频播放结束。。。

3.2 CGLIB 动态代理(Enhancer)

这是基于继承方式的动态代理,是由第三方 cglib 库提供。

使用的类和方法:cglib.proxy.Enhancer.create

它要求被代理对象不能是最终类(无法继承),否则出现以下异常;

java.lang.IllegalArgumentException: Cannot subclass final class xxx

可参考以下博客:

CGLIB原理及实现机制
CGLib动态代理的介绍及用法(单回调、多回调、不处理、固定值、懒加载)


create 方法参数解读:

  • Class type :被代理对象的字节码文件。
  • Callback callback :回调函数,可以使用这个接口的子接口提供的实现类中写增强方法的代码。与 JDK 动态代理中的 InvocationHandler 大同小异,这里是用于拦截方法的,所以用到的是方法拦截接口:MethodInterceptor ,接口中有唯一且需要被重写方法 intercept()

intercept() 方法参数解读:

  • Object o :被代理对象。
  • Method method:拦截到的方法
  • Object[] objects:被拦截方法的参数数组,基本类型会被包装成包装类型。
  • MethodProxy methodProxy:用于调用父类中未被拦截的方法的代理。

代理过程演示:

1 创建被代理类

public class Person {
    public void walk() {
        System.out.println("视频播放:人类行走前进中。。。");
        // int a = 1/0;        //手动制造异常,默认在观看人类行走时产生疑惑
    }
}

2 创建代理对象

public class PersonProxy {
    Person person = new Person();

    public Object getPersonProxy(){
        return Enhancer.create(person.getClass(), new MethodInterceptor() {
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                System.out.println(o.getClass());
                try{
                    Object obj = null;
                    System.out.println("视频播放:人类匍匐前进中。。。");       // 被增强方法执行前的动作
                    obj = method.invoke(person,objects);                	 // 被增强方法被明确调用,保证原有的功能
                    System.out.println("视频播放:人类奔跑前进中。。。");     // 被增强方法执行后执行的动作
                    return obj;
                }catch (Exception e){
                    System.out.println("观众产生疑问。。。");          //被增强方法执行过程中出现异常执行的动作
                    throw new RuntimeException(e);
                }finally {
                    System.out.println("视频播放结束。。。");         // 无论方法是否正常执行,都会执行的动作
                }
            }
        });
    }
}

3 测试方法

public static void main(String[] args) {
    PersonProxy pp = new PersonProxy();
    Person p = (Person)pp.getPersonProxy();
    p.walk();
}

打印结果:

class main.a_proxy.cglib.Person$$EnhancerByCGLIB$$7f3ac652
视频播放:人类匍匐前进中。。。
视频播放:人类行走前进中。。。
视频播放:人类奔跑前进中。。。
视频播放结束。。。

以上动态代理内容作为了解即可,下面用 Spring 中的 AOP 完成上面代理的过程。


4 入门案例

4.1 创建 maven 工程,导入相关依赖

<!--Spring 核心-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<!--解析切点表达式-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

4.2 创建被代理的接口及其实现类

public interface AccountService {    
    void saveAccount();			//模拟保存    
    void updateAccount(int id);	//模拟更新    
    int deleteAccount();		//模拟删除
}
public class AccountServiceImpl implements AccountService {
    @Override
    public void saveAccount() {
        System.out.println("保存方法执行了。。。");       
        // int a = 1/0; //制造异常通知
    }
    @Override
    public void updateAccount(int id) {
        System.out.println("更新方法执行了。。。");
    }
    @Override
    public int deleteAccount() {
        System.out.println("删除方法执行了。。。");
        return 0;
    }
}

4.3 配置 AOP

<?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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/aop
                           http://www.springframework.org/schema/aop/spring-aop.xsd">
	
	<!--配置 Service 对象实例化-->
    <bean id="accountService" class="com.jk.xml.service.impl.AccountServiceImpl"/>

    <!--1 配置通知类-->
    <bean id="aopUtil" class="com.jk.xml.utils.AopUtil"/>
    <aop:config>
        <!--2 配置切面-->
        <aop:aspect id="aopAdvice" ref="aopUtil">
            <!--3 配置前置通知,并关联 通知(advice) 和 切入点(point cut)(切入点表达式)-->
            <aop:before method="beforeAdvice" pointcut="execution(int com.jk.xml.service.impl.*.*(..))"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 要配置 AOP 需要引入 aop 名称空间。
  • 配置切入点时用到的切入点表达式,如果看不懂没关系,下一篇博客会详细介绍切入点表达式。

4.4 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:xml/bean.xml"})
public class TestBean1 {
    @Autowired
    AccountService as;

    //测试通知配置
    @Test
    public void testBeforeAdvice(){
        as.saveAccount();
        as.updateAccount(1);
        as.deleteAccount();
    }
}

这里使用 Spring 整合了 Junit 测试单元,关于配置可以看这篇博客:Spring 整合 JUnit 测试单元

执行结果:

保存方法执行了。。。
更新方法执行了。。。
<==before,前置通知加上了==>
删除方法执行了。。。
  • 我这里的切入点表示式指定的是 service 实现类中返回值为 int 的方法,所以只有 int deleteAccount() 被增强了。

以上就是关于 Spring AOP 的概念和动态代理模式的介绍,下一篇将会详细介绍切入点表达式、基于 xml 配置实现 AOP,基于注解实现 AOP,基于完全注解的方式实现 AOP,博客连接:

Spring AOP 的配置及使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值