乐趣区

关于aop:代理模式AOP绪论

本篇能够了解为 Spring AOP 的铺垫,以前大略会用 Spring 框架提供的 AOP,然而对 AOP 还不是很理解,于是打算零碎的梳理一下代理模式和 AOP。

简略的谈一下代理

软件世界是对事实世界形象,所以软件世界的某些概念也不是凭空产生,也是从事实世界中脱胎而来,就像多态这个概念一样,Java 官网出的指导书《The Java™ Tutorials》在讲多态的时候,首先提到多态首先是一个生物学的概念。那么设计模式中的代理模式,咱们也能够认为是从事实世界中形象而来,在事实世界代理这个词也是很常见的,比方房产中介代理房东的房子,经纪人代理明星谈商演。基本上代理人都在肯定范畴内代理被代表人的事务。

那咱们为什么须要代理模式呢? 咱们事实上也能够从事实世界去寻找答案,为什么房东要找房产中介呢? 因为房东也是人,也有本人的生存,不可能将全身心都搁置在将本人的房屋出租上,不违心做扭转。那么软件世界的代理模式,我认为也是有着同样的起因,原来的某个对象须要再度承接一个性能,然而咱们并不违心批改对象从属的类,起因可能有很多,兴许旧的对象曾经对外应用,一旦批改兴许会有其余意想不到的反馈,兴许咱们没方法批改等等。起因有很多,同时对一个过来的类批改也不合乎开闭准则,对扩大凋谢,对批改关闭。于是咱们就心愿在不批改指标对象的性能前提下,对指标的性能进行扩大。

网上的大多数博客在讲代理模式的时候,大多都会从一个接口动手, 而后需要是扩大实现对该接口实现类的加强,而后写代码演示。像是上面这样:

public interface IRentHouse {
    /**
     * 实现该接口的类将可能对外提供房子
     */
    void rentHouse();}
public class Landlord implements IRentHouse {
    @Override
    public void rentHouse() {System.out.println("我向你提供房子.....");
    }
}
public class HouseAgent implements IRentHouse {

    @Override
    public void rentHouse() {System.out.println("在该类的办法之前执行该办法");
        IRentHouse landlord = new Landlord();
        landlord.rentHouse();
        System.out.println("在该类的办法之后执行该办法");
    }
}
public class Test {public static void main(String[] args) {IRentHouse landlord = new HouseAgent();
        landlord.rentHouse();}
}

测试后果:

许多博客在刚讲动态代理的时候通常会从这里动手,在不扭转原来类同时,对原来的类进行增强。对,这算是一个相当事实的需要,尤其是在原来的类在零碎中应用的比拟多的状况下,且运行比较稳定,一旦改变就算是再小心翼翼,也无奈保障对原来的零碎一点影响都没有,最好的办法是不改,那如何加强原有的指标对象,你新加强的个别就是要满足新的调用者的需要,那我就新增一个类吧,供你调用。很完满,那问题又来了,为什么各个博客都是从接口登程呢?

因为代理类和指标类实现雷同接口,是为了尽可能的保障代理对象的内部结构和指标对象保持一致,这样咱们对代理对象的操作都能够转移到指标对象身上,咱们只用着眼于加强代码的编写。

从面向对象的设计角度来看, 如果我这个时候我不搁置一个顶层的接口,像下面我将接口中的办法挪动至类中,不再有接口。那你这个时候加强又改该怎么做呢?这就要聊聊类、接口、抽象类的区别了,这也是常见的面试题,在学对象的时候,咱们经常和过来的面向过程进行比拟,强调的一句话是类领有属性和行为,但在面向过程系语言自身是不提供这种个性的。在学习 Java 的时候,在学完类,接着就是抽象类和接口,咱们能够说接口强调行为,是一种契约,咱们进行面向对象设计的时候,将多个类行为形象进去,放到接口中,这样扩展性更强,该怎么了解这个扩展性更强呢?就像下面那样,面向接口编程。

假如咱们固执的不进行形象,许多类中都搁置了雷同的办法,那么在应用的时候就很难对旧有的类进行扩大,进行降级,咱们不得不改变旧有的类。那抽象类呢?该怎么了解抽象类呢? 如果接口是对许多类行为的形象,那么抽象类就是对这一类对象行为的形象,形象的档次是不一样的。就像是乳制品企业大家都要实现一个规范,然而怎么实现的国家并不论。抽象类形象的是鸟、蜂鸟、老鹰。这一个体系的类的共性,比方都会飞。

