学习笔记-Java基础:代理模式

代理(Proxy)是一种设计模式,提供了间接对目标对象进行访问的方式;即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能.这就符合了设计模式的开闭原则,即在对既有代码不改动的情况下进行功能的扩展。

代理模式的关键点是:代理对象与目标对象.代理对象是对目标对象的扩展,并会调用目标对象

静态代理

静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.

静态代理模式一般会有三个角色:

抽象角色:指代理角色和真实角色对外提供的公共方法,一般为一个接口。

真实角色:需要实现抽象角色接口,定义了真实角色所要实现的业务逻辑,以便供代理角色调用。也就是真正的业 务逻辑在此。

代理角色:需要实现抽象角色接口,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附 加自己的操作。将统一的流程控制都放到代理角色中处理。

静态代理在使用时,需要定义接口或者父类被代理对象与代理对象一起实现相同的接口或者是继承相同父类。一般来说,被代理对象和代理对象是一对的关系,当然一个代理对象对应多个被代理对象也是可以的。静态代理,一对一则会出现时静态代理对象量多、代码量大,从而导致代码复杂,可维护性差的问题,一对多则代理对象会出现扩展能力差的问题。

示例代码如下:

public class Test {
    /**
     * 代理接口。处理给定名字的任务。
     */
    interface Subject {
        /**
         * 执行给定名字的任务。
         *
         * @param taskName 任务名
         */
        public void dealTask(String taskName);
    }

    /**
     * 真正执行任务的类,实现了代理接口。
     */
    public class RealSubject implements Subject {

        /**
         * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间
         *
         * @param taskName
         */
        @Override
        public void dealTask(String taskName) {
            System.out.println("正在执行任务:" + taskName);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     *  代理类,实现了代理接口。
     */
    public class ProxySubject implements Subject {
        //代理类持有一个委托类的对象引用
        private Subject delegate;

        public ProxySubject(Subject delegate) {
            this.delegate = delegate;
        }

        /**
         * 将请求分派给委托类执行,记录任务执行前后的时间,时间差即为任务的处理时间
         *
         * @param taskName
         */
        @Override
        public void dealTask(String taskName) {
            long start_time = System.currentTimeMillis();
            //将请求分派给委托类处理
            delegate.dealTask(taskName);
            long end_time = System.currentTimeMillis();
            System.out.println("执行任务耗时" + (end_time - start_time) + "毫秒");

        }
    }

    public class SubjectStaticFactory {
        //客户类调用此工厂方法获得代理对象。
        //对客户类来说,其并不知道返回的是代理类对象还是委托类对象。
        public static Subject getInstance() {
            return new ProxySubject(new RealSubject());
        }
    }

    public static void main(String[] args) {
        Subject proxy = SubjectStaticFactory.getInstance();
        proxy.dealTask("一炮干掉鬼子的指挥官");
    }
}

静态代理类优缺点
优点:在不修改目标对象的功能前提下,对目标功能扩展.
缺点:
1)代理对象的一个接口只服务于一种类型的对象,如果要代理的方法很多,势必要为每一种方法都进行代理,静态代理在程序规模稍大时就无法胜任了。
2)如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

动态代理

动态代理类的源码是在程序运行期间由JVM根据反射等机制动态的生成,所以不存在代理类的字节码文件。代理类和委托类的关系是在程序运行时确定。

java.lang.reflect.Proxy 这是 Java 动态代理机制生成的所有动态代理类的父类,它提供了一组静态方法来为一组接口动态地生成代理类及其对象。

// 方法 1: 该方法用于获取指定代理对象所关联的调用处理器  
static InvocationHandler getInvocationHandler(Object proxy)   
  
// 方法 2:该方法用于获取关联于指定类装载器和一组接口的动态代理类的类对象  
static Class getProxyClass(ClassLoader loader, Class[] interfaces)   
  
// 方法 3:该方法用于判断指定类对象是否是一个动态代理类  
static boolean isProxyClass(Class cl)   
  
