JAVA的代理(proxy)模式

  代理(proxy)模式: 是一种设计模式, 提供了间接对目标对象进行访问的方式. 即通过代理对象访问目标对象.这样做的好处是:可以在目标对象实现的功能上,增加额外的功能补充,即扩展目标对象的功能. 有的时候也可以使用代理模式, 在不修改他人代码的情况下, 增强功能(对方法进行扩展)

  举例: 老板的助理, 明星的经纪人等, 这里的助理和经纪人都可以看做是代理对象, 而老板和明星就是目标对象, 别人通过联系助理和经纪人可以达到相同的目的, 通俗一点的解释就是在操作一个目标对象或对象中的方法时,不是直接操作这个对象,而是通过一个代理对象来操作这个实际的目标对象

  关键词: 目标对象, 代理对象. (代理对象就是对目标对象的扩展, 并会调用目标对象)

  图示:

  

 

 

   代理分类: 静态代理 和 动态代理以及Cglib代理

   注意: 接下来所有的代理模式示例都将以CURD操作为基础, 只是演示代理模式的方式, 不做真正的数据库操作!!!

 1.静态代理

  说明: 静态代理在使用的时候, 需要定义目标接口,被代理对象(子类, 也就是目标对象)与代理对象都需要一起实现相同的这个目标接口.

     代码示例: 

  目标接口: ICurd.java

package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;

