zoukankan      html  css  js  c++  java
  • 代理模式(动态代理、静态代理)

    1、代理模式

    (1)概念

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

    • 功能增强:其中目标对象实现真正的功能,但是代理对象可以对目标对象的功能做进一步的扩充

    (2)设计模式

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

    (3)举例

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

    (4)作用

    功能增强:在原有的功能上增加额外的功能

    控制访问:代理类不让你访问目标类

    (5)实现代理的方式

    静态代理:代理类是自己手动创建的,所需要代理的目标类是确定的,实现简单容易理解

    2、静态代理

    • 案例一

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

    (1)创建一个接口:

    interface Animal {
      public abstract   void show();
    }

    (2)代理类:

    public class Fish implements Animal {
        Sheep sheep=new Sheep();
        @Override
        public void show() {
            sheep.show();
           System.out.println("我爱游泳!");
        }
    }

    创建被代理类的对象,在代理类的方法中调用被代理类的方法,在这个过程中可以实现对被代理类的功能的扩充。

    (3)被代理类:

    public class Sheep implements Animal{
        @Override
        public void show() {
            System.out.println("我爱吃青草");
        }
    }

    (4)测试类:

    public class Test {
        public static void main(String [] args){
            Fish fish=new Fish();
            fish.show();
        }
    }

    测试结果:

    Sheep最爱吃青草,在他被fish类代理之后还学会了一项新的技能:游泳

    (5)静态代理的缺点

    如果有多个类需要代理,那么就需要创建多个代理类分别代理目标对象,工作量较大,不利于维护。

    •  案例二

    (1)创建一个接口

    public interface UsbSell {
        float sell(float price);
    }

    这里面是厂家和商家都要完成的功能

    (2)创建工厂类

    //厂家,不接受用户的单独购买,需要商家
    public class UsbFactory implements UsbSell {
        @Override
        public float sell(float price) {
            System.out.println("目标类");
            return 70;//厂家的U盘价格
        }
    }

    在工程类中定义的是U盘的出厂价,但是改价格是不能被普通的消费者使用的,只能是商家使用

    (3)创建商家类(代理类)

    淘宝类:

    public class TaoBao implements UsbSell {
        //声明商家代理的是哪一个厂家
        private UsbSell factory=new UsbFactory();
        @Override
        public float sell(float price) {//调用目标方法,增强功能,增加价格,优惠券
            float p=factory.sell(price);
            float p1=p+25;
            System.out.println("返回5元优惠券");
            return p1;
        }
    }

    微商类:

    public class WeiShang implements UsbSell {
        private UsbSell factory=new UsbFactory();
        @Override
        public float sell(float price) {//调用目标方法,增强功能
            float p = factory.sell(price);
            float p2=p+1;
            return p2;
        }
    }

    这两个类都实现了与工厂类相同的接口,增强了工程类(目标类)的方法

    (4)创建测试类(普通消费者)

    public class ShopMain {
        public static void main(String[] args) {
            TaoBao taoBao=new TaoBao();
            float price=taoBao.sell(100);
            System.out.println("淘宝的销售价:"+price);
        }
    }
    目标类
    返回5元优惠券
    淘宝的销售价:95.0

    (5)优点

    • 实现简单
    • 容易理解

    (6)缺点

    当目标类增多了,代理类也需要增加(例如:上例中创建了一个工厂类,那么该类只能代表一个工厂,当建立了其它品牌的工厂后,还需要为该工厂创建代理类)

    当接口的方法增加或修改的时候,很多类都需要修改。因为,目标类和代理类都实现了相同的接口

    3、动态代理(JDK代理,接口代理)

    • 案例一

    (1)好处

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

    可以实现AOP编程

    解耦

    (2)创建一个接口

    interface Animal {
      public abstract   void show();
    }

    (3)创建代理类

    实现动态代理需要将要扩展的功能写在InvocationHandler实现类里

    使用newProxyInstance方法,该方法需要接收三个参数

    代理对象不需要实现接口,但是目标对象一定要实现接口

    public class Fish implements Animal {
        @Override
        public void show() {
           Animal objectProxy= (Animal) Proxy.newProxyInstance(//创建接口实例
                   Animal.class.getClassLoader(),//用目标对象有相同的类加载器,动态代理类运行时创建,将类加载到内存(反射)
                   new Class[]{Animal.class},//被代理的类所实现的接口(可以是多个)
                   new InvocationHandler() {//绑定代理类的方法(我们自己写的,代理类要完成的功能)
                       @Override//提供invoke方法,代理类的每一个方法执行时,都将调用一次invoke
                       public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                           System.out.println("我在前!!");
                           method.invoke(new Sheep(),args);//执行目标对象的方法
                           System.out.println("我在后!!");
                           return null;
                       }//proxy:代理后的实例对象
                        //method:对象被调用的方法
                        //args:调用时的参数
                   }
           );
           objectProxy.show();
        }
    }

    (4)被代理类

    public class Sheep implements Animal{
        @Override
        public void show() {
            System.out.println("我爱吃青草");
        }
    }

    (5)测试类

    public class Test {
        public static void main(String [] args){
            Fish fish=new Fish();
            fish.show();
        }
    }

    测试结果:

    • Method的使用

    (1)创建接口

    public interface HelloService {
        public void sayHello(String name);
    }

    (2)接口的实现类

    public class HelloServiceImpl  implements HelloService {
        public void sayHello(String name){
            System.out.println("hello"+name);
        }
    }

    (3)测试类

    普通方式调用sayHello方法:

    public class Test {
        public static void main(String[] args) {
            HelloService helloService=new HelloServiceImpl();
            helloService.sayHello("zhai");
        }
    }
    hellozhai

    使用反射执行sayHello方法:

    public class Test {
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            //使用反射机制执行sayHello方法,核心是Method类中的方法
            HelloService target=new HelloServiceImpl();
            //获取sayHello名称对于Method的对象
            Method method=HelloService.class.getMethod("sayHello",String.class);
            //通过Method可以执行方法的调用
            //invoke是Method类中的一个方法,表示添加方法的调用,参数1:表示对象,要执行这个对象的方法
            //参数2:方法执行的时候的参数值   参数3:方法要执行的时候的返回值
            //表示执行target对象的sayHello方法,参数是zhai,method代表的是执行的方法
            Object ret=method.invoke(target,"zhai");
        }
    }
    hellozhai

    (4)为接口添加一个实现类:

    public class HelloServiceImpl2 implements HelloService{
        public void sayHello(String name){
            System.out.println("nihao"+name);
        }
    }

    测试类:

    public class Test {
        public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            //使用反射机制执行sayHello方法,核心是Method类中的方法
            HelloService target=new HelloServiceImpl();
            //获取sayHello名称对于Method的对象
            Method method=HelloService.class.getMethod("sayHello",String.class);
            //通过Method可以执行方法的调用
            //invoke是Method类中的一个方法,表示添加方法的调用,参数1:表示对象,要执行这个对象的方法
            //参数2:方法执行的时候的参数值   参数3:方法要执行的时候的返回值
            //表示执行target对象的sayHello方法,参数是zhai,method代表的是执行的方法
            Object ret=method.invoke(target,"zhai");
    
            HelloService target2= new HelloServiceImpl2();
            Object ret2=method.invoke(target2,"zhai");
        }
    }
    hellozhai
    nihaozhai

    也就是说method代表的是sayHello方法,也就是目标类的方法

    • JDK动态代理的实现

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

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

    调用目标方法,执行目标方法的功能

    增强功能

    (2)invoke方法

    invoke(Object proxy, Method method, Object[] args)

    method:目标类中的方法,jdk负责提供method对象

    Object[] args:目标类中的参数

    Object proxy:jdk创建的代理对象,无需赋值

    (3)使用过程

    • InvocationHandler接口:表示代理要干什么(定义目标类要完成的功能)
    • 创建目标类实现接口
    • 创建InvocationHandler接口的实现类,在invoke方法中完成代理类的功能

    invoke方法:重写invoke方法,把原来静态代理中代理类要完成的功能写在方法内

    method.invoke()是用来执行目标方法的

    • 使用Proxy类的静态方法,来创建代理对象,并把返回值转换为接口类型

    核心的对象,创建代理对象,之前的对象都是new类的构造方法,现在我们使用的是Proxy类的方法,代替new的使用

    方法newProxyInstance(),作用是创建代理对象,需要三个参数

    • 动态代理案例二

    (1)创建接口

    public interface UsbSell {
        float sell(float price);
    }

    (2)创建U盘的工厂类

    public class UsbFactory implements UsbSell {
        @Override
        public float sell(float price) {
            System.out.println("目标类");
            return 70f;//厂家的U盘价格
        }
    }

    (3)创建InvocationHandler 接口的实现类

    //必须实现InvocationHandler接口,完成代理类的功能(调用目标方法、功能增强)
    public class MySellHandler implements InvocationHandler {
        private  Object target=null;
        //动态代理的目标对象是活动的,需要传入进来,传进来的是谁就给谁创建代理
        public MySellHandler(Object target){
            this.target=target;
        }
        //args代表传进来的参数(100)
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            Object res=null;
            res=method.invoke(target,args);
            if(res!=null){
                Float price=(Float)res;
                price=price+25;
                res=price;
            }
            System.out.println("淘宝商家返回5元优惠券");
            return res;
        }
    }

    (4)测试类

    public class Test {
        public static void main(String[] args) {
             //创建目标对象
            UsbSell usbFactory=new UsbFactory();
            //创建invocationHandler对象
            InvocationHandler invocationHandler=new MySellHandler(usbFactory);
            //创建代理对象
            UsbSell proxy= (UsbSell) Proxy.newProxyInstance(
                    usbFactory.getClass().getClassLoader(),
                    usbFactory.getClass().getInterfaces(),
                    invocationHandler
            );
            System.out.println(proxy.sell(100));
        }
    }

    (5)添加接口的方法

    public interface UsbSell {
        float sell(float price);
        void hello();
    }
    public class UsbFactory implements UsbSell {
        @Override
        public float sell(float price) {
            System.out.println("目标类");
            return 70f;//厂家的U盘价格
        }
    
        @Override
        public void hello() {
            System.out.println("hello");
        }
    }

    只需要修改接口的方法和目标类的方法,用Proxy对象调用即可

    在不修改工厂类和接口的情况下可以增加目标类(工厂类)的方法的功能

    jdk的动态代理必须有接口,目标类一定要实现接口,没有接口的时候使用cglib动态代理 

    4、cglib代理(cglib字节码增强)

    (1)概念:

    需要导入jar包:核心包和依赖包(spring_core.jar已经集成了这两个包,因此,导入此包即可)

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

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

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

    • 总结

    (1)优点

    在静态代理中的目标类很多的时候,可以使用动态代理,避免静态代理的缺点

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

    (2)概念

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

    (3)作用

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

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

    代理方式:

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

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

    (4)实现方式

    jdk动态代理:

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

    cglib动态代理:

    cglib是第三方的工具类

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

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

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

  • 相关阅读:
    时装画基础知识--如何画人体
    马士兵java视频学习顺序
    Mysql 中文字符乱码问题
    zendstudio 设置默认编码 utf-8 gbk
    MYSQL 本地无ROOT权限 忘记密码
    windows 3389 远程
    windows 老掉牙CMD的命令
    mysql-常用注入渗透手法
    ubuntu 添加多个IP
    windows下简单配置apache
  • 原文地址:https://www.cnblogs.com/zhai1997/p/12257150.html
Copyright © 2011-2022 走看看