AOP层层递进 第一部分 代理

一 理解代理

  代理是一种设计模式,指对目标对象方法的间接调用,可以实现不修改原方法逻辑的前提下扩展功能,这遵循了开闭原则。但我始终觉得代理这个词描述的不准确,原因在于代理的虽然意指代为执行,但实际上对象方法的执行并非跳过,代理之后的表现仅仅为原对象方法执行的前后多了补充逻辑,这难道不是函数增强吗?

  代理的需求来自实际项目的实施过程中产生的问题,某些特定场景下开发者不被允许或很难去调整一个既有的方法,然而目标是对方法进行增强实现,那么能做就是在函数调用的前后来补充一些设计逻辑,为了避免对原函数进行重构/调整,减轻测试的压力,开发者想到了一个绝妙的解决方案——代理。

  所以本文从需求开始,逐步介绍代理的各类实现以及在AOP层面的应用。

二 增强函数

  举个例子,一个信息发送的需求,目前以短信方式实现:

public class MessageClient
{
	public void sendMessage(String msg)
	{
		System.out.println("发送短信:" + msg);
	}
}

  现在需求发生了变化,要求在将发送的短信内容进行扩充,但是不允许调整原函数,怎么办?一种以面向对象的方式的解决方案是从MessageClient派生一个子类,重写sendMessage函数:

public class EnhancedMessageClient extends MessageClient
{
	@Override
	public void sendMessage(String msg)
	{
		System.out.println("sendMessage之前的补充处理逻辑");
		super.sendMessage(msg);
	}
}

  这是一个最常见的解决方案,重写父类方法,对参数执行增强处理逻辑后,重新调用父类方法,使其表现的行为和父类一致,这就增强了函数实现。

  但这种实现方式并不优雅,代理模式就是为解决这个问题诞生的。

三 代理模式

  代理模式来完成上述需求似乎更加妥当,但是代理模式要求方法为接口声明,那么我们重新调整信息发送客户端:

public interface SendMessage
{
	public void doSend(String msg);
}

public class MessageClient implements SendMessage
{
	public void sendMessage(String msg)
	{
		System.out.println("发送短信:" + msg);
	}
}

  接下来需要设计一个代理类,代理类同样实现了SendMessge接口,其目的是为了和被代理类的行为表现一致。其次,还需要让代理类持有一个被代理类的对象成员,用以执行被代理类的真正逻辑,而代理类仅作额外的处理逻辑:

public class MessageClientProxy implements SendMessage
{
	private SendMessage impl = null;

	public MessageClientProxy(SendMessage impl)
	{
		this.impl = impl;
	}

	@Override
	public void sendMessage(String msg)
	{
		before();
		impl.sendMessage(msg);
		after();
	}

	private void before()
	{
		System.out.println("sendMessage之前的补充处理逻辑");
	}

	private void after()
	{
		System.out.println("sendMessage之后的补充处理逻辑");
	}
}

  这样我们就可以毫无痕迹的实现对MessageClient的sendMessage方法做补充处理,而MessageClient毫无察觉,应用转为通过MessageClientProxy来调用sendMessage。这种代理模式亦称之为静态代理。

  代理模式和通过派生子类重写父类逻辑一样,有一个致命缺陷,当需求不断发生变化的时候,或者需要很多被增强的函数的场景下,类型定义(代理类定义或派生类定义)爆发,这对项目维护是致命的。

  那么如何在满足需求的前提下,遏制类型定义爆发呢?动态代理!

四 JDK动态代理

  Java原生支持动态代理,我们常称之为JDK动态代理,它要求我们实现一个接口InvocationHandler,如此MessageClientProxy就不再需要了,我们仅仅实现一个InvocationHandler接口就可以:

public class DynamicProxy implements InvocationHandler
{
	private Object targe;

	public DynamicProxy(Object target)
	{
		this.targe = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] params) throws Throwable
	{
		before();
		method.invoke(targe, params);
		after();
		return null;
	}

	private void before()
	{
		System.out.println("sendMessage之前的补充处理逻辑");
	}

	private void after()
	{
		System.out.println("sendMessage之后的补充处理逻辑");
	}
}

  光实现InvocationHandler接口不够,它还要求应用程序通过Proxy来返回一个实现了SendMessage接口的代理类——即MessageClient:

public static void main(String[] args)
{
	DynamicProxy proxy = new DynamicProxy(new MessageClient());
	SendMessage impl = (MessageClient) Proxy.newProxyInstance(MessageClient.class.getClassLoader(),
			MessageClient.class.getInterfaces(), proxy);
	impl.sendMessage("我被代理了");
}

  Proxy.newProxyInstance方法的三个参数分别为:

  1. ClassLoader
  2. 实现类的接口
  3. 代理对象

  从参数上可以隐讳的看到JDK动态代理的一个弊端——要求被代理类的增强函数一定通过接口声明!这是JDK对动态代理的实现逻辑决定的,这部分介绍大家可以参考网上的各类资料,都很详细。

  而我要强调的是,虽然我们通过JDK动态代理刻画了一个以不变应万变的模板,但是JDK动态代理的调用方式确实恶心,所有调用处必须通过Proxy.newProxyInstance方法来获取接口的实现对象,所以我对其进行改进:

public class DynamicProxy implements InvocationHandler
{
	...

	@SuppressWarnings("unchecked")
	public <T> T getProxy()
	{
		return (T) Proxy.newProxyInstance(this.targe.getClass().getClassLoader(), this.getClass().getInterfaces(),
				this);
	}
	...
}

  如上在DymanicProxy中对Proxy.newProxyInstance函数进行封装,注意getProxy方法的返回值为泛型,如此定义是为了便于应用调用函数时,不必进行类型的强制转换,那么应用在使用的时候就化简成了如下形式:

public static void main(String[] args)
{
	DynamicProxy proxy = new DynamicProxy(new MessageClient());
	SendMessage impl = proxy.getProxy();
	impl.sendMessage("我被代理了");
}

  到此为止,我们真的解决之前所说的问题了吗?绝不!首先DymanicProxy虽然能够对任何接口实现类进行函数增强,但是其增强逻辑是一样的!!!其次,JDK动态代理只能针对接口模式实现函数增强,如果一个实现类没有任何接口声明呢?

  先说第一个问题,既然能想到函数增强逻辑写死不好,那么动用万能的面向对象特性——封装即可解决,我们况且认为对一个函数进行增强,分前后两端,当然了原函数逻辑就不用妄想了,除非用字节码技术进行处理。所以我们定义一个增强处理接口:

public interface EnhanceMethod
{
	public void before();

	public void after();
}

  如此应用再去使用代理模板的时候,仅仅需要传入一个增强接口的实现即可:

public static void main(String[] args)
{
	DynamicProxy proxy = new DynamicProxy(new MessageClient(), new EnhanceMethod()
	{

		@Override
		public void before()
		{
			System.out.println("sendMessage之前的补充处理逻辑");
		}

		@Override
		public void after()
		{
			System.out.println("sendMessage之后的补充处理逻辑");
		}
	});
	SendMessage impl = proxy.getProxy();
	impl.sendMessage("我被代理了");
}

  回头再说第二个问题,JDK动态代理的实现机制决定了它无法对非接口实现类进行函数增强,这个问题怎么解决?CGLIB给了我们一份满分答卷。

五 CGLIB动态代理

  CGLIG专门为解决非接口类动态代理而实现,它的实现方法又有变化,它要求实现接口MethodInterceptor,从接口命名可以隐讳的看出CGLIB动态代理针对方法进行增强,我们对DynamicProxy重写,改名为CgligProxy:

public class CglibProxy implements MethodInterceptor
{
	private Class<?> clazz;
	private EnhanceMethod enhance;

	public CglibProxy(Class<?> clazz, EnhanceMethod enhance)
	{
		this.clazz = clazz;
		this.enhance = enhance;
	}

	@SuppressWarnings("unchecked")
	public <T> T getProxy()
	{
		return (T) Enhancer.create(clazz, this);
	}

	@Override
	public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable
	{
		enhance.before();
		Object result = proxy.invokeSuper(obj, args);
		enhance.after();
		return result;
	}
}

  按照如上方式实现CGLIB版本的动态代理模板后,应用的使用逻辑相应调整为:

public static void main(String[] args)
{
	CglibProxy proxy = new CglibProxy(MessageClient.class, new EnhanceMethod()
	{

		@Override
		public void before()
		{
			System.out.println("sendMessage之前的补充处理逻辑");
		}

		@Override
		public void after()
		{
			System.out.println("sendMessage之后的补充处理逻辑");
		}
	});
	SendMessage impl = proxy.getProxy();
	impl.sendMessage("我被代理了");
}

  点到为止,对本文进行小结,首先代理并非真正的代为执行,对其理解为函数的无侵入增强更为合适,其次代理的实现一般分为两类:静态代理和动态代理,动态代理有根据实现机制的不同分为:JDK动态代理和CGLIB动态代理。

  理解了代理和其应用之后,第二部分将对AOP进行介绍,包括AOP的前生今世,应用场景,Spring等主流框架的实现机制以及如何自行实现一个AOP框架等等。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

柠檬睡客

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值