一 、静态代理
静态代理实际上就是设计模式中的代理模式。
在设计模式中,如上图所示代理模式就是调用实现A接口func方法的B类重写的func方法,由第三个实现A接口func方法的代理C类调用完成。
//需要代理的方法的接口
interface A{ function func() }
//实际执行
class B implement A{
@Override
function func(){}
}
//代理类
class C implement A{
A a;//内置一个实际执行func方法的对象
C(A a){this.a=a; }
@Override
function func(){
a.func();
}
}
调用:
A proxy =new C(new B())
proxy.func()//实际输出的是B的a方法
也就是说调用b的方法是通过c代理调用的。
例如:小刚在寝室睡觉,但是课堂上老师要点名,小刚就让小明替他上课答到,这时答到这个动作就是接口的方法,小明是小刚的代理类,代替小刚答到。这个是静态代理的实现方式。
新建一个Person接口,它有一个去上课的动作。
public interface Person {
void gotoClass();
}
再建一个Student接口,实现了Person的gotoClass方法。正常的去上课的学生,遇到老师点名,都会答到,这时候重写gotoClass方法。
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
@Override
public void gotoClass() {
System.out.println(name + ":到,我在!");
}
}
但是,小刚并没有去教室,而是让小明代替他去上课,小明就是一个代理类的对象。
/**
* 学生代理类,同样也实现了上课的动作
* 需要内置一个{@link Person}对象,这个对象是真正上课的{@link Student},代理只是做了代替的工作
*/
public class StudentProxy implements Person {
public StudentProxy(Person person) {
this.person = person;
}
private Person person;
@Override
public void gotoClass() {
beforeMethod();
person.gotoClass();
}
//执行代理上课前的准备工作,比如心里活动
public void beforeMethod() {
System.out.println("心里想:我是代替上课的学生,老师应该发现不了吧?");
}
}
在这里,小明重了gotoClass方法,但是实际上,并不是小明在答到,而是提小刚答到,因此在gotoClass里,应该实现的是实际执行对象的gotoClass()。
执行一下:
public static void main(String[] args) {
Student xiaoGang=new Student("小刚");
StudentProxy xiaoming =new StudentProxy(xiaoGang);
xiaoming.gotoClass();
}
输出结果:
心里想:我是代替上课的学生,老师应该发现不了吧?
小刚:到,我在!
可以看到,小明执行了gotoClass,但是实际上输出的是小明的心理活动,以及小刚答到,而小刚的答到动作是小明代理执行的。
这个模式就是代理模式,也就是静态代理。
二、 动态代理
如果一个工程里需要很多的代理,就需要创建不同的代理类,这样就变得很麻烦。
例如,这时又有一个程序猿的类,他的主要工作是写代码,这时候,就需要一个Developer类和IDeveloper接口。IDeveloper有一个writeCode的方法,Developer实现了IDeveloper接口。
//写代码接口
public interface IDeveloper {
void writeCode();
}
//程序员类
public class Developer implements IDeveloper {
private String name;
public Developer(String name) {
this.name = name;
}
@Override
public void writeCode() {
System.out.println("Developer " + name + " writes codes.");
}
}
如果程序员小王请假了,需要小张代理写代码。这事,就有需要创建一个写代码的代理类。
public class DeveloperProxy implements IDeveloper {
private IDeveloper developer;
public DeveloperProxy(IDeveloper developer) {
this.developer = developer;
}
@Override
public void writeCode() {
before();
this.developer.writeCode();
}
public void before(){
System.out.println("他请假了。我是代理写代码的人。");
}
}
执行一下:
public static void main(String[] args) {
Developer xiaozhang = newDeveloper("小张");
IDeveloper xiaowang = new DeveloperProxy(xiaozhang);
xiaowang.writeCode();
}
输出结果:
他请假了。我是代理写代码的人。
Developer 小张 writes codes.
在一个工程里这样做就会显得很麻烦,这时候就需要动态代理了。
动态代理的类需要实现InvocationHandler接口,然后去重写invoke方法。在invoke方法中 调用method.invoke(target, args)来实现代理。在这里边,target实际的作用和上面class C中的a对象是一致的,不过a是一个A类型的对象 而target则是T类型的对象,这样就保证了的动态代理可以代理不同类型的接口。
/**
* 动态代理类,需要实现{@link InvocationHandler}接口
* */
public class MyDynamicProxy<T> implements InvocationHandler {
public MyDynamicProxy(T target) {
this.target = target;
}
private T target;
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
return method.invoke(target, args);
}
}
这时,不管是上面的Student代替上课,还是Developer代替写代码,都可以使用这个代理类进行代理。
public static void main(String[] args) {
InvocationHandler xiaomingHandler=new MyDynamicProxy<Person>(new Student("小刚"));
Person xiaohong = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, xiaomingHandler);
xiaohong.gotoClass();
System.out.println();
InvocationHandler developHandler=new MyDynamicProxy<IDeveloper>(new Developer("小张"));
IDeveloper developer = (IDeveloper) Proxy.newProxyInstance(IDeveloper.class.getClassLoader(), new Class<?>[]{IDeveloper.class}, developHandler);
developer.writeCode();
}
输出:
小刚:到,我在!
Developer 小明 writes codes.
三、 动态代理的原理
上面我们利用Proxy类的newProxyInstance方法创建了一个动态代理对象,查看该方法的源码:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException {
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
它利用下面俩段代码封装了创建动态代理类的步骤。
final Class<?>[] intfs = interfaces.clone();
...
final Constructor<?> cons = cl.getConstructor(constructorParams);
其实,我们最应该关注的是下面这行代码:
Class<?> cl = getProxyClass0(loader, intfs);
这行代码产生了代理类,后面代码中的构造器也是通过这里产生的类来获得,可以看出,这个类的产生就是整个动态代理的关键,由于是动态生成的类文件,而这个类文件时缓存在java虚拟机中的。
我们可以通过下面的方法将其打印到文件里面:
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
String path = "xxxxxx/StudyProxy.class";
try(FileOutputStream fos = new FileOutputStream(path)) {
fos.write(classFile);
fos.flush();
System.out.println("代理类class文件写入成功");
} catch (Exception e) {
System.out.println("写文件错误");
}
对这个class文件进行反编译,我们看看jdk为我们生成了什么样的内容:
public final class $Proxy0 extends Proxy implements Person {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*注意这里是生成代理类的构造方法,方法参数为InvocationHandler类型
*代理对象调用方法都是执行InvocationHandler中的invoke方法,而InvocationHandler又持有一个被代理对象的实例。
*
*super(paramInvocationHandler),是调用父类Proxy的构造方法。
*父类持有:protected InvocationHandler h;
*Proxy构造方法:
* protected Proxy(InvocationHandler h) {
* Objects.requireNonNull(h);
* this.h = h;
* }
*
*/
public $Proxy0(InvocationHandler paramInvocationHandler) throws {
super(paramInvocationHandler);
}
/**
*
*这里调用代理对象的giveMoney方法,直接就调用了InvocationHandler中的invoke方法,并把m3传了进去。
*this.h.invoke(this, m3, null);这里简单明了。
*代理对象持有一个InvocationHandler对象,InvocationHandler对象持有一个被代理的对象,
*/
public final void giveMoney() throws {
try {
this.h.invoke(this, m3, null);
return;
} catch (Error|RuntimeException localError) {
throw localError;
} catch (Throwable localThrowable){
throw new UndeclaredThrowableException(localThrowable);
}
}
......
static {
try {
//看看这儿静态块儿里面有什么,是不是找到了giveMoney方法。请记住giveMoney通过反射得到的名字m3,其他的先不管
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("proxy.Person").getMethod("giveMoney", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
} catch (NoSuchMethodException localNoSuchMethodException) {
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
} catch (ClassNotFoundException localClassNotFoundException) {
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
}
可以看到jdk为我们的生成了一个叫$Proxy0(这个名字后面的0是编号,有多个代理类会一次递增)的代理类,这个类文件时放在内存中的,我们在创建代理对象时,就是通过反射获得这个类的构造方法,然后创建的代理实例。通过对这个生成的代理类源码可以很清楚的看到动态代理实现的具体过程。
实际上InvocationHandler就是一个持有被代理对象的中介类,而在invoke方法中调用了被代理对象的相应方法。通过聚合方式持有被代理对象的引用,把外部对invoke的调用最终都转为对被代理对象的调用。
代理类调用自己方法时,通过自身持有的中介类对象来调用中介类对象的invoke方法,从而达到代理执行被代理对象的方法。也就是说,动态代理通过中介类实现了具体的代理功能。