动态代理


代理的概念与作用

生活中的代理:一个人想买电脑,他可以去本地的代理商那儿买,也可以去很远的城市的总部去买。在两个地方都能买到电脑,也就是说代理商是电脑生产厂家的一个代理。
程序中的代理:加入要为已存在的多个具有相同接口的目标类的各个方法增加一些系统功能,例如,异常处理、日志、计算方法的运行时间、事务管理、等等,这个时候就要用到代理了,编写一个与目标类具有相同接口的代理类,代理类的每个方法调用目标类的相同方法,并在调用方法时加上系统功能的代码。如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。

AOP面向切面编程

aop是面向切面编程的意思,它也是spring框架中一个重要的概念利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
系统中存在交叉业务,一个交叉业务就是要切入到系统中的一个方面,如下所示:

                              安全       事务         日志

StudentService ------|----------|------------|-------------

CourseService  ------|----------|------------|-------------

MiscService      ------|----------|------------|------------

用具体的程序代码描述交叉业务:

method1         method2          method3

{                      {                       {

------------------------------------------------------切面

....            ....              ......

------------------------------------------------------切面

}                       }                       }

交叉业务的编程问题即为面向方面的编程(Aspectoriented program ,简称AOP),AOP的目标就是要使交叉业务模块化。可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:

------------------------------------------------------切面

func1         func2            func3

{             {                {

....            ....              ......

}             }                }

------------------------------------------------------切面

使用代理技术正好可以解决这种问题,代理是实现AOP功能的核心和关键技术。
要使用动态代理,被代理类必须要实现接口。

动态代理的实现

要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,如果全部采用静态代理方式,那么静态代理类的数量将非常多,这会是一件非常麻烦的事情!
JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
JVM生成的动态类必须实现一个或多个接口,所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。
CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中

JDK动态代理中包含一个类和一个接口: 
InvocationHandler接口: 
public interface InvocationHandler { 
public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 

参数说明: 
Object proxy:指被代理的对象。 
Method method:要调用的方法。
Object[] args:方法调用时所需要的参数。
现在用一个小例子来展示动态代理的使用方法,实现一个动态代理,使它可以拥有ArrayList的所有方法,并且可以计算每个操作所需的时间:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ProxyTest1 {
	public static void main(String[] args) {
		final List list = new ArrayList();
		Collection collection = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				long startTime = System.currentTimeMillis();
				Object obj = method.invoke(list, args);
				long endTime = System.currentTimeMillis();
				System.out.println(method.getName() + "操作所需的时间为:" + (endTime - startTime));
				return obj;
			}
		});
		//Collection collection = (Collection)getProxy(list, new MyAdvice());
		collection.add("abc");
		collection.add("xyz");
		System.out.println("list的大小为:"+collection.size());
	}
}


运行结果为:
add操作所需的时间为:1
add操作所需的时间为:0
size操作所需的时间为:0
list的大小为: 2
动态生成的类实现了Collection接口(可以实现若干接口),生成的类有Collection接口中的所有方法和一个如下接受InvocationHandler参数的构造方法。构造方法接受一个InvocationHandler对象,接受对象了要干什么用呢?该方法内部的代码会是怎样的呢?实现Collection接口的动态类中的各个方法的代码又是怎样的呢?InvocationHandler接口中定义的invoke方法接受的三个参数又是什么意思?图解说明如下:


由这个图可以看出方法的调用实际上传给了Object proxy这个对象,也就是说上面程序中add()和size()方法实际上还是传给了ArrayList的list对象,那现在又出现了一个问题:
public class ProxyTest1 {
	public static void main(String[] args) {
		final List list = new ArrayList();
		Collection collection = (Collection)Proxy.newProxyInstance(Collection.class.getClassLoader(), new Class[]{Collection.class}, new InvocationHandler() {
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				long startTime = System.currentTimeMillis();
				Object obj = method.invoke(list, args);
				long endTime = System.currentTimeMillis();
				System.out.println(method.getName() + "操作所需的时间为:" + (endTime - startTime));
				return obj;
			}
		});
		System.out.println(collection.getClass().getName());
	}
}

按照刚才说的这个程序应该输出的是ArrayList的Class对象才对,但是程序实际运行结果却是:
com.sun.proxy.$Proxy0
这是代理对象的Class对象。为什么会这样呢?
因为getClass()方法实际上是继承Object类获得的方法,代理类对继承Object的方法都有自己的实现,hashCode(),toString(),equals()这三个方法除外,这三个方法还是会传给被代理的对象由它们自己去实现。

动态代理的优化

上面的程序写的很死板,没有实现可插拔式的代码,现在将上面的代码优化一下实现,将切面功能作为一个对象传递进去:
首先实定义一个接口,这个接口定义了两个方法,目标对象执行目标方法之前做什么,之后做什么
import java.lang.reflect.Method;

public interface Advice {

	public void beforeMethod(Method method);
	
	public void afterMethod(Method method);
}

比如还是实现上面的计算程序执行的时间,在方法之前打印一句话,之后也打印一句话,接口的实现:
import java.lang.reflect.Method;

public class MyAdvice implements Advice {
	private long start;

	@Override
	public void beforeMethod(Method method) {
		start = System.currentTimeMillis();
		System.out.println(method.getName() + " 操作执行之前");
	}

	@Override
	public void afterMethod(Method method) {
		long after = System.currentTimeMillis();
		System.out.println(method.getName() + " 操作花费的时间为:" + (after - start)
				+ "毫秒");
	}
}

代这样生成代理类的方法就需要两个参数,一个是被代理的对象,还有一个是切面方法,也就是Advice对象。
最初的代码只是针对Collection类进行了代理,这次改为对所有对象都可以代理:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class ProxyTest1 {
	public static void main(String[] args) {
		final List list = new ArrayList();
		Collection collection = (Collection)getProxy(list, new MyAdvice());
		collection.add("abc");
	}
	
	public static Object getProxy(final Object target, final Advice advice){
		Object proxy = Proxy.newProxyInstance(
				target.getClass().getClassLoader(), 
				target.getClass().getInterfaces(), 
				new InvocationHandler() {
			
			@Override
			public Object invoke(Object proxy, Method method, Object[] args)
					throws Throwable {
				advice.beforeMethod(method);
				Object obj = method.invoke(target, args);
				advice.afterMethod(method);
				return obj;
			}
		});
		return proxy;
	}
}



执行结果为:
add 操作执行之前
add 操作花费的时间为:0毫秒
这样就实现了代理类的重构了,而且比之前的要灵活很多,如果我们想修改切面功能,在定义一个切面对象传递给产生代理的方法就行了,不用再去修改产生代理的内部代码了。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值