博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring AOP源码(下)
阅读量:2350 次
发布时间:2019-05-10

本文共 23940 字,大约阅读时间需要 79 分钟。


前言

上一篇我们讲解了Spring对aop配置的处理,但是还没有讲Spring中是怎么运用这些配置的。今天我们就俩看下Spring是怎么让这些东西起作用的。

BeanPostProcessor

在讲解之前,我们需要先认识一下BeanPostProcessor接口。

我们回到AbstractApplicationContext的refresh方法。

@Override    public void refresh() throws BeansException, IllegalStateException {        synchronized (this.startupShutdownMonitor) {            // Prepare this context for refreshing.            prepareRefresh();            // Tell the subclass to refresh the internal bean factory.            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();            // Prepare the bean factory for use in this context.            prepareBeanFactory(beanFactory);            try {                // Allows post-processing of the bean factory in context subclasses.                postProcessBeanFactory(beanFactory);                // Invoke factory processors registered as beans in the context.                invokeBeanFactoryPostProcessors(beanFactory);                // Register bean processors that intercept bean creation.                registerBeanPostProcessors(beanFactory);                // Initialize message source for this context.                initMessageSource();                // Initialize event multicaster for this context.                initApplicationEventMulticaster();                // Initialize other special beans in specific context subclasses.                onRefresh();                // Check for listener beans and register them.                registerListeners();                // Instantiate all remaining (non-lazy-init) singletons.                finishBeanFactoryInitialization(beanFactory);                // Last step: publish corresponding event.                finishRefresh();            }            catch (BeansException ex) {                logger.warn("Exception encountered during context initialization - cancelling refresh attempt", ex);                // Destroy already created singletons to avoid dangling resources.                destroyBeans();                // Reset 'active' flag.                cancelRefresh(ex);                // Propagate exception to caller.                throw ex;            }        }    }

我们解析aop配置的操作是在 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();中完成的。

我们继续往下看,可以看到有registerBeanPostProcessors(beanFactory);方法

这个方法就是把所有的实现BeanPostProcessor接口的Bean都注入到AbstractBeanFactory的beanPostProcessors属性中。这个跟invokeBeanFactoryPostProcessors方法中将所有实现了BeanFactoryPostProcessor接口的Bean都注入到相应的list集合中并调用其postProcessBeanFactory方法的实现是大体一样的,这里就不再说了,可以看看之前讲解Spring解析属性文件的源码解析部分。

然后我们就直接看到实例化bean的代码段把,在refresh就是 finishBeanFactoryInitialization(beanFactory);的具体实现,我们不一层一层看了,直接看AbstractAutowireCapableBeanFactory类的

initializeBean

protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) {        if (System.getSecurityManager() != null) {            AccessController.doPrivileged(new PrivilegedAction() {                @Override                public Object run() {                    invokeAwareMethods(beanName, bean);                    return null;                }            }, getAccessControlContext());        }        else {            invokeAwareMethods(beanName, bean);        }        Object wrappedBean = bean;        if (mbd == null || !mbd.isSynthetic()) {            wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);        }        try {            invokeInitMethods(beanName, wrappedBean, mbd);        }        catch (Throwable ex) {            throw new BeanCreationException(                    (mbd != null ? mbd.getResourceDescription() : null),                    beanName, "Invocation of init method failed", ex);        }        if (mbd == null || !mbd.isSynthetic()) {            wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);        }        return wrappedBean;    }

这个方法我们已经不陌生了,我们在讲解SpringMVC的时候提到过InitializingBean接口,如果Bean实现了这个接口,会在实例化Bean的时候通过该方法中的invokeInitMethods来调用afterPropertiesSet()方法。而今天在我们关注的BeanPostProcessor接口中,在这个类对应的方法很显然就是

applyBeanPostProcessorsBeforeInitialization和applyBeanPostProcessorsAfterInitialization方法。

@Override    public Object applyBeanPostProcessorsBeforeInitialization(Object existingBean, String beanName)            throws BeansException {        Object result = existingBean;        for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {            result = beanProcessor.postProcessBeforeInitialization(result, beanName);            if (result == null) {                return result;            }        }        return result;    }

