美文网首页服务端之旅Java后端
由swizzle想到java中的Aop

由swizzle想到java中的Aop

作者: 纸简书生 | 来源:发表于2017-02-19 23:27 被阅读63次

    无论是在服务端开发还是客户端开发很多基本的思想和设计模式都是想通。比如去年记得阿里开源了BeeHive就是受了java中的Spring的思想,除此之外其实还有很多很多类似由一门语言迁移到另一门语言的三方库。

    平时在看一些iOS三方源码的时候发现都会用到swizzle技术,今天就来一起看看在java中,或者说andriod中通过什么方式可以达到类似的效果。

    读完本文,你可以了解到:

    1. iOS中的代理和java中的代理是怎么回事
    2. java中的静态代理与静态代理的原理
    3. 面向切片(AOP)的思想
    4. Android中Aop和iOS中Aop的比较。

    代理模式

    关于什么是代理设计模式,这里就不多说了。考虑到有些同学坑你还不是特别了解设计模式。那么如果想继续读下去还是去复习一下设计模式基础知识会好一点。这里推荐C#可以看《大话设计模式》,java可以看《Java设计模式》。不用一一看完,大致了解一下,需要用得到时候在仔细看也不迟。

    在iOS开发中经常会用到代理设计模式,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理。在iOS面试的时候经常会问的就是请介绍一下block、代理、通知三者之间的区别和联系。

    这里我用最为通俗的方式来解释一下我对代理的理解:就是在原有的代码里面打个标记(调用一个函数,这个函数一般情况下需要接口来约束一下。),当走到这里的时候就去其他执行代码,那么以后如果有更为复杂的逻辑就可以在其他地方改了。这样就达到一定的解耦。

    目的一般就是:

    • 不修改已有代码进行功能增强。
    • 剔除方法中的非核心逻辑,精简代码。

    Java静态代理

    所谓静态也就是在程序运行前就已经存在代理类的字节码文件,代理类在运行前就确定了。

    例子

    • 定义代理接口 (类似于OC中定义protocal):一般用于约束代理的行为
    public interface UserManager {
        public void addUser(String userId);
    }
    
    • 定义委托类(类似于OC中单独用一个类实现协议一个协议):具体处理业务
    public class UserManagerImpl implements UserManager {
        public void addUser(String userId) {
            System.out.print("add User----UserManagerImpl");
        }
    }
    
    • 静态代理类 (OC中委托类和代理类一般是放在一个类中,这点和java有比较大的差别)
    public class UserManagerImplProxy implements UserManager {
        // 目标对象
        private UserManager userManager;
        // 通过构造方法传入目标对象
        public UserManagerImplProxy(UserManager userManager){
            this.userManager=userManager;
        }
       public void addUser(String userId) {
            // 调用实际处理业务的委托类,达到解耦的目的
            userManager.addUser(userId);
            System.out.println("add User--UserManagerImplProxy" + userId);
            // userManager.addUser(userId);
        }
    }
    
    • 测试代码
    public static void main(String[] args) {
            UserManager userManagerImpl = new UserManagerImpl();
            UserManager userManagerImplProxy = new UserManagerImplProxy(userManagerImpl);
            userManagerImplProxy.addUser("1234");
        }
    

    运行结果:

    add User----UserManagerImpl1234
    add User--UserManagerImplProxy1234

    其实上面的代码有一点点Aop的雏形。也就是在不改变原来代码的情况下,增加新的功能。这里如果以后需要扩展业务就不用改UserManagerImplProxy代码了,直接在UserManagerImpl中修改业务逻辑就可以了。不知道自己讲清楚没有。

    始终记住aop,或者代理的目的就是:

    • 不修改已有代码进行功能增强。
    • 剔除方法中的非核心逻辑,精简代码。

    缺点明显

    静态代理在iOS其实用得非常之多,这里只是用java举了个例。一个很明显的缺点就是:

    • 代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。

    一个更为实际的例子,比如在iOS中要统计每个页面停留的时间,不可能每个控制器都去统计吧;在java中如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能。

    静态代理最为严重的缺点就是:静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

    java动态代理

    动态代理解决了静态代理的缺点,可以不与某个类或接口强绑定。它的原理也是有点复杂的,大致过程就是程序运行期间动态的生成一个比特数组,这个数组能够表示为目标类的子类,然后把数组交给ClassLoader进行解析,并返回子类的实例,这个子类的实例实际上可以看做目标类的代理类。(有点类似iOS中一个很著名的Aop框架Aspect的原理,都是在运行的时候生成子类。)

    java能够做到动态代理因为Java是个编译型的语言,首先会把Java代码编译成class字节码,然后JVM去加载、解释字节码,通过ClassLoader类可以在程序运行期间动态的加载字节码生成一个类。

    java实现动态代理有两种方式,一种是JDK自带的动态代理,要求目标类必须实现一个接口InvocationHandler,接口方法就是增强方法;另一种是CGLIB动态代理,要求目标类不能为final,否则不能生成子类。今天讲的就是用InvocationHandler实现动态代理。

    目前大致的如下方法实现Aop:

    接口InvocationHandler

    文档里面这样介绍的:

    public interface InvocationHandler
    InvocationHandler is the interface implemented by the invocation handler of a proxy instance.
    Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.

    大致意思就是:

    每一个动态代理类都必须要实现InvocationHandler这个接口,并且每个代理类的实例都关联到了一个handler,当我们通过代理对象调用一个方法的时候,这个方法的调用就会被转发为由InvocationHandler这个接口的 invoke 方法来进行调用。

    invoke方法:Object invoke(Object proxy, Method method, Object[] args) throws Throwable。三个参数意思分别是

    • proxy:  指代我们所代理的那个真实对象
    • method:  指代的是我们所要调用真实对象的某个方法的Method对象
    • args:   指代的是调用真实对象某个方法时接受的参数

    Proxy

    文档说明:

    Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.

    Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,但是我们用的最多的就是 newProxyInstance 这个方法

    public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException参数说明:

    • loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载。
    • interfaces: 一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了。
    • h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上。

    最后我们还是来看看实际例子

    例子

    接口还是用UserManager.

    • 定义代理接口:
    public interface UserManager {
        public void addUser(String userId);
    }
    
    • 定义委托类:
    public class UserManageDynamicImpl implements UserManager {
        public void addUser(String userId) {
            System.out.println("UserManageDynamicImpl:   add User" + userId);
        }
    }
    
    • 定义动态代理类(日志记录)
    public class LogInvocationHander implements InvocationHandler {
        // 引入日志框架
        private Logger logger = Logger.getLogger(this.getClass().getSimpleName());
    
        // 真实对象,目标代理类
        private Object target;
        // 代理类
        private Object proxy;
    
        // 存储日记记录字典
        private static HashMap<Class<?>, LogInvocationHander> invoHandlers = new HashMap<Class<?>, LogInvocationHander>();
    
        // java中单例的写法
        private LogInvocationHander() {
    
        }
    
        /**
         * 类方法
         * @param clazz
         * @param <T>
         * @return
         */
        public synchronized static <T> T  getProxyInstance(Class<T> clazz) {
            LogInvocationHander invoHandler = invoHandlers.get(clazz);
    
            if (invoHandler == null) {
                invoHandler = new LogInvocationHander();
                try {
                    T tar  = clazz.newInstance();
                     // 设置目标代理类
                    invoHandler.setTarget(tar);
                    // Proxy这个类的作用就是用来动态创建一个代理对象的类,它提供了许多的方法,
                    // 但是我们用的最多的就是 newProxyInstance 这个方法
    
                    /*loader:  一个ClassLoader对象,定义了由哪个ClassLoader对象来对生成的代理对象进行加载
    
                    interfaces:  一个Interface对象的数组,表示的是我将要给我需要代理的对象提供一组什么接口,如果我提供了一组接口给它,那么这个代理对象就宣称实现了该接口(多态),这样我就能调用这组接口中的方法了
    
                    h:  一个InvocationHandler对象,表示的是当我这个动态代理对象在调用方法的时候,会关联到哪一个InvocationHandler对象上
                    */
                   // 设置代理类
                    invoHandler.setProxy(Proxy.newProxyInstance(tar.getClass().getClassLoader(),tar.getClass().getInterfaces(),invoHandler));
    
                } catch (Exception e) {
                    e.printStackTrace();
                }
    
                invoHandlers.put(clazz, invoHandler);
            }
    
            return (T)invoHandler.getProxy();
        }
    
        public Object getTarget() {
            return target;
        }
    
        public void setTarget(Object target) {
            this.target = target;
        }
    
        public Object getProxy() {
            return proxy;
        }
    
        public void setProxy(Object proxy) {
            this.proxy = proxy;
        }
    
        /**
         proxy:  指代我们所代理的那个真实对象
         method: 指代的是我们所要调用真实对象的某个方法的Method对象
         args:  指代的是调用真实对象某个方法时接受的参数
         */
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //  在代理真实对象前我们可以添加一些自己的操作
            logger.info("before call");
    
            // 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象关联的handler对象的invoke方法来进行调用
            Object result = method.invoke(target, args); // 执行业务处理
    
            // 打印日志
            logger.info("____invoke method: " + method.getName()
                    + "; args: " + (null == args ? "null" : Arrays.asList(args).toString())
                    + "; return: " + result);
    
            //  在代理真实对象后我们也可以添加一些自己的操作
            logger.info("after call");
    
            return result;
        }
    }
    

    上面的代码注释已经足够说明问题了。还有一部分需要解释下:

    • 通过 Proxy.newProxyInstance 创建的代理对象是在jvm运行时动态生成的一个对象,它并不是我们的InvocationHandler类型,也不是我们定义的那组接口的类型,而是在运行是动态生成的一个对象,并且命名方式都是这样的形式,以$开头,proxy为中,最后一个数字表示对象的标号。

    • 通过代理对象来调用实现的那种接口中的方法,这个时候程序就会跳转到由这个代理对象关联到的 handler 中的invoke方法去执行,而我们的这个 handler 对象又接受了一个 target类型的参数,表示我要代理的就是这个真实对象,所以此时就会调用 handler 中的invoke方法去执行。

    相关文章

      网友评论

        本文标题:由swizzle想到java中的Aop

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