理解Java 动态代理和AOP(可以自己动手写AOP框架!)

说到AOP,很容易就想到 spring AOP。因为它是AOP里最优秀的实现框架。但本文不打算讨论 spring AOP,只想把如何通过动态代理方式实现AOP思想说通。当然,整明白了这个道理,理解 spring AOP 也就简单了!

首先我觉得需特别强调一下什么是面向接口编程!

用本人的意思理解,面向接口编程,有两个方面的角色,一个是接口的实现者,一个是接口的使用者,而接口本质上是一种服务规范,它规定了一个规范是通过什么方式向服务对象提供服务的,有什么参数,有什么约束,会有什么异常无法处理等。对接口的实现者来说,他们只需要按接口规定的要求提供服务即可,不必关心谁会来使用接口;对接口使用者来看,他只关注接口能提供什么功能和服务,不必关心接口是怎么实现的。

比如 JDK 里,有一个接口 java.util.List。它规定了一个列表可以提供的一系列服务:添加元素、移除元素、根据索引取元素、得到元素数量……。而接口有两个实现:ArrayList 和 LinkedList。

我们编写程序时,作为接口的使用者,可以用

List lst = new ArrayList();

然后把它当作 List 接口的一个实例即可。如果哪天不高兴,把 new ArrayList(); 改为 new LinkedList(); ,对我们写的程序来说不会有业务逻辑上的变化。

而对接口的实现者,就是sun公司(现在是Oracle公司),他们在实现 ArrayList 和 LinkedList 时,并没有限制这两个实现只能用到XXX系统里,而不能用到YYY系统里。也就是说,接口的实现者不关心接口会被什么人使用。

 

Java动态代理的基本例子网上有很多,在本文也是随便找了一篇阅读之后略微改了一下。

首先是定义一个接口(Animate):

package com.csjl.tangram.test;

public interface Animate {

	public void printName();
	
	public String getName();
}

然后是接口的实现(Dog、Cat):

package com.csjl.tangram.test;

public class Dog implements Animate {

	@Override
	public void printName() {
		System.out.println("This is dog!");
	}

	@Override
	public String getName() {
		return "dog";
	}

}
package com.csjl.tangram.test;

public class Cat implements Animate {

	@Override
	public void printName() {
		System.out.println("This is cat!");
	}

	@Override
	public String getName() {
		return "cat";
	}

}

然后是一个代理处理类(SimpleProxy):

package com.csjl.tangram.test;

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

public class SimpleProxy implements InvocationHandler {

	private Object subject;
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		
		System.out.println("Begin invoke method: " + method.getName());
		Object result = method.invoke(this.subject, args);
		System.out.println("Finished invoke method: " + method.getName());
		
		return result;
	}

	public SimpleProxy(Object subject){
		this.subject = subject;
	}
}

最后是测试类(Client):

package com.csjl.tangram.test;

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

public class Client {

	
	public static void main(String[] args){
		Cat cat = new Cat();
		
		InvocationHandler handler = new SimpleProxy(cat);
		
		Animate animate = 
				(Animate)Proxy.newProxyInstance(
						Cat.class.getClassLoader(), 
						Cat.class.getInterfaces(), 
						handler);
		
		System.out.println("Animate: " + animate.getClass().getName());
		
		System.out.println(">>>>>>>>>>>>>>>>>>>>");
		animate.printName();
		System.out.println();
		System.out.println("animate.getName() = " + animate.getName());
		System.out.println(">>>>>>>>>>>>>>>>>>>>");
	}
}

最后运行 Client 类的结果是:

Animate: com.sun.proxy.$Proxy0
>>>>>>>>>>>>>>>>>>>>
Begin invoke method: printName
This is cat!
Finished invoke method: printName

Begin invoke method: getName
Finished invoke method: getName
animate.getName() = cat
>>>>>>>>>>>>>>>>>>>>

注意到第一条打印语句是:Animate: com.sun.prox.$Proxy0

这个本人没有深入研究,网上别人的文章说这是 jvm 自动创建的类。但不管怎么样,它确实实现了 Animate 接口。

 

把上面的代码分为几大块:

1、接口定义(Animate)

2、接口实现(Cat、Dog 类)

3、得到 Animate 实例(Client.main 方法中,Proxy.newProxInstance  语句之前的代码全部)

