spring5源码--spring AOP源码分析三---切面源码分析 (上)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: spring5源码--spring AOP源码分析三---切面源码分析 (上)

一. AOP切面源码分析


源码分析分为三部分


1. 解析切面

2. 创建动态代理

3. 调用


  • 源码的入口


源码分析的入口, 从注解开始:


组件的入口是一个注解, 比如启用AOP的注解@EnableAspectJAutoProxy. 在注解的实现类里面, 会有一个@Import(""). 这个@Import("")就是引入的源码实现类. 比如AOP的@Import(AspectJAutoProxyRegistrar.class)


通常, Spring要开启某一个功能, 都会增加一个注解, 如果我们再想要看某一个功能的源码, 那么就可以从他的注解跟进去看,在找到@Import("")就找到源码的入口了

源码分析的入口, AOP注解:


package com.lxl.www.aop;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configurable
// 使用注解的方式引入AOP
@EnableAspectJAutoProxy
@ComponentScan("com.lxl.www.aop")
public class MainConfig {
}

引入AOP, 我们需要在配置文件中增加@EnableAspectJAutoProxy代理. 那么想要去掉AOP的引入, 只需要将这个注解注释掉就可以了.  这个注解解释整个AOP的入口.


提示: 其他组件的引入也是类似的, 通常引入组件, 需要增加一个注解, 而整个功能的入口就在这个主机上面.


接下来, 进入到注解类


package org.springframework.context.annotation;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(AspectJAutoProxyRegistrar.class)
public @interface EnableAspectJAutoProxy {
    boolean proxyTargetClass() default false;
    boolean exposeProxy() default false;
}

这是, 我们看到EnableAspectJAutoProxy类增加了一个@Import注解类, 我们知道Import注解可以向IoC容器中增加一个bean.

 

下面进入到AspectJAutoProxyRegistrar类

 

package org.springframework.context.annotation;
import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    /**
     * Register, escalate, and configure the AspectJ auto proxy creator based on the value
     * of the @{@link EnableAspectJAutoProxy#proxyTargetClass()} attribute on the importing
     * {@code @Configuration} class.
     */
    @Override
    public void registerBeanDefinitions(
            AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy =
                AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

我们看到, 使用ImportBeanDefinitionRegistrar注册了一个BeanDefinition.


需要记住的是, 通常使用ImportBeanDefinitionRegistrar结合@Import可以向容器中注册一个BeanDefinition.


如何注册的呢? 看具体实现.

AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);

注册名字是internalAutoProxyCreator的AnnotationAwareAspectJAutoProxyCreator

@Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
            BeanDefinitionRegistry registry, @Nullable Object source) {
        /**
         * 注册一个AnnotationAwareAspectJAutoProxyCreator类型的bean定义
         */
        return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }

如上结构梳理如下:

1187916-20201224103712141-2107879457.png

我们看到,  注册了类AnnotationAwareAspectJAutoProxyCreator类型的bean. 这是一个什么样的类呢? 我们来看一下类的结构. 这个类的继承结构很庞大, 我们只看和本次内容相关的继承结构

1187916-20201214054357872-1063843324.png

解析切面, 创建动态代理, 都是在bean的后置处理器中进行的, 下面对照着AOP的实现原理以及createBean(创建bean)的过程来看

1187916-20201222202729071-1606841709.png

上图是bean加载过程中调用的9次后置处理器. 在创建bean之前调用了InstantiationAwareBeanPostProcessor后置处理器判断是否需要为这个类创建AOP, 也就是解析切面的过程. 所以在AnnotationAwareAspectJAutoProxyCreator里面实现了InstantiationAwareBeanPostProcessor后置处理器的接口. 重写了postProcessBeforeInstantiation方法.


在createBean的第三阶段初始化之后, 要创建AOP的动态代理, 调用了BeanPostProcess后置处理器, AnnotationAwareAspectJAutoProxyCreator也实现了BeanPostProcess接口. 重写了postProcessAfterInitialization.


同时也需要处理AOP的循环依赖的问题, 处理循环依赖是在属性赋值之前调用SmartInstantiationAwareBeanPostProcessor后置处理器, 然后重写getEarlyBeanReference方法. 我们看到AnnotationAwareAspectJAutoProxyCreator也实现了SmartInstantiationAwareBeanPostProcessor接口. 并重写getEarlyBeanReference方法.

 

1) AOP解析切面

 

通过上面的分析,我们知道了, 解析切面是在重写了InstantiationAwareBeanPostProcessor后置处理器的postProcessBeforeInstantiation方法. 所以,我们要找到AnnotationAwareAspectJAutoProxyCreator重写的postProcessBeforeInstantiation方法.

