一. 代理的概念
代理就是有多个类,这些类拥有同一个接口,为这些类中的方法增加一些系统功能,例如异常处理,计算方法的运行时间等等,这样我们在客户端使用该类时,配合工厂模式和修改配置文件的方式可以选择使用代理类,还是目标类,我们代理的结构图如下:
类似于这种使用代理的编程思想叫做面向方面的编程,简称AOP(Aspect Oriented Program),专业说法是交叉业务的模块化,就是为不同对象加载相同功能。
在代理类中,我们要添加的功能性代码一般放在以下位置:
① 调用目标方法之前
② 调用目标方法之后
③ 调用目标方法前后
④ 在处理目标方法异常catch块中
二. 动态代理
要为系统中的各个接口下的类增加代理功能,需要许多的代理类,但是在反射包中有这么一个类,Proxy他可以动态的生产类的字节码文件,也就是说我们可以在运行时期动态的获得一个类,这个类往往被用作代理类,这就是动态代理。
虚拟机生成的动态类必须实现一个或者多个接口,所以虚拟机生成的动态类只能用作具有相同接口的目标类的代理,如果某个类没有实现接口,我们可以使用CGLIB库,他可以动态的生成一个类的子类,这个动态生成的子类可以用作该类的代理。
三. 动态代理的使用
1.Proxy介绍
Proxy类是动态代理的基石,此类可以动态生成类的字节码文件,下面来看一下他最为常用的方法。
(1)Proxy.getProxyClass(ClassLoader loader,Class<?>… interfaces)
此方法接收一个类加载器和一个或者多个接口的字节码,返回一个该接口下的动态字节码文件,该字节码的名称为:$Proxy+编号,这个字节码只有一个有参构造方法,参数为InvocationHanderler handler。
(2)Proxy.newProxyInstace(ClassLoader loader ,Class<?>[] interfaces, InvocationHandler handler)
此方法用于直接创建某个接口下的实例对象
这两种方法对代理的实现方法都是通过InvocationHandler对象来实现的,InvocationHandler是一个接口,当我们拿到一个字节码之后,通过创建一个InvocationHandler对象把目标类方法和我们需要添加的内容进行整合。在此接口中只有一个方法:invoke,他的参数列表为:Object proxy(代表哪个对象)Method method(哪个方法)Object[] args(方法接收的参数)。
代理的实现流程为:代理—>InvocationHandler—>目标,而目标把返回结果再逆序返回给代理。对于接口或者代理类中从Object继承的方法除去hashCode(),equals(),toString()之外都不派发给handler完成。
/*
* 代理类
* 通过动态创建一个Collection接口下的动态类是演示代理类的使用
* ① 首先通过反射包下的一个类Proxy来动态生成Collection类字节码
* 通过他的方法Proxy.getProxyClass(loader, interfaces)生成
*
* ②拿到Collection接口的字节码后可以做的事情就多了,比如用它来创建一个实例对象
*
* ③我们还可以利用Proxy的静态方法直接new一个接口下的实例对象
*
* ④无论哪一种方法都要返回InvocationHandler对象,一般采用匿名内部类的形式,在handler对象中
* 的invoke方法是代理要调用的,而我们对原方法进行功能的加强就是写在invoke方法中
* */
class ProxyTest {
public static void main(String[] args) throws Exception{
//①动态获取从Collection接口下生成的代理类字节码
Class proxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(proxy.getName());
//②创建Collection接口下的实例对象,他没有空参的构造方法,所以只能通过构造器去创建
Constructor constructor = proxy.getConstructor(InvocationHandler.class);
//通过匿名内部类的方式来创建该实例对象
Collection coll = (Collection)constructor.newInstance(new InvocationHandler() {
ArrayList al = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long start = System.currentTimeMillis();
Object value = method.invoke(al, args);
Thread.sleep(300);
long end = System.currentTimeMillis();
System.out.println(method.getName()+"方法运行时间为"+(end - start));
return value;
}
});
coll.add("adsf");
coll.size();
//③利用Proxy的静态方法newProxyInstance直接new对象出来
Collection coll2 = (Collection)Proxy.newProxyInstance(
//Collection的类加载器
Collection.class.getClassLoader(),
new Class[]{Collection.class},
//在InvocationHandler的匿名内部类中完成代理功能,此处用来计算方法所耗费的时间
new InvocationHandler() {
//在此处定义目标,无论coll2运行什么方法他的目标都是al
ArrayList al = new ArrayList();
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
long startTime = System.currentTimeMillis();
Object value = method.invoke(al, args);;
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"方法的运行时间为:"+(endTime - startTime));
return value;
}
});
}
}
2.动态加载系统功能
InvocationHandler的invoke方法是代理功能的实现地,我们采用动态加载代理的功能,也就是不再invoke方法中把功能写死,而是通过接收参数的形式来接收用户具体指定的功能,实际应用中,目标一般也是由用户指定。
代理的功能,也就是方法,在此我们的做法是传入一个对象,在JavaScript中可以传入字符串,把字符串解析为方法命令,java暂时无此功能,因此采用传入对象的方式,把代理功能代码封装成一个对象,一般我们都是以接口的形式来获取该接口下对应的对象,所以首先定义一个接口,在这个接口中定义系统功能的方法声明。
//为动态代理服务的类
public interface Advice {
void startMethod(Method method);
void endMethod(Method method);
}
自定义一个类实现该接口,并且编写真正的代理功能代码。
/*
* 自定义类实现接口以便传入dialing方法中
* */
public class MyAdvice implements Advice {
//因为要使用到同一资源,所以定义成成员变量
long startTime = 0;
//复写前端方法
@Override
public void startMethod(Method method) {
startTime = System.currentTimeMillis();
}
@Override
public void endMethod(Method method) {
long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"方法的运行时间为:"+(endTime - startTime));
}
}
在真正使用时,构建方法,将自定义类的对象、目标类都作为参数传递给该方法,这两个参数的可变性造就代理功能的可变性。
public class ProxyTest {
public static void main(String[] args) throws Exception{
//①动态获取从Collection接口下生成的代理类字节码
Class proxy = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(proxy.getName());
/*对代理中附加的功能进行动态化编程,就是不写死,通过加载参数的形式来让用户指定代理的功能
* 具体做法:将代理和目标转为参数传递给proxy
* ①把生成代理的方法整合成一个方法getProxy
* ②编写一个接口Adice,这个接口中有前端方法和后端方法,要传入代理方法中对象都是该接口下的对象
* ③编写一个类MyAdvice实现我们自定义的接口,并真正的定义前端方法和后端方法
* ④将自定义类的对象传入方法中
*/
//定义目标和代理功能
final ArrayList al = new ArrayList();
final MyAdvice md = new MyAdvice();
//这就是整合后的方法
Collection coll3 = (Collection)getProxy(al,md);
coll3.add("小绿");
System.out.println(coll3.size());
coll3.remove(2);
}
//这个方法就可以接收Advice接口下的方法,而且可以动态的接收,用户可以通过修改MyAdcice来实现不同的代理功能
private static Object getProxy(final Object target,final Advice a) {
Object clazz = Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
a.startMethod(method);
Object value = method.invoke(target, args);;
a.endMethod(method);
return value;
}
});
return clazz;
}
}