4、调用 Animate 方法,实现业务逻辑

根据前面说的面向接口编程,上面的几块代码中:1是接口定义;2是接口实现;3和4是接口使用。而对方法的调用,是由自定义的代理类来实现的,那么代理在具体调用方法前、后,以及调用发生异常时都可以进行一些动作。这就是Java能够实现AOP的前提条件。在上面的程序中,只是在调用前后打印了一些语句,实际项目中会复杂得多。

如果使用了AOP框架,上面的第3块代码中得到 Animate 实例的具体实现一般是由框架根据配置文件动态生成,使用哪个代理也可以由配置文件动态指定。当然在 spring AOP 中,机制会复杂得多、功能会强大得多。本文只描述对AOP基本思想的理解。

看看下面修改后的程序,应该可以对AOP思想有了比较清晰的理解

接口定义(Animate,同上,略过)

接口实现(Dog、Cat,同上,略过)

定义了抽象的代理类(AbstractProxy),定义了抽象的方法调用前、后的操作,具体怎么操作由子类完成

package com.csjl.tangram.test;

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

public abstract class AbstractProxy implements InvocationHandler {

	private Object subject;
	
	public Object getSubject(){
		return subject;
	}
	
	public void setSubject(Object value){
		subject = value;
	}
	
	public abstract boolean beforeInvoke(Method method, Object[] args);
	
	public abstract Object afterInvoke(Method method, Object[] args, Object result);
	
	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		
		if(this.beforeInvoke(method, args) == false)
			return null;
		
		Object result = method.invoke(this.subject, args);
		
		return this.afterInvoke(method, args, result);
	}
}

实现切片操作的代理类(RealProxy),实现代理类可以有多个,具体由哪个进行代理,可以由配置文件指定

package com.csjl.tangram.test;

import java.lang.reflect.Method;

public class RealProxy extends AbstractProxy {

	@Override
	public boolean beforeInvoke(Method method, Object[] args) {
		System.out.println("调用方法 " + method.getName() + " 之前的操作,看我能先准备点什么");
		return true;
	}

	@Override
	public Object afterInvoke(Method method, Object[] args, Object result) {
		System.out.println("完成对方法 " + method.getName() + " 的调用,看我需要做什么收尾工作");
		return result;
	}

}

最后是测试类

package com.csjl.tangram.test;

import java.lang.reflect.Proxy;

public class Client {

	private static Animate getAnimate(){
		
		try{
			Class<?> cls = null;
			
			// 使用框架时,如何实例化animate会从配置文件中读取
			cls = Class.forName("com.csjl.tangram.test.Cat");
			Animate animate = (Animate)cls.newInstance();
			
			// 使用框架时,如何实现化 handler 会从配置文件中读取
			cls = Class.forName("com.csjl.tangram.test.RealProxy");
			RealProxy handler = (RealProxy)cls.newInstance();
			handler.setSubject(animate);
			
			Animate result = 
					(Animate)Proxy.newProxyInstance(
							animate.getClass().getClassLoader(), 
							animate.getClass().getInterfaces(),
							handler);
			
			return result;
		}catch(Exception ex){
			ex.printStackTrace();
			return null;
		}
	}
	
	
	public static void f2(){
		Animate animate = getAnimate();
		System.out.println("我要调用接口了");
		animate.printName();
	}
	
	public static void main(String[] args){
		f2();
	}
}

在以上测试类中的 getAnimate 方法,在实际项目中一般建议使用一个工厂类使用工厂模式创建,这里简化起见直接写到 Client 类中了。

 

从以上的代码结构中我们可以看到:

1、接口的定义和传统没什么两样

2、接口的实现和传统没什么两样

3、接口的调用和传统没什么两样,不一样的是修改 getAnimate 方法的实现,但这不会影响到主业务逻辑

4、但通过继承 AbstractProxy 类,可以在 Animate.printName() 和 Animate.getName() 方法的调用前后、异常发生时做一些逻辑处理工作。并且这些操作是可以通过配置进行动态修改的。

 

如果理解了Java动态代理,读者可以自己写一个小框架实现简单的AOP了!其实也就是上面代码中注释里提到的那些内容!

©️2020 CSDN 皮肤主题: 大白 设计师: CSDN官方博客 返回首页
实付0元
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值