当前位置: 首页 > news >正文

AutowiredAnnotationBeanPostProcessor

文章目录

    • 概述
    • 源码解析
      • 加载到容器
      • 构造方法
      • postProcessMergedBeanDefinition 方法
        • findAutowiringMetadata 方法
      • postProcessProperties 方法
        • InjectionMetadata 注入元信息对象
        • AutowiredFieldElement
        • AutowiredMethodElement

概述

org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor,主要处理@Autowired@Value注解进行依赖注入。

在这里插入图片描述
可以看到AutowiredAnnotationBeanPostProcessor实现了MergedBeanDefinitionPostProcessorInstantiationAwareBeanPostProcessor两个接口

源码解析

加载到容器

在 XML 文件中的<context:component-scan />标签的处理过程中,会底层借助于ClassPathBeanDefinitionScanner扫描器,去扫描指定路径下符合条件(@Component注解)的BeanDefinition们,关于@ComponentScan注解的解析也是借助于这个扫描器实现的。扫描过程如下:

// ClassPathBeanDefinitionScanner.java
public int scan(String... basePackages) {
    // <1> 获取扫描前的 BeanDefinition 数量
    int beanCountAtScanStart = this.registry.getBeanDefinitionCount();

    // <2> 进行扫描,将过滤出来的所有的 .class 文件生成对应的 BeanDefinition 并注册
    doScan(basePackages);

    // Register annotation config processors, if necessary.
    // <3> 如果 `includeAnnotationConfig` 为 `true`(默认),则注册几个关于注解的 PostProcessor 处理器(关键)
    // 在其他地方也会注册,内部会进行判断,已注册的处理器不会再注册
    if (this.includeAnnotationConfig) {
        AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
    }

    // <4> 返回本次扫描注册的 BeanDefinition 数量
    return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
}

在第<3>步会调用AnnotationConfigUtilsregisterAnnotationConfigProcessors(BeanDefinitionRegistry)方法,如下:

// AnnotationConfigUtils.java
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    registerAnnotationConfigProcessors(registry, null);
}

/**
 * Register all relevant annotation post processors in the given registry.
 * @param registry the registry to operate on
 * @param source the configuration source element (already extracted)
 * that this registration was triggered from. May be {@code null}.
 * @return a Set of BeanDefinitionHolders, containing all bean definitions
 * that have actually been registered by this call
 */
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
        BeanDefinitionRegistry registry, @Nullable Object source) {

    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    if (beanFactory != null) {
        if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
            beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
        }
        if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
            beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
        }
    }

    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

    // 处理 Spring 应用上下文中的配置类
    if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 处理 @Autowired 以及 @Value 注解
    if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // (条件激活)处理 JSR-250 注解 @Resource,如 @PostConstruct、@PreDestroy 等
    // Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
    if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // Processor 对象(条件激活)处理 JPA 注解场景
    // Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
    if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition();
        try {
            def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
                    AnnotationConfigUtils.class.getClassLoader()));
        }
        catch (ClassNotFoundException ex) {
            throw new IllegalStateException(
                    "Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
        }
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
    }

    // 处理标注 @EventListener 的 Spring 事件监听方法
    if (!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(EventListenerMethodProcessor.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_PROCESSOR_BEAN_NAME));
    }

    // 用于 @EventListener 标注的事件监听方法构建成 ApplicationListener 对象
    if (!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)) {
        RootBeanDefinition def = new RootBeanDefinition(DefaultEventListenerFactory.class);
        def.setSource(source);
        beanDefs.add(registerPostProcessor(registry, def, EVENT_LISTENER_FACTORY_BEAN_NAME));
    }

    return beanDefs;
}

在这个方法中可以看到会注册AutowiredAnnotationBeanPostProcessorCommonAnnotationBeanPostProcessor两个处理器,然后在 Spring 应用上下文刷新阶段会将其初始化并添加至AbstractBeanFactorybeanPostProcessors集合中,那么接下来我们先来分析这两个处理器

