java代理模式在java程序设计中非常常见,比如说在spring框架中,使用的aop功能,就是动态代理的方式加入我们的切面功能,从而不会侵入到原始代码中而带来增强功能的效果,在java代理模式中,我们又将代理模式分为静态代理和动态代理。
静态代理
静态代理,就是继承相同的接口,然后把需要被代理的类当作参数传入到我们的静态代理内部当作成员变量,然后静态代理的方法内部就调用被代理类的方法,在该方法内部也可以加入我们的增强功能(比如:打印日志,在被代理方法的前后各加上aop函数等等)。
如下代码示例:
//购物接口
public interface shop{
public String[] SeaAmoy(); //海淘动作
}
//购物实现类
public shopImp implement shop{
public String[] SeaAmoy(){
String[] goods={"包包","口红","手机"};
System.out.print("买了三个物品"+goods);
return goods;
}
}
//购物代理类
public proxyshopImp implement shop{
shop isProxyshop;
aop a;
proxyshopImp(shop s){
isProxyshop= s;
a=new aop();
}
public String[] SeaAmoy(){
a.costbefore();
String[] goods=isProxyshop.SeaAmoy();
a.costafter();
Log.e("这次购物花费了XXX元!");
return goods;
}
}
//aop类
public class aop{
public void costbefore(){
System.out.print("代理拿点回扣"+cost);
}
public void costafter(){
System.out.print("代理偷换了一个劣质包包!");
}
}
通过上面的例子可以看出来,我们用购物实现类也可以实现我们的购物,但是我们不仅仅需要购物这个功能,可能还需要一些附带功能,那么我们就可以使用代理模式完成这个功能。如果我们需要代理的类有很多怎么办,总不可能每一个类写一个代理类,那不要累死吗?这个时候我们可以使用动态代理来完成代理类的功能。
动态代理
为了更优雅地实现代理模式,JDK提供了动态代理方式,可以简单理解为JVM可以在运行时帮我们动态生成一系列的代理类,这样我们就不需要手写每一个静态的代理类了。依然以购物为例,
用动态代理代码实现如下:
动态代理
class shopInvocatioHandler implements InvocationHandler {
private Object target;
private aop aopImp;
public shopInvocatioHandler(Object target) {
this.target = target;
aopImp-new aop();
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
aopImp.costbefore();
Object result = method.invoke(target, args);
aopImp.costafter();
return result;
}
// 生成代理对象
public Object getProxy() {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
Class<?>[] interfaces = target.getClass().getInterfaces();
return Proxy.newProxyInstance(loader, interfaces, this);
}
}
//代理类的使用
public class ProxyTest {
public static void main(String[] args) {
shopImp sImp = new shopImp();
shopInvocatioHandler handler = new shopInvocatioHandler (sImp);
shop s = (shop)handler.getProxy();
s.SeaAmoy();
}
}
动态代理的方式有个静态代理没有的好处就是,动态代理的方式可以访问任何域和方法,因为动态代理的原理就是通过反射实现的,可以修改访问权限;静态代理就没有这样的功能了。
我们通过反编译工具可以看到动态代理类会自动生成一个代理class,放在内存中,但是不会编程出claa文件,通过流将class字节码保存在文件中,字节码如下:
public final class $proxy1 extends Proxy implements shop{
public $proxy1(InvocationHandler invocationhandler) {
super(invocationhandler);
}
public final boolean equals(Object obj) {
try {
return ((Boolean)super.h.invoke(this, m1, new Object[] {
obj
})).booleanValue();
}
catch(Error _ex) { }
catch(Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, m2, null);
}
catch(Error _ex) { }
catch(Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final void SeaAmoy() {
try {
super.h.invoke(this, m3, null);
return;
}
catch(Error _ex) { }
catch(Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
public final int hashCode() {
try {
return ((Integer)super.h.invoke(this, m0, null)).intValue();
}
catch(Error _ex) { }
catch(Throwable throwable) {
throw new UndeclaredThrowableException(throwable);
}
}
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
Class.forName("java.lang.Object")
});
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("com.shop").getMethod("SeaAmoy", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
}
catch(NoSuchMethodException nosuchmethodexception) {
throw new NoSuchMethodError(nosuchmethodexception.getMessage());
}
catch(ClassNotFoundException classnotfoundexception) {
throw new NoClassDefFoundError(classnotfoundexception.getMessage());
}
}
}
通过上面代码可以清楚的发现:
- 就是jvm自动生成一个
$proxy1
类继承Proxy 并且实现了接口shop,而且这个$proxy1
类内部根据接口shop反射出所有的方法,和类Object的所有方法。 - super.h.invoke(this, m3, null);方法其实就是调用父类proxy的h变量(对应这么我们的shopInvocatioHandler类的实例)的invoke方法,shopInvocatioHandler中的invoke(Object proxy, Method method, Object[] args)函数中的proxy参数就是
$proxy1
(this)本身这个实例,method就是调用的方法,args方法参数。 - 由此可见动态代理的原理就是通过反射实现全部的接口方法,这些实现的方法内部就调用我们自己实现的shopInvocatioHandler类的involve方法,这个involve方法正好实现了代理功能,根据参数就可以找到对应的被代理类的方法,也就实现了对应的方法调用。
代理对象的生成过程由Proxy类的newProxyInstance方法实现,分为3个步骤:
1、ProxyGenerator.generateProxyClass方法负责生成代理类的字节码,生成逻辑比较复杂,有兴趣的同学可以继续分析源码 sun.misc.ProxyGenerator;
/**
* proxyName:格式如 "com.sun.proxy.$Proxy.1";
* interfaces:代理类需要实现的接口数组;
* accessFlags:代理类的访问标识;
* */
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces, accessFlags);
2、native方法Proxy.defineClass0负责字节码加载的实现,并返回对应的Class对象。
Class clazz = defineClass0(loader, proxyName, proxyClassFile, 0, proxyClassFile.length);
3、利用clazz.newInstance反射机制通过第二步的clazz类生成代理类的对象;
如图所示:最后$Proxy1
动态代理类包含了shopInvocationHandler
对象作为成员变量,$proxy1
自身实现了shop接口,实现的逻辑就是通过反射将method参数传递给shopInvocationHandler
involve方法,这个involve方法内部书写的逻辑是程序员实现shopInvocationHandler
类的具体逻辑,先调用AopFunc切面功能,在使用反射调用shopimp对象的真正的实现的方法。那么这样就实现了动态代理的效果了。
注:
为了更清楚的理解动态代理,通过以下方式把代理类字节码生成class文件。
byte[] classFile = ProxyGenerator.generateProxyClass("com.sun.proxy.$Proxy.1", service.getClass().getInterfaces());
FileOutputStream out = new FileOutputStream("com.sun.proxy.$Proxy.1.class");
out.write(classFile);
out.flush();
在java中动态代理只能代理接口对象,而不能代理继承关系的对象,如果需要使用代理模式只能采用静态代理方法复写需要代理的类;据说CGLib可以实现继承关系的代理。
了解了动态代理的知识,在加上以前看的android Framework的源码,就可以继续学习安卓Hook的知识了。下篇文章见。