美文网首页
Spring aop 原理解析(AOP 简介 和 Java 动态

Spring aop 原理解析(AOP 简介 和 Java 动态

作者: kokokokokoishi | 来源:发表于2018-03-08 13:44 被阅读0次

    AOP是什么

    在软件业,AOP为Aspect Oriented Programming的缩写,意为:[面向切面编程] 通过[预编译]方式和运行期动态代理实现程序功能的统一维护的一种技术(摘自百度百科)。不知道是不是又很多人和我一样,在第一次看到这句话的时候一头雾水。
    在我的理解来看,AOP可以理解为一种独立于模块的,可以穿插在方法间执行的技术。当还没有接触AOP这一概念的时候,我们习惯于用OO的思想去解决问题,但并不是什么情况下OO都是合适的。
    比方说,当我们想要去统计系统中某个类和它子类的方法调用的次数。如果我们用OO的思想,一般会这样。首先,定义一个工具类,然后让基类持有这个工具类。但可能有一天,需求变了,可能范围变成了某个包下的类,这个时候就麻烦了。因为有可能需要对这个包下的所有类都进行改动,这肯定不是我们想要的结果。
    那么理想情况下是什么呢,假设有一个东西叫切面,在这个切面上我们可以定义一个切点以及一系列的操作,而只有当切点满足的时候(比如在某个包下的类的某个方法被执行),这些操作才会被执行,这样,给予刚刚的那种情况,我们只需要定义一个切面,其切点为某个包下的所有类的方法,然后在定义一个统计操作,这样每次方法被调用时,就会去触发统计操作。

    在jvm中,一个线程在方法进入时会压栈,在退出时会出栈,这些方法进栈和出栈的节点可以被理解为一个个jointPoint 而我们所添加的操作也时在这些个JointPoint上执行。 main-thread.png
    其实对于AOP而言,AspectJ 也是一种非常优秀的解决方案,AspectJ 会在编译期对源码进行织入,从而达到方法增强的效果,不过要使用AspectJ 需要对字节码有一定的了解,而且还要学习AspectJ的语法,需要一定的学习成本。对于Spring 来说,采用动态织入的方法,在运行期生成代理类,从而达到增强的效果。
    在了解spring中的aop之前,我们先来复习一下代理模式。 delagate.png (作图工具只找到了这条黑线,╮(╯▽╰)╭,凑合着看吧。)

    代理类和被代理实现同一接口,同时代理类持有被代理类,并对代理类的接口进行增强,客户端在调用接口时,实际上调用的时被代理的接口。在spring-aop中,实际上使用了java自带的动态代理和cglib这个第三方生成类库,这个类库封装了asm(一个操作字节码的库), 可以在运行时动态生成class,这两种策略个有其使用场景。

    java动态代理

    我们首先先简单的了解一下java动态代理,要使用java的动态代理,首先需要实现java.lang.reflect.InvocationHandler 这个接口

    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
    

    一般来说,可以在自己实现的InvationHandler里去持有被代理的对象,然后通过java.lang.reflect.Proxy#newProxyInstance 去生成代理对象

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

    这里 iterfaces 为代理对象所实现的接口,我们看一个简单的例子

    dynamic-delegate.png
    在这个例子中,我们先定义一个顶层空接口ISub
    package com.iplay.dynamic_proxy;
    
    public interface ISub {
    
    }
    

    之后我们分别定义两个接口IHelloICal 并分别继承ISub

    package com.iplay.dynamic_proxy;
    
    public interface IHello extends ISub{
        void sayHello();
    }
    
    package com.iplay.dynamic_proxy;
    
    public interface ICal extends ISub{
        int add (int a, int b);
    }
    

    之后我们定义一个Sub,分别实现IHelloICal

    package com.iplay.dynamic_proxy;
    
    public class Sub implements ICal, IHello {
        
        public void sayHello() {
            System.out.println("Hello");
        }
    
        public int add(int a, int b) {
            return a + b;
        }
    
    }
    

    之后我们定义一个CallHandler并让它实现InvocationHandler

    package com.iplay.dynamic_proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    
    public class CallHandler implements InvocationHandler {
        
        private final ISub isub;
        
        public CallHandler(ISub isub) {
            this.isub = isub;
        }
        
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            System.out.println("before called");
            
            Object ret = method.invoke(isub, args);
            
            System.out.println("after called");
            
            return ret;
        }
    
    }
    

    最后我们看一下客户端类

    package com.iplay.dynamic_proxy;
    
    import java.lang.reflect.Method;
    import java.lang.reflect.Modifier;
    import java.lang.reflect.Proxy;
    
    /**
     * Hello world!
     *
     */
    public class App {
        
        public static void main(String args[]) {
            ISub isub = new Sub();
            CallHandler callHandler = new CallHandler(isub);
            Object proxy = Proxy.newProxyInstance(Sub.class.getClassLoader(), new Class<?>[] {ICal.class, IHello.class}, callHandler);
            Class<?> clz = proxy.getClass();
            System.out.println("sup clz " + clz.getSuperclass());
            System.out.println(Modifier.toString(clz.getModifiers()) + " " + clz.getName());
            
            Class<?>[] itfs = clz.getInterfaces();
            for(int i=0; i<itfs.length; i++) {
                System.out.println(itfs[i].getName());
            }
            
            Method[] methods = clz.getMethods();
            for(int i=0; i<methods.length; i++) {
                System.out.println(Modifier.toString(methods[i].getModifiers()) + " " + methods[i].getName());
            }
            
            ((IHello) proxy).sayHello();
            System.out.println(((ICal) proxy).add(1, 2));
        }
        
    }
    

    执行后结果

    sup clz class java.lang.reflect.Proxy
    public final com.sun.proxy.$Proxy0
    com.iplay.dynamic_proxy.ICal
    com.iplay.dynamic_proxy.IHello
    public final add
    public final equals
    public final toString
    public final hashCode
    public final sayHello
    public static isProxyClass
    public static getInvocationHandler
    public static transient getProxyClass
    public static newProxyInstance
    public final wait
    public final wait
    public final native wait
    public final native getClass
    public final native notify
    public final native notifyAll
    before called
    Hello
    after called
    before called
    after called
    3
    

    可以看到Proxy#newInstance 生成了一个新的类$Proxy0, 这个类是final的,继承了Proxy, 并实现了我们传入了的两个接口IHelloICal并将实现的方法都设置为了final,同时这个类持有了我们传入的CallHandler,CallHandler中持有被代理对象,所有当我们通过ICal, IHello调用目标对象时,实际是通过$Proxy调用对应的方法,在调用时,$Proxy 会通过反射获取对应的Method,传给InvocationHandler#invoke执行对应的操作。

    相关文章

      网友评论

          本文标题:Spring aop 原理解析(AOP 简介 和 Java 动态

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