Spring AOP底层实现- JDK动态代理和CGLIB动态代理

Spring AOP是运行时织入的,那么运行时织入到底是怎么实现的呢?答案就是代理对象。
代理又可以分为静态代理和动态代理。

静态代理:由程序员创建或特定工具自动生成源代码,再对其编译。在程序运行前,代理类的.class文件就已经存在了。
动态代理:在程序运行时,运用反射机制动态创建而成。

静态代理的每一个代理类只能为一个接口服务,这样一来程序开发中必然会产生过多的代理,而且,所有的代理操作除了调用的方法不一样之外,其他的操作都一样,则此时肯定是重复代码。解决这一问题最好的做法是可以通过一个代理类完成全部的代理功能,那么此时就必须使用动态代理完成。

动态代理 与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

Spring AOP使用动态代理技术在运行期间织入增强的代码,主要有两种代理机制:基于JDK的动态代理;基于CGLib的动态代理。JDK本身只提供接口的代理,而不支持类的代理。


代理模式

首先我们来了解代理模式的基本定义。
代理模式的英文叫做Proxy,就是一个人或者一个机构代表另一个人或者另一个机构采取行动。在一些情况下,一个客户不想或者不能够直接引用一个对象,而代理对象可以在客户端和目标对象之间起到中介的作用 。
Spring AOP底层实现- JDK动态代理和CGLIB动态代理_第1张图片

//Subject接口
public interface Subject {
    void request();
}
//目标对象RealSubject
public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }
}
//Proxy代理类
public class Proxy implements Subject{

    private RealSubject realSubject;

    public Proxy(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    @Override
    public void request() {
        System.out.println("before");
        try{
            realSubject.request();
        }catch (Exception e){
            System.out.println("ex:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after");
        }
    }
}
//Client客户端
public class Client {
    public static void main(String[] args){
        Subject subject = new Proxy(new RealSubject());
        subject.request();
    }
}

这样就会输出:

before
real subject execute request
after

是不是达到和AOP一样的效果了呢?AOP将真正要执行的代码交给RealSubject来执行,然后通过proxy来对额外的代码进行织入。


动态代理

上面的案例就是一个典型的静态代理的案例,这样有什么缺点呢?
如果当你要代理的方法越多时,你需要重复的逻辑就越多,假设你的目标类有100个方法,那么你的代理类就需要对这100个方法进行委托,但是又可能他们前后需要执行的逻辑时一样的,这样就会产生很多冗余。
这样,就有个更好的 动态代理的方法出现了。

动态代理也分为两类:基于接口的代理和基于继承的代理
两类实现的代表是:JDK代理 与 CGlib代理


JDK代理模式

JDK动态代理主要涉及java.lang.reflect包下的两个类:Proxy类和InvocationHandler接口。
JDK代理实现的三个要点:

  1. 通过java.lang.reflect.Proxy类来动态生成代理类
  2. 代理类要实现InvocationHandler接口
  3. JDK代理只能基于接口进行动态代理的
//客户端Client
public class Client {
    /*
    *newProxyInstance方法参数解析
    *ClassLoader loader:类加载器 
    *Class[] interfaces:得到全部的接口 
    *InvocationHandler h:得到InvocationHandler接口的子类实例 
    */
    public static void main(String[] args){
        Subject subject = (Subject) Proxy.newProxyInstance(Client.class.getClassLoader(),new Class[]{Subject.class},new JdkProxySubject(new RealSubject()));
        subject.hello();
        subject.request();
    }
}
//代理类JdkProxySubject
public class JdkProxySubject implements InvocationHandler{

    private RealSubject realSubject;

    public JdkProxySubject(RealSubject realSubject) {
        this.realSubject = realSubject;
    }

    /*
    *invoke方法方法参数解析
    *Object proxy:指被代理的对象。 
    *Method method:要调用的方法 
    *Object[] args:方法调用时所需要的参数 
    *InvocationHandler接口的子类可以看成代理的最终操作类。
    */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("before");
        Object result = null;
        try{
            //利用反射动态的来反射方法,这就是动态代理和静态代理的区别
            result = method.invoke(realSubject,args);
        }catch (Exception e){
            System.out.println("ex:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after");
        }
        return result;
    }
}
//目标对象RealSubject
public class RealSubject implements Subject{
    @Override
    public void request() {
        System.out.println("real subject execute request");
    }

    @Override
    public void hello() {
        System.out.println("hello");
    }
}
//subject接口,这个是jdk动态代理必须的前提。
public interface Subject {
    void request();
    void hello();
}

输出

before
hello
after
before
real subject execute request
after

我们在subject接口中新增加了一个hello()方法,然后再RealSubject中对hello()方法进行实现,但是在代理类中,我们不需要再去为hello方法再去写一个代理方法,而是通过反射调用目标对象的方法,来动态的生成代理类。

总结:

1、因为利用JdkProxySubject生成的代理类实现了接口,所以目标类中所有的方法在代理类中都有。
2、生成的代理类的所有的方法都拦截了目标类的所有的方法。而拦截器中invoke方法的内容正好就是代理类的各个方法的组成体。
3、利用JDK代理方式必须有接口的存在。


CGlib代理模式

CGLib采用非常底层的字节码技术,可以为一个类创建子类,并在子类中采用方法拦截的技术拦截所有的父类方法的调用,并顺势织入横切逻辑。
CGlib和JDK的原理类似,也是通过方法去反射调用目标对象的方法。

//客户端
public class Client {
    public static void main(String[] args){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(RealSubject.class);
        enhancer.setCallback(new DemoMethodInterceptor());
        // 此刻,realSubject不是单纯的目标类,而是增强过的目标类  
        RealSubject realSubject = (RealSubject) enhancer.create();
        realSubject.hello();
        realSubject.request()
    }
}
//代理对象
public class DemoMethodInterceptor implements MethodInterceptor{
    @Override
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        System.out.println("before in cglib");
        Object result = null;
        try{
            result = proxy.invokeSuper(obj, args);
        }catch (Exception e){
            System.out.println("get ex:"+e.getMessage());
            throw e;
        }finally {
            System.out.println("after in cglib");
        }
        return result;
    }
}
//目标对象RealSubject,cglib不需要定义目标类的统一接口
public class RealSubject {

    public void request() {
        System.out.println("real subject execute request");
    }

    public void hello() {
        System.out.println("hello");
    }
}

输出:

before in cglib
hello
after in cglib
before in cglib
real subject execute request
after in cglib

Spring两种代理方式

若目标对象实现了接口,spring默认使用JDK的动态代理。
优点:因为有接口,所以使系统更加松耦合
缺点:为每一个目标类创建接口

若目标对象没有实现任何接口,spring使用CGLIB进行动态代理。
优点:因为代理类与目标类是继承关系,所以不需要有接口的存在。
缺点:因为没有使用接口,所以系统的耦合性没有使用JDK的动态代理好。

若目标对象实现了接口,但是强制cglib代理,则使用cglib代理

@SpringBootApplication
//强制使用cglib代理
@EnableAspectJAutoProxy(proxyTargetClass = tree)
public class AopDemoApplication{
    public static void main(String[] args){
        SpringApplication.run(AopDemoApplication.class,args);
    }
}

你可能感兴趣的:(Spring)