Bootstrap

代理设计模式

代理模式-AOP

1. 什么是代理模式

定义:为其他对象提供一种代理以控制对这个对象的访问

作用:为其他对象提供一种代理以控制对这个对象的访问。代理对象可以在客户端和目标对象(被代理对象)之间起到中介的作用

即在不改变源码的情况下,实现对被代理对象的功能扩展

抽象角色:(subject)通过接口或抽象类声明被代理对象实现的业务方法

角色具有的行为放入该接口或抽象类中

代理角色(Proxy):实现抽象角色,是真实角色的代理,通过真实角色的业务逻辑方法来实现抽象方法,并可以附加自己的操作

真实角色(RealSubject):实现抽象角色,定义真实角色所要实现的业务逻辑,供代理角色调用

2. 优点

  • 优点:
    • 可以屏蔽用户真正请求的对象,是用户程序和真实对象解耦。
    • 使用代理来担当那些创建耗时的对象的替身。
    • 代理对象可以在用户端和被代理角色之间起到中介的作用
    • 代理对象中可以有更多的操作,且不改变被代理对象的源码

3. 静态代理

静态代理实现:目标类和代理类实现了相同的接口,在代理类中依赖了目标类,代理类的方法中调用了目标类的方法,并做了一些增强性的工作。

静态是指代理类在编译阶段生成,在程序运行之前就已经存在了

要求:

  1. 代理对象和真实对象都要实现同一个接口
  2. 代理对象要代理真实角色,所以需要传参【真实角色】——》通过构造函数完成传参
  3. 好处:代理对象可以做很多真实对象做不了的事情;真实对象专注做自己的事情
  • 实例1——租房

    • 租房接口
    public interface RentingAHouse {
        public void rentingAHouse();
    }
    
    • 房东
    public class Landlord implements RentingAHouse{
        @Override
        public void rentingAHouse() {
            System.out.println("租房子");
        }
    }
    
    • 租房中介
    public class Intermediary implements RentingAHouse{
        private RentingAHouse target;
        public Intermediary(RentingAHouse target){
            this.target=target;
        }
        @Override
        public void rentingAHouse() {
            System.out.println("看房子");
            target.rentingAHouse();
            System.out.println("收租金");
        }
    }
    
    • 测试
    • 调用方法时不使用目标类,而是使用代理类来进行调用,增强操作可以替换成其他代码或方法
    public class Test {
        public static void main(String[] args) {
            Intermediary intermediary=new Intermediary(new Landlord() );
            intermediary.rentingAHouse();
        }
    }
    
  • 实例2-婚前公司

public class StaticProxy {
    public static void main(String[] args) {
        // You you=new You();
        WeddingCompany wedding =new WeddingCompany(new You());
        wedding.HappyMarry();
        //上述两行等价于
        new WeddingCompany(new You()).HappyMarry();
    }
}
//1.创建一个接口Marry
interface Marry{
    //HappyMarry方法
    void HappyMarry();
}
//2. 创建真实角色,继承Marry接口
class You implements Marry{
    @Override
    public void HappyMarry() {
        System.out.println("结婚快乐!!!!!");
    }
}
//3.创建代理角色——婚庆公司,继承Marry接口
class WeddingCompany implements Marry{
    //3.1 需要构造有参方法,以便传递真实角色的值
    private Marry targer;

    public WeddingCompany(Marry targer) {
        this.targer = targer;
    }
    @Override
    public void HappyMarry() {
        //结婚前
        before();
        this.targer.HappyMarry();
        after();
    }
    private void before() {
        System.out.println("结婚之前布置场景");
    }
    private void after() {
        System.out.println("结婚之后收尾款");
    }
}

缺点:代理对象必须提前写出, 需要为每一个被代理的接口或类都编写一个代理类,工作量太大 。如果接口层发生了变化,代理对象的代码也要进行维护。

4. 动态代理

在运行期生成代理类

4.1 JDK代理

Proxy类:创建代理角色的方法。

InvocationHandler接口:提供的执行被代理角色方法的方法。

调用Proxy类的静态方法newProxyInstance即可,该方法会返回代理类对象。

