动态代理
前言
静态代理只能代理指定接口的实现类。即一个类只要实现了接口,就可以用一个实现了同一接口的代理类来代理它。那么问题来了,如果我有两个类,是分别实现了不同的接口,其内部方法各不相同,又都想有代理,且代理内容一样,怎么办呢?生成两个代理类?
比如,我们有一个订单管理类,一个商品管理类。订单管理类需要新增订单、更改订单状态、查询订单信息。商品管理类需要上架商品,下架商品,更新库存,查询商品信息。以上各业务每接受一次请求都需要写入日志。
我们学过静态代理模式,可以很快确定,写日志这件事,交给代理类去做,订单管理类和商品管理类专注核心功能即可。但,这就需要建两个代理类?这两个代理类干的边边角角的活还一样?明显不优雅了吧。
于是,动态代理他来了。
JDK提供了一种动态代理,只要被代理的类是实现了接口的类,就能被代理。注意:是只要实现了接口就可,不是要实现同一个接口才可。区别很大,思考一下就感觉到天地宽广了许多。
但有些类,它就是没有实现接口,又还是想要有代理类帮它处理边边角角怎么办呢?不慌,我们有CGLib动态代理。它不是java自带的,而是由第三方提供的优秀类库。CGLib动态代理的被代理类不需要实现接口,只要是能被继承类,都能被代理。(注:即被final修饰的类不能实现CGLib动态代理)
这篇博文,我们只讲JDK动态代理,CGLib容后。
JDK动态代理
jdk动态代理,存在于java自带的核心内库,不需要引入jar包依赖什么的。
现在我们来回忆一下静态代理,代理类与被代理类实现了相同的接口,它们具有相同的方法进行代理和被代理。总结:我们需要代理类与被代理类的方法相同。
那么可否有一个方法,传出与被代理对象继承了同一个接口的代理对象。这样,我们就可以得到一个与被代理对象有同样的方法的代理对象了。
当然,方法得写在内中,那就建一个生产动态代理对象的类JdkDynamicProxy
1. 被代理的对象还是设为类属性,通过构造方法传入。因为被代理类的类型不确定,设为Object类型。(注:别忘了所有实现了接口的类都可以做为被代理类,所以我们用*父类来接。)
2. 方法getProxy()返回与被代理类实现了同一个接口的对象做为代理对象。同样,因为被代理类的类型不确定,这个方法的返回值也设为Object.
生产代理对象的类JdkDynamicProxy
public class JdkDynamicProxy {
//被代理对象
private Object obj;
//一个参数的构造方法,传入被代理对象
public JdkDynamicProxy(Object obj){
this.obj=obj;
}
/**
*
* @return 代理类对象。它与被代理对象实现同样的接口
*/
Object getProxy(){
return 返回一个与 obj实现了相同接口的对象,做为代理类;
}
来个客户端测试
public class JdkDynamicProxyTest { public static void main(String[] args) { JdkDynamicProxy jdkDynamicProxy=new JdkDynamicProxy(new ProductDaoImpl()); IGeneralDao proxy =(IGeneralDao) jdkDynamicProxy.getProxy(); proxy.delete(); } }
|
好象架子搭起来了?
只是目前还有两个问题没解决
1.怎么造一个实现了被代理对象的接口的类,并生成一个代理对象出来。
2.proxy.delete()调用的方法在哪里?怎么写代码?
先来解决第一个问题:怎么造一个实现了被代理对象的接口的类,并生成一个代理对象出来。
此时 Proxy类闪亮登场。Proxy类在java.lang.reflect包中,使用它的静态方法newProxyInstance()就可以得到我们想要的代理对象。
Proxy.newProxyInstance方法
此方法需要传入三个参数,返回值是Object.很明显,返回的Object就是我们需要的代理对象。
参数列表:
1. ClassLoader loader:被代理对象的类加载器,用于定义代理类
得到类加载器的代码实现:
ClassLoader classLoader=obj.getClass().getClassLoader();
2. Class<?>[] interfaces:被代理对象实现的接口列表,代理类统统都要实现。(友情提醒,java类是可以实现多个接口的,所以这里是个数组)
得到接口列表代码实现:
Class<?>[] interfaces=obj.getClass().getInterfaces();
事情进展到这里,我们已经得到了类加载器,可以造类了。也得到了接口列表,可以造一个把这列表里的接口统统实现了的类,没问题吧。类动态构造好了,类里的方法又怎么动态写呢?比如:delete()这个方法,怎么在代理对象里加上边边角角,在核心被代理类的delete()方法里走一圈,又回到代理对象里加边边角角呢?newProxyInstance方法的第三个参数帮你解决所有疑问。
3. InvocationHandler h:
InvocationHandler是一个接口,那我们就写一个这个接口的实现类,再new它的对象传进去试试先?
这个接口只有一个方法invoke()方法。我们需要一个实现了这个接口的对象做为参数传入,只要实现这一个方法就可以了。不管三七二十一,走一波看看效果。
InvocationHandler的实现类
注:暂时将这个类写成JdkDynamicProxy的内部类
/** * InvocationHandler的实现类 */ class InvocationHandlerImpl implements InvocationHandler{ @Override public Object invoke( Object proxy, Method method, Object[] args) throws Throwable { //注意这句,测试效果看这里 System.out.println("成功了"); return null; } }
|
getProxy()方法
/** * @param obj 被代理的对象 * @return 代理对象,它与被代理对象实现同样的接口 */ public Object getProxy(){ //得到被代理对象的类加载器 ClassLoader classLoader= obj.getClass().getClassLoader(); //得到被代理对象实现的接口列表 Class<?>[] interfaces= obj.getClass().getInterfaces(); Object o = Proxy.newProxyInstance( classLoader, interfaces, new InvocationHandlerImpl()); // return o; }
|
客户端测试运行结果:
显然有运行invoke方法
我们的俄罗斯套娃又进去一层,再来分析invoke()方法吧。
invoke方法
传入参数有三个
1.Object proxy:代理对象
2.Method method:要执行的方法(如delete)
3.Object[] args: 要执行的方法的参数列表(此例中delete方法没有参数,则args为null)
返回值:
Object :执行的方法的返回值 (此例中delete的返回值是void)
这个方法里要怎么干,好像也很明显了?反射,强大的无所不能的反射出现了。我们有对象,有方法,还怕不能调用吗?不可能撒。于是我们把这个方法改成这样试一试呢
@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("进来代理了"); Object result=method.invoke(obj); System.out.println("我写日志了"); return result; }
|
客户端测试运行结果:
代理了成功!
动态代理成功,但还没打完。
我们继续思考一下那个内部类,它的存在好象有些累赘?不想要它,觉得碍眼,不优雅。此时我们有两种解决方案。
1. 用JdkDynamicProxy来实现InvocationHandler接口,重写invoke方法。在调用newProxyInstance方法时传入
this即可。
2. 在调用newProxyInstance方法时,第三个参数直接new一个匿名内部类对象,用这个匿名内部 类实现InvocationHandler接口,并重写invoke方法。
JDK动态代理代码:
生产代理对象的类JdkDynamicProxy(实现接口的方式)
顺手把边边角角也封装到begin 和
last方法中
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDynamicProxy
implements InvocationHandler{
//被代理对象
private Object obj; //一个参数的构造方法,传入被代理对象
public JdkDynamicProxy(Object obj){
this.obj=obj;
}
/**
*
* @return代理类对象。它与被代理对象实现同样的接口
*/
public Object getProxy(){
//得到被代理对象的类加载器
ClassLoader classLoader=
this.obj.getClass().getClassLoader();
//得到被代理对象实现的接口列表
Class<?>[] interfaces=
this.obj.getClass().getInterfaces();
Object o = Proxy.newProxyInstance(
classLoader,
interfaces,
this);
return o;
}
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args) throws Throwable {
begin();
Object result=method.invoke(obj);
last();
return result;
}
private void begin(){
System.out.println("进来代理了");
}
private void last(){
System.out.println("我写日志了");
}
}
|
生产代理对象的类JdkDynamicProxy(匿名内部类的方式)
注意这个方式中 调用Proxy.newProxyInstance()方法的第三个参数,是直接new的一个匿名类内部的对象,个人感觉有点乱,不是太推荐这种写法。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JdkDynamicProxy{
//被代理对象
private Object obj; //一个参数的构造方法,传入被代理对象
public JdkDynamicProxy(Object obj){
this.obj=obj;
}
/**
*
* @return代理类对象。它与被代理对象实现同样的接口
*/
public Object getProxy(){
//得到被代理对象的类加载器
ClassLoader classLoader=
this.obj.getClass().getClassLoader();
//得到被代理对象实现的接口列表
Class<?>[] interfaces=
this.obj.getClass().getInterfaces();
Object o = Proxy.newProxyInstance(
classLoader,
interfaces,
new InvocationHandler() {
@Override
public Object invoke(
Object proxy,
Method method,
Object[] args) throws Throwable {
begin();
Object result=method.invoke(obj);
last();
return result;
}
});
return o;
}
private void begin(){
System.out.println("进来代理了");
}
private void last(){
System.out.println("我写日志了");
}
}
|
客户端测试类
public class JdkDynamicProxyTest {
public static void main(String[] args) {
//将生成的代理类字节码文件写到磁盘上 路径在当前项目目录下 /com/sun/proxy目录下
System.getProperties(). put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
JdkDynamicProxy jdkDynamicProxy=new JdkDynamicProxy(new ProductDaoImpl());
IGeneralDao proxy =(IGeneralDao) jdkDynamicProxy.getProxy();
proxy.delete();
}
}
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true"); 将生成的代理类字节码文件写到磁盘上 路径在当前项目目录下 /com/sun/proxy目录下
JDK动态代理讲完 , 打完收工。