Java 代理模式

代理模式

什么是代理模式?(简单举例几个例子)

1、打王者段位一直打不上王者段位怎么办?请游戏代练。

2、过年回家自已抢不到高铁票怎么办?找黄牛帮我们抢票。

3、.......(生活中处处可见。)

由上可知,代理模式 就是:

给某一个对象提供一个代理对象,并由代理对象控制对 原对象的引用。

通俗滴讲就是我们常说的:中介。

有哪几种代理模式(它们的区别与优缺点)?

一般分为两种:

1、静态代理 (代理类与被代理类的关系在程序运行前就已经确定好了。)

2、动态代理 (代理类与被代理类的关系在程序运行时才确定。)

  1. jdk动态代理
  2. cglib动态代理

什么是静态代理?

在程序运行前就已经存在的代理类的字节码文件。

代理类和委托类的关系在运行前就确定了。

什么是动态代理?

在程序运行时,通过反射机制动态创建的。

静态代理 与 动态代理 优缺点

优点

实现在不改变目标对象源码情况下,对目标对象进行功能扩展。

静态代理缺点

假设项目中有多个类,则需要编写多个代理类,工作量大,不好修改,不好维护,不能应对变化。

JDK动态代理缺点

被代理的类必须实现接口,未实现接口则没办法完成动态代理。

CGLIB动态代理缺点

被代理类必须不是 final 类。

代码实现代理

代码背景:某人(王五)吃苹果。

简单的代理(耦合性太强,采用继承的方式)不推荐使用

没有代理的情况下:

/**
 * 王五
 * @author oukele
 */
public class WangWu {

    /**
     * 吃苹果
     */
    public void eatApple(){
        System.out.println("王五吃苹果!");
    }

}

运行结果:

    public static void main(String[] args) {

        WangWu wangWu = new WangWu();
        wangWu.eatApple();

    }

王五吃苹果!

有代理的情况下:

(张三将削好的苹果递给王五,然后王五吃苹果!)

/**
 * 张三
 * @author oukele
 */
public class ZhangSan extends WangWu {

    @Override
    public void eatApple() {

        System.out.println("张三从众多苹果中挑了一个苹果。");
        System.out.println("1. 然后拿去洗了洗...。");
        System.out.println("2. 将苹果鲜红的外衣悄悄的脱掉...。");
        System.out.println("N. N步操作在这里省略...。");
        System.out.println("将处理好的苹果递给王五。");
        // 王五吃苹果
        super.eatApple();
    }
}

运行结果:

    public static void main(String[] args) {

        ZhangSan zhangSan = new ZhangSan();
        zhangSan.eatApple();

    }

张三从众多苹果中挑了一个苹果。

  1. 然后拿去洗了洗...。
  2. 将苹果鲜红的外衣悄悄的脱掉...。

​ N. N步操作在这里省略...。
​ 将处理好的苹果递给王五。
​ 王五吃苹果!

上面的这些例子就是一个简单的代理行为。这个简单代理,耦合性太强了。作为演示就好了。


1、静态代理

代码背景:还是上面的例子,某人(王五)吃苹果。

第一步:创建 服务接口

/**
 * 吃的服务行为接口
 * @author oukele
 */
public interface Eat {
    
    /**
     * 吃的行为
     */
    void eat();
}

第二步:实现服务接口

/**
 * 王五(被代理类)
 * @author oukele
 */
public class WangWu implements Eat {
    
    @Override
    public void eat() {
        System.out.println("王五吃苹果!");
    }
}

第三步:创建代理类(让张三来帮忙削皮)

/**
 * 张三(代理类)
 *
 * @author oukele
 */
public class ZhangSan implements Eat {

    /**
     * 被代理类(王五)
     */
    private Eat eat = null;

    public ZhangSan(Eat eat){
        this.eat = eat;
    }

    @Override
    public void eat() {
        System.out.println("0.-----张三的操作-----");
        System.out.println("1.张三帮王五削好了苹果。");
        // 王五吃苹果
        eat.eat();
        System.out.println("3.王五吃完了,张三顺便帮忙收拾好。");
    }
}

第四步:进行测试

    public static void main(String[] args) {

        System.out.println("-------------------没有代理----------------------");
        // 被代理类
        WangWu wangWu = new WangWu();
        wangWu.eat();
        System.out.println("-------------------没有代理----------------------");
        System.out.println();
        System.out.println("-------------------有代理----------------------");
        // 代理类
        ZhangSan zhangSan = new ZhangSan(wangWu);
        zhangSan.eat();
        System.out.println("-------------------有代理----------------------");
    }

运行结果:

-------------------没有代理----------------------
王五吃苹果!
-------------------没有代理----------------------

-------------------有代理----------------------
0.-----张三的操作-----
1.张三帮王五削好了苹果。
王五吃苹果!
3.王五吃完了,张三顺便帮忙收拾好。
-------------------有代理----------------------