static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler h )
接收的三个参数依次为:

  • ClassLoader loader:指定当前目标对象使用类加载器,写法固定
  • Class<?>[] interfaces:目标对象实现的接口的类型,写法固定
  • InvocationHandler h:提供的执行被代理角色方法的方法。
  • 实例

    • 接口
    public interface RentingAHouse {
        public void rentingAHouse();
    }
    
    • 被代理类
    public class Landlord implements RentingAHouse {
        @Override
        public void rentingAHouse() {
            System.out.println("租房子");
        }
    }
    
    • 代理类
    public class JdkProxy {
        //被代理的类必须实现接口
        private RentingAHouse target;
        public JdkProxy(RentingAHouse target){
            this.target=target;
        }
        //获取代理对象
        public RentingAHouse getProxyObject(){
            /*
                ClassLoader loader,被代理对象的类加载器
                Class<?>[] interfaces,被代理对象实现的接口的反射类。被代理的方法
                InvocationHandler h,回调函数
             */
            //获取被代理对象的类加载器
            ClassLoader classLoader = target.getClass().getClassLoader();
            //被代理对象必须实现了接口
            Class<?>[] interfaces = target.getClass().getInterfaces();
            InvocationHandler h=new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    System.out.println("看房子");
                    Object result= method.invoke(target);
                    System.out.println("收房租");
                    return result;
                }
            };
            //通过jdk代理类Proxy获取代理对象
            Object o = Proxy.newProxyInstance(classLoader,interfaces,h);
            return (RentingAHouse) o;
        }
    }
    
    • 测试
    public class Test {
        public static void main(String[] args) {
           JdkProxy jdkProxy=new JdkProxy(new Landlord());
            RentingAHouse proxyObject = (RentingAHouse) jdkProxy.getProxyObject();
            proxyObject.rentingAHouse();
        }
    }
    

    缺点:同静态代理一样,目标对象即被代理类必须实现一个或多个接口

4.2 Cglib代理

前提条件:

  • 需要引入cglib的jar文件,由于Spring的核心包中已经包括了Cglib功能,所以也可以直接引入spring-core-3.2.5jar
  • 目标类不能为final
  • 目标对象的方法如果为final/static,那么就不会被拦截,即不会执行目标对象额外的业务方法。

原理: 通过字节码技术为一个类创建子类,并在子类中采用方法拦截的技术拦截所有父类方法的调用。由于是通过创建子类来代理父类,因此不能代理被final修饰的类(代理final修饰的类会抛异常,代理final修饰的方法只会原样执行委托类的方法而不能做任何拦截)。

  • 实例

    • 被代理的类——不能用final修饰,要作为父类
    public class Landlord  {
        public void rentingAHouse() {
            System.out.println("租房子");
        }
    }
    
    • 代理类——子类
    public class ProxyFactory implements MethodInterceptor {
        //维护目标对象
        private Object target;
        public ProxyFactory(Object target){
            this.target=target;
        }
        //给目标对象创建一个代理对象,得到代理对象
        public Object getProxyInstance(){
            //工具类
            Enhancer en=new Enhancer();
            //设置父类——cglib
            en.setSuperclass(target.getClass());//设置被代理对象的父类
            //设置回调函数
            en.setCallback(this);
            //创建子类(代理对象)
            return en.create();
        }
    
        //在执行任何目标方法前先执行拦截方法
        @Override
        public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
            System.out.println("看房子!");
            //执行被代理类对象的方法
            Object invoke = method.invoke(target);
            System.out.println("收房租!");
            return invoke;
        }
    }
    
    • 测试
    public class Main {
        public static void main(String[] args) {
            ProxyFactory proxyFactory=new ProxyFactory(new Landlord());
            Landlord proxyInstance = (Landlord) proxyFactory.getProxyInstance();
            proxyInstance.rentingAHouse();
        }
    }
    

5. 总结

三种代理模式各有优缺点和相应的适用范围,主要看目标对象是否实现了接口。以Spring框架所选择的代理模式举例

在Spring的AOP编程中:
如果加入容器的目标对象有实现接口,用JDK代理
如果目标对象没有实现接口,用Cglib代理

  • 静态代理:代理类在编译阶段生成,在程序运行之前就已经存在了

    • 要实现接口
    • 缺点:要为每个被代理的接口实现代理类,代码冗余
  • 动态代理:在运行时生成代理类。

    根据是否需要实现接口分为两种

    • JDK动态代理:需要被代理类实现接口。

      • 通过调用Proxy代理类的newProxyInstance静态方法获得代理类对象
    • Cgilb动态代理:不需要被代理类实现接口

      • 被代理类不能被final修饰,因为要作为父类
      • 通过创建子类代理父类,在子类中采用方法拦截技术拦截所有父类方法的调用。
;