zoukankan      html  css  js  c++  java
  • Spring 静态代理和动态代理

    现在我们来模拟一下,某位学生去考试。

      假设他(小明)正常的考试。

      运行结果:

           结果:

    突然某一天,他睡过头了,来不急去考试,所有他打算叫另一个人(Cheater)去代替他考试。

      运行结果:

     结果:

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

    静态代理:

    优点:

      1、  实现松散耦合。

      2、做到在不修改目标对象的功能前提下,对目标功能扩展。

    还是拿上面的例子进行修改吧,小明还是一样起晚了,叫Cheater去代替他考试。

    定义一个Exam接口 ,代表着考试的行为。

    public interface Exam {//考试的接口
        void exam();
    }

    去Student类中实现此接口,并实现方法。 (被代理)

    public class Student implements Exam {
        
        public void exam(){
            System.out.println("奋笔疾书,完成考试啦");
        }
    }

    Cheater也实现Exam接口,并实现方法 (代理)

    public class Cheater implements Exam {
      //被代理的对象
        private final Exam student;
    
        public Cheater(Exam student){
            this.student = student;
        }
    
        public void exam() {
            System.out.println("考试的时候唱了一首凉凉,差点被劝退了。");
            student.exam();//调用Student类的方法
        }
    }

    测试:

    public class Main {
    
        public static void main(String[] args) {
    
            Exam xiaoMing = new Student();
            xiaoMing.exam();//原来的行为
    System.out.println("-----------下面是代理的行为------------");
    Exam cheater
    = new Cheater(xiaoMing); cheater.exam();//代理的行为 } }

    结果:

    1 奋笔疾书,完成考试啦
    2 -----------下面是代理的行为------------
    3 考试的时候唱了一首凉凉,差点被劝退了。 4 奋笔疾书,完成考试啦

    静态代理的缺点:

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

    如果想解决上面的问题,可以使用动态代理。

    动态代理:

    1、使用JDK内置的Proxy实现

    还是拿上面的例子进行修改吧,小明还是一样起晚了,叫人去代替他考试。(重新开始哈)

    定义一个Exam接口。

    public interface Exam {
        void exam();
    }

    Student 实现 Exam接口 

    public class Student implements Exam {
        public void exam() {
            System.out.println("奋笔疾书,完成考试啦");
        }
    }

    创建一个 JdkProxy 类 实现 InvocationHandler 接口

    public class JdkProxy implements InvocationHandler {
    
        private Object object;//被代理的对象
    
        public JdkProxy(){}
    
        public JdkProxy(Object object){//初始化的时候就赋值
            this.object = object;
        }/**
         * 当用户调用对象中的每个方法时都通过下面的方法执行,方法必须在接口
         * proxy 被代理后的对象
         * method 将要被执行的方法信息(反射)
         * args 执行方法时需要的参数
         */
        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 x){
                System.out.println("异常信息:"+x.getMessage());
            }return invoke;//调用被代理对象原来的方法(行为)
        }
    }

    测试:

    public class Main {
        public static void main(String[] args) {
    
            /*
             * 通过Proxy的newProxyInstance方法来创建我们的代理对象,我们来看看其三个参数
             * 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载我们的代理对象
             * 第二个参数person1.getClass().getInterfaces(),我们这里为代理对象提供的接口是真实对象所实行的接口,表示我要代理的是该真实对象,这样我就能调用这组接口中的方法了
             * 第三个参数handler, 我们这里将这个代理对象关联到了上方的 InvocationHandler 这个对象上
             */
            ClassLoader cl = Thread.currentThread().getContextClassLoader();
            Exam o = (Exam) Proxy.newProxyInstance(
                    cl,//类加载器
                    new Class[]{Exam.class},//获取被代理对象的所有接口
                    new JdkProxy(new Student())//InvocationHandler对象
            );
            o.exam();//代理后的行为
    
            // 另一种写法
    //        Exam o1 = (Exam) Proxy.newProxyInstance(
    //                Thread.currentThread().getContextClassLoader(),
    //                Student.class.getInterfaces(),
    //                new JdkProxy(new Student())
    //        );
    //        o1.exam();
    
        }
    }

    运行结果:

    1 考试的时候,吃颗糖压压惊,没有人知道,这次不是本人来考试的
    2 奋笔疾书,完成考试啦

    使用内置的Proxy实现动态代理有一个问题被代理的类必须实现接口,未实现接口则没办法完成动态代理。

    2、动态代理,使用cglib实现

    还是拿上面的例子来说吧。

    这次我们不写接口了。不过 我们要实现MethodInterceptor接口,并实现方法

    去maven 中心仓库 找到 CGlib 的依赖

    Student类

    public class Student {
    
        public void exam(){
            System.out.println("奋笔疾书,完成考试啦");
        }
    
    }

    创建一个 CglibProxy 的类 并 实现  MethodInterceptor 接口 ,并实现方法

    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    public class CglibProxy implements MethodInterceptor {
    
        /*
        * 参数
        * Object 为由CGLib动态生成的代理类实例
        * method 为上文中实体类所调用的被代理的方法引用
        * objects 为参数值列表
        * methodProxy 为生成的代理类对方法的代理引用
        * return 从代理实例的方法调用返回的值
        * */
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("考试的时候,唱了一首 我爱洗澡 ,差点被劝退 ");
            return methodProxy.invokeSuper(o,objects);
        }
    
    }

    测试:

    import net.sf.cglib.proxy.Enhancer;
    
    public class Main {
        public static void main(String[] args) {
    
            //增强器,动态代码生成器
            Enhancer enhancer = new Enhancer();
            //设置 生成类 的 父类
            enhancer.setSuperclass(Student.class);
            //回调函数
            enhancer.setCallback(new CglibProxy());
            //动态生成字节码并返回代理对象
            Student o = (Student) enhancer.create();
            o.exam();
            
            //这里是简化写法
            //第一个参数 设置 生成类 的父类 ,第二参数 被代理类的所有接口 ,回调函数
            Student student = (Student) new Enhancer().create(Student.class, null, new CglibProxy());
            student.exam();
        }
    }

    运行结果:

    1 考试的时候,唱了一首 我爱洗澡 ,差点被劝退 
    2 奋笔疾书,完成考试啦 

    这种代理的缺点:被代理的类必须不是final类。

    小结:

    使用cglib可以实现动态代理,即使被代理的类没有实现接口,但被代理的类必须不是final类。

    示例代码下载地址: https://github.com/oukele/Spring-Proxy

  • 相关阅读:
    04_特征工程
    03_特征清洗
    02_数据探索
    01_简介
    cache是什么文件?
    gulp详细入门教程
    HTML5实战与剖析之触摸事件(touchstart、touchmove和touchend)
    h4和h5的区别
    弹性盒布局
    js面向对象
  • 原文地址:https://www.cnblogs.com/oukele/p/10132015.html
Copyright © 2011-2022 走看看