// 方法 4:该方法用于为指定类装载器、一组接口及调用处理器生成动态代理类实例  
static Object newProxyInstance(ClassLoader loader, Class[] interfaces, InvocationHandler h)  

java.lang.reflect.InvocationHandler 这是调用处理器接口,它自定义了一个 invoke 方法,用于集中处理在动态代理类对象上的方法调用,通常在该方法中实现对委托类的代理访问。每次生成动态代理类对象时都要指定一个对应的调用处理器对象。

// 该方法负责集中处理动态代理类上的所有方法调用。第一个参数既是代理类实例,第二个参数是被调用的方法对象  
// 第三个方法是调用参数。调用处理器根据这三个参数进行预处理或分派到委托类实例上反射执行  
Object invoke(Object proxy, Method method, Object[] args)   

java.lang.ClassLoader 这是类装载器类,负责将类的字节码装载到 Java 虚拟机(JVM)中并为其定义类对象,然后该类才能被使用。Proxy 静态方法生成动态代理类同样需要通过类装载器来进行装载才能使用,它与普通类的唯一区别就是其字节码是由 JVM 在运行时动态生成的而非预存在于任何一个 .class 文件中。
每次生成动态代理类对象时都需要指定一个类装载器对象。

实例代码如下:

public class Test {
    /**
     * 代理接口。处理给定名字的任务。
     */
    public interface Subject {
        /**
         * 执行给定名字的任务。
         *
         * @param taskName 任务名
         */
        public void dealTask(String taskName);
    }

    /**
     * 真正执行任务的类,实现了代理接口。
     */
    public static class RealSubject implements Subject {

        /**
         * 执行给定名字的任务。这里打印出任务名,并休眠500ms模拟任务执行了很长时间
         *
         * @param taskName
         */
        @Override
        public void dealTask(String taskName) {
            System.out.println("正在执行任务:" + taskName);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        RealSubject realSubject = new RealSubject();
        Object object = Proxy.newProxyInstance(Test.class.getClassLoader(), new Class[]{Subject.class},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                        long start_time = System.currentTimeMillis();
                        //利用反射机制将请求分派给委托类处理。Method的invoke返回Object对象作为方法执行结果。
                        //因为示例程序没有返回值,所以这里忽略了返回值处理
                        method.invoke(realSubject, objects);
                        long end_time = System.currentTimeMillis();
                        System.out.println("执行任务耗时" + (end_time - start_time) + "毫秒");
                        return null;
                    }
                });
        Subject subject = (Subject) object;
        subject.dealTask("一炮干掉鬼子的指挥官");
    }
}

查看Proxy源码得知Proxy .newProxyInstance会创建一个Class, 与静态代理不同,这个Class不是由具体的java源文件编译 而来,即没有真正的文件,只是在内存中按照Class格式生成了一个Class。

        String name = com.enjoy.lib.Subject.class.getName() + "$Proxy0";
        //生成代理指定接口的Class数据
        byte[] bytes = ProxyGenerator.generateProxyClass(name, new Class[]{com.enjoy.lib.Subject.class});
        FileOutputStream fos = new FileOutputStream("lib/" + name + ".class");
        fos.write(bytes);
        fos.close();

编译后可以得到生产的class文件:

public final class Subject$Proxy0 extends Proxy implements Subject {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

    public Subject$Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void dealTask(String var1) throws  {
        try {
            super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("com.enjoy.lib.statics.Test$Subject").getMethod("dealTask", Class.forName("java.lang.String"));
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

这里的方法dealTask()中h其实就是InvocationHandler接口,所以我们在使用动态代理时,传递的InvocationHandler就是一个 监听,在代理对象上执行方法,都会由这个监听回调出来。

动态代理的优缺点

优点:
动态代理与静态代理相比较,最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)。这样,在接口方法数量比较多的时候,我们可以进行灵活处理,而不需要像静态代理那样每一个方法进行中转。

缺点:由于设计的原因动态代理仅支持 interface 代理,而Java 的继承机制注定了这些动态代理类们无法实现对 class 的动态代理,原因是多继承在 Java 中本质上就行不通。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值