/**
 * 定义目标接口
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public interface ICurd {

    void add();
    
    void delete();
    
    void update();
    
    void find();
}

  目标对象: CurdImpl.java

package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;

/**
 * 目标对象, 实现父接口
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public class CurdImpl implements ICurd {

    @Override
    public void add() {
        System.out.println("CurdImpl执行了add操作");
    }

    @Override
    public void delete() {
        System.out.println("CurdImpl执行了delete操作");
    }

    @Override
    public void update() {
        System.out.println("CurdImpl执行了update操作");
    }

    @Override
    public void find() {
        System.out.println("CurdImpl执行了find操作");
    }
}

  代理对象: Proxy.java

package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;

/**
 * 定义代理对象
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public class Proxy implements ICurd {

    private ICurd curd;

    public Proxy(ICurd curd) {
        this.curd = curd;
    }

    @Override
    public void add() {
        // 扩展, 执行目标对象之前的行为
        System.out.println("执行add操作之前 ...");
        // 执行真正的目标对象的方法
        curd.add();
        // 扩展, 执行目标对象之后的行为
        System.out.println("执行add操作之后 ...");
    }

    @Override
    public void delete() {
     // 类似add方法
    }

    @Override
    public void update() {
     // 类似add方法
    }

    @Override
    public void find() {
     // 类似add方法
    }
}

  测试类: ProxyTest.java

package demo.java.jiangkd.design_pattern.proxy.staticProxy.demo;

import org.junit.Test;

public class ProxyTest {

    @Test
    public void test() {
        // 创建目标对象
        CurdImpl curdImpl = new CurdImpl();
        // 创建代理对象, 代理目标对象
        Proxy proxy = new Proxy(curdImpl);
        // 执行代理对象的add方法
        proxy.add();
    }
}

  结果输出:

  

执行add操作之前 ...
CurdImpl执行了add操作
执行add操作之后 ...

  静态代理总结: 

    1. 可以做到在不修改目标对象的功能前提下,对目标功能扩展.(执行目标对象方法的前后的输出就是扩展, 可以改为其他方法的执行, 这里为了演示只是输出)

    2. 缺点: 因为代理对象需要与目标对象实现一样的接口, 所以如果都采用这种方式, 久而久之会发现有很多的代理类, 很繁琐, 加入你有两个目标接口, 都需要代理, 那么你就会产生两个对应的目标对象和代理对象, 如果是20个呢, 简直要崩溃呀!!! 而且一旦目标接口改变方法, 例如添加新的方法, 那么目标对象和代理对象都需要相应的进行添加方法, 维护麻烦, 所以, 解决这个问题的方案就是动态代理!!!

 2.动态代理

   动态代理特点: 

    1. 代理对象不需要实现目标接口

    2. 代理对象的生成,是利用JDK的API,动态的在内存中构建代理对象(需要我们指定创建代理对象/目标对象实现的接口的类型)

    3. 动态代理也叫作JDK动态代理

  JDK动态代理了解: 

    1. 代理类所在包:java.lang.reflect.Proxy

    2. JDK实现代理只需要使用Proxy的静态方法newProxyInstance方法,但是该方法需要接收三个参数,完整的写法是:  

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )

     接收的三个参数依次为:      

        ClassLoader loader: 指定当前目标对象使用类加载器,获取加载器的方法是固定的

        Class<?>[] interfaces: 目标对象实现的接口的类型,使用泛型方式确认类型

        InvocationHandler h: 事件处理,执行目标对象的方法时,会触发事件处理器的方法,会把当前执行目标对象的方法作为参数传入

    代码示例:

  目标接口和目标对象依然同上面的静态代理, 这里不再展示, 不同是这里的代理对象不需要定义类来实现目标接口, 而是实现
JDK的代理类接口MyInvocationHandler中的invoke方法来动态的生成代理对象

    代理对象: MyInvocationHandler.java, 实现JDK的代理类接口 

package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.jdk.demo;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * 实现jdk自带的动态代理类接口并重写invoke方法
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public class MyInvocationHandler implements InvocationHandler {

    /**
     * 持有需要被代理的真实目标对象
     */
    private Object obj;

    public MyInvocationHandler(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 扩展:执行代理之前的操作
        System.out.println("代理之前 ...");
     // 注意: 这里传入的参数是持有的目标对象obj和参数args Object invoke
= method.invoke(obj, args); // 扩展:执行代理之后的操作 System.out.println("代理之后 ..."); return invoke; } }

  测试: ProxyTest.java

package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.jdk.demo;

import java.lang.reflect.Proxy;

import org.junit.Test;

public class ProxyTest {

    @Test
    public void test() {
        // 定义目标对象
        CurdImpl curdImpl = new CurdImpl();
        // 执行静态方法获取代理对象, 注意三个参数的获取方式, 也有其他写法, 但是变的都是表面而已, 实际类型都是一样的
        ICurd curd = (ICurd)Proxy.newProxyInstance(curdImpl.getClass().getClassLoader(),
            curdImpl.getClass().getInterfaces(), new MyInvocationHandler(curdImpl));
        //
        curd.add();
    }
}

  测试结果输出:

    同静态代理输出一样(懒得贴了)

  总结:
    代理对象不需要实现接口而是执行静态方法newProxyInstance来获取的, 但是目标对象一定要实现接口,否则不能用动态代理

 3.Cglig代理

  上面的静态代理和动态代理模式都是要求目标对象实现一个接口(所谓的目标接口), 但是有时候目标对象只是一个单独的对象, 并没有实现任何的接口, 这个时候就可以使用以目标对象子类的方式类实现代理,这种方法就叫做:Cglib代理

  Cglib代理,也叫作子类代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展. Cglib是一个强大的高性能的代码生成包,它可以在运行期扩展java类与实现java接口.它广泛的被许多AOP的框架使用,例如Spring AOP和synaop,为他们提供方法的interception(拦截)

  Cglib子类代理实现方法:

    1.需要引入cglib的jar文件,但是如果是Spring项目, 那么核心包中已经包括了Cglib功能,所以直接引入spring-core-3.2.5.jar即可. 否则需要cglib-nodep-3.2.4.jar(版本自己视情况而定)

    2.引入功能包后,就可以在内存中动态构建子类

    3.目标对象的方法如果为final/static,那么就不会被代理(本人测试过static方法, 如果采用代理对象调用static方法, 会报出警告, 推荐使用类直接调用, 而且执行了也不会执行代理扩展的方法, 只会执行自身的方法)

    4. 需要一个代理类, 实现接口MethodInterceptor中的invoke方法, 然后还需要有一个生成代理对象的方法

  代码示例:

  没有目标接口, 只是一个类, 作为目标对象

package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;

/**
 * 单纯的一个类, 作为目标对象
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public class CurdService {

    public void add() {
        System.out.println("CurdService -> add");
    }

    public String delete() {
        System.out.println("CurdService -> delete");
        return "delete";
    }

    public static void update() {
        System.out.println("CurdService -> update");
    }
}

  代理类: MyMethodInterceptor.java 需要实现MethodInterceptor接口中的invoke方法

package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * cglib代理类, 用来获取代理对象
 * 
 * @author jiangkd
 * @date 2020/06/04
 */
public class MyMethodInterceptor implements MethodInterceptor {

    /**
     * 持有目标对象
     */
    private Object obj;

    public MyMethodInterceptor(Object obj) {
        this.obj = obj;
    }

    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        //
        System.out.println("before ...");
        Object invoke = method.invoke(obj, args);
        System.out.println("返回:" + invoke);
        System.out.println("after ...");
        return invoke;
    }

    /**
     * 创建代理对象的方法
     * 
     * @return
     */
    public Object getProxyObj() {
        //
        Enhancer enhancer = new Enhancer();
        // 设置父类, 就是目标对象类
        enhancer.setSuperclass(obj.getClass());
        // 设置回调, 就是当前代理类
        enhancer.setCallback(this);
        // 创建代理对象
        return enhancer.create();
    }
}

  测试类: ProxyTest.java

package demo.java.jiangkd.design_pattern.proxy.dynamicProxy.cglib.demo;

import org.junit.Test;

public class ProxyTest {

    @Test
    public void test() {
        // 目标对象
        CurdService curdService = new CurdService();
        // 获取代理对象
        CurdService proxyObj = (CurdService)new MyMethodInterceptor(curdService).getProxyObj();
        // 
        proxyObj.add();
        // 静态方法, 不会通过代理, 报出警告, 推荐下面的写法
        proxyObj.update();
        // 直接正常调用, 不使用代理
        CurdService.update();
    }
}

  测试输出:

   不妨自己写一个代理, 测试一下看看啊(好吧, 其实就是我懒得贴了)

 

posted @ 2020-06-04 15:30  KILLNPE  阅读(254)  评论(0编辑  收藏  举报