spring aop原理 JDK动态代理和CGLIB动态代理

Spring的两大特性是IOC和AOP IOC负责将对象动态的注入到容器,从而达到一种须要谁就注入谁,何时须要就何时注入的效果。理解spring的ioc也很重要。 可是今天主要来和你们讲讲aop。 AOP 普遍应用于处理一些具备横切性质的系统级服务,AOP 的出现是对 OOP 的良好补充,用于处理系统中分布于各个模块的横切关注点,好比事务管理、日志、缓存等等。java

AOP实现的关键在于AOP框架自动建立的AOP代理。

AOP代理主要分为静态代理和动态代理,spring

  • 静态代理的表明为AspectJ;
  • 动态代理则以Spring AOP为表明

1,AspectJ

AspectJ是静态代理的加强,采用编译时生成 AOP 代理类,所以也称为编译时加强,具备更好的性能。 缺点:但须要使用特定的编译器进行处理数组

2,Spring AOP

Spring AOP使用的动态代理,运行时生成 AOP 代理类,所谓的动态代理就是说AOP框架不会去修改字节码,而是在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的所有方法,而且在特定的切点作了加强处理,并回调原对象的方法。 缺点:因为 Spring AOP 须要在每次运行时生成 AOP 代理,所以性能略差一些。缓存

因为aspectj的使用还须要使用特定的编译器进行处理,处理起来有点麻烦。今天重要来说解Spring AOPbash

Spring AOP动态代理主要有两种方式,JDK动态代理和CGLIB动态代理。框架

  • JDK动态代理经过反射来接收被代理的类,而且要求被代理的类必须实现一个接口。JDK动态代理的核心是InvocationHandler接口和Proxy类。
  • 若是目标类没有实现接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,能够在运行时动态的生成某个类的子类(经过修改字节码来实现代理)。 注意,CGLIB是经过继承的方式作的动态代理,所以若是某个类被标记为final,那么它是没法使用CGLIB作动态代理的。 jdk和cglib动态代理来共同实现咱们的aop面向切面的功能。

下面就来用简单的代码来演示下jdk和cglib动态代理的实现原理。maven

一,jdk动态代理实现AOP拦截

  • 1,为target目标类定义一个接口JdkInterface,这是jdk动态代理实现的前提
/**
 * Created by qcl on 2018/11/29
 * desc: jdk动态aop代理须要实现的接口
 */
public interface JdkInterface {
    public void add();
}
复制代码
  • 2,用咱们要代理的目标类JdkClass实现上面咱们定义的接口,咱们的实验目标就是在不改变JdkClass目标类的前提下,在目标类的add方法的先后实现拦截,加入自定义切面逻辑。这就是aop的魅力所在:代码与代码之间没有耦合。
/**
 * Created by qcl on 2018/11/29
 * desc: 被代理的类,即目标类target
 */
public class JdkClass implements JdkInterface {
    @Override
    public void add() {
        System.out.println("目标类的add方法");
    }
}
复制代码

-3 ,到了关键的一步,用咱们的MyInvocationHandler,实现InvocationHandler接口,而且实现接口中的invoke方法。仔细看invoke方法,就是在该方法中加入切面逻辑的。目标类方法的执行是由mehod.invoke(target,args)这条语句完成。ide

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

/**
 * Created by qcl on 2018/11/29
 * desc:这里加入切面逻辑
 */
public class MyInvocationHandler implements InvocationHandler {
    private Object target;

    public MyInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before-------切面加入逻辑");
        Object invoke = method.invoke(target, args);//经过反射执行,目标类的方法
        System.out.println("after-------切面加入逻辑");
        return invoke;
    }
}
复制代码
  • 4,测试结果
/**
 * Created by qcl on 2018/11/29
 * desc:测试
 */
public class JdkTest {
    public static void main(String[] args) {
        JdkClass jdkClass = new JdkClass();
        MyInvocationHandler handler = new MyInvocationHandler(jdkClass);
        // Proxy为InvocationHandler实现类动态建立一个符合某一接口的代理实例
        //这里的proxyInstance就是咱们目标类的加强代理类
        JdkInterface proxyInstance = (JdkInterface) Proxy.newProxyInstance(jdkClass.getClass().getClassLoader(),
                jdkClass.getClass()
                        .getInterfaces(), handler);
        proxyInstance.add();
        //打印加强过的类类型
        System.out.println("=============" + proxyInstance.getClass());

    }
}
复制代码