2、JDK动态代理

代码背景:还是上面的例子,某人(王五)吃苹果。

第一步:创建 服务接口

/**
 * 吃的服务行为接口
 * @author oukele
 */
public interface Eat {
    
    /**
     * 吃的行为
     */
    void eat();
}

第二步:实现服务接口

/**
 * 王五(被代理类)
 * @author oukele
 */
public class WangWu implements Eat {
    
    @Override
    public void eat() {
        System.out.println("王五吃苹果!");
    }
}

第三步:创建代理类并实现 InvocationHandler 接口,这次就不叫 张三来帮忙了

/**
 * 使用JDK内置的Proxy实现动态代理(代理类)
 * @author oukele
 */
public class JdkProxy implements InvocationHandler {

    /**
     * 被代理类
     */
    private Object object = null;

    private JdkProxy(){};

    public JdkProxy(Object object){
        this.object = object;
    }

    /**
     * 用户调用代理对象的什么方法,都是在调用处理器的invoke方法。【被拦截】
     * @param proxy 被代理后的对象
     * @param method 将要被执行的方法
     * @param args 调用方法时需要的参数
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("某某人....帮王五削好苹果。");
        Object invoke = null;
        try {
            invoke = method.invoke(object, args);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("出现异常:" +  e.getMessage());
        }
        System.out.println("某某人....帮忙收拾好现场。");
        return invoke;
    }

}

第四步:进行测试

    public static void main(String[] args) {

        Eat eat = (Eat) Proxy.newProxyInstance(
                WangWu.class.getClassLoader(),
                WangWu.class.getInterfaces(),
                new JdkProxy(new WangWu())
                );
        eat.eat();

    }
注意 Proxy.newProxyInstance() 方法接受三个参数
    1. ClassLoader loader: 指定当前目标对象使用的类加载器,获取加载器的方法是固定的。A
    2. Class<?>[] interfaces: 指定目标对象实现的接口类型。 B
    3. InvocationHandler: 指定动态处理器,执行目标对象的方法时,触发事件处理器的方法。C
大白话:
    A: 生成代理对象使用哪个类装载器【一般我们使用的是被代理类的装载器】
    B: 生成哪个对象的代理对象,通过接口指定【指定要被代理类的接口】
    C: 生成的代理对象的方法里干什么事【实现InvocationHandler接口,我们想怎么增强原有对象的功能就随意怎么增强。】

运行结果:

某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。


3、CGLIB动态代理

其原理:通过字节码技术为一个类创建子类。

代码背景:还是上面的例子,某人(王五)吃苹果。

注意:需要 cglib 的依赖;

此处使用的版本为

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

1、第一步:创建一个 王五 类

/**
 * 王五
 * @author oukele
 */
public class WangWu {

    /**
     * 吃苹果
     */
    public void eatApple(){
        System.out.println("王五吃苹果!");
    }

}

2、第二步:创建 CGLIB 代理类 并实现 MethodInterceptor 接口

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * Cglib 动态代理类
 * @author oukele
 */
public class CglibProxy  implements MethodInterceptor {

    /**
     *
     * @param o 由CGLib动态生成的代理类实例
     * @param method 上文中实体类所调用的被代理的方法引用
     * @param objects 参数值列表
     * @param methodProxy 生成的代理类对方法的代理引用
     * @return 代理实例的方法调用返回的值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("某某人....帮王五削好苹果。");
        Object invoke = null;
        try {
            invoke = method.invoke(o, objects);
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException("出现异常:" + e.getMessage());
        }
        System.out.println("某某人....帮忙收拾好现场。");
        return invoke;
    }
}

3、第三步:进行测试

    public static void main(String[] args) {

        // 增强器,动态代码生成器
        Enhancer enhancer = new Enhancer();
        // 设置 被代理类
        enhancer.setSuperclass(WangWu.class);
        // 放置 代理类
        enhancer.setCallback(new CglibProxy());
        // 动态生成字节码并返回代理对象
        WangWu wangWu = (WangWu) enhancer.create();
        wangWu.eat();
        
        // 简写
        WangWu wangWu1 = (WangWu)Enhancer.create(
                WangWu.class,
                new CglibProxy()
        );
        wangWu1.eat(); 
        
    }

运行结果:

某某人....帮王五削好苹果。
王五吃苹果!
某某人....帮忙收拾好现场。

小结:

JDK 动态代理 和 CGLIB 字节码生成的区别?

JDK动态代理

只能对实现了接口的类生成代理

CGLIB动态代理

针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,
并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final.

JDK 代理 与 CGLIB 代理 中如何选择?

目标对象是否实现了接口

采用JDK的动态代理

采用CGLIB动态代理

posted @ 2020-08-10 01:25  追梦滴小蜗牛  阅读(159)  评论(0编辑  收藏  举报