聊一聊Spring中的代理

代理的概念本文就不细说了,相信网上的文章很多,动态代理,静态代理,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代理,代理加强的代码帮我们做了太多的事情。我们首先来调试下这段代码,来看看调用栈的信息:


debug-stack.png

信息量非常巨大,至少你能够看到的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代理类出现


debug-stack-noproxy.png

这一点至少能说明,引入了@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));
        }
    }

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。