小贴士


如何找到呢? 在idea中使用快捷键ctrl + o, 找到当前类重写的所有方法. 在搜索postProcessBefo


进入创建动态代理的bean的后置处理器, 这是解析切面的第一个入口


@Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        ......
    }

我们在postProcessBeforeInstantiation方法的入口处打一个断点,  接下来看一下这个接口的调用链

1187916-20201222205323882-1299592625.png


如上图, 可以看出我们的入口是main方法, 然后调用了refresh()方法, 执行的是refresh()方法的finishBeanFactoryInitialization()方法, 然胡调用了doGetBean()下的createBean().然后调用的是resolveBeforeInstantiation的applyBeanPostProcessorsBeforeInstantiation方法,在这里获取到所有的bean的后置处理器, 判断这个bean的后置处理器是否是InstantiationAwareBeanPostProcessor的一个实例. 如果是, 那么就调用postProcessBeforeInstantiation()方法.

@Nullable
    protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
        /**
         * 获取容器中所有的后置处理器
         * 这之前有一个注册bean定义的方法, 已经注册过了. 所以在这里可以获取到列表
         *
         * 9次bean的后置处理器, 都是一个类实现InstantiationAwareBeanPostProcessor类, 重写postProcessBeforeInstantiation方法
         */
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

下面就来分析postProcessBeforeInstantiation()方法

@Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) {
        /**
         * 在第一个bean创建的时候, 就会去调用所有的bean的后置处理器, 并且解析所有的切面.
         * 这一步是非常消耗性能的. 所以, 会放到缓存当中
         */
        // 构建缓存的key
        Object cacheKey = getCacheKey(beanClass, beanName);
        // 没有beanName或者不包含在targetSourcedBeans
        if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) {
            // 判断是否已经被解析过?
            if (this.advisedBeans.containsKey(cacheKey)) {
                // 解析过, 则直接返回
                return null;
            }
            /*
             * 判断当前这个类是不是需要跳过的类.如果是基础类或者是应该跳过里的类, 则返回null, 表示这个类不需要被解析
             *
             * 判断是不是基础bean(是不是切面类, 通知, 切点). 因为如果类本身是一个通知, 切面, 那我们不需要解析它
             * 跳过的类: 默认是false. 在shouldSkip里面拿到所有的bean定义, 标记是不是@Aspect, 然后将每一个通知生成一个advisor
             */
            if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
                /**
                 * advisedBean是一个集合, 用来保存类是否是一个advise
                 */
                this.advisedBeans.put(cacheKey, Boolean.FALSE);
                return null;
            }
        }
        // Create proxy here if we have a custom TargetSource.
        // Suppresses unnecessary default instantiation of the target bean:
        // The TargetSource will handle target instances in a custom fashion.
        TargetSource targetSource = getCustomTargetSource(beanClass, beanName);
        if (targetSource != null) {
            if (StringUtils.hasLength(beanName)) {
                this.targetSourcedBeans.add(beanName);
            }
            Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource);
            // 创建了代理
            Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource);
            this.proxyTypes.put(cacheKey, proxy.getClass());
            return proxy;
        }
        return null;
    }

第一步: 构建缓存

构建缓存的key
Object cacheKey = getCacheKey(beanClass, beanName);

在第一个bean创建的时候, 就会去调用所有的bean的后置处理器, 并且解析所有的切面.

这一步是非常消耗性能的. 所以, 会放到缓存当中. 已经创建过的,后面将不再创建

第二步: 校验bean是否被解析过. 如果已经解析过, 则不再解析

// 判断是否已经被解析过
if (this.advisedBeans.containsKey(cacheKey)) {
    // 解析过, 则直接返回
    return null;
}  

第三步: 判断类是否是需要跳过的类

if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
    /**
     * advisedBean是一个集合, 用来保存类是否是一个advise
     */
    this.advisedBeans.put(cacheKey, Boolean.FALSE);
    return null;
}

如果是基础类或者是应该跳过的类, 则返回null, 表示这个类不需要被解析.

这里有两个判断.


isInfrastructureClass(beanClass) 判断当前这个类是不是基础类, 这里的基础类的含义如下: Advice、Pointcut、Advisor、AopInfrastructureBean。如果本身就是基础类,那么不用在解析了

