【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(中)

简介: 【小家Spring】详解Spring AOP的底层代理JdkDynamicAopProxy和ObjenesisCglibAopProxy的源码分析(介绍CGLIB使用中的坑)(中)

CglibAopProxy


ObjenesisCglibAopProxy


环境我们只需要让Demo不再实现接口就成了~


public class Main {
    public static void main(String[] args) {
        ProxyFactory proxyFactory = new ProxyFactory(new Demo());
        proxyFactory.addAdvice((MethodBeforeAdvice) (method, args1, target) -> {
                    System.out.println("你被拦截了:方法名为:" + method.getName() + " 参数为--" + Arrays.asList(args1));
                }
        );
        Demo demo = (Demo) proxyFactory.getProxy();
        //你被拦截了:方法名为:hello 参数为--[]
        //this demo show
        demo.hello();
    }
}
// 不要再实现接口,就会用CGLIB去代理
class Demo {
    public void hello() {
        System.out.println("this demo show");
    }
}


这个两个放在一起说,因为ObjenesisCglibAopProxy继承自CglibAopProxy,它只重写了createProxyClassAndInstance方法:


// 它是Spring4.0之后提供的
class ObjenesisCglibAopProxy extends CglibAopProxy {
  // 下面有解释,另外一种创建实例的方式(可议不用空的构造函数哟)
  private static final SpringObjenesis objenesis = new SpringObjenesis();
  public ObjenesisCglibAopProxy(AdvisedSupport config) {
    super(config);
  }
  // 创建一个代理得实例
  @Override
  protected Object createProxyClassAndInstance(Enhancer enhancer, Callback[] callbacks) {
    Class<?> proxyClass = enhancer.createClass();
    Object proxyInstance = null;
    // 如果为true,那我们就采用objenesis去new一个实例~~~
    if (objenesis.isWorthTrying()) {
      try {
        proxyInstance = objenesis.newInstance(proxyClass, enhancer.getUseCache());
      } catch (Throwable ex) {
        logger.debug("Unable to instantiate proxy using Objenesis, " +
            "falling back to regular proxy construction", ex);
      }
    }
    // 若果还为null,就再去拿到构造函数(指定参数的)
    if (proxyInstance == null) {
      // Regular instantiation via default constructor...
      try {
        Constructor<?> ctor = (this.constructorArgs != null ?
            proxyClass.getDeclaredConstructor(this.constructorArgTypes) :
            proxyClass.getDeclaredConstructor());
        // 通过此构造函数  去new一个实例
        ReflectionUtils.makeAccessible(ctor);
        proxyInstance = (this.constructorArgs != null ?
            ctor.newInstance(this.constructorArgs) : ctor.newInstance());
      } catch (Throwable ex) {
        throw new AopConfigException("Unable to instantiate proxy using Objenesis, " +
            "and regular proxy instantiation via default constructor fails as well", ex);
      }
    }
    ((Factory) proxyInstance).setCallbacks(callbacks);
    return proxyInstance;
  }
}


本来要想使用ASM和CGLIB,我们是需要引入cglib相关的jar包的。但是从Spring3.2以后,我们就不用再单独因此此Jar了,因为Spring已经帮我们集成在Spring-core里面了


那么接下来,我们只需要看看CglibAopProxy内容即可,他处理的是核心内容。


