动态代理
考虑到静态代理的缺点,如果系统能自动生成代理类,我们只需要考虑相关代理功能的实现,是最好的。实现动态代理有两种方式:
- 所有的目标类都有一个共同的父类(接口),这样代理对象只需要实现接口,通过多态的形式,内部持有目标对象,这样就可以通过代理对象对外暴露相关方法了。这就是JDK动态代理的思想,缺陷是目标对象必须有个父类接口。
- 不计较目标对象,只要给目标对象,创建一个对象继承目标对象,针对目标对象中需要业务代理的方法,进行代理业务,实际功能是调用父类(目标对象)的方。这就是cglib。没有缺点,什么目标对象都可以。
java文件和class文件
正常的静态代理,需要手动写个java文件(新建代理类),动态代理的作用就是省去java文件,单并不是说系统会自动生成java文件,而是会生成java对应的class文件,或者说生成一个Class对象。具体可以看看class文件的加载过程。
JDK动态代理
上述讨论的动态代理的形式,提供一系列的接口,可以生成相关接口的代理对象。jdk提供了一个Proxy类,查看Proxy的方法:
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
根据指定的接口,生成相关的代理对象的Class对象,我们先看看到底生成的是什么样的代理对象。
public class DynProxyTest {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class<?> proxyClass = Proxy.getProxyClass(DynProxyTest.class.getClassLoader(), new Class[]{Singer.class});
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
printClassInfo(CaiQin.class);
printClassInfo(Singer.class);
printClassInfo(proxyClass);
Constructor<?> constructor = proxyClass.getConstructor(InvocationHandler.class);
Singer singerProxy =(Singer) constructor.newInstance(new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
CaiQin caiQin = new CaiQin();
System.out.println("proxy");
System.out.println(proxy.getClass().getName());
System.out.println(method.getName());
Object result = method.invoke(caiQin,args);
return result;
}
});
singerProxy.sing("你好刀郎");
}
private static void printClassInfo(Class cla){
System.out.println("className: "+cla.getName());
System.out.println("构造函数:");
for (Constructor constructor : cla.getConstructors()) {
StringBuilder sb = new StringBuilder();
for (Class param : constructor.getParameterTypes()) {
sb.append(","+param.getTypeName());
}
if(sb.length() == 0){
System.out.println(constructor.getName()+"()");
} else{
System.out.println(constructor.getName()+"("+sb.substring(1)+")");
}
}
System.out.println("方法:");
for (Method method : cla.getMethods()) {
StringBuilder sb = new StringBuilder();
for (Class param : method.getParameterTypes()) {
sb.append(","+param.getTypeName());
}
if(sb.length() == 0){
System.out.println(method.getName()+"()");
} else{
System.out.println(method.getName()+"("+sb.substring(1)+")");
}
}
System.out.println("属性:");
for (Field field : cla.getDeclaredFields()) {
System.out.println(field.getName()+"="+field.getDeclaringClass().getName());
}
System.out.println();
System.out.println();
System.out.println();
}
}
输出内容:
className: com.jkf.java.proxy.dyn.CaiQin
构造函数:
com.jkf.java.proxy.dyn.CaiQin()
方法:
sing(java.lang.String)
wait()
wait(long,int)
wait(long)
equals(java.lang.Object)
toString()
hashCode()
getClass()
notify()
notifyAll()
属性:
className: com.jkf.java.proxy.sta.Singer
构造函数:
方法:
sing(java.lang.String)
属性:
className: com.sun.proxy.$Proxy0
构造函数:
com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)
方法:
equals(java.lang.Object)
toString()
hashCode()
sing(java.lang.String)
isProxyClass(java.lang.Class)
getInvocationHandler(java.lang.Object)
newProxyInstance(java.lang.ClassLoader,java.lang.Class[],java.lang.reflect.InvocationHandler)
getProxyClass(java.lang.ClassLoader,java.lang.Class[])
wait()
wait(long,int)
wait(long)
getClass()
notify()
notifyAll()
属性:
m1=com.sun.proxy.$Proxy0
m3=com.sun.proxy.$Proxy0
m2=com.sun.proxy.$Proxy0
m0=com.sun.proxy.$Proxy0
经过比对Proxy的Class对象,在接口的基础上,补充了一些公共方法equals、hashCode、wait、notify等方法,最终重要的提供了:构造函数(入参InvocationHandler),这样通过Class对象我们就可以直接生成代理对象了。这就是JDK动态代理的功能,父接口不能直接创建对象,但是JDK动态代理提供了带有构造函数的Class对象,解决了接口不能创建对象的情况。剩下就是通过Class反射创建对象。
InvocationHandler
JDK动态代理生成的Class对象,有个入参为InvocationHandler的构造函数,先看看InvocationHandler。
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
InvocationHandler是接口,有且只有一个方法:invoke,这样我们需要匿名类实现该接口。通过注解我们了解到invoke方法的入参:
proxy:代理对象,注意这是代理对象,不是目标对象。
method:父类接口的方法对象。
args:代理对象方法的入参。
了解反射的相关应用,我们可以通过method.invoke(Object obj,Object[] args),执行方法,通过比较,发现method.invoke和InvocationHanlder的invoke是类似的。其实从InvocationHandler的命名就可以发现,方法调用时,就会执行这个handler。
Proxy创建代理对象
刚才通过Proxy获取代理对象的Class文件,再手动创建对象比较繁琐,Proxy提供了一种快速的创建方法:
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
可以直接创建代理对象。
参数说明:
- ClassLoader主要用来声明父接口的加载来源,正常情况,我们可以使用:当前类的Class对象的classLoader,例如:XXX.class.getClassLoader()。
- interfaces:父接口。
- InvocationHandler:方法调用的处理对象。
创建一个代理对象,看看执行的流程。
public class DynProxyJdkTest {
public static void main(String[] args) {
Singer singer = (Singer) Proxy.newProxyInstance(DynProxyJdkTest.class.getClassLoader(),new Class[]{Singer.class},new InvocationHandler(){
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println(proxy.getClass());
System.out.println(method.getName()+"==="+method.getDeclaringClass().getName());
return null;
}
});
singer.sing("你好啊");
}
}
输出内容:
class com.sun.proxy.$Proxy0
sing===com.jkf.java.proxy.sta.Singe
根据输出内容,的确代理对象执行了,定义的InvocationHandler,但是尴尬的是,这个怎么执行目标对象的方法呢?考虑到InvocationHandler中,提供了Method对象,根据反射+目标实例,就可以执行目标方法了,因此可以对InvocationHandler进行改进。
public class DynProxyJdkTest {
public static void main(String[] args) {
Singer singer = (Singer) Proxy.newProxyInstance(DynProxyJdkTest.class.getClassLoader(),
new Class[]{Singer.class},
new SingerInvocationHandler(new DaoLang()));
singer.sing("你好啊");
}
}
class SingerInvocationHandler implements InvocationHandler {
private Singer singer;
public SingerInvocationHandler(Singer singer) {
this.singer = singer;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("收到演出邀约");
System.out.println();
System.out.println();
Object ret = method.invoke(singer, args);
System.out.println();
System.out.println();
System.out.println("收到尾款");
return ret;
}
}
输出内容:
收到演出邀约
大家好,我是:刀郎......
开始演唱:你好啊
结束演唱:你好啊
收到尾款
这就是常用的JDK动态代理的样例,自定义InvocationHandler,然后通过多态持有目标对象。
Proxy0.class
我们可以直接通过反编译工具查看JDK动态代理生成的class文件。
- 设置本地生成class文件。
//跟踪Proxy的源码,你会发现有一个判断saveGeneratedFiles
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
2 查看本地class文件
如下图:
在target的同级中,你会发现一个com文件,点进去,就会发现
P
r
o
x
y
0.
c
l
a
s
s
文
件
,
这
就
是
P
r
o
x
y
生
成
的
c
l
a
s
s
文
件
。
这
也
是
刚
才
输
出
的
代
理
类
名
称
:
c
o
m
.
s
u
n
.
p
r
o
x
y
.
Proxy0.class文件,这就是Proxy生成的class文件。这也是刚才输出的代理类名称:com.sun.proxy.
Proxy0.class文件,这就是Proxy生成的class文件。这也是刚才输出的代理类名称:com.sun.proxy.Proxy0
-
文件分析
通过反编译工具,查看class文件。$Proxy0.class是继承自Proxy,同时实现了父接口。而且其中的equals、toString、hashCode方法都是Object类的方法,但是实际执行的时候,并没有调用Object的实例,而是调用InvocationHandler的invoke方法,而Handler中的invoke,转手就调用了目标对象的相关方法,就是说方法的调用Proxy、代理对象谁都没有实现具体功能,但是Proxy把处理的时机对我们暴露了,我们把需要的操作添加在了目标对象执行的前面或者后面。从这可以看出Proxy实际上什么业务逻辑都没有做,只是提供了一个接口的子类,并且给子类添加了构造方法。 -
更多的接口
(1) 大家可以通过Proxy.newInstance提供更多的接口,看看有什么区别没。
(2) 顺便看看在目标对象中,调用另外一个代理方法,是不是会触发两次代理效果,答案是:不会的,这是使用动态代理需要注意的地方。
JDK动态代理的缺点
- 共同接口:所有的目标对象,都必须要实现共同的接口,才能针对上述接口进行动态代理。
- 如果想对已有代码进行动态代理,但是发现共同接口中,只想代理某一个/几个功能,那么InvocationHandler里面就需要对方法进行特殊判断后,再进行代理。