Spring AOP之动态代理

AOP之动态代理

1. 什么是AOP

我们知道Spring一直致力于简化我们的Java开发,并且用到了依赖注入(Dependency Injection)与AOP(Aspect-Oriented Programming)这两项非常重要的技术:

  • DI主要解决了在类和类之间有依赖关系的时候,如何通过注入的方式(属性注入、构造器注入)形成松耦合
  • 而今天要学习的AOP则是考虑如何把散落在应用中多处相同的功能剥离出来,使得这些剥离出来的逻辑与业务逻辑相分离的问题。

让我们先来看一个生活中的案例:

每家每户都有一个电表来监控用电量,这样电力公司就知道应该收取多少费用了。虽然每一台用电设备都可以自装一个用电统计的硬件,但这样是极不合理,成本上也不划算的,因为每个用电设备更多关注的是自身的功能是否完善的问题,吸尘器考虑的是清洁效果,微波炉考虑的是加热效果等等。电力公司只需要在合适的地方,比如用电线路入户的地方,统一安装一个电表,就能很好的解决监控所有用电设备电量的问题。

软件系统中的一些功能就像我们家里的电表一样。这些功能需要用到应用程序的多个地方,但是我们又不想在每个点都明确调用它们。

图中展示了一个被划分为模块的典型应用。每个模块的核心功能都是为特定业务领域提供服务,但是这些模块都需要类似的辅助功能,例如安全和事务管理。

在软件开发中,散布于应用中多处的功能,被称为横切关注点(cross-cutting concern)。通常来讲,这些横切关注点从概念上是与应用的业务逻辑相分离的(但往往会直接嵌入到应用的业务逻辑中)。把这些横切关注点与业务逻辑相分离正是AOP所要解决的问题。

2. 代理模式简介

2.1 代理模式的概念

AOP在实现上采用了设计模式中的动态代理模式,因此,在深入学习SpringAOP之前,我们先来一起了解和学习一下这种强大的设计模式。

代理模式的定义:为其他对象提供一种代理,以控制对这个对象的访问。换句通俗的话来说,它是一种使用代理对象来执行目标对象的方法,并在代理对象中增强目标对象方法的一种设计模式。

生活中最常见的代理模式就是”中介“。假如说我现在想买一辆二手车,虽然我可以自己去找车源,做质量检测等一系列的车辆过户流程,但是这确实太浪费我得时间和精力了。我只是想买一辆车而已为什么我还要额外做这么多事呢?于是我就通过中介公司来买车,他们来给我找车源,帮我办理车辆过户流程,我只是负责选择自己喜欢的车,然后付钱就可以了。
在这里插入图片描述

2.2 代理模式的好处

从上图我们可以看出,在程序设计中使用代理模式的一些好处:

  • 中介隔离:在调用方(Caller)不能或不想直接与目标对象(Target)打交道的时候,代理对象(Proxy)可以起到两者之间中介的作用。
  • 开闭原则:我们可以通过给代理对象增加新的功能来扩展目标对象的功能,这样我们只需要修改代理类,而不需要修改目标类,符合代码设计的OCP原则(Open Closed Principle,对扩展是开放的,对修改是关闭的)。

2.3 代理模式的种类

根据代理对象创建的不同,分为两种代理模式:

  • 静态代理:由程序员或者特定工具生成源代码来产生代理对象。在程序运行前,代理类的字节码文件(.class)就已经存在了
  • 动态代理:在程序运行期间,运用反射机制、字节码生成技术来产生代理类和实例。

3. 静态代理模式

要实现静态代理,我们首先使用接口的方式来封装被代理的行为:

public interface BuyCar {

	void buy();

}

分别让目标和代理都来实现这个接口,这样,对于调用方来说,无论和代理还是目标打交道,执行的代码都是一致的,都可以执行购买行为。

public class Customer implements BuyCar {

	@Override
	public void buy() {
		System.out.println("客户选车、付款");
	}

}