protected boolean isInfrastructureClass(Class<?> beanClass) {
        // 如果这个类是一个Advice类型的类, 或者 Pointcut类型的类, 或者Adivsor类型的类, 或者AOPInsfrastructureBean类型的类.
        boolean retVal = Advice.class.isAssignableFrom(beanClass) ||
                Pointcut.class.isAssignableFrom(beanClass) ||
                Advisor.class.isAssignableFrom(beanClass) ||
                AopInfrastructureBean.class.isAssignableFrom(beanClass);
        if (retVal && logger.isTraceEnabled()) {
            logger.trace("Did not attempt to auto-proxy infrastructure class [" + beanClass.getName() + "]");
        }
        return retVal;
    }

shouldSkip(beanClass, beanName)判断当前是否是需要跳过的类 .

@Override
    protected boolean shouldSkip(Class<?> beanClass, String beanName) {
        // 找到候选的Advisors(前置通知, 后置通知等)
        List<Advisor> candidateAdvisors = findCandidateAdvisors();
        for (Advisor advisor : candidateAdvisors) {
            if (advisor instanceof AspectJPointcutAdvisor &&
                    ((AspectJPointcutAdvisor) advisor).getAspectName().equals(beanName)) {
                return true;
            }
        }
        return super.shouldSkip(beanClass, beanName);
    }

findCandidateAdvisors(); 找到候选的类, 然后将候选类构造成Advisor对象. 进到方法里看看是如何筛选出候选对象的.


AnnotationAwareAspectJAutoProxyCreator.findCandidateAdvisors()

@Override
    protected List<Advisor> findCandidateAdvisors() {
        // Add all the Spring advisors found according to superclass rules.
        // 找到xml方式配置的Advisor和原生接口的AOP的advisor 以及找到事务相关的advisor
        List<Advisor> advisors = super.findCandidateAdvisors();
        // Build Advisors for all AspectJ aspects in the bean factory.
        // 将找到的aspect, 封装为一个Advisor
        if (this.aspectJAdvisorsBuilder != null) {
            //buildAspectJAdvisors()方法就是用来解析切面类, 判断是否含有@Aspect注解, 然后将每一个通知生成一个advisor
            advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());
        }
        // 返回所有的通知
        return advisors;
    }

这里做了两件事

第一步: 解析xml方式配置的Advisor (包括原生接口方式配置的advisor 以及找到事务相关的advisor)

第二步: 解析注解方式的切面. buildAspectJAdvisors()方法是用来解析切面类的. 解析每一个切面类中的通知方法, 并为每个方法匹配切点表达式.

 1187916-20210108155031515-899371525.png


public List<Advisor> buildAspectJAdvisors() {
    /*
     * aspectNames: 用于保存切面名称的集合
     * aspectNames是缓存的类级别的切面, 缓存的是已经解析出来的切面信息
     */
    List<String> aspectNames = this.aspectBeanNames;
    // 如果aspectNames值为空, 那么就在第一个单例bean执行的时候调用后置处理器(AnnotationAwareAspectJAutoProxy)
    if (aspectNames == null) {
      // 加锁, 防止多个线程, 同时加载 Aspect
      synchronized (this) {
        aspectNames = this.aspectBeanNames;
        // 双重检查
        if (aspectNames == null) {
          // 保存所有从切面中解析出来的通知
          List<Advisor> advisors = new ArrayList<>();
          // 保存切面名称的集合
          aspectNames = new ArrayList<>();
          /*
           * 扫描Object的子类. 那就是扫描所有的类
           *
           * 这里传入要扫描的对象是Object.class. 也就是说去容器中扫描所有的类.
           * 循环遍历. 这个过程是非常耗性能的, 所以spring增加了缓存来保存切面
           *
           * 但事务功能除外, 事务模块是直接去容器中找到Advisor类型的类 选择范围小
           * spring 没有给事务模块加缓存
           */
          String[] beanNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
              this.beanFactory, Object.class, true, false);
          // 循环遍历beanNames
          for (String beanName : beanNames) {
            if (!isEligibleBean(beanName)) {
              continue;
            }
            // We must be careful not to instantiate beans eagerly as in this case they
            // would be cached by the Spring container but would not have been weaved.
            // 通过beanName去容器中获取到对应class对象
            Class<?> beanType = this.beanFactory.getType(beanName);
            if (beanType == null) {
              continue;
            }
            // 判断bean是否是一个切面, 也就是脑袋上是否有@Aspect注解
            if (this.advisorFactory.isAspect(beanType)) {
              aspectNames.add(beanName);
              // 将beanName和class对象构建成一个AspectMetadata对象
              AspectMetadata amd = new AspectMetadata(beanType, beanName);
              if (amd.getAjType().getPerClause().getKind() == PerClauseKind.SINGLETON) {
                MetadataAwareAspectInstanceFactory factory =
                    new BeanFactoryAspectInstanceFactory(this.beanFactory, beanName);
                // 解析切面类中所有的通知--一个通知生成一个Advisor.
                List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
                // 加入到缓存中
                if (this.beanFactory.isSingleton(beanName)) {
                  this.advisorsCache.put(beanName, classAdvisors);
                } else {
                  this.aspectFactoryCache.put(beanName, factory);
                }
                advisors.addAll(classAdvisors);
              } else {
                // Per target or per this.
                if (this.beanFactory.isSingleton(beanName)) {
                  throw new IllegalArgumentException("Bean with name '" + beanName +
                      "' is a singleton, but aspect instantiation model is not singleton");
                }
                MetadataAwareAspectInstanceFactory factory =
                    new PrototypeAspectInstanceFactory(this.beanFactory, beanName);
                this.aspectFactoryCache.put(beanName, factory);
                advisors.addAll(this.advisorFactory.getAdvisors(factory));
              }
            }
          }
          this.aspectBeanNames = aspectNames;
          return advisors;
        }
      }
    }
    if (aspectNames.isEmpty()) {
      return Collections.emptyList();
    }
    List<Advisor> advisors = new ArrayList<>();
    for (String aspectName : aspectNames) {
      List<Advisor> cachedAdvisors = this.advisorsCache.get(aspectName);
      if (cachedAdvisors != null) {
        advisors.addAll(cachedAdvisors);
      } else {
        MetadataAwareAspectInstanceFactory factory = this.aspectFactoryCache.get(aspectName);
        advisors.addAll(this.advisorFactory.getAdvisors(factory));
      }
    }
    return advisors;
  }

