代理模式(三)-JDK动态代理

时间:2023-02-23 19:04:45

动态代理

前言

静态代理只能代理指定接口的实现类。即一个类只要实现了接口,就可以用一个实现了同一接口的代理类来代理它。那么问题来了,如果我有两个类,是分别实现了不同的接口,其内部方法各不相同,又都想有代理,且代理内容一样,怎么办呢?生成两个代理类?

比如,我们有一个订单管理类,一个商品管理类。订单管理类需要新增订单、更改订单状态、查询订单信息。商品管理类需要上架商品,下架商品,更新库存,查询商品信息。以上各业务每接受一次请求都需要写入日志。

我们学过静态代理模式,可以很快确定,写日志这件事,交给代理类去做,订单管理类和商品管理类专注核心功能即可。但,这就需要建两个代理类?这两个代理类干的边边角角的活还一样?明显不优雅了吧。

于是,动态代理他来了。

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;
}


客户端测试运行结果:

代理模式(三)-JDK动态代理

显然有运行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;
}


客户端测试运行结果:

代理模式(三)-JDK动态代理


代理了成功!

动态代理成功,但还没打完。

我们继续思考一下那个内部类,它的存在好象有些累赘?不想要它,觉得碍眼,不优雅。此时我们有两种解决方案。

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动态代理

JDK动态代理讲完 , 打完收工。