class CglibAopProxy implements AopProxy, Serializable {
  // 它的两个getProxy()相对来说比较简单,就是使用CGLIB的方式,利用Enhancer创建了一个增强的实例
  // 这里面比较复杂的地方在:getCallbacks()这步是比较繁琐的
  // setCallbackFilter就是看看哪些方法需要拦截、哪些不需要~~~~
  @Override
  public Object getProxy() {
    return getProxy(null);
  }
  // CGLIB重写的这两个方法
  @Override
  public boolean equals(Object other) {
    return (this == other || (other instanceof CglibAopProxy &&
        AopProxyUtils.equalsInProxy(this.advised, ((CglibAopProxy) other).advised)));
  }
  @Override
  public int hashCode() {
    return CglibAopProxy.class.hashCode() * 13 + this.advised.getTargetSource().hashCode();
  }
  // 最后,所有的被代理得类的所有的方法调用,都会进入DynamicAdvisedInterceptor#intercept这个方法里面来(相当于JDK动态代理得invoke方法)
  // 它实现了MethodInterceptor接口
  private static class DynamicAdvisedInterceptor implements MethodInterceptor, Serializable {
    private final AdvisedSupport advised;
    public DynamicAdvisedInterceptor(AdvisedSupport advised) {
      this.advised = advised;
    }
    @Override
    @Nullable
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
      Object oldProxy = null;
      boolean setProxyContext = false;
      Object target = null;
      // 目标对象源
      TargetSource targetSource = this.advised.getTargetSource();
      try {
        if (this.advised.exposeProxy) {
          oldProxy = AopContext.setCurrentProxy(proxy);
          setProxyContext = true;
        }
        // 拿到目标对象   这里就是使用targetSource的意义,它提供多个实现类,从而实现了更多的可能性
        // 比如:SingletonTargetSource  HotSwappableTargetSource  PrototypeTargetSource  ThreadLocalTargetSource等等
        target = targetSource.getTarget();
        Class<?> targetClass = (target != null ? target.getClass() : null);
        // 一样的,也是拿到和这个方法匹配的 所有的增强器、通知们 和JDK Proxy中是一样的
        List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);
        Object retVal;
        // 没有增强器,同时该方法是public得  就直接调用目标方法(不拦截)
        if (chain.isEmpty() && Modifier.isPublic(method.getModifiers())) {
          Object[] argsToUse = AopProxyUtils.adaptArgumentsIfNecessary(method, args);
          retVal = methodProxy.invoke(target, argsToUse);
        }
        else {
          // CglibMethodInvocation这里采用的是CglibMethodInvocation,它是`ReflectiveMethodInvocation`的子类   到这里就和JDK Proxy保持一致勒 
          retVal = new CglibMethodInvocation(proxy, target, method, args, targetClass, chain, methodProxy).proceed();
        }
        retVal = processReturnType(proxy, target, method, retVal);
        return retVal;
      }
      finally {
        if (target != null && !targetSource.isStatic()) {
          targetSource.releaseTarget(target);
        }
        if (setProxyContext) {
          AopContext.setCurrentProxy(oldProxy);
        }
      }
    }
    @Override
    public boolean equals(Object other) {
      return (this == other ||
          (other instanceof DynamicAdvisedInterceptor &&
              this.advised.equals(((DynamicAdvisedInterceptor) other).advised)));
    }
    /**
     * CGLIB uses this to drive proxy creation.
     */
    @Override
    public int hashCode() {
      return this.advised.hashCode();
    }
  }
}


细节:


  • 和JDK的一样,Object的方法,只有toString()会被拦截(执行通知)
  • 生成出来的代理对象,Spring默认都给你实现了接口:SpringProxy、DecoratingProxy、Advised
  • 它和JDK不同的是,比如equals和hashCode等方法根本就不会进入intecept方法,而是在getCallbacks()那里就给特殊处理掉了

Objenesis:另一种实例化对象的方式


它专门用来创建对象,即使你没有空的构造函数,都木有问题~~ 可谓非常的强大

它不使用构造方法创建Java对象,所以即使你有空的构造方法,也是不会执行的。


Objenesis是一个Java的库,主要用来创建特定的对象。


由于不是所有的类都有无参构造器又或者类构造器是private,在这样的情况下,如果我们还想实例化对象,class.newInstance是无法满足的。


使用ObjenesisStd