构造方法

public class AutowiredAnnotationBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter
      implements MergedBeanDefinitionPostProcessor, PriorityOrdered, BeanFactoryAware {

   /**
    * 保存需要处理的注解
    */
   private final Set<Class<? extends Annotation>> autowiredAnnotationTypes = new LinkedHashSet<>(4);

   private String requiredParameterName = "required";

   private boolean requiredParameterValue = true;

   private int order = Ordered.LOWEST_PRECEDENCE - 2;

   @Nullable
   private ConfigurableListableBeanFactory beanFactory;

   private final Set<String> lookupMethodsChecked = Collections.newSetFromMap(new ConcurrentHashMap<>(256));

   private final Map<Class<?>, Constructor<?>[]> candidateConstructorsCache = new ConcurrentHashMap<>(256);

   /**
	* 缓存需要注入的字段元信息
	*/
   private final Map<String, InjectionMetadata> injectionMetadataCache = new ConcurrentHashMap<>(256);

   /**
    * Create a new {@code AutowiredAnnotationBeanPostProcessor} for Spring's
    * standard {@link Autowired @Autowired} annotation.
    * <p>Also supports JSR-330's {@link javax.inject.Inject @Inject} annotation,
    * if available.
    */
   @SuppressWarnings("unchecked")
   public AutowiredAnnotationBeanPostProcessor() {
      this.autowiredAnnotationTypes.add(Autowired.class);
      this.autowiredAnnotationTypes.add(Value.class);
      try {
         this.autowiredAnnotationTypes.add((Class<? extends Annotation>)
               ClassUtils.forName("javax.inject.Inject", AutowiredAnnotationBeanPostProcessor.class.getClassLoader()));
         logger.trace("JSR-330 'javax.inject.Inject' annotation found and supported for autowiring");
      }
      catch (ClassNotFoundException ex) {
         // JSR-330 API not available - simply skip.
      }
   }
}

可以看到会添加@Autowired@Value两个注解,如果存在JSR-330javax.inject.Inject注解,也是支持的。

postProcessMergedBeanDefinition 方法

postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName)方法,找到@Autowired@Value注解标注的字段(或方法)的元信息,如下:

@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
    // 找到这个 Bean 所有需要注入的属性(@Autowired 或者 @Value 注解)
    InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
    metadata.checkConfigMembers(beanDefinition);
}

直接调用findAutowiringMetadata(...)方法获取这个 Bean 的注入元信息对象

findAutowiringMetadata 方法
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    // Fall back to class name as cache key, for backwards compatibility with custom callers.
    // 生成一个缓存 Key
    String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
    // Quick check on the concurrent map first, with minimal locking.
    // 先尝试从缓存中获取
    InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) { // 是否需要刷新,也就是判断缓存是否命中
        synchronized (this.injectionMetadataCache) {
            metadata = this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) { // 加锁,再判断一次
                if (metadata != null) {
                    metadata.clear(pvs);
                }
                // 构建一个需要注入的元信息对象
                metadata = buildAutowiringMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }
    return metadata;
}

首先尝试从缓存中获取这个 Bean 对应的注入元信息对象,没有找到的话则调用buildAutowiringMetadata(final Class<?> clazz)构建一个,然后再放入缓存中