一目了然,就是遍历所有的BeanPostProcessor实现类(已经在registerBeanPostProcessors得到了所有的实现类集合),然后以当前Bean的信息为参数来调用他的postProcessBeforeInitialization方法。

同理如果是applyBeanPostProcessorsAfterInitialization方法,便是遍历后调用postProcessAfterInitialization方法。

分析到这里,很明显我们就可以吧目光转向AspectJAwareAdvisorAutoProxyCreator的这两个方法了。

AspectJAwareAdvisorAutoProxyCreator

在我们上一篇讲解ConfigBeanDefinitionParser的parse方法的时候,说到 configureAutoProxyCreator(parserContext, element);的时候说先不细说,那么到了这一篇就需要从这里入手了。

private void configureAutoProxyCreator(ParserContext parserContext, Element element) {        AopNamespaceUtils.registerAspectJAutoProxyCreatorIfNecessary(parserContext, element);    }    public static void registerAspectJAutoProxyCreatorIfNecessary(            ParserContext parserContext, Element sourceElement) {        BeanDefinition beanDefinition = AopConfigUtils.registerAspectJAutoProxyCreatorIfNecessary(                parserContext.getRegistry(), parserContext.extractSource(sourceElement));        useClassProxyingIfNecessary(parserContext.getRegistry(), sourceElement);        registerComponentIfNecessary(beanDefinition, parserContext);    }

1.得到一个包含AspectJAwareAdvisorAutoProxyCreator实例的Bean定义。

2.根据element节点的相关属性设置一些属性。

3.将1中得到的Bean定义注册到Spring容器中。

我们详细说一下1和2.

registerAspectJAutoProxyCreatorIfNecessary

public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry, Object source) {        return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);    }    private static BeanDefinition registerOrEscalateApcAsRequired(Class
cls, BeanDefinitionRegistry registry, Object source) { Assert.notNull(registry, "BeanDefinitionRegistry must not be null"); if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; }

1)当Spring容器中已经有名为org.springframework.aop.config.internalAutoProxyCreator的Bean。

获取对应的Bean定义,把这个Bean定义的ClassName和AspectJAwareAdvisorAutoProxyCreator比较,如果不相同,比较两个ClassName的优先级。如果AspectJAwareAdvisorAutoProxyCreator的优先级高,则把当前的Bean定义的ClassName设置为AspectJAwareAdvisorAutoProxyCreator。优先级为:

AnnotationAwareAspectJAutoProxyCreator>AspectJAwareAdvisorAutoProxyCreator>InfrastructureAdvisorAutoProxyCreator。

2)如果当前容器中并没有这个BeanName存在。那么就使用AspectJAwareAdvisorAutoProxyCreator构造出一个Bean定义,并注册一些相关属性,把order优先级设置为最低等等。然后返回。

useClassProxyingIfNecessary

private static void useClassProxyingIfNecessary(BeanDefinitionRegistry registry, Element sourceElement) {        if (sourceElement != null) {            boolean proxyTargetClass = Boolean.valueOf(sourceElement.getAttribute(PROXY_TARGET_CLASS_ATTRIBUTE));            if (proxyTargetClass) {                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);            }            boolean exposeProxy = Boolean.valueOf(sourceElement.getAttribute(EXPOSE_PROXY_ATTRIBUTE));            if (exposeProxy) {                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);            }        }    }

是不是看到了熟悉的东西,proxy-target-class属性在我们一开始学习aop配置的时候经常会提到,人们通常会告诉你,如果你想对类直接使用动态,那可以在\节点里配置属性:proxy-target-class=true即可,那么动态代理就会使用cglib来完成,cglib可以直接针对类来实现动态代理,而如果我们在aop配置的时候没有设置这个属性或者设置为false,那么就会使用JDK动态代理,这个时候就只能针对接口实现动态代理。

那么这个方法其实他的主要作用就是为了上述的Bean定义注册两个属性,分别是proxyTargetClass和exposeProxy。

好了到现在,我们终于往Spring容器中注入了AspectJAwareAdvisorAutoProxyCreator构成的Bean定义。