public class MainTest {
    public static void main(String[] args) throws Exception {
        Objenesis objenesis = new ObjenesisStd();
        // 它竟然创建成功了
        MyDemo myDemo = objenesis.newInstance(MyDemo.class);
        System.out.println(myDemo); //com.fsx.maintest.MyDemo@1f32e575
        System.out.println(myDemo.code); //null  特别注意:这里是null,而不是10
        // 若直接这样创建 就报错 java.lang.InstantiationException: com.fsx.maintest.MyDemo
        System.out.println(MyDemo.class.newInstance());
    }
}
class MyDemo {
    public String code = "10";
    public MyDemo(String code) {
        this.code = code;
    }
}

实用ObjectInstantiator


    public static void main(String[] args) throws Exception {
        Objenesis objenesis = new ObjenesisStd();
        // 相当于生成了一个实例创建的工厂,接下来就可以很方便得创建实例了
        // 如果你要创建多个实例,建议这么来创建
        ObjectInstantiator<MyDemo> instantiator = objenesis.getInstantiatorOf(MyDemo.class);
        MyDemo myDemo1 = instantiator.newInstance();
        MyDemo myDemo2 = instantiator.newInstance();
        System.out.println(myDemo1);
        System.out.println(myDemo1.code); //null
        System.out.println(myDemo2);
    }


使用SpringObjenesis


这是Spring对Objenesis接口的一个实现。由Spring4.2之后提供的(ObjenesisCglibAopProxy可是Spring4.0就有了哦)

基本实用上,我们只需要换个实现就成:


Objenesis objenesis = new SpringObjenesis();


Spring为我们提供了一个isWorthTrying()方法:

  // 是否需要尝试:也就是说,它是否还没有被使用过,或者已知是否有效。方法返回true,表示值得尝试
  // 如果配置的Objenesis Instantiator策略被确定为不处理当前JVM。或者系统属性"spring.objenesis.ignore"值设置为true,表示不尝试了
  // 这个在ObjenesisCglibAopProxy创建代理实例的时候用到了。若不尝试使用Objenesis,那就还是用老的方式用空构造函数吧
  public boolean isWorthTrying() {
    return (this.worthTrying != Boolean.FALSE);
  }



Objenesis Vs class.newInstance


从以上代码可以发现class构造器需要参数,而Objenesis可以绕过去, Objenesis主要应用场景:


1.序列化,远程调用和持久化 -对象需要实例化并存储为到一个特殊的状态,而没有调用代码


2.代理,AOP库和Mock对象 -类可以被子类继承而子类不用担心父类的构造器。


3.容器框架 -对象可以以非标准的方式被动态实例化(比如Spring就是容器框架)。


Enhancer:CGLIB增强器


也是位于cglib相关的包内。org.springframework.cglib.proxy

CGLIB是一个强大的高性能的代码生成包。它被许多AOP的框架(例如Spring AOP)使用,为他们提供方法的interception(拦截)


CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM,来转换字节码并生成新的类。不鼓励直接使用ASM,因为它要求你必须对JVM内部结构包括class文件的格式和指令集都很熟悉



public class MainTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyDemo.class);
        // 注意此处得MethodInterceptor是cglib包下的   AOP联盟里还有一个MethodInterceptor
        enhancer.setCallback((MethodInterceptor) (o, method, args1, methodProxy) -> {
            System.out.println(method.getName() + "---方法拦截前");
            // 此处千万不能调用method得invoke方法,否则会死循环的 只能使用methodProxy.invokeSuper 进行调用
            //Object result = method.invoke(o, args1);
            Object result = methodProxy.invokeSuper(o, args1);
            System.out.println(method.getName() + "---方法拦截后");
            return result;
        });
        //MyDemo myDemo = (MyDemo) enhancer.create(); // 这里是要求必须有空的构造函数的
        MyDemo myDemo = (MyDemo) enhancer.create(new Class[]{String.class}, new Object[]{"fsx"});
        // 直接打印:默认会调用toString方法以及hashCode方法  此处都被拦截了
        System.out.println(myDemo);
        //System.out.println(myDemo.code);
    }
}
class MyDemo {
    public String code = "10";
    public MyDemo(String code) {
        this.code = code;
    }
}
输出:
toString---方法拦截前
hashCode---方法拦截前
hashCode---方法拦截后
toString---方法拦截后
com.fsx.maintest.MyDemo$$EnhancerByCGLIB$$b07b3819@7960847b
fsx