private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
    List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
    Class<?> targetClass = clazz;

    do {
        // <1> 创建 `currElements` 集合,用于保存 @Autowired、@Value 注解标注的字段
        final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();

        // <2> 遍历这个 Class 对象的所有字段
        ReflectionUtils.doWithLocalFields(targetClass, field -> {
            // <2.1> 找到该字段的 @Autowired 或者 @Value 注解,返回 `ann` 对象,没有的话返回空对象,则直接跳过不进行下面的操作
            AnnotationAttributes ann = findAutowiredAnnotation(field);
            if (ann != null) {
                // <2.2> 进行过滤,static 修饰的字段不进行注入
                if (Modifier.isStatic(field.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static fields: " + field);
                    }
                    return;
                }
                // <2.3> 获取注解中的 `required` 配置
                boolean required = determineRequiredStatus(ann);
                // <2.4> 根据该字段和 `required` 构建一个 AutowiredFieldElement 对象,添加至 `currElements`
                currElements.add(new AutowiredFieldElement(field, required));
            }
        });

        // <3> 遍历这个 Class 对象的所有方法
        ReflectionUtils.doWithLocalMethods(targetClass, method -> {
            // <3.1> 尝试找到这个方法的桥接方法,没有的话就是本身这个方法
            Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
            // <3.2> 如果是桥接方法则直接跳过
            if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                return;
            }
            // <3.3> 找到该方法的 @Autowired 或者 @Value 注解,返回 `ann` 对象,没有的话返回空对象,则直接跳过不进行下面的操作
            AnnotationAttributes ann = findAutowiredAnnotation(bridgedMethod);
            if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                // <3.4> 进行过滤,static 修饰的方法不进行注入
                if (Modifier.isStatic(method.getModifiers())) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation is not supported on static methods: " + method);
                    }
                    return;
                }
                if (method.getParameterCount() == 0) {
                    if (logger.isInfoEnabled()) {
                        logger.info("Autowired annotation should only be used on methods with parameters: " +
                                method);
                    }
                }
                // <3.5> 获取注解中的 `required` 配置
                boolean required = determineRequiredStatus(ann);
                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                // <3.6> 构建一个 AutowiredMethodElement 对象,添加至 `currElements`
                currElements.add(new AutowiredMethodElement(method, required, pd));
            }
        });

        elements.addAll(0, currElements);
        // <4> 找到父类,循环遍历
        targetClass = targetClass.getSuperclass();
    }
    while (targetClass != null && targetClass != Object.class);

    // <5> 根据从这个 Bean 解析出来的所有 InjectedElement 对象生成一个 InjectionMetadata 注入元信息对象,并返回
    return new InjectionMetadata(clazz, elements);
}

postProcessProperties 方法

postProcessProperties(PropertyValues pvs, Object bean, String beanName)方法,根据@Autowired@Value注解标注的字段(或方法)的元信息进行依赖注入,如下:

@Override
public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // 找到这个 Bean 的注入元信息对象
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // 进行注入
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}
InjectionMetadata 注入元信息对象

org.springframework.beans.factory.annotation.InjectionMetadata,某个 Bean 的注入元信息对象

public class InjectionMetadata {

	private static final Log logger = LogFactory.getLog(InjectionMetadata.class);

	private final Class<?> targetClass;

	/**
	 * 需要注入的字段(或方法)的元信息
	 */
	private final Collection<InjectedElement> injectedElements;

	@Nullable
	private volatile Set<InjectedElement> checkedElements;

	public InjectionMetadata(Class<?> targetClass, Collection<InjectedElement> elements) {
		this.targetClass = targetClass;
		this.injectedElements = elements;
	}

	public void inject(Object target, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
		Collection<InjectedElement> checkedElements = this.checkedElements;
		Collection<InjectedElement> elementsToIterate =
				(checkedElements != null ? checkedElements : this.injectedElements);
		if (!elementsToIterate.isEmpty()) {
			for (InjectedElement element : elementsToIterate) {
				if (logger.isTraceEnabled()) {
					logger.trace("Processing injected element of bean '" + beanName + "': " + element);
				}
				element.inject(target, beanName, pvs);
			}
		}
	}
}

可以看到注入方法非常简单,就是遍历所有的InjectedElement对象,调用他们的inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs)方法

AutowiredFieldElement

AutowiredAnnotationBeanPostProcessor的私有内部类,注入字段对象,如下:

private class AutowiredFieldElement extends InjectionMetadata.InjectedElement {
    /** 是否必须 */
    private final boolean required;
    /** 是否缓存起来了 */
    private volatile boolean cached = false;
    /** 缓存的对象 */
    @Nullable
    private volatile Object cachedFieldValue;

