基于JDK动态代理模式的一些简单封装

  • Post author:
  • Post category:其他


代理模式的作用:增强原始对象的方法。

静态代理模式的弊端:需要的类繁多,额外功能修改不方便。

解决方法:动态代理模式(JDK原生支持)。

我的理解:

代理三要素:原始对象(本来就有),额外功能(在InvocationHandler.invoke实现),封装(Proxy.newInstance(classloader,interfaces,invocationahandler))。

可知,JDK的原生,所需要的额外功能类需要实现InvocationHandler接口,通过java.reflect.Proxy类来获得代理对象。但是我们也可以发现,获取该代理对象的步骤,代码大致相同。若要修改额外功能,则需要重复书写一些代码,存在着冗余,所以,我们能不能将重复的代码进行封装一下呢?让我们真正的只写额外功能呢?

所以我们需要对一些代码进行封装。那么怎么封装呢?我想到了Servlet,只要继承HttpServlet就可以实现Servlet的功能了,其中的一些相同的方法也已经实现了。所以,我们将其进行类似于Servlet的封装。

分析:

将额外功能添加到代理类上,进行原有功能的加强。通过观察InvocationHandler接口可知,其中真实方法,真实参数都具有(因为实现了相同的接口),但缺少真实对象。所以我们需要把真实对象给设置进去。

原有功能:

public interface ISell {

    public void sell();
    public void sayOut(String words);
}
public class Seller implements ISell {


    @Override
    public void sell() {
        System.out.println("real sell...");
    }

    @Override
    public void sayOut(String words) {
        System.out.println(words);
    }
}

额外功能:

为了减少耦合,额外功能在一个独立的类中,例如

public class Ext1 implements InvocationHandler {

    private Object realObject;

    public void setRealObject(Object realObject) {
        this.realObject = realObject;
    }

    public Object getProxyObject(){
        return Proxy.newProxyInstance(Ext1.class.getClassLoader(),realObject.getClass().getInterfaces(),this);
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//        额外公
        System.out.println("---log---");
//        原来方法的执行
        Object ret= method.invoke(realObject,args);
        return ret;
    }
}

test代码:

  @Test
    public void test() throws Exception {
        Ext1 ex1 = new Ext1();
        ex1.setRealObject(new Seller());
        ISell seller = (ISell) ex1.getProxyObject();
        seller.sell();
    }

运行结果:

如果额外功能变更的话,我们需要重新写一遍和Ext1一样的代码,而只是invoke方法之中有变化而已,所以产生了冗余,所以我们进行它的封装。

额外功能应该只有额外功能而已,但是得到代理对象的代码也在其中,不符合面向对象的思想。

封装改进:

应用设计模式:工厂设计模式,适配器设计模式。

通过观察InvocationHandler的实现类,我们可以得到重复的代码:

所以,每一个代理类都必须有画红线的那3句代码。

我们先设计代理类的顶层接口。

为什么要设置Class对象呢,你猜???

public interface IExt extends InvocationHandler {
    public void setRealClass(Class realClass )throws Exception;
//    运行在原始方法之前,之后的方法,额外功能无非在原始方法运行之前和之后。
    public  void before();
    public  void after();
}

但是那个realObject还没有呢?那么你想到了怎么来实现吗?对就是抽象类,我们将接口实现在抽象类之中,对于重复的代码我们直接实现,对于不确定的代码让程序员自己实现,写在before和after中不就可以了。到这里是不是就有Servlet那味了呢?

public abstract class GenericExt implements IExt{
    private Object realObject;

    @Override
    public void setRealClass(Class realClass) throws Exception {
        this.realObject = realClass.getDeclaredConstructor().newInstance();
    }

    //Invocationhandle是调用的此接口,强制类型转换,只会有这一个方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        this.before();
        Object ret = method.invoke(realObject,args);
        this.after();
        return ret;
    }

    @Override
    public void before() {

    }

    @Override
    public void after() {

    }

这就设计出我们额外功能类的模板了,我们以后所写的额外功能类只要继承这个GenericExt就可以了。要想在额外功能实现之前,之后的方法,随便自己定义。

那么怎么获得代理类(原始对象加强类)呢?我们仔细思考。如何获得Proxy.newInstance呢?其实就两点,实现InvocationHandler接口的类,原始功能的接口数组。

有人就不明白了,还有个类加载器呢?去哪里了。其实,这个类加载器ClassLoader是谁的都可以,比如说你传一个Integer.class.getClassLoader也可以。为啥了?

JVM通过类加载器将.Java文件(源码)加载到虚拟机中转化成.class文件,一个.Java对应一个类加载器,一个Class文件。但是动态代理类是没有.Java文件的,所以就没有类加载。但是JVM要根据类加载器给对象分配内存,实例化该类;动态代理设计模式是直接在JVM中自动生成的,是直接就在JVM虚拟机中的啊,所以不需要.Java文件,那么按道理JVM是不会给其实例化的,但是我们又要用,所以,我们可以借一个类加载器,借谁的呢?无所谓,只要是一个有.Java源文件的就可以。动态代理是不是很鸡贼啊!

所以,工厂产生代理类的代码如下:为了避免强制类型转换,我使用了泛型。

public class ProxyGet {
    private ProxyGet() {
    }

    /**
     * @param ext  表示额外功能的类
     * @param realClass 真实对象的class文件
     * @param <T> 泛型,
     * @return 返回代理对象
     * @throws Exception 异常一次性抛出。
     */
    public static <T> T getProxyObject(IExt ext,Class<T> realClass) throws Exception {
        ext.setRealClass(realClass);
        return (T) Proxy.newProxyInstance(realClass.getClassLoader(), realClass.getInterfaces(),(InvocationHandler) ext);
    }
}

定义额外功能:

public class ExtImpl extends GenericExt{
    @Override
    public void before() {
        System.out.println("111...");
    }

    @Override
    public void after() {
        System.out.println("222...");
    }
}

Test代码如下:

 @Test
    public void test() throws Exception {
        ExtImpl ex = new ExtImpl();
        ISell sell = ProxyGet.getProxyObject(ex,Seller.class);
        sell.sayOut("hello world!");
    }

运行结果如下:

但是,有有人可能会问了,你这个before和after是没有参数共享的,如果我要计算代码运行时间并输出呢?这就需要在before写一下代码,在after写一段代码。两段代码之间有联系。确实,还并不完善。我也想了很久,算是想到了一个解决方案吧。就是在代理类的invoke方法作用域中内置一个Map集合(Map是最为合适的)。然后before和after方法把这个Map当参数(因为Java是引用传递的)。那么就可以实现两个代理方法间的数据共享。

最后,填坑了。

为什么要用Class对象呢。

其实就是个人喜好,传对象也可以获得Class文件,就需要先getClass,有点长,看着不舒服。再者,传个对象感觉怪怪的(毕竟我没有真实对象,拿什么传呢?狗头保命)。

还能再修改的地方:比如说一个服务,UserService,应该是一个单例就可以的。而我却是在代理类中创建一个新的。怎么解决我还没想好。

解决:在我的工厂模式的介绍中,书写了通过工厂来获得该单例的方法(正好是通过class文件哦!),大家可以去看看哦。

spring框架中也可以解决。它的aop做的非常好。



版权声明:本文为weixin_46083389原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。