这样我们就简单的实现了,对一个对象进行增强。


还有一种创建代理实例的方式,就是我们只用Enhancer把Class类型创建出来,然后创建实例的工作交给Objenesis 这样我们就拜托了对构造函数的依赖


    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(MyDemo.class);
        // 如国实用createClass方式来创建代理的实例  是不能直接添加callback得
        //enhancer.setCallback();
        enhancer.setNamingPolicy(SpringNamingPolicy.INSTANCE);
        enhancer.setStrategy(new DefaultGeneratorStrategy());
        enhancer.setCallbackFilter(new CallbackHelper(MyDemo.class, null) {
            @Override
            protected Object getCallback(Method method) {
                return (MethodInterceptor) (o, method1, args1, methodProxy) -> {
                    System.out.println(method1.getName() + "---方法拦截前");
                    // 此处千万不能调用method得invoke方法,否则会死循环的 只能使用methodProxy.invokeSuper 进行调用
                    //Object result = method.invoke(o, args1);
                    Object result = methodProxy.invokeSuper(o, args1);
                    System.out.println(method1.getName() + "---方法拦截后");
                    return result;
                };
            }
        });
        enhancer.setCallbackTypes(new Class[]{MethodInterceptor.class});
        // 这里我们只生成Class字节码,并不去创建对象
        Class clazz = enhancer.createClass();
        // 创建对象的操作交给
        Objenesis objenesis = new SpringObjenesis();
        MyDemo myDemo = (MyDemo) objenesis.newInstance(clazz);
        System.out.println(myDemo);
        System.out.println(myDemo.code);
    }
输出:
com.fsx.maintest.MyDemo$$EnhancerBySpringCGLIB$$6558edaa@5700d6b1
null


这样即使你没有空的构造函数,我依然可议给你创建一个实例。

CGLIB整个过程如下


1.Cglib根据父类,Callback, Filter 及一些相关信息生成key


2.然后根据key 生成对应的子类的二进制表现形式


3.使用ClassLoader装载对应的二进制,生成Class对象,并缓存


4.最后实例化Class对象,并缓存


生成二进制Class的方法


针对不同场景, CGlib准备了不同的Class生成方法

二进制文件存在在哪儿?


放在byte数组中,下面这行代码就截取于方法AbstractClassGenerator.create(Object key)


byte[] b = strategy.generate(this); 


然后通过 ReflectUtils.defineClass(className, b, loader)生成对应的Class实例,并缓存入cache2


Cglib如何把二进制Load生成的Class


上面说了,事ReflectUtils.defineClass这个方法:

    public static Class defineClass(String className, byte[] b, ClassLoader loader) throws Exception {
        return defineClass(className, b, loader, PROTECTION_DOMAIN);
    }
    public static Class defineClass(String className, byte[] b, ClassLoader loader, ProtectionDomain protectionDomain) throws Exception {
        Object[] args;
        Class c;
        if (DEFINE_CLASS != null) {
            args = new Object[]{className, b, new Integer(0), new Integer(b.length), protectionDomain};
            c = (Class)DEFINE_CLASS.invoke(loader, args);
        } else {
            if (DEFINE_CLASS_UNSAFE == null) {
                throw new CodeGenerationException(THROWABLE);
            }  
            args = new Object[]{className, b, new Integer(0), new Integer(b.length), loader, protectionDomain};
            c = (Class)DEFINE_CLASS_UNSAFE.invoke(UNSAFE, args);
        }
        Class.forName(className, true, loader);
        return c;
    }