执行上面测试类能够获得以下结果 spring-boot

结果
能够看到,目标类的add方法先后已经加入了自定义的切面逻辑,AOP拦截机制生效了。再看class com.sun.proxy.$Proxy0。这里进一步证实__JDK动态代理的核心是InvocationHandler接口和Proxy类__

二,cglib动态代理实现AOP拦截

  • 1,定义一个要被代理的Base目标类(cglib不须要定义接口)
/**
 * Created by qcl on 2018/11/29
 * desc:要被代理的类
 */
public class Base {
    public void add(){
        System.out.println("目标类的add方法");
    }
}
复制代码
  • 2,定义CglibProxy类,实现MethodInterceptor接口,实现intercept方法。该代理的目的也是在add方法先后加入了自定义的切面逻辑,目标类add方法执行语句为proxy.invokeSuper(object, args)
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;

/**
 * Created by qcl on 2018/11/29
 * desc:这里加入切面逻辑
 */
public class CglibProxy implements MethodInterceptor {
    @Override
    public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy)
            throws Throwable {
        System.out.println("before-------切面加入逻辑");
        methodProxy.invokeSuper(object, args);
        System.out.println("after-------切面加入逻辑");
        return null;
    }
}
复制代码
  • 3,测试类
/**
 * Created by qcl on 2018/11/29
 * desc:测试类
 */
public class CglibTest {
    public static void main(String[] args) {
        CglibProxy proxy = new CglibProxy();
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Base.class);
        //回调方法的参数为代理类对象CglibProxy,最后加强目标类调用的是代理类对象CglibProxy中的intercept方法
        enhancer.setCallback(proxy);
        //此刻,base不是单车的目标类,而是加强过的目标类
        Base base = (Base) enhancer.create();
        base.add();

        Class<? extends Base> baseClass = base.getClass();
        //查看加强过的类的父类是否是未加强的Base类
        System.out.println("加强过的类的父类:"+baseClass.getSuperclass().getName());
        System.out.println("============打印加强过的类的全部方法==============");
        FanSheUtils.printMethods(baseClass);


        //没有被加强过的base类
        Base base2 = new Base();
        System.out.println("未加强过的类的父类:"+base2.getClass().getSuperclass().getName());
        System.out.println("=============打印增未强过的目标类的方法===============");
        FanSheUtils.printMethods(base2.getClass());//打印没有加强过的类的全部方法

    }
}
复制代码

下面是打印结果 工具

结果
经过打印结果能够看到

  • cglib动态的拦截切入成功了
  • cglib动态代理的方式是在运行时动态的生成目标类(Base)的子类,而且在目标类现有方法的基础上添加了不少cglib特有的方法。 下面贴出用反射打印类全部方法的工具类
public class FanSheUtils {

    //打印该类的全部方法
    public static void printMethods(Class cl) {
        System.out.println();
        //得到包含该类全部其余方法的数组
        Method[] methods = cl.getDeclaredMethods();
        //遍历数组
        for (Method method : methods) {
            System.out.print(" ");
            //得到该方法的修饰符并打印
            String modifiers = Modifier.toString(method.getModifiers());
            if (modifiers.length() > 0) {
                System.out.print(modifiers + " ");
            }
            //打印方法名
            System.out.print(method.getName() + "(");

            //得到该方法包含全部参数类型的Class对象的数组
            Class[] paramTypes = method.getParameterTypes();
            //遍历数组
            for (int i = 0; i < paramTypes.length; i++) {
                if (i > 0) {
                    System.out.print(",");
                }
                System.out.print(paramTypes[i].getName());
            }
            System.out.println(");");
        }
    }
}
复制代码

注意:上面用到了cglib.jar和asm.jar。在咱们的maven的pom.xml里引入下面类库便可

<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
复制代码

by年糕妈妈qcl

相关文章
相关标签/搜索