我们来看下AspectJAwareAdvisorAutoProxyCreator的继承结构。

public class AspectJAwareAdvisorAutoProxyCreator extends AbstractAdvisorAutoProxyCreator {
public abstract class AbstractAdvisorAutoProxyCreator extends AbstractAutoProxyCreator {
public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
public interface SmartInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessor {
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
public class ProxyProcessorSupport extends ProxyConfig implements Ordered, BeanClassLoaderAware, AopInfrastructureBean {

我们发现他的祖先类有实现了BeanPostProcessor接口。

翻阅源码,你会发现AspectJAwareAdvisorAutoProxyCreator以及他的祖先类都没有对postProcessBeforeInitialization方法有具体的实现,而只有对postProcessAfterInitialization方法有具体实现。

这个方法的实现在祖先类AbstractAutoProxyCreator中。

@Override    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {        if (bean != null) {            Object cacheKey = getCacheKey(bean.getClass(), beanName);            if (!this.earlyProxyReferences.contains(cacheKey)) {                return wrapIfNecessary(bean, beanName, cacheKey);            }        }        return bean;    }

我们看方法的重点wrapIfNecessary

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {        if (beanName != null && this.targetSourcedBeans.contains(beanName)) {            return bean;        }        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {            return bean;        }        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {            this.advisedBeans.put(cacheKey, Boolean.FALSE);            return bean;        }        // Create proxy if we have advice.        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);        if (specificInterceptors != DO_NOT_PROXY) {            this.advisedBeans.put(cacheKey, Boolean.TRUE);            Object proxy = createProxy(bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));            this.proxyTypes.put(cacheKey, proxy.getClass());            return proxy;        }        this.advisedBeans.put(cacheKey, Boolean.FALSE);        return bean;    }

1.通过getAdvicesAndAdvisorsForBean来获取可以对当前Bean做代理的AdviceBean。

2.如果AdviceBean不为空,则创建动态代理类,最后把其放入缓存,然后返回。

getAdvicesAndAdvisorsForBean

AbstractAdvisorAutoProxyCreator

@Override    protected Object[] getAdvicesAndAdvisorsForBean(Class
beanClass, String beanName, TargetSource targetSource) { List
advisors = findEligibleAdvisors(beanClass, beanName); if (advisors.isEmpty()) { return DO_NOT_PROXY; } return advisors.toArray(); }

利用当前bean的Class和beanName来找到匹配的Advisors

protected List
findEligibleAdvisors(Class
beanClass, String beanName) { List
candidateAdvisors = findCandidateAdvisors(); List
eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); extendAdvisors(eligibleAdvisors); if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; }

1)先获取所有的Advisor

2)然后调用findAdvisorsThatCanApply在所有的Advisor找到符合条件的Advisor集合。

protected List
findAdvisorsThatCanApply( List
candidateAdvisors, Class
beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } } public static List
findAdvisorsThatCanApply(List
candidateAdvisors, Class
clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List
eligibleAdvisors = new LinkedList
(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor) { // already processed continue; } if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }

我们可以看到选出适合的Advisor的主要方法是canApply,我们主要看这个方法。

canApply

public static boolean canApply(Pointcut pc, Class
targetClass, boolean hasIntroductions) { Assert.notNull(pc, "Pointcut must not be null"); if (!pc.getClassFilter().matches(targetClass)) { return false; } MethodMatcher methodMatcher = pc.getMethodMatcher(); IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null; if (methodMatcher instanceof IntroductionAwareMethodMatcher) { introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; } Set
> classes = new LinkedHashSet
>(ClassUtils.getAllInterfacesForClassAsSet(targetClass)); classes.add(targetClass); for (Class
clazz : classes) { Method[] methods = clazz.getMethods(); for (Method method : methods) { if ((introductionAwareMethodMatcher != null && introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) || methodMatcher.matches(method, targetClass)) { return true; } } } return false; }

接下来看创建代理对象

createProxy

protected Object createProxy(            Class
beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); for (Advisor advisor : advisors) { proxyFactory.addAdvisor(advisor); } proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } return proxyFactory.getProxy(getProxyClassLoader()); }

这个方法大体的意思就是新建一个ProxyFactory对象,然后将满足条件的Advisor信息都注入到这个对象中,最后调用 proxyFactory.getProxy(getProxyClassLoader());来创建代理对象。

getProxy

public Object getProxy(ClassLoader classLoader) {        return createAopProxy().getProxy(classLoader);    }

createAopProxy

protected final synchronized AopProxy createAopProxy() {        if (!this.active) {            activate();        }        return getAopProxyFactory().createAopProxy(this);    }

最终调用的是DefaultAopProxyFactory的createAopProxy方法

@Override    public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {        if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {            Class
targetClass = config.getTargetClass(); if (targetClass == null) { throw new AopConfigException("TargetSource cannot determine target class: " + "Either an interface or a target is required for proxy creation."); } if (targetClass.isInterface()) { return new JdkDynamicAopProxy(config); } return new ObjenesisCglibAopProxy(config); } else { return new JdkDynamicAopProxy(config); } }

方法很容易理解,如果proxy-target-class属性指定为true,而且不是对接口进行动态代理,就调用ObjenesisCglibAopProxy来完成动态代理,即使用CGLIB来完成动态代理。否则就是调用JdkDynamicAopProxy来完成动态代理,即使用JDK动态代理来实现。

我们先看我们熟悉的JDK动态代理的具体实现。

JdkDynamicAopProxy

@Override    public Object getProxy(ClassLoader classLoader) {        if (logger.isDebugEnabled()) {            logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());        }        Class
[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised); findDefinedEqualsAndHashCodeMethods(proxiedInterfaces); return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this); }