    public AutowiredFieldElement(Field field, boolean required) {
        super(field, null);
        this.required = required;
    }

    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        // <1> 获取 `field` 字段
        Field field = (Field) this.member;
        Object value;
        // <2> 如果进行缓存了,则尝试从缓存中获取
        if (this.cached) {
            value = resolvedCachedArgument(beanName, this.cachedFieldValue);
        }
        // <3> 否则,开始进行解析
        else {
            // <3.1> 创建一个依赖注入描述器 `desc`
            DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
            desc.setContainingClass(bean.getClass());
            Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            try {
                /**
                 * <3.2> 通过 {@link org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency} 方法
                 * 找到这个字段对应的 Bean(们)
                 */
                value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
            }
            catch (BeansException ex) {
                throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
            }
            // <3.3> 和缓存相关,如果有必要则将本次找到的注入对象缓存起来,避免下次再进行解析
            synchronized (this) {
                if (!this.cached) {
                    if (value != null || this.required) {
                        this.cachedFieldValue = desc;
                        registerDependentBeans(beanName, autowiredBeanNames);
                        if (autowiredBeanNames.size() == 1) {
                            String autowiredBeanName = autowiredBeanNames.iterator().next();
                            if (beanFactory.containsBean(autowiredBeanName) &&
                                    beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                                this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                        desc, autowiredBeanName, field.getType());
                            }
                        }
                    }
                    else {
                        this.cachedFieldValue = null;
                    }
                    this.cached = true;
                }
            }
        }
        // <4> 如果获取到该字段对应的对象,则进行属性赋值(依赖注入)
        if (value != null) {
            ReflectionUtils.makeAccessible(field);
            field.set(bean, value);
        }
    }
}

AutowiredMethodElement

AutowiredAnnotationBeanPostProcessor的私有内部类,注入方法对象,如下:

private class AutowiredMethodElement extends InjectionMetadata.InjectedElement {
    /** 是否必须 */
    private final boolean required;
    /** 是否缓存起来了 */
    private volatile boolean cached = false;
    /** 缓存的方法参数对象 */
    @Nullable
    private volatile Object[] cachedMethodArguments;

    public AutowiredMethodElement(Method method, boolean required, @Nullable PropertyDescriptor pd) {
        super(method, pd);
        this.required = required;
    }

