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的方法不能被代理(因为子类无法访问),其余的访问权限级别的,都能够被正常代理。 简单的说就是只要子类能够访问的权限,都能够被正常代理