getProxy的方法很简单,就是使用当前的ClassLoader来加载当前Bean实现的所有接口,最后生成相应的动态代理实例。

至此,创建动态代理实例的过程就分析完了。

但是更重要的是当我们调用动态代理实例时,JdkDynamicAopProxy中对InvocationHandler接口的的具体实现。

invoke

@Override    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {        MethodInvocation invocation;        Object oldProxy = null;        boolean setProxyContext = false;        TargetSource targetSource = this.advised.targetSource;        Class
targetClass = null; Object target = null; try { if (!this.equalsDefined && AopUtils.isEqualsMethod(method)) { // The target does not implement the equals(Object) method itself. return equals(args[0]); } if (!this.hashCodeDefined && AopUtils.isHashCodeMethod(method)) { // The target does not implement the hashCode() method itself. return hashCode(); } if (!this.advised.opaque && method.getDeclaringClass().isInterface() && method.getDeclaringClass().isAssignableFrom(Advised.class)) { // Service invocations on ProxyConfig with the proxy config... return AopUtils.invokeJoinpointUsingReflection(this.advised, method, args); } Object retVal; if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true; } // May be null. Get as late as possible to minimize the time we "own" the target, // in case it comes from a pool. target = targetSource.getTarget(); if (target != null) { targetClass = target.getClass(); } // Get the interception chain for this method. List chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass); // Check whether we have any advice. If we don't, we can fallback on direct // reflective invocation of the target, and avoid creating a MethodInvocation. if (chain.isEmpty()) { // We can skip creating a MethodInvocation: just invoke the target directly // Note that the final invoker must be an InvokerInterceptor so we know it does // nothing but a reflective operation on the target, and no hot swapping or fancy proxying. retVal = AopUtils.invokeJoinpointUsingReflection(target, method, args); } else { // We need to create a method invocation... invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain); // Proceed to the joinpoint through the interceptor chain. retVal = invocation.proceed(); } // Massage return value if necessary. Class
returnType = method.getReturnType(); if (retVal != null && retVal == target && returnType.isInstance(proxy) && !RawTargetAccess.class.isAssignableFrom(method.getDeclaringClass())) { // Special case: it returned "this" and the return type of the method // is type-compatible. Note that we can't help if the target sets // a reference to itself in another returned object. retVal = proxy; } else if (retVal == null && returnType != Void.TYPE && returnType.isPrimitive()) { throw new AopInvocationException( "Null return value from advice does not match primitive return type for: " + method); } return retVal; } finally { if (target != null && !targetSource.isStatic()) { // Must have come from TargetSource. targetSource.releaseTarget(target); } if (setProxyContext) { // Restore old proxy. AopContext.setCurrentProxy(oldProxy); } } }

我们直接看重点,直接跳过对不需要执行增强方法的方法的处理。

调用this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);来获得当前方法匹配的Advisor集合。

当Advisor集合不为空的时候,执行

// We need to create a method invocation...                invocation = new ReflectiveMethodInvocation(proxy, target, method, args, targetClass, chain);                // Proceed to the joinpoint through the interceptor chain.                retVal = invocation.proceed();

这两行就是关键。

我们重点看ReflectiveMethodInvocation的proceed()方法

@Override    public Object proceed() throws Throwable {        //  We start with an index of -1 and increment early.        if (this.currentInterceptorIndex == this.interceptorsAndDynamicMethodMatchers.size() - 1) {            return invokeJoinpoint();        }        Object interceptorOrInterceptionAdvice =                this.interceptorsAndDynamicMethodMatchers.get(++this.currentInterceptorIndex);        if (interceptorOrInterceptionAdvice instanceof InterceptorAndDynamicMethodMatcher) {            // Evaluate dynamic method matcher here: static part will already have            // been evaluated and found to match.            InterceptorAndDynamicMethodMatcher dm =                    (InterceptorAndDynamicMethodMatcher) interceptorOrInterceptionAdvice;            if (dm.methodMatcher.matches(this.method, this.targetClass, this.arguments)) {                return dm.interceptor.invoke(this);            }            else {                // Dynamic matching failed.                // Skip this interceptor and invoke the next in the chain.                return proceed();            }        }        else {            // It's an interceptor, so we just invoke it: The pointcut will have            // been evaluated statically before this object was constructed.            return ((MethodInterceptor) interceptorOrInterceptionAdvice).invoke(this);        }    }

这里的主要操作就是使用责任链模式来调用chain集合里的interceptor属性的invoke方法。

所以我们只要来看看这个chain到底是怎么个东西就好了。具体怎么得到这个chain的我们就不细说了,有兴趣的可以看源码(DefaultAdvisorAdapterRegistry的getInterceptors方法),chain集合是一个MethodInterceptor集合。

这个Intercepor集合主要包含下面几种:

AspectJAfterThrowingAdvice 对应着aop配置里面\节点里的方法。

AspectJAfterAdvice 对应着 \

AspectJAroundAdvice 对应着 \

AfterReturningAdviceInterceptor 对应着AfterReturningAdvice 对应着 \

MethodBeforeAdviceInterceptor 对应着 MethodBeforeAdvice 对应着\

这几种MethodInterceptor调用的先后顺序,如果没有在配置文件中指定Order,那么就会以在配置文件中出现的顺序来执行。执行的过程是一个责任链模式的应用,有兴趣的同学可以先一个例子debug深入研究。

转载地址:http://jdmvb.baihongyu.com/

你可能感兴趣的文章
Android 代码混淆异常
查看>>
Android drawable微技巧,你所不知道的drawable的那些细节
查看>>
理解Fragment生命周期
查看>>
最靠谱的禁止ViewPager滑动方法
查看>>
android错误之android.content.res.Resources$NotFoundException:
查看>>
Android监听软键盘打开收起事件(软键盘自带收起按钮)
查看>>
huffman code and encode
查看>>
exception in c++
查看>>
java并发编程lock
查看>>
阿里云技术教程系列-ECS远程连接 Linux 实例
查看>>
Linux新建用户并允许docker
查看>>
Drools Workbench 7.5.0.Final安装运行
查看>>
Docker快速部署Redis
查看>>
Spring boot shiro session cache ecache redis 共存配置
查看>>
一看就懂的设计模式--设计模式分类
查看>>
一看就懂的设计模式--模板方法
查看>>
一看就懂的设计模式--享元模式
查看>>
一看就懂的设计模式--策略模式
查看>>
spring Cloud 组建图
查看>>
腾讯云
查看>>