    @Override
    protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
        if (checkPropertySkipping(pvs)) {
            return;
        }
        // <1> 获取 `method` 方法
        Method method = (Method) this.member;
        // <2> 如果进行缓存了,则尝试从缓存中获取方法参数对象
        Object[] arguments;
        if (this.cached) {
            // Shortcut for avoiding synchronization...
            arguments = resolveCachedArguments(beanName);
        }
        // <3> 否则,开始进行解析
        else {
            // <3.1> 获取方法的参数类型集合 `paramTypes`,根据参数位置确定参数
            Class<?>[] paramTypes = method.getParameterTypes();
            arguments = new Object[paramTypes.length];
            // <3.2> 构建一个依赖注入描述器数组 `descriptors`,用于保存后续创建的对象
            DependencyDescriptor[] descriptors = new DependencyDescriptor[paramTypes.length];
            Set<String> autowiredBeans = new LinkedHashSet<>(paramTypes.length);
            Assert.state(beanFactory != null, "No BeanFactory available");
            TypeConverter typeConverter = beanFactory.getTypeConverter();
            // <3.3> 根据参数顺序遍历该方法的参数
            for (int i = 0; i < arguments.length; i++) {
                // <3.3.1> 为第 `i` 个方法参数创建一个 MethodParameter 对象
                MethodParameter methodParam = new MethodParameter(method, i);
                // <3.3.2> 创建依赖描述器 `currDesc`,并添加至 `descriptors` 数组
                DependencyDescriptor currDesc = new DependencyDescriptor(methodParam, this.required);
                currDesc.setContainingClass(bean.getClass());
                descriptors[i] = currDesc;
                try {
                    /**
                     * <3.3.3> 通过 {@link org.springframework.beans.factory.support.DefaultListableBeanFactory#resolveDependency} 方法
                     * 找到这个方法参数对应的 Bean(们)
                     */
                    Object arg = beanFactory.resolveDependency(currDesc, beanName, autowiredBeans, typeConverter);
                    if (arg == null && !this.required) {
                        arguments = null;
                        break;
                    }
                    arguments[i] = arg;
                }
                catch (BeansException ex) {
                    throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(methodParam), ex);
                }
            }
            // <3.4> 和缓存相关,如果有必要则将本次找到的方法参数对象缓存起来,避免下次再进行解析
            synchronized (this) {
                if (!this.cached) {
                    if (arguments != null) {
                        Object[] cachedMethodArguments = new Object[paramTypes.length];
                        System.arraycopy(descriptors, 0, cachedMethodArguments, 0, arguments.length);
                        registerDependentBeans(beanName, autowiredBeans);
                        if (autowiredBeans.size() == paramTypes.length) {
                            Iterator<String> it = autowiredBeans.iterator();
                            for (int i = 0; i < paramTypes.length; i++) {
                                String autowiredBeanName = it.next();
                                if (beanFactory.containsBean(autowiredBeanName) &&
                                        beanFactory.isTypeMatch(autowiredBeanName, paramTypes[i])) {
                                    cachedMethodArguments[i] = new ShortcutDependencyDescriptor(
                                            descriptors[i], autowiredBeanName, paramTypes[i]);
                                }
                            }
                        }
                        this.cachedMethodArguments = cachedMethodArguments;
                    }
                    else {
                        this.cachedMethodArguments = null;
                    }
                    this.cached = true;
                }
            }
        }
        // <4> 如果找到该方法的参数(们),则进行属性赋值(依赖注入)
        if (arguments != null) {
            try {
                ReflectionUtils.makeAccessible(method);
                // 通过反射机制调用该方法
                method.invoke(bean, arguments);
            }
            catch (InvocationTargetException ex) {
                throw ex.getTargetException();
            }
        }
    }
}

核心就是通过DefaultListableBeanFactory#resolveDependency(...方法找到方法参数对应的 Bean,具体查看源码解析:
DefaultListableBeanFactory

相关文章:

  • AIDD-人工智能药物设计-双扩散模型结合多目标优化策略助力3D小分子药物设计
  • 产品经理课程
  • Go语言常用算法实现
  • c++进阶--c++11
  • 更详细的广度优先搜索合集
  • LLM-大语言模型浅谈
  • 【Python使用】嘿马python数据分析教程第3篇:全渠道业务概述,目的,获取数据,连带率【附代码文档】
  • 浔川代码编辑器v1.1.0(测试版)使用教程
  • Python学习之numpy
  • JSX、支持HTML标签、Ref的使用、虚拟DOM的使用
  • 设计模式 Day 3:抽象工厂模式(Abstract Factory Pattern)详解
  • xv6 内存管理
  • 存储模块cache
  • 视觉_transform
  • 第一章 基础概念
  • 源支付开源全套,源支付V7开源全套,源支付V1.8.9,源支付开源版
  • ebay万亿韩元市场突围战:TOP3热销品类正改写平台流量规则
  • Batch Normalization:深度学习训练的加速引擎
  • 【Linux】线程池详解及基本实现
  • 条件生成对抗网络(Conditional GAN, CGAN)原理及实现(pytorch版)
  • 搭建品牌电商网站怎么做/网页搜索引擎优化技术
  • 可以做微信公众号封面的网站/百度最新秒收录方法2022
  • 环球旅行社网站建设规划书论文/软文推广文章范文
  • 白云做网站SEO/手机一键优化
  • 凡科建站官网/网络营销的方法有哪些?
  • 沈阳网站制作方法/企业文化标语经典