我们来看看如何生成List<Advisor>的


// 解析切面类中所有的通知--一个通知生成一个Advisor.
List<Advisor> classAdvisors = this.advisorFactory.getAdvisors(factory);
public List<Advisor> getAdvisors(MetadataAwareAspectInstanceFactory aspectInstanceFactory) {
        // 获取标记了@Aspect的类
        Class<?> aspectClass = aspectInstanceFactory.getAspectMetadata().getAspectClass();
        // 获取切面类的名称
        String aspectName = aspectInstanceFactory.getAspectMetadata().getAspectName();
        // 验证切面类
        validate(aspectClass);
        // We need to wrap the MetadataAwareAspectInstanceFactory with a decorator
        // so that it will only instantiate once.
        // 使用包装的模式来包装 aspectInstanceFactory, 构建成MetadataAwareAspectInstanceFactory类
        MetadataAwareAspectInstanceFactory lazySingletonAspectInstanceFactory =
                new LazySingletonAspectInstanceFactoryDecorator(aspectInstanceFactory);
        // 通知的集合, 按照排序后
        List<Advisor> advisors = new ArrayList<>();
        // 获取切面类中所有的通知方法, 除了带有@Pointcut注解的方法
        for (Method method : getAdvisorMethods(aspectClass)) {
            // 将候选方法解析为Advisor. Advisor中包含advise和pointcut. 注意: getAdvisor()方法中定义了切面解析的顺序
            Advisor advisor = getAdvisor(method, lazySingletonAspectInstanceFactory, 0, aspectName);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        // If it's a per target aspect, emit the dummy instantiating aspect.
        if (!advisors.isEmpty() && lazySingletonAspectInstanceFactory.getAspectMetadata().isLazilyInstantiated()) {
            Advisor instantiationAdvisor = new SyntheticInstantiationAdvisor(lazySingletonAspectInstanceFactory);
            advisors.add(0, instantiationAdvisor);
        }
        // Find introduction fields.
        for (Field field : aspectClass.getDeclaredFields()) {
            Advisor advisor = getDeclareParentsAdvisor(field);
            if (advisor != null) {
                advisors.add(advisor);
            }
        }
        return advisors;
    }

这里主要有两点, 第一个是getAdvisorMethods(aspectClass)获取当前切面类的所有的AdvisorMethod , 第二个是封装成的Advisor对象


  • 第一步: 解析切面类中所有的通知方法.getAdvisorMethods(aspectClass)


/**
     * 获取切面类中所有的方法, 且方法中有@Pointcut注解
     * @param aspectClass
     * @return
     */
    private List<Method> getAdvisorMethods(Class<?> aspectClass) {
        final List<Method> methods = new ArrayList<>();
        // 调用doWithMethods. 第二个参数是一个匿名函数, 重写了doWith方法
        ReflectionUtils.doWithMethods(aspectClass, method -> {
            // 解析切面类中所有的方法, 除了Pointcut
            if (AnnotationUtils.getAnnotation(method, Pointcut.class) == null) {
                methods.add(method);
            }
        }, ReflectionUtils.USER_DECLARED_METHODS);
        if (methods.size() > 1) {
            // 对方法进行排序
            methods.sort(METHOD_COMPARATOR);
        }
        return methods;
    }


这个方法是, 扫描切面类的所有方法, 将其添加到methods中, 除了Pointcut注解的方法

然后对methods进行排序, 如何排序呢?

private static final Comparator<Method> METHOD_COMPARATOR;
    static {
        Comparator<Method> adviceKindComparator = new ConvertingComparator<>(
                new InstanceComparator<>(
                        Around.class, Before.class, After.class, AfterReturning.class, AfterThrowing.class),
                (Converter<Method, Annotation>) method -> {
                    AspectJAnnotation<?> ann = AbstractAspectJAdvisorFactory.findAspectJAnnotationOnMethod(method);
                    return (ann != null ? ann.getAnnotation() : null);
                });
        Comparator<Method> methodNameComparator = new ConvertingComparator<>(Method::getName);
        METHOD_COMPARATOR = adviceKindComparator.thenComparing(methodNameComparator);
    }

按照Aroud, Before, After, AferReturning, AfterThrowing的顺序对通知方法进行排序

  • 第二步: 将候选的方法解析为Advisor. 这里也是有两步.具体如下:
/**
     * 解析切面类中的方法
     * @param candidateAdviceMethod 候选的方法
     */
    @Override
    @Nullable
    public Advisor getAdvisor(Method candidateAdviceMethod, MetadataAwareAspectInstanceFactory aspectInstanceFactory,
            int declarationOrderInAspect, String aspectName) {
        validate(aspectInstanceFactory.getAspectMetadata().getAspectClass());
        // 获取切面中候选方法的切点表达式
        AspectJExpressionPointcut expressionPointcut = getPointcut(
                candidateAdviceMethod, aspectInstanceFactory.getAspectMetadata().getAspectClass());
        if (expressionPointcut == null) {
            return null;
        }
        // 将切点表达式和通知封装到InstantiationModelAwarePointcutAdvisorImpl对象中, 这是一个Advisor通知
        return new InstantiationModelAwarePointcutAdvisorImpl(expressionPointcut, candidateAdviceMethod,
                this, aspectInstanceFactory, declarationOrderInAspect, aspectName);
    }



相关文章
|
24天前
|
缓存 Java 开发工具
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
三级缓存是Spring框架里,一个经典的技术点,它很好地解决了循环依赖的问题,也是很多面试中会被问到的问题,本文从源码入手,详细剖析Spring三级缓存的来龙去脉。
Spring是如何解决循环依赖的?从底层源码入手,详细解读Spring框架的三级缓存
|
3天前
|
Java Spring 容器
Spring IOC、AOP与事务管理底层原理及源码解析
Spring框架以其强大的控制反转(IOC)和面向切面编程(AOP)功能,成为Java企业级开发中的首选框架。本文将深入探讨Spring IOC和AOP的底层原理,并通过源码解析来揭示其实现机制。同时,我们还将探讨Spring事务管理的核心原理,并给出相应的源码示例。
26 9
|
9天前
|
缓存 Java Spring
手写Spring Ioc 循环依赖底层源码剖析
在Spring框架中,IoC(控制反转)是一个核心特性,它通过依赖注入(DI)实现了对象间的解耦。然而,在实际开发中,循环依赖是一个常见的问题。
20 4
|
14天前
|
XML 缓存 Java
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
spring源码剖析-spring-beans(内部核心组件,BeanDefinition的注册,BeanWapper创建)
40 10
|
14天前
|
XML 存储 Java
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
spring源码刨析-spring-beans(内部核心组件,beanDefinition加载过程)
|
14天前
|
XML 存储 Java
Spring-源码深入分析(二)
Spring-源码深入分析(二)
|
14天前
|
XML 设计模式 Java
Spring-源码深入分析(一)
Spring-源码深入分析(一)
|
5月前
|
安全 Java Spring
Spring之Aop的底层原理
Spring之Aop的底层原理
|
5月前
|
设计模式 Java uml
Spring AOP 原理
Spring AOP 原理
32 0
|
5月前
|
监控 Java Spring
Spring AOP的作用和底层原理、AOP相关术语
Spring AOP的作用和底层原理、AOP相关术语
78 0