代理的概念本文就不细说了,相信网上的文章很多,动态代理,静态代理,JDKProxy和CGLIB,他们的使用区别等。我们今天就重点聊一聊CGLIB,找一个具体的场景聊聊
最近项目使用了SpringBoot的缓存,玩着玩着我就觉得这个@Cacheable
真是牛逼,一个注解,就能向redis发送存取缓存指令,完成了我们要写千万次的get/set代码。
比如如下的代码:
@EnableCaching
@SpringBootApplication
public class MinimalApplication {
@Autowired private CacheService cacheService;
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(MinimalApplication.class, args);
MinimalApplication app = context.getBean(MinimalApplication.class);
String cache = app.cacheService.getStr("hi");
System.out.println(cache);
}
}
@Service
public class CacheService {
@Cacheable(cacheNames="XA", key="#hi")
public String getStr(String hi) {
return "Aloha, 大猪小猪在菜盘!";
}
}
这里做了太多的事情,寻找缓存Map, 建立redis连接,读写操作 and so on...
其实这个场景下就用了CGLIB代理,代理加强的代码帮我们做了太多的事情。我们首先来调试下这段代码,来看看调用栈的信息:
信息量非常巨大,至少你能够看到的getStr()
方法跟真正执行getStr()
方法中间垮了12层调用栈。。。
你也能看到一些很奇怪的信息,比如第二行就是这样的:
invoke:-1, CacheService$$FastClassBySpringCGLIB$$e3b31d3f
我擦了,这是什么鬼畜代码?我咋不记得我有写过这火星文玩意儿的类?
其实这这个类就是CGLIB帮我们生成的类,这个类在我们的编译器中是看不到的,我们需要做一些特殊的处理,来查看这生成的类,在main方法中添加这一段代码,再次运行debug:
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"~/Desktop/cglib");
完毕之后去这个目录下查看生成的文件,我们发现就有这么一个火星文明明的class:
CacheService$$EnhancerBySpringCGLIB$$4872f987.class
打开这个class瞅瞅,代码已经完全不可读了:
public class CacheService$$EnhancerBySpringCGLIB$$4872f987 extends CacheService implements SpringProxy, Advised, Factory {
private boolean CGLIB$BOUND;
public static Object CGLIB$FACTORY_DATA;
private static final ThreadLocal CGLIB$THREAD_CALLBACKS;
private static final Callback[] CGLIB$STATIC_CALLBACKS;
private MethodInterceptor CGLIB$CALLBACK_0;
private MethodInterceptor CGLIB$CALLBACK_1;
private NoOp CGLIB$CALLBACK_2;
private Dispatcher CGLIB$CALLBACK_3;
private Dispatcher CGLIB$CALLBACK_4;
private MethodInterceptor CGLIB$CALLBACK_5;
private MethodInterceptor CGLIB$CALLBACK_6;
private static Object CGLIB$CALLBACK_FILTER;
private static final Method CGLIB$getStr$0$Method;
private static final MethodProxy CGLIB$getStr$0$Proxy;
private static final Object[] CGLIB$emptyArgs;
private static final Method CGLIB$equals$1$Method;
private static final MethodProxy CGLIB$equals$1$Proxy;
private static final Method CGLIB$toString$2$Method;
private static final MethodProxy CGLIB$toString$2$Proxy;
private static final Method CGLIB$hashCode$3$Method;
private static final MethodProxy CGLIB$hashCode$3$Proxy;
private static final Method CGLIB$clone$4$Method;
private static final MethodProxy CGLIB$clone$4$Proxy;
static void CGLIB$STATICHOOK103() {
CGLIB$THREAD_CALLBACKS = new ThreadLocal();
CGLIB$emptyArgs = new Object[0];
Class var0 = Class.forName("zsh.demos.simple.minimal.serv.CacheService$$EnhancerBySpringCGLIB$$4872f987");
Class var1;
CGLIB$getStr$0$Method = ReflectUtils.findMethods(new String[]{"getStr", "(Ljava/lang/String;)Ljava/lang/String;"}, (var1 = Class.forName("zsh.demos.simple.minimal.serv.CacheService")).getDeclaredMethods())[0];
CGLIB$getStr$0$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/String;)Ljava/lang/String;", "getStr", "CGLIB$getStr$0");
Method[] var10000 = ReflectUtils.findMethods(new String[]{"equals", "(Ljava/lang/Object;)Z", "toString", "()Ljava/lang/String;", "hashCode", "()I", "clone", "()Ljava/lang/Object;"}, (var1 = Class.forName("java.lang.Object")).getDeclaredMethods());
CGLIB$equals$1$Method = var10000[0];
CGLIB$equals$1$Proxy = MethodProxy.create(var1, var0, "(Ljava/lang/Object;)Z", "equals", "CGLIB$equals$1");
CGLIB$toString$2$Method = var10000[1];
CGLIB$toString$2$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/String;", "toString", "CGLIB$toString$2");
CGLIB$hashCode$3$Method = var10000[2];
CGLIB$hashCode$3$Proxy = MethodProxy.create(var1, var0, "()I", "hashCode", "CGLIB$hashCode$3");
CGLIB$clone$4$Method = var10000[3];
CGLIB$clone$4$Proxy = MethodProxy.create(var1, var0, "()Ljava/lang/Object;", "clone", "CGLIB$clone$4");
}
...
这个class其实就是CGLIB生成的代理增强类,这是整个也是Spring AOP的基础,我们终于见到了庐山真面目了。
光看了这个class文件也看不出什么东西,我们还是要关心这个getStr()
方法加了@Cacheable
之后到底Spring做了哪些事情,还是再次回到我们调用栈来:
在ideallij开发IDE中,点击CGLIB增强的class的栈都直接会回到原类本身,所以可以暂时忽略这一层,第8层是lambda表达式解释层也可以忽略,我们就看中间的AOP层做了些什么事情:
11. invoke:204, MethodProxy (org.springframework.cglib.proxy)
10. invokeJoinpoint:746, CglibAopProxy$CglibMethodInvocation (org.springframework.aop.framework)
9. proceed:163, ReflectiveMethodInvocation (org.springframework.aop.framework)
8. lambda$invoke$0:53, CacheInterceptor (org.springframework.cache.interceptor)
7. invoke:-1, 718057154 (org.springframework.cache.interceptor.CacheInterceptor$$Lambda$464)
6. invokeOperation:337, CacheAspectSupport (org.springframework.cache.interceptor)
5. execute:392, CacheAspectSupport (org.springframework.cache.interceptor)
4. execute:317, CacheAspectSupport (org.springframework.cache.interceptor)
3. invoke:61, CacheInterceptor (org.springframework.cache.interceptor)
2. proceed:185, ReflectiveMethodInvocation (org.springframework.aop.framework)
1. intercept:688, CglibAopProxy$DynamicAdvisedInterceptor (org.springframework.aop.framework)
第一层,最难理解的可能就是第一层了,什么?我getStr()
方法为什么会跳到这个DynamicAdvisedInterceptor
类里面的intercept()
方法里执行?其实这一步我们需要拿出CGLIB之前生成的class来看,我们找到getStr()
方法:
public final String getStr(String var1) {
try {
MethodInterceptor var10000 = this.CGLIB$CALLBACK_0;
if (this.CGLIB$CALLBACK_0 == null) {
CGLIB$BIND_CALLBACKS(this);
var10000 = this.CGLIB$CALLBACK_0;
}
return var10000 != null ? (String)var10000.intercept(this, CGLIB$getStr$0$Method, new Object[]{var1}, CGLIB$getStr$0$Proxy) : super.getStr(var1);
} catch (Error | RuntimeException var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
真正被实例化的CacheService
对象其实是由这个CGLIB生成的class生成的。如果明白这一点,那么我们便很好理解,上面代码中的var10000
其实就是MethodInterceptor
的实现类DynamicAdvisedInterceptor
。
第2层到第11层暂略
那么我们再看一下,为什么我们的getStr()
方法会被增强代理?这里先做个测试,如果将getStr()
方法的@Cacheable
注解去掉,我们看看调用栈的变化:
@Service
public class CacheService {
// @Cacheable(cacheNames="XA", key="#hi")
public String getStr(String hi) {
return "Aloha, 大猪小猪在菜盘!";
}
}
调用栈如下,没有任何的CGLIB代理类出现
这一点至少能说明,引入了@Cacheable
注解会让SpringBoot对他进行代理增强,处理变得复杂。程序员是有理想,有文化,有道德的,不继续深入怎么行?我们就看看@Cacheable
注解到底是怎么告诉SpringBoot去做代理增强的
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(CachingConfigurationSelector.class)
public @interface EnableCaching {
boolean proxyTargetClass() default false;
AdviceMode mode() default AdviceMode.PROXY;
int order() default Ordered.LOWEST_PRECEDENCE;
}
这个注解其实包含了很丰富的信息,我们其实从注解的attribute中已经能够看出一些端倪,这里面有proxy,有AdviceMode,如果你熟悉了AOP的概念,你就会知道Advice(通知)的具体作用。同时这个注解中有@Import(CachingConfigurationSelector.class)
我们进入这个导入类查看
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
private final Log logger = LogFactory.getLog(getClass());
/**
* Register, escalate, and configure the standard auto proxy creator (APC) against the
* given registry. Works by finding the nearest annotation declared on the importing
* {@code @Configuration} class that has both {@code mode} and {@code proxyTargetClass}
* attributes. If {@code mode} is set to {@code PROXY}, the APC is registered; if
* {@code proxyTargetClass} is set to {@code true}, then the APC is forced to use
* subclass (CGLIB) proxying.
* <p>Several {@code @Enable*} annotations expose both {@code mode} and
* {@code proxyTargetClass} attributes. It is important to note that most of these
* capabilities end up sharing a {@linkplain AopConfigUtils#AUTO_PROXY_CREATOR_BEAN_NAME
* single APC}. For this reason, this implementation doesn't "care" exactly which
* annotation it finds -- as long as it exposes the right {@code mode} and
* {@code proxyTargetClass} attributes, the APC can be registered and configured all
* the same.
*/
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
boolean candidateFound = false;
Set<String> annoTypes = importingClassMetadata.getAnnotationTypes();
for (String annoType : annoTypes) {
AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annoType);
if (candidate == null) {
continue;
}
Object mode = candidate.get("mode");
Object proxyTargetClass = candidate.get("proxyTargetClass");
if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
Boolean.class == proxyTargetClass.getClass()) {
candidateFound = true;
if (mode == AdviceMode.PROXY) {
AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
if ((Boolean) proxyTargetClass) {
AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
return;
}
}
}
}
if (!candidateFound) {
String name = getClass().getSimpleName();
logger.warn(String.format("%s was imported but no annotations were found " +
"having both 'mode' and 'proxyTargetClass' attributes of type " +
"AdviceMode and boolean respectively. This means that auto proxy " +
"creator registration and configuration may not have occurred as " +
"intended, and components may not be proxied as expected. Check to " +
"ensure that %s has been @Import'ed on the same class where these " +
"annotations are declared; otherwise remove the import of %s " +
"altogether.", name, name, name));
}
}
}