其实到这里,动态代理基本上就讲完了,代理模式着眼于在不扭转旧的类的根底上进行加强,那么加强通常说的就是办法,行为加强,属性是减少。那么为了扩展性强,咱们设计的时候能够将行为搁置在接口中或者你放在抽象类里也行,这样咱们就能够无缝加强。对于设计模式,我感觉咱们不要被拘谨住,我感觉设计模式是一种理念,践行的形式不一样而已。

动态代理着眼于加强一个类的性能,那么当咱们的零碎中有很多类都须要加强的时候,就有点不适宜了,假如有三十个类都须要加强,且设计都比拟好,都将行为形象搁置到了接口中,这种状况下,你总不能写三十个动态代理类吧。当然不能让咱们本人写,咱们让 JVM 帮咱们写。这也就是动静代理。

动静代理

换个角度看创建对象的过程

对于 Java 程序员来说,一个对象的创立过程可能是这样的:

咱们在思考下,将面向对象的这种思维贯彻到底,思考一下,作为 Java 程序员咱们应用类来对事实世界的事物进行建模,那么类的行为是否也应该建模呢?也就是形容类的类,也就是位于 JDK 中的类: java.lang.Class。每个类仅会有一个 Class 对象,从这个角度来看,Java 中类的关系构造如下图所示:

所以如果我想创立一个对象,JVM 确实会将该类的字节码加载进入 JVM 中,那么在该对象创立之前,该对象的 Class 对象会后行创立, 所以对象创立过程就变成了上面这样:

在创立任何类的对象之前,JVM 会首先创立给类对应的 Class 对象,每个类仅对应一个,如果曾经创立则不再创立。而后在创立该类的时候,用于获取该类的元信息。

基于接口的代理

咱们这里再度强调一下咱们的指标:

  • 咱们有一批类,而后咱们想在不扭转它们的根底之上,加强它们, 咱们还心愿只着眼于编写加强指标对象代码的编写。
  • 咱们还心愿由程序来编写这些类,而不是由程序员来编写,因为太多了。

第一个指标咱们能够让指标对象和代理对象实现独特的接口,这样咱们就能只着眼于编写指标对象代码的编写。

那第二个指标该如何实现呢? 咱们晓得接口是无奈实例化的,咱们下面讲了指标对象有一个 Class 类对象,领有该类对象的构造方法,字段等信息。咱们通过 Class 类对象就能够代理指标类,那对于加强代码的编写,JDK 提供了 java.lang.reflect.InvocationHandler(接口)和 java.lang.reflect.Proxy 类帮忙咱们在运行时产生接口的实现类。

咱们再回忆一下咱们的需要,不想代理类,让 JVM 写。那么怎么让 JVM 晓得你要代理哪个类呢?个别的设计思维就是首先你要告知代理类和指标类须要独特实现的接口,你要告知要代理的指标类是哪一个类由哪一个类加载器加载。这也就是 Proxy 类的:
getProxyClass 办法,咱们先大抵看一下这个办法:

public static Class<?> getProxyClass(ClassLoader loader,  Class<?>... interfaces)throws IllegalArgumentException

该办法就是依照上述的思维设计的,第一个参数为指标类的类加载器,第二个参数为代理类和指标类独特实现的接口。
那加强呢? 说好的加强呢? 这就跟下面的 InvocationHandler 接口有关系了,通过 getProxyClass 获取代理类,这是 JDK 为咱们创立的代理类,然而它没有本体 (或者 JDK 在为咱们创立完本地就把这个类删除掉了),只能通过 Class 类对象,通过反射接口来间接的创建对象。
所以下面的动态代理如果革新成动态代理的话,就能够这么革新:

 private static void dynamicProxy() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        /**
         *  第一个参数为指标类的加载器
         *  第二个参数为指标类和代理类须要实现的接口
         */
        Class<?> rentHouseProxy = Proxy.getProxyClass(IRentHouse.class.getClassLoader(), IRentHouse.class);
        // 这种由 JDK 动静代理的类,会有一个参数类型为 InvocationHandler 的构造函数。咱们通过反射来获取
        Constructor<?> constructor = rentHouseProxy.getConstructor(InvocationHandler.class);
        // 通过反射创建对象,向其传入 InvocationHandler 对象,指标类和代理类独特实现的接口中的办法被调用时, 会先调用                               
        // InvocationHandler 的 invoke 办法有指标对象须要加强的办法。为指标对象须要加强的办法调用所须要的的参数
        IRentHouse iRentHouseProxy = (IRentHouse) constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {IRentHouse iRentHouse = new Landlord();
                System.out.println("办法调用之前.............");
                Object result = method.invoke(iRentHouse, args);
                System.out.println("办法调用之后.............");
                return result;
            }
        });
        iRentHouseProxy.rentHouse();}   