注意事项


JDK代理只能针对实现了接口的类以反射的方式生成代理,而不能针对类 ,所以也叫接口代理。

CGLIB是针对类实现代理的,主要对指定的类以字节码转换的方式(ASM框架)生成一个子类,并重写其中的方法。


所以使用CGLIB做动态代理,必须要保证有一个空的构造函数。(那是之前,其实现在不需要了,因为我们有了Objenesis的帮助),但是类不能是Final的


  • 关于final方法

- JDK代理:因为接口的方法不能使用final关键字,所以编译器就过不去

- CGLIB代理:final修饰某个方法后,不报错。但也不会拦截了


  • 关于static方法

- JDK代理:static修饰接口上的方法,要求有body体(JDK8后支持)。但是因为子类不能@Override了,所以编译就报错了

- CGLIB代理:父类方法用static修饰后,子类也是无法进行重写的。因此不抱错,但也不会拦截了


使用代理的时候,尽量不要使用final和static关键字


  • 关于非public方法

- JDK代理:接口中的方法都是public的,所以对于它不存在这种现象

- CGLIB代理:记住结论 只有private的方法不能被代理(因为子类无法访问),其余的访问权限级别的,都能够被正常代理。 简单的说就是只要子类能够访问的权限,都能够被正常代理



相关文章
|
7天前
|
设计模式 Java 测试技术
spring复习04,静态代理动态代理,AOP
这篇文章讲解了Java代理模式的相关知识,包括静态代理和动态代理(JDK动态代理和CGLIB),以及AOP(面向切面编程)的概念和在Spring框架中的应用。文章还提供了详细的示例代码,演示了如何使用Spring AOP进行方法增强和代理对象的创建。
spring复习04,静态代理动态代理,AOP
|
2月前
|
缓存 安全 Java
Spring AOP 中两种代理类型的限制
【8月更文挑战第22天】
16 0
|
2月前
|
Java Spring
|
3月前
|
缓存 安全 Java
Spring高手之路21——深入剖析Spring AOP代理对象的创建
本文详细介绍了Spring AOP代理对象的创建过程,分为三个核心步骤:判断是否增强、匹配增强器和创建代理对象。通过源码分析和时序图展示,深入剖析了Spring AOP的工作原理,帮助读者全面理解Spring AOP代理对象的生成机制及其实现细节。
35 0
Spring高手之路21——深入剖析Spring AOP代理对象的创建
|
4月前
|
存储 安全 Java
Spring Security 6.x OAuth2登录认证源码分析
上一篇介绍了Spring Security框架中身份认证的架构设计,本篇就OAuth2客户端登录认证的实现源码做一些分析。
171 2
Spring Security 6.x OAuth2登录认证源码分析
|
4月前
|
存储 Java Spring
Spring IOC 源码分析之深入理解 IOC
Spring IOC 源码分析之深入理解 IOC
125 2
|
5月前
|
设计模式 安全 Java
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
深入理解Spring Boot AOP:CGLIB代理与JDK动态代理的完全指南
1361 1
|
5月前
|
Dubbo Java 应用服务中间件
Dubbo 第四节: Spring与Dubbo整合原理与源码分析
DubboConfigConfigurationRegistrar的主要作⽤就是对propties⽂件进⾏解析并根据不同的配置项项⽣成对应类型的Bean对象。
139 0
|
5月前
|
安全 Java 数据安全/隐私保护
【Spring Security】Spring Security 认证过程源码分析
【Spring Security】Spring Security 认证过程源码分析
71 0
|
5月前
|
缓存 Java uml
SpringBoot2 | Spring IOC 流程中核心扩展接口的12个扩展点源码分析(十一)
SpringBoot2 | Spring IOC 流程中核心扩展接口的12个扩展点源码分析(十一)
94 0
下一篇
无影云桌面