---------------------- 黑马程序员 Android培训、期待与您交流! ----------------------
代理
批发进货的成本和运输费的优势,比你自己直接到北京总部买的总成本要低吧。
代理的概念和作用示意图:为代理类增加一些其它功能,例如:计算运行时间
示意图解析:
Client客户端原来是直接调用的Target目标类,现在不让客户端直接调用Target了,调用Proxy代理(白话说:原来买电脑是直接去联想总部,现在不用这么麻烦了,直接找个代理商就可以拿到联想总部一样的电脑了)
Proxy代理和Target目标实现了相同的接口,也就是说对外有相同的方法,在客户端编程序的时候,不是直接引用目标,也不是直接引用代理,而是用这个Inteface接口来进行引用。
例如:Collectoin也是一个接口,可以用它直接引用HashSet也可以直接用它引用ArrayList
它可以不用改源代码就可以直接进行切换
Proxy代理的方法干什么事情呢?
一定要调用目标的对应的方法,但在这个目标方法前面或者后面,会添加一些系统功能代码
Ø 如果采用工厂模式和配置文件的方式进行管理,则不需要修改客户端程序,在配置文件中配置是使用目标类、还是代理类,这样以后很容易切换,譬如,想要日志功能时就配置代理类,否则配置目标类,这样,增加系统功能很容易,以后运行一段时间后,又想去掉系统功能也很容易。
如果使用配置文件管理,在这里可以配置使用目标类还是代理类,这样就很容易实现切换
如果我的程序现在在调试阶段,没有真正的上线,我在家里运行,监控我的每个方法执行的时间,我是不是通过代理监控每个方法到底执行了多长时间啊,当我监控了两个月以后,发现我的性能都很高,我就直接卖给用户了。交给用户是不是就不需要每个方法监控运行时间了,因为监视每个方法的运行时间是不是要额外的开销呢,那就不要了。不要了怎么办呢?那就去掉那个系统监视功能,在配置文件里面一改,改成那个直接用目标类就可以了。
AOP Aspect oriented program ,简称AOP面向方面编程
《面向方面的编程》AOP要做的就是将 交叉业务 模块化。就是将切入到每个对象里面的代码,只写一份,不是在每一个地方都去写。那么我们就需要使用代理的方式去写。
我们不可能去深入到目标Target的内部去修改,但是我们可以让用户感觉到我就是在方法前加的代码,或者是在方法后面加的代码。怎么办呢?我把这个方法移到方法的外面,或者方法之前或之后,从运行的效果来看是一样的。
例如:在进入你的方法之前,就让用户看到“哈哈”,或者在你的方法结束之后,让用户看到“哈哈”
只要是面向方面的编程,就要用到代理。
动态代理技术
l 要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!写成百上千个代理类,是不是太累!
l
l JVM可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类,即动态代理类。
l ――――――――――――――――――――――――――――――――――――
l JVM生成的动态类必须实现一个或多个接口,(你看,你委托我这个Java虚拟机生成一个类,那我肯定要问你,你的类中有什么方法啊?你总不能告诉我这个类有这个那个方法吧。不好。。。你还不如告诉我,你要实现这个接口,通过这个接口我立马就能知道这个接口里面的所有方法。所以你只要告诉我接口,我就能知道我生成的类里面都有哪些方法了。)
l 所以,JVM生成的动态类只能用作具有相同接口的目标类的代理。(如果我们有一个目标类,这个目标类本身没有实现接口,那么我生成的代理类的方法要不要跟这个目标类的方法一样。 要。但是这个目标实又没有实现的接口,那么我要通过什么方式来告诉Java虚拟机,哎呀,你生成的类跟那个类有相同的方法。Java虚拟机干不了这个事,因为这个目标类没有接口,你无法跟Java虚拟机说这个目标的方法。这时候就有了一个第三方库:CGLIB
l CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。(CGLIB库不是SUN公司的东西,它是一个开源东西,也许在新JDK里面,它就成了SUM公司的一部分了。
l 代理类的各个方法中通常除了要调用目标的相应方法和对外返回目标返回的结果外,还可以在代理方法中的如下四个位置加上系统功能代码:
Ø 1.在调用目标方法之前
Ø 2.在调用目标方法之后
Ø 3.在调用目标方法前后
Ø 4.在处理目标方法异常的catch块中
需要写一个示意代码进行辅助说明,例如
Class proxy{
void sayHello(){
……….
try{
target.sayHello();
}catch(Exception e){
………..
}
………….
}
}
分析JVM动态生成的类
l 创建实现了Collection接口的动态类和查看其名称,分析Proxy.getProxyClass方法的各个参数。(getProxyClass返回的是一个Class类的字节码,每个Class类都可以通过getClassLoader得到自己的类加载器〈就好比每个人都有一个妈妈〉,你还要告诉它这个类的字节码实现了哪个接口Interface)
getProxyClass(ClassLoader loader, Class<?> ...interface
类加载器, 实现的接口
Proxy.getProxyClass返回的是一个Class类.
通过Proxy.getProxyClass(Loader类加载器,Interface类的接口)
l 编码列出动态类中的所有构造方法和参数签名
l 编码列出动态类中的所有方法和参数签名
l 创建动态类的实例对象
Ø 用反射获得构造方法
Ø 编写一个最简单的InvocationHandler类
Ø 调用构造方法创建动态类的实例对象,并将编写的InvocationHandler类的实例对象传进去
Ø 打印创建的对象和调用对象的没有返回值的方法和getClass方法,演示调用其他有返回值的方法报告了异常。
Ø 将创建动态类的实例对象的代理改成匿名内部类的形式编写,锻炼大家习惯匿名内部类。
l 总结思考:让jvm创建动态类及其实例对象,需要给它提供哪些信息?
Ø 三个方面:
• 生成的类中有哪些方法,通过让其实现哪些接口的方式进行告知;
• 产生的类字节码必须有个一个关联的类加载器对象;
• 生成的类中的方法的代码是怎样的,也得由我们提供。把我们的代码写在一个约定好了接口对象的方法中,把对象传给它,它调用我的方法,即相当于插入了我的代码。提供执行代码的对象就是那个InvocationHandler对象,它是在创建动态类的实例对象的构造方法时传递进去的。在上面的InvocationHandler对象的invoke方法中加一点代码,就可以看到这些代码被调用运行了。
l 用Proxy.newInstance方法直接一步就创建出代理对象。
代码块如下
创建代理类
/*
* Proxy:代理类
* Proxy.getProxyClass返回的是一个Class类.
* 通过Proxy.getProxyClass(Loader类加载器, Interface类的接口)
* 它是通过一个指定的接口来获取这个类所具备的方法的。
* getProxyClass(类加载器Loader,接口.class Interface)
* 接收两个参数,一个是后边参数的字节码的加载器,一个是所要实现代理的接口的字节码
*/
public class DaiLiProxyTest {
public static void main(String[] args) throws Exception{
// TODO Auto-generated method stub
Class clazzProxy1 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
System.out.println(clazzProxy1.getName());//通过类的字节码,可以得到它的类名
System.out.println("-----------获取这个类身上所有的Constructor构造方法--------------");
Constructor[] constructors = clazzProxy1.getConstructors();
for(Constructor constructor : constructors){
String name = constructor.getName();//获取构造方法名字
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParamets = constructor.getParameterTypes();//获取所有参数
for(Class clazzParamet : clazzParamets){
sBuilder.append(clazzParamet.getName()).append(',');//追加到sBuilder后
}
if(clazzParamets!=null && clazzParamets.length!=0)//判断长度及是否为空
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder);
}
System.out.println("---------获取这个类身上所有的Method方法-----------");
Method[] methods = clazzProxy1.getMethods();
for(Method method : methods){
String name = method.getName();
StringBuilder sBuilder = new StringBuilder(name);
sBuilder.append('(');
Class[] clazzParamets = method.getParameterTypes();
for(Class clazzParamet : clazzParamets){
sBuilder.append(clazzParamet.getName()).append(',');
}
if(clazzParamets.length!=0 && clazzParamets!=null)
sBuilder.deleteCharAt(sBuilder.length()-1);
sBuilder.append(')');
System.out.println(sBuilder);
}
System.out.println("---------方式一:创建实例对象newInstance of -----------");
//1.通过代理类,指定它所要实现的接口,并得到接口的类加载器
Class clazzProxy2 = Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class);
//Object obj = clazzProxy2.newInstance();//调用无参构造方法,没有啊
//2.有了代理类,就可以得到 有参的构造方法,并指定参数的类型InvocationHandler
Constructor constructor = clazzProxy2.getConstructor(InvocationHandler.class);
//3.对指定类型实例化:InvocationHandler是一个接口,不可以new对象,进行实例化,那我们就自己做个实现类
class MyInvocationHandler1 implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {return null;}
}
Collection proxy1 = (Collection) constructor.newInstance(new MyInvocationHandler1());
System.out.println(proxy1);
proxy1.clear();
//proxy1.size();//空指针异常,不可以调用有返回值的方法,因为它要去调用内部的invoke返回null,而size返回整数。
System.out.println("---------方式二:匿名内部类方式 创建实例对象newInstance of -----------");
Collection proxy2 = (Collection)constructor.newInstance(new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
return null;
}
});
System.out.println("---------方式三:创建动态代理类及其实例对象合并newInstance of -----------");
//Proxy.newProxyInstance(Loader,new Class[]{},handler):可以将字节码和实例对象一块搞出来
/*Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),//类加载器
new Class[]{Collection.class}, //注意:这里的接口有可能有多个,所以定义成数组
new InvocationHandler(){ //将InvocationHandler匿名实例化
//注意:客户端在调用ObjProxy.add方法的时候涉及三要素:要操作的哪个对象,操作对象的哪个方法,然后调用这个方法时传递的什么参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
ArrayList target = new ArrayList();//创建目标对象
Long startTime = System.currentTimeMillis();
Object reVal = method.invoke(target, args);//这里通过Method.invoke方法,里面接收(代理的哪个目标,目标的什么参数)
Long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"rum Time of "+(endTime - startTime));
return reVal;
}
});*/
System.out.println("---------方式三:创建动态代理类及其实例对象合并newInstance of 加入目标Target-----------");
/* 目标Target要定义在哪呢?
* 首先,代理类肯定要代理目标Target中的信息。那就是代理中包含目标,所以要将目标创建到InvocationHandler中
*/
//-----------------------------------------非重构代理------------------------------------------------------
/*Collection proxy3 = (Collection)Proxy.newProxyInstance(
Collection.class.getClassLoader(),//类加载器
new Class[]{Collection.class}, //注意:这里的接口有可能有多个,所以定义成数组
new InvocationHandler(){ //将InvocationHandler匿名实例化
ArrayList target = new ArrayList();//创建目标对象
//注意:客户端在调用ObjProxy.add方法的时候涉及三要素:要操作的哪个对象,操作对象的哪个方法,然后调用这个方法时传递的什么参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Long startTime = System.currentTimeMillis();
Object reVal = method.invoke(target, args);//这里通过Method.invoke方法,里面接收(代理的哪个目标,目标的什么参数)
Long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"rum Time of "+(endTime - startTime));
return reVal;
}
});*/
//-----------------------------------------代理重构后------------------------------------------------------
final ArrayList target = new ArrayList();//创建目标对象
Collection proxy3 = (Collection) getProxy(target,new MyAdvice());//将实例化的MyAdvice类传进来
/*为什么打印的集合长度为0?
//因为它每调用一次add方法,都会去调用InvocationHandler对象的Invoke方法.
//每调用一次都会执行一次全新目标ArrayList target = new ArrayList();
//每个目标都是各自立,相互之间没有关联关系。所以打印的效果是0;
//如果将ArrayList集合,放到动态代理类上面,就会打印出size集合的长度*/
proxy3.add("zxx");
proxy3.add("lhm");
proxy3.add("bxd");
System.out.println(proxy3.size());
System.out.println(proxy3.getClass().getName());
/*
* 注意:只有HashCode,toString,equals这三个方法才会委托给InvocationHandler,其它的都不委托
*/
}
//------------------------------------重构成方法代码块-----------------------------------------------------------
private static Object getProxy(final Object target, final Advice advice) {
Object proxy3 = Proxy.newProxyInstance(
target.getClass().getClassLoader(), //通过目标类获取加载器
target.getClass().getInterfaces(), //根据目标对象,获取相应接口
new InvocationHandler(){ //将InvocationHandler匿名实例化
//注意:客户端在调用ObjProxy.add方法的时候涉及三要素:要操作的哪个对象,操作对象的哪个方法,然后调用这个方法时传递的什么参数
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*Long startTime = System.currentTimeMillis();
Object reVal = method.invoke(target, args);//这里通过Method.invoke方法,里面接收(代理的哪个目标,目标的什么参数)
Long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"rum Time of "+(endTime - startTime));
return reVal;*/
//通过接口,创建系统功能
advice.beforMethod(method);
Object reVal = method.invoke(target, args);
advice.afterMethod(method);
return reVal;
}
});
return proxy3;
}
}
创建Advice进行时间计算
public class MyAdvice implements Advice {
Long startTime; //因为有两个方法用到了startTime(下面相减的输出方法调用中)
@Override
public void afterMethod(Method method) {
System.out.println("从传智播客毕业了");
Long endTime = System.currentTimeMillis();
System.out.println(method.getName()+"rum Time of "+(endTime - startTime));
}
@Override
public void beforMethod(Method method) {
System.out.println("到传智播客学习了");
startTime = System.currentTimeMillis();
}
}
创建接口Advice
import java.lang.reflect.Method;
public interface AdviceTwo {
void befor(Method method);
void after(Method method);
}