美文网首页
java-代理模式

java-代理模式

作者: 倔强青铜弟中弟 | 来源:发表于2020-12-10 21:49 被阅读0次

    讲解内容:
    1、代理模式
    2、jdk动态代理
    3、cglib动态代理

    代理模式

    (1)概念:

    • 代理就是帮别人做事情,如:工厂的中介,中介负责为工厂招收工人,那么中介就是工厂的代理;客户通过商家购买东西,商家向厂家购买货物,商家就是工厂的代理
    • 在开发中存在a类需要调用c类的方法,完成某一个功能,但是c禁止a调用。这时,可以在a和c之间创建一个b类代理,a类访问b类,b类访问c类。例如:登录的时候需要进行短信验证,这个时候代理就是中国移动的子公司来完成短信的发送功能
    • 代理模式就是为其他对象提供一种代理来控制这个对象的访问,在某些情况下一个对象不适合或不能直接引用另一个对象,而代理对象可以在客户类和目标对象直接起到中介的作用
      参考见下图
      ea094ad9-efc5-337d-a8e8-ce9223511144.jpg
    • 功能增强:其中目标对象实现真正的功能,但是代理对象可以对目标对象的功能做进一步的扩充


      `5EJR[YOSZQH)Y$CU`QT}HN.png

    (2)设计模式

    设计模式代表了最佳的实践,通常被有经验的面向对象的软件开发者所采用的,它是开发中面临的一般问题的解决方案,这些解决方案是众多的软件开发人员经过相当长的实践的经验和错误总结出来的

    (3)举例:

    如果设计一个类,该类中含有加减乘除四个方法,现在需要给每一个类添加测试方法执行时间的代码,如果不适用代理类,就需要对每一个方法进行修改,需要修改多次。违背开闭原则(OCP,对扩展开放,修改关闭)和单一职责(SRP)。

    (4)作用:

    • 功能增强:在原有类的功能上增加额外的功能
    • 控制访问:代理类不让你访问目标类

    (5)实现方式:

    • 静态代理:代理类自己手动写,所需要的目标类确定,容易理解,简单。编译的时候就会生产一个类
    • 动态代理:在代码运行的过程中生成一个临时的代理类,在内存中。

    静态代理

    是在程序运行前就已经存在代理类的字节码文件,静态代理通常是对原有业务逻辑的扩充,通过让代理类持有真实对象,在代理类的源代码中调用被代理类方法来添加我们需要的业务逻辑。

    例子:


    (1)创建一个接口

    interface Anmal{
        public abstract void show();
    }
    

    (2) 实现类

    public class Fish implements Animal{
    
        @Override
        public void show() {
            System.out.println("我是鱼");
        }
    }
    

    (3)代理类:

    public class ProxyFish implements Animal{
    
        Fish fish = new Fish();
        @Override
        public void show() {
            fish.show();
            System.out.println("我在游泳");
            //也可以写其他逻辑代码
        }
    }
    

    (4)测试类:

    public class mainTest {
        public static void main(String arg[]) {
            ProxyFish proxyFish = new ProxyFish();
            proxyFish.show();
        }
    }
    

    缺点:如果有多个类需要代理,那么就需要创建多个代理类分别代理目标对象,工作量较大,不利于维护。可以结合工厂模式

    动态代理:

    动态代理是利用的反射机制动态地生成代理的对象,我们不需要知道谁代理谁。代理类的那部分代码被固定下来了,不会因为业务的增加而逐渐庞大

    jdk动态代理

    注意:jdk动态代理是要求实现类有接口。动手敲的时候别忘了写接口。cglib动态代理可以直接代理实现类,后面会有这两个对比

    举例:

    还是上面的案例,注意看代理类。
    (1)创建一个接口:

    interface Anmal{
        public abstract void show();
    }
    

    (2)实现类

    public class Fish implements Animal{
    
        @Override
        public void show() {
            System.out.println("我是鱼");
        }
    }
    

    (3)实现代理类
    这里为了让大家看清,代理类写到一个main方法里面了,后面会封装的。

    public class mainTest {
        public static void main(String arg[]) {
            Animal objectProxy = (Animal)Proxy.newProxyInstance(//创建接口实例
                    Animal.class.getClassLoader(),//用目标对象相同的类加载器动态代理运行时创建,将类加载到内存(反射)
                    new Class[]{Animal.class}, // 被代理的类所实现的接口,可以是多个
                    new InvocationHandler() {  // 绑定代理类的方法,将代理类和原有的实现类绑定,这里面要我们自己写代理类的逻辑功能
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            System.out.println("我是动态代理的鱼头");
                            method.invoke(new Fish(), args); // 执行目标对象的方法,就是原有实现类的方法。
                            System.out.println("我是动态代理的鱼尾");
                            //可以任写其他业务逻辑
                            return null;
                        }
                    }
            );
    
            objectProxy.show(); // 调用代理类的方法
        }
    }
    

    (4)封装代理类:

    public class FishProxy implements InvocationHandler {
        private Object object;
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("我是动态代理的鱼头");
            Object result = method.invoke(object, args); // 执行目标对象的方法,就是原有实现类的方法。
            System.out.println("我是动态代理的鱼尾");
            return result;
        }
    
        public Object getFishProxy(Object targetObject){
            //为目标target赋值
            this.object = targetObject;
            //使用jdk动态代理,
            return Proxy.newProxyInstance(
                    targetObject.getClass().getClassLoader(),//用目标对象相同的类加载器动态代理运行时创建,将类加载到内存(反射)
                    targetObject.getClass().getInterfaces(), //被代理的类所实现的接口,可以是多个
                    this //绑定代理类的方法,将代理类和原有的实现类绑定,这个类就是我们写的代理绑定方法,在invoke方法中
                    );
        }
    }
    

    测试类:

    public class mainTest {
        public static void main(String arg[]) {
    //      Animal objectProxy = (Animal)Proxy.newProxyInstance(//创建接口实例
    //              Animal.class.getClassLoader(),//用目标对象相同的类加载器动态代理运行时创建,将类加载到内存(反射)
    //              new Class[]{Animal.class}, // 被代理的类所实现的接口,可以是多个
    //              new InvocationHandler() {  // 绑定代理类的方法,将代理类和原有的实现类绑定,这里面要我们自己写代理类的逻辑功能
    //                  @Override
    //                  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    //                      System.out.println("我是动态代理的鱼头");
    //                      method.invoke(new Fish(), args); // 执行目标对象的方法,就是原有实现类的方法。
    //                      System.out.println("我是动态代理的鱼尾");
    //                      //可以任写其他业务逻辑
    //                      return null;
    //                  }
    //              }
    //      );
    //
    //      objectProxy.show(); // 调用代理类的方法
            FishProxy fishProxy = new FishProxy();
            Animal animal = (Animal) fishProxy.getFishProxy(new Fish());
            animal.show();
        }
    }
    
    • 一定要自己手敲,并debug一遍,这样你可以看清他生产的代理类。
    • 玩法很多,可以不传对象,直接传class,通过反射获取对象。可以在类上声明一个泛型<T>,直接通过泛型获取class,然后代理。。。。

    JDK动态代理讲解:

    invoke():
    表示代理对象要执行的功能代码,你的代理类要完成的功能就写在invoke()方法中

    (1)代理类要完成的功能:

    • 调动目标方法,执行目标方法的功能
    • 增强功能

    (2)invoke方法:
    invoke(Object proxy, Method method, Object[] args)

    • method: 目标类中的方法,jdk复制提供method对象
    • Object[] args: 目标类中的参数
    • Object proxy:jdk创建的代理对象,无需赋值

    (3)使用过程:
    1、invocationHandler接口:标识代理要干什么,定义目标代理类要完成的功能
    2、创建目标类实现的接口
    3、创建Invocationhandler接口的实现类,在invoke方法中完成代理类的功能书写。
    method.invoke()方法用来执行目标方法的
    4、使用Proxy.newProxyInstance静态方法,创建代理对象,代替new

    Proxy.newProxyInstance需要三个参数:

    • 目标类的类加载器
    • 类实现的接口,可以是多个
    • 代理类和原实现类的绑定办法(个人理解,不知道对不对)

    cglib动态代理(cglib字节码增强)

    • 类是在调用的时候才生成的

    • 使用目标对象的子类的方式实现的代理,它是在内存中构建一个子类对象从而实现对目标对象功能的扩展,能够在运行时动态生成字节码,可以解决目标对象没有实现接口的问题

    • 缺点:finalstatic修饰的类不能用cglib代理,因为它们不会被拦截,不会执行目标对象的额外业务方法

    举例:

    还是上面的鱼例子,但是没有接口了
    一个鱼的实现类

    public class Fish {
        public void show() {
            System.out.println("我是鱼");
        }
    }
    

    cglib对鱼的代理类

    public class FishCGProxy implements MethodInterceptor {
        Object object;
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
    
            /**Object o: 
             * Method method: 被代理类中的方法 
             * Object[] objects: 
             * MethodProxy methodProxy:
             */
            System.out.println("我是动态代理的鱼头");
            Object result = method.invoke(object, objects); // 方法执行,参数:target 目标对象 arr参数数组
            System.out.println("我是动态代理的鱼尾");
            return result;
        }
    
        public Object getFishCGProxy(Object target){
            this.object = target;
            Enhancer enhancer = new Enhancer();
            //设置父类,因为Cglib是针对指定的类生成一个子类,所以需要指定父类
            enhancer.setSuperclass(target.getClass());
            enhancer.setCallback(this); // 设置回调
    
            return enhancer.create();//创建并返回代理对象
        }
    }
    

    总结:

    (1)优缺点:

    • 在静态代理中的目标类很多的时候,可以使用动态代理,避免静态代理的缺点
    • 动态代理中目标类使用的即使很多,代理类的数量可以很少
    • 修改接口的方法的时候不会影响到代理类

    (2)概念:

    在程序执行的过程中,使用jdk的反射机制,创建代理类对象并动态地指定要代理的目标类。也就是说动态代理是一种创建java对象的能力,使得我们不用创建淘宝类或微商类,就能创建代理类对象

    (3)作用:

    控制访问:在代理中,控制是否可以调用目标对象的方法

    功能增强:可以在完成目标对象的调用时,附加一些额外的功能

    代理方式:

    • 静态代理:代理类是手工创建的,目标对象是规定的

    • 动态代理:使用反射机制,在程序执行的时候创建代理类对象,不用创建代理类的类文件,代理类的目标类是可以设置的

    (4)实现方式:

    jdk动态代理:

    使用java反射包中的类和接口实现动态代理的功能,反射包是java.lang.reflect,里面有三个类:InvocationHandler、Method、Proxy

    cglib动态代理:

    cglib是第三方的工具类

    原理是继承,通过继承目标类创建它的子类,在子类中重写父类中的方法,实现功能的修改

    要求目标类不能是final的,方法也不能是final的

    对于没有接口的类,创建动态代理就要使用cglib

    相关文章

      网友评论

          本文标题:java-代理模式

          本文链接:https://www.haomeiwen.com/subject/rpksgktx.html