下面这种写法还要咱们在调用的时候显式的 new 一下咱们想要加强的类,属于硬编码,不具备通用性,假如我想动静代理另一个类,那我还得再写一个吗?事实上我还能够这么写:

   private static Object getProxy(final Object target) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Class<?> proxyClazz = Proxy.getProxyClass(target.getClass().getClassLoader(), target.getClass().getInterfaces());
        Constructor<?> constructor = proxyClazz.getConstructor(InvocationHandler.class);
        Object proxy = constructor.newInstance(new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println(method.getName() + "办法开始执行..........");
                Object object = method.invoke(target, args);
                System.out.println(method.getName() + "办法执行完结..........");
                return object;
            }
        });
        return proxy;
    }

这样咱们就将指标对象,传递进来了,通用性更强。事实上还能够这么写:

private static Object getProxyPlus(final Object target) {Object proxy = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("办法开始执行..........");
                Object obj = method.invoke(target, args);
                System.out.println("办法执行完结..........");
                return obj;
            }
        });
        return proxy;
    }

CGLIB 代理简介

而后我想加强一个类,这个类凑巧没有实现接口怎么办?这就须要 Cglib 了,其实情理倒是相似的,你有接口,我就给你创立实现类,你没接口还要加强,我就给你动静的创立子类。通过“继承”能够继承父类所有的公开办法,而后能够重写这些办法,在重写时对这些办法加强,这就是 cglib 的思维。

Cglib 代理一个类的通常思路是这样的,首先实现 MethodInterceptor 接口,MethodInterceptor 接口简介:

咱们能够这么实现:

public class MyMethodInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {System.out.println("办法执行前执行");
        System.out.println(args);
        Object returnValue = methodProxy.invokeSuper(obj, args);
        System.out.println("办法执行后执行");
        return returnValue;
    }
}

留神调用在 intercept 办法中调用代理类的办法,会再度回到 intercept 办法中,造成死循环。intercept 就能够认为是代理类的加强办法,本人调用本人会导致递归,所以搜门下面的调用是用 methodProxy 调用继承的父类的函数,这就属于代理类。
测试代码:

private static void cglibDemo() {
        // 个别都是从 Enhancer 动手
        Enhancer enhancer = new Enhancer();
        // 设置须要加强的类。enhancer.setSuperclass(Car.class);
        // 设置加强类的理论加强者
        enhancer.setCallback(new MyMethodInterceptor());
        // 创立理论的代理类
        Car car = (Car) enhancer.create();
        System.out.println(car.getBrand());
    }

这种加强是对类所有的私有办法进行加强。这里对于 Cglib 的介绍就到这里,在学习 Cglib 的动静代理的时候也查了网上的一些材料,怎么说呢? 总是不那么尽如人意,总是存在这样那样的缺憾。想想还是本人做翻译吧,然而 Cglib 的文档又略微有些宏大,想想还是不放在这里吧,心愿各位同学留神领会思维就好。

总结一下

不论是动静代理还是动态代理都是着眼于在不批改原来类的根底上进行加强,动态代理是咱们手动的编写指标类的加强类,这种代理在咱们的代理类有很多的时候就有些不实用了,咱们并不想为每一个须要加强的累都加一个代理类。这也就是须要动静代理的时候,让 JVM 帮咱们创立代理类。创立的代理类也有两种模式,一种是基于接口的(JDK 官网提供的),另一种是基于类的(Cglib 来实现)。基于接口的是在运行时创立接口的实现类,基于类是在运行时创立须要加强类的子类。

参考资料

  • 设计模式(四)——搞懂什么是代理模式
  • Java 动静代理作用是什么?
  • Java bean 是个什么概念?
  • 类加载与 Class 对象
  • CGLIB(Code Generation Library)详解
  • Java 两种动静代理 JDK 动静代理和 CGLIB 动静代理
  • Java Proxy 和 CGLIB 动静代理原理
退出移动版