为目标Customer编写对应的代理类,在其中增加新的服务功能:

public class CarProxy implements BuyCar {

	private Customer customer;

	public CarProxy(Customer customer) {
		this.customer = customer;
	}

	@Override
	public void buy() {
		System.out.println("售前服务:寻找车源、质量检测");
		// 让用户执行真正的购买行为
		customer.buy();
		System.out.println("售后服务:过户服务、售后咨询");
	}

}

从上面可以看到,我们最终让客户执行了购买行为,除此之外,为了让客户享受更为完善的服务,我们还扩展了寻找车源、质量检测、售后咨询等其它服务。在一个复杂的应用中,我们当然不是仅仅打印几行字,我们可以封装单独的方法来做这些事情,甚至还可以调用其它的类来执行这些辅助逻辑。

测试代码:

public static void main(String[] args) {
    BuyCar caller = new CarProxy(new Customer());
    caller.buy();
}

观察上面这个写法,虽然对于caller来说,等式右边出现了一个客户(target),看上去好像caller还是和target有关联,但是不要忘了,我们可以借助Spring的DI技术来让这个客户注入给二手车代理(proxy),同时也让这个二手车代理”消失“在等式的右边。

4. 动态代理模式

静态代理模式最大的缺陷就是,我们需要为每一个被代理的目标类都编写一个代理类。而动态代理可以很好的解决这个问题。JDK的Proxy和开源框架CGLIB可以分别在不同的情况帮助我们生成代理。

4.1 JDK Proxy

当目标的被代理方法抽取了接口时,可以使用JDK Proxy。

首先需要编写一个调用处理器

public class CarHandler implements InvocationHandler {

	private Object target;

	public CarHandler(Object target) {
		this.target = target;
	}

	// proxy 代理
	// method 被代理的方法
	// args 被代理方法执行所需的参数
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("售前服务");
        // 方法委派
		Object result = method.invoke(target, args);
		System.out.println("售后服务");
		return result;
	}

}

invoke方法会在代理对象的被代理方法调用的时候触发。

测试代码:

public static void main(String[] args) {
    Customer customer = new Customer();
    Class<?> clazz = Customer.class;
    // 参数1:目标类的类加载器
    // 参数2:目标类所实现的接口数组
    // 参数3:调用处理器
    BuyCar caller = (BuyCar) Proxy.newProxyInstance(
                        clazz.getClassLoader(), 
                        clazz.getInterfaces(),
                        new CarHandler(customer));
    caller.buy();
}

根据测试代码可以看出,Proxy将会根据类加载器的定义,生成一个实现了目标接口的代理对象来为调用方提供代理服务。在这个代理对象中,包含了我们想要扩展的其它服务逻辑,这些逻辑是通过我们编写的CarHandler单独封装出来的。

4.2 CGLIB

当目标类没有实现接口时,我们可以通过开源的CGLIB来实现。

引入Cglibku

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.2.8</version>
</dependency>

编写Handler

public class CglibHandler implements MethodInterceptor {

    // obj 目标对象
    // method intercept方法本身
    // args 方法调用时所需的参数
    // proxy 被代理的方法对象
	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
		System.out.println("售前服务");
        // 方法委派
		Object result = proxy.invokeSuper(obj, args);
		System.out.println("售后服务");
		return result;
	}

}

测试代码

public static void main(String[] args) {
    // 创建代理生成工具
    Enhancer enhancer = new Enhancer();
    // 设置目标为代理的父类型
    enhancer.setSuperclass(Customer.class);
    // 设置回调处理器
    enhancer.setCallback(new CglibHandler());
    // 创建代理对象
    Customer proxy = (Customer) enhancer.create();
    proxy.buy();
}

目标为代理的父类型
enhancer.setSuperclass(Customer.class);
// 设置回调处理器
enhancer.setCallback(new CglibHandler());
// 创建代理对象
Customer proxy = (Customer) enhancer.create();
proxy.buy();
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值