学成路更宽,吊打面试官。 ——小马哥

版本修订

  • 2021.5.19:去除目录

简介

大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间《小马哥讲Spring AOP 编程思想》基础上形成的个人一些总结。希望能帮助各位小伙伴, 祝小伙伴早日学有所成。

AbstractAutoProxyCreator 功能

从命名可知这个抽象类主要功能就是自动创建代理,怎么实现自动?这就得与 Spring IoC 容器打通。所以此抽象类实现了 SmartInstantiationAwareBeanPostProcessorBeanFactoryAware

  • 使用 AOP 代理包装每个合格 bean 用 BeanPostProcessor 实现,在调用 bean 本身之前将委托给指定的拦截器。
  • 这个类区分了公共的拦截器(为它创建的所有代理共享)以及 特定的拦截器(每个 bean 实例都是唯一的)。如果有公共的拦截器,则使用 interceptorNames 属性设置它们(与 ProxyFactoryBean 一样,使用当前工厂中的拦截器名称而不是 bean 引用来允许正确处理原型 advisor 和拦截器)
  • 如果有大量 bean 需要用类似的代理进行包装,即委托给相同的拦截器,那么这种自动代理特别有用。您可以向 bean 工厂注册一个这样的后处理程序来实现相同的效果,而不是为 x 个目标 bean 重复定义 x 个代理。
  • 子类可以应用任何策略(子类可以实现 getAdvicesAndAdvisorsForBean 方法)来决定一个 bean 是否要被代理,例如根据类型、名称、定义细节等。它们还可以返回应该只应用于特定 bean 实例的额外拦截器。一个简单的具体实现是 BeanNameAutoProxyCreator,它通过给定的名称标识要代理的 bean。
  • 任意的 TargetSourceCreator 实现都可以用于创建自定义目标源:例如,将原型对象池化。即使没有 advice,只要 TargetSourceCreator 指定了自定义 TargetSource,也会发生自动代理。如果没有设置 TargetSourceCreators ,或者没有匹配的,默认情况下将使用 SingletonTargetSource 来包装目标 bean 实例。

AbstractAutoProxyCreator 类图

相关类介绍

AopInfrastructureBean

标记接口,用于识别 Spring AOP 基础结构一部分的 bean,避免当 Pointcut 匹配时生成代理对象。

ProxyConfig

用于创建代理时配置的便利超类,以确保所有代理创建器具有一致的属性。

主要配置:

  1. proxyTargetClass:同 @EnableAspectJAutoProxy 中的属性 proxyTargetClass 一样语义,是否使用 CGLIB 代理,默认是 false 使用 JDK 基于接口的动态代理
  2. optimize:设置代理是否应该执行主动优化。主动优化的确切含义在不同的代理之间是不同的,但通常会有一些权衡。默认设置是 false。例如,优化通常意味着在创建代理之后通知更改不会生效。因此,优化在默认情况下是禁用的。如果其他设置排除了优化,那么“true”的优化值可能会被忽略;例如,如果 exposeProxy 被设置为 true,而这与优化不兼容。
  3. opaque:默认值是 false,任何 AOP 代理都可以强制转换为 Advised
  4. exposeProxy:同 @EnableAspectJAutoProxy 中的属性 exposeProxy 一样语义,是否将代理对象暴露在 AopContext 类中。
  5. frozen:设置该配置是否应该被冻结。当一个配置被冻结时,不能做出任何 Advice 更改。这对于优化很有用,当我们不希望调用方能够在强制转换为 Adviced 后操作配置时也很有用。

ProxyProcessorSupport

具有代理处理器通用功能的基类,特别是 ClassLoader 管理和 evaluateProxyInterfaces 算法。

管理 ClassLoader

通过属性 boolean classLoaderConfigured 管理属性 proxyClassLoader 只允许设置一次

evaluateProxyInterfaces() 方法解读

检查给定 bean 类上的接口,并将它们应用到 ProxyFactory(如果可用的话)。调用 isConfigurationCallbackInterface(Class<?>) 和 sInternalLanguageInterface(Class<?>) 来过滤合理的代理接口。如果没有可用接口则使用 CGLIB 代理。

protected void evaluateProxyInterfaces(Class<?> beanClass, ProxyFactory proxyFactory) {// 获取类中的所有接口,如果是接口返回它自己Class<?>[] targetInterfaces = ClassUtils.getAllInterfacesForClass(beanClass, getProxyClassLoader());boolean hasReasonableProxyInterface = false;/***  获取的接口列表,只要接口列表满足一个不是 Spring 框架 InitializingBean、DisposableBean、Aware 接口扩展接口和*  不是 Jdk 中 AutoCloseable 和 Closeable 接口并且不是内部语言接口(接口名称是 groovy.lang.GroovyObject*  或者是以 .cglib.proxy.Factory 结尾或者 .bytebuddy.MockAccess 结尾)并且不是空接口*/for (Class<?> ifc : targetInterfaces) {if (!isConfigurationCallbackInterface(ifc) && !isInternalLanguageInterface(ifc) &&ifc.getMethods().length > 0) {hasReasonableProxyInterface = true;break;}}// 如果有可代理的接口添加到代理工厂中if (hasReasonableProxyInterface) {for (Class<?> ifc : targetInterfaces) {proxyFactory.addInterface(ifc);}}else {// 没有接口就设置 CGLIB 代理proxyFactory.setProxyTargetClass(true);}
}

BeanFactoryAware

实现 setBeanFactory((BeanFactory beanFactory) 方法。并且该实现类交由 Spring IoC 管理。则由 Spring IoC 容器在填充普通 bean 属性之后,但在初始化回调(如 iinitializingBean.afterPropertiesSet() 或自定义初始化方法之前调用。

BeanPostProcessor

Bean 的后置处理器,可以通过实现此接口覆写 postProcessBeforeInitialization 和 postProcessAfterInitialization 方法对 bean 进行修改,比如 AbstractAutoProxyCreato 就是通过覆写 postProcessAfterInitialization 返回代理对象

InstantiationAwareBeanPostProcessor

BeanPostProcessor 的子接口,它添加了一个实例化之前的回调函数和一个实例化之后但在显式属性设置或自动装配发生之前的回调函数。通常用于抑制特定目标 bean 的默认实例化,例如创建带有特殊 targetsource 的代理(池化目标、延迟初始化目标等),或者实现额外的注入策略,如字段注入。

SmartInstantiationAwareBeanPostProcessor

此接口扩展了 InstantiationAwareBeanPostProcessor接口,添加一个 predictBeanType 回调,用于预测已处理 bean 的最终类型。

AopProxy

通过委派已配置的 Aop 代理接口,创建实际的代理对象。Spring AOP 通过 DefaultAopProxyFactory 提供了开箱即用 的 JDK 动态代理(JdkDynamicAopProxy) 和 CGLIB 代理(ObjenesisCglibAopProxy)

  • 通过 getProxy方法获取代理对象(默认的 ClassLoader)
  • 通过指定 ClassLoadergetProxy(@Nullable ClassLoader classLoader) 方法获取代理对象

AopProxyFactory

基于 AdvisedSupport 配置对象新增 AOP 代理的工厂接口

代理应该遵守以下契约:

  • 应该实现配置的所有接口
  • 实现 Advised 接口
  • 实现 equals 方法用于比较代理接口、advice 和 目标对象
  • 如果所有的 advisor 和 目标对象都是可序列化的,代理对象应该也是可序列化的
  • 如果 advisor 和目标对象是线程安全的则代理对象也应该是线程安全的

唯一实现:DefaultAopProxyFactory#createAopProxy(AdvisedSupport config) 方法分析

AopProxyFactory 默认实现,创建不是 JDK 动态代理就是 CGLIB 代理。

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {/*** 对于给定的 AdvisedSupport 实例,如果满足以下任意条件,则创建一个 CGLIB 代理:* 1. 优化标志被设置* 2. proxyTargetClass 标志被设置(即使设置了如果被代理对象是接口还是使用 JDK 动态代理)* 3. 没有指定代理接口*/ 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.");}// 目标对象是接口或者是已经被 JDK 动态代理代理过的对象则创建 JDK 动态代理if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}
}

AbstractAutoProxyCreator 简介

AdvisorAdapterRegistry 管理

  • 通过 advisorAdapterRegistry 属性管理 AdvisorAdapterRegistry 默认是 GlobalAdvisorAdapterRegistry(DefaultAdvisorAdapterRegistry),
  • 可以通过 setAdvisorAdapterRegistry() 方法进行设置

TargetSourceCreator 使用

在前面 AbstractAutoProxyCreator 功能介绍最后一条中介绍即使没有 Advice,只要 TargetSourceCreator 指定了自定义 TargetSource,也会发生自动代理,将会在 postProcessBeforeInstantiation 介绍

  • 可以通过 setCustomTargetSourceCreators(TargetSourceCreator… targetSourceCreators) 方法设置

公共拦截器管理

  • 通过 interceptorNames 属性管理公共拦截器
  • 通过 setInterceptorNames(String... interceptorNames)方法进行设置
  • 通过 setApplyCommonInterceptorsFirst(boolean applyCommonInterceptorsFirst) 方法设置公共拦截器与特殊拦截的先后顺序

BeanFactory 管理

由于 AbstractAutoProxyCreator 实现了 BeanFactoryAware 便于底层 IoC 打通,具有依赖查找的能力

  • 通过 getBeanFactory() 获取 BeanFactory
  • setBeanFactory 方法由 Spring IoC 容器回调

SmartInstantiationAwareBeanPostProcessor 实现

 @Override@Nullablepublic Class<?> predictBeanType(Class<?> beanClass, String beanName) {// 如果代理类型缓存为空则跳过if (this.proxyTypes.isEmpty()) {return null;}/*** 如果 bean 名称为空则 cacheKey 则是类对象;* 如果有 bean 类型是 FactoryBean 则 cacheKey 则是 &beanName;* 否则 cacheKey 就是 beanName*/Object cacheKey = getCacheKey(beanClass, beanName);// 通过 cacheKey 去代理类型缓存获取return this.proxyTypes.get(cacheKey);}@Override@Nullablepublic Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName) {return null;}@Overridepublic Object getEarlyBeanReference(Object bean, String beanName) {/*** 如果 bean 名称为空把 bean Class 对象作为 cacheKey ;* 如果有 bean 类型是 FactoryBean 则 cacheKey 则是 &beanName;* 否则 cacheKey 就是 beanName*/Object cacheKey = getCacheKey(bean.getClass(), beanName);// 缓存早期代理引用this.earlyProxyReferences.put(cacheKey, bean);// 根据条件生成代理return wrapIfNecessary(bean, beanName, cacheKey);}

获取代理对象

wrapIfNecessary() 方法分析

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果通过了自定义 TargetSourceCreator 方式处理过目标实例则不需要代理if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {return bean;}// 内部基础 bean 或者不需要代理的 bean 则不代理if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}/*** 是否是基础设施(默认规则 Advice、Pointcut、Advisor、AopInfrastructureBean)* 或者不需要进行代理(默认规则是否是类名与 bean 名称长度必须相同并且 bean 名称以类名开头以 .ORIGINAL)则不代理。并放入 advisedBeans 缓存中*/if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {      // 方入 advisedBean 缓存以便重复创建 bean 时提供性能this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 根据子类实现不同的规则获取 Advice/Advisor.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;}// 如果没有符合条件的 advice 则不生成代理并缓存this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;
}

createProxy() 方法分析

protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {/***  获取 beanName 的 BeanDefinition 并设置一个*  属性名为 org.springframework.aop.framework.autoproxy.AutoProxyUtils.originalTargetClass 值为 beanClass 属性*/if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}// 通过 ProxyFactory 来创建代理对象ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);// 使用 JDK 动态代理if (!proxyFactory.isProxyTargetClass()) {/***  通过 beanName 的 BeanDefinition 获取属性名  *  org.springframework.aop.framework.autoproxy.AutoProxyUtils.originalTargetClass.preserveTargetClass*  的属性值是否为 true 如果是 true 即使 proxyFactory.isProxyTargetClass() 是 false 还是会使用 CGLIB 动态代理*/if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {// 父类 ProxyProcessorSupport#evaluateProxyInterfaces 方法在上面已经分析过了evaluateProxyInterfaces(beanClass, proxyFactory);}}// 构建 Advisor(后面分析)Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);// 空方法留给子类扩展customizeProxyFactory(proxyFactory);// 是否冻结代理对象(默认是 false)proxyFactory.setFrozen(this.freezeProxy);// 是否对 Advisor 预过滤(默认是 false 留给子类扩展 AbstractAdvisorAutoProxyCreator 是 true)if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// 通过代理工厂根据 ClassLoader 获取代理对象 return proxyFactory.getProxy(getProxyClassLoader());
}

buildAdvisors() 方法分析

确定给定 bean 的 Advisor,包括特定的拦截器和公共拦截器,然后把这些拦截器都适配成 Advisor 对象。

protected Advisor[] buildAdvisors(@Nullable String beanName, @Nullable Object[] specificInterceptors) {/*** 根据内部关联 BeanFactory 通过 IoC 根据公共的拦截器的 bean 名称获取 bean * 然后再通过内部关联的 DefaultAdvisorAdapterRegistry 包装成类型为`DefaultPointcutAdvisor` 的 Advisor*/Advisor[] commonInterceptors = resolveInterceptorNames();List<Object> allInterceptors = new ArrayList<>();// 特定的拦截器不为空再根据是否设置了公共拦截器在前,合并这些拦截器if (specificInterceptors != null) {allInterceptors.addAll(Arrays.asList(specificInterceptors));if (commonInterceptors.length > 0) {if (this.applyCommonInterceptorsFirst) {allInterceptors.addAll(0, Arrays.asList(commonInterceptors));}else {allInterceptors.addAll(Arrays.asList(commonInterceptors));}}}Advisor[] advisors = new Advisor[allInterceptors.size()];for (int i = 0; i < allInterceptors.size(); i++) {// 通过内部关联的 DefaultAdvisorAdapterRegistry 把拦截器包装成 Advisoradvisors[i] = this.advisorAdapterRegistry.wrap(allInterceptors.get(i));}return advisors;
}

子类

AbstractAdvisorAutoProxyCreator

自动构建 Advisor 代理列表,子类覆写 findCandidateAdvisors() 获取 Advisor 列表。也可以通过覆写 AbstractAutoProxyCreator.shouldSkip(java.lang.Class<?>, java.lang.String) 方法跳过自动代理。 通过 @Order 注解或者实现 Ordered 接口来控制 Adivosr/Advice。通过 AnnotationAwareOrderComparator 来排序 Advisor/Advice。没有 @Order 注释或没有实现 Ordered 接口的 Advisor 将被认为是无序的;它们将以未定义的顺序出现在 Advisor 链的末尾。

方法

getAdvicesAndAdvisorsForBean() 方法

实现 AbstractAutoProxyCreator#getAdvicesAndAdvisorsForBean 方法,委派给本类 findEligibleAdvisors() 方法(子类可以覆写)

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {// 委派给 findEligibleAdvisors() 方法(子类可以覆写)List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}
findEligibleAdvisors() 方法
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 通过 IoC 容器获取所有 Advisor 实例List<Advisor> candidateAdvisors = findCandidateAdvisors();/***  如果 Advisor 类型是 IntroductionAdvisor 获取关联的 ClassFilter 进行匹配*  如果 Advisor 类型是 PointcutAdvisor 获取关联的 ClassFilter 进行匹配,如果匹配成功再获取 MethodMatcher 进行静态匹配(2个参数 matches() 方法 )*/List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 扩展接口子类实现extendAdvisors(eligibleAdvisors);if (!eligibleAdvisors.isEmpty()) {// 进行排序eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

AspectJAwareAdvisorAutoProxyCreator

AbstractAdvisorAutoProxyCreator 子类,它暴露 AspectJ 的调用上下文,并在多个 advice 来自相同的 aspect 时推断 AspectJ 的 advice 优先级规则(AspectJPrecedenceComparator 比较器)。

主要覆盖方法

  • extendAdvisors:通过 ExposeInvocationInterceptorMethodInvocation 暴露到上下文,以便在 AspectJ 中操作(例如 AbstractAspectJAdvice#currentJoinPoint 方法和 AspectJExpressionPointcut#matches(Method method, Class<?> targetClass, Object… args) 方法)
  • shouldSkip:通过定义的 Aspect
  • sortAdvisors:通过 PartiallyComparableAdvisorHolder 中关联的AnnotationAwareOrderComparator 进行排序

AnnotationAwareAspectJAutoProxyCreator

AspectJAwareAdvisorAutoProxyCreator 子类,它处理当前应用上下文用中所有的 AspectJ 中 @Aspect 注解 以及 Spring Advisor ,通过 ReflectiveAspectJAdvisorFactory 和 BeanFactoryAspectJAdvisorsBuilderAdapter 相互搭配实现

主要覆盖方法

findCandidateAdvisors() 方法
@Override
protected List<Advisor> findCandidateAdvisors() {// 通过 Spring IoC 容器获取所有 Advisor BeanList<Advisor> advisors = super.findCandidateAdvisors();// 通过 Spring IoC 容器获取所有标注 @AspectJ 注解的 Bean,再获取标注 AspectJ 注解(除了@Pointcut 其他的注解 @After、@Before、@AfterReturning 等)的方法if (this.aspectJAdvisorsBuilder != null) {advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors());}
return advisors;
}

总结

自动代理创建主要利用 Spring IoC 的 BeanPostProcessor#postProcessAfterInitialization 方法进行扩展,再利用 getAdvicesAndAdvisorsForBean 模板方法根据不同规则获取 Advice/Advisor(比如 Bean 名称,Bean 类型以及 AspectJ ),然后由 ProxyFactory 委派给 DefaultAopProxyFactory(默认,也可以替换)获取代理对象

跟着小马哥学系列之 Spring AOP(AbstractAutoProxyCreator 详解)相关推荐

  1. 跟着小马哥学系列之 Spring AOP(Pointcut 组件详解)

    学好路更宽,钱多少加班. --小马哥 版本修订 2021.5.19:去除目录 2021.5.21:引用 Spring 官方 Pointcut 概念,修改 Pointcut 功能表述 简介 大家好,我是 ...

  2. 跟着小马哥学系列之 Spring AOP(Advisor 详解)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  3. 跟着小马哥学系列之 Spring AOP(基于 XML 定义 Advice 源码解析)

    学好路更宽,钱多少加班. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  4. 跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期)

    跟着小马哥学系列之 Spring IoC(源码篇:Bean 生命周期) 简介 Bean 元信息来源 Bean 元信息解析成 BeanDefinition 并注册 BeanDefinition 转变成 ...

  5. 跟着小马哥学系列之 Spring IoC(源码篇:@Import)

    跟着小马哥学系列之 Spring IoC(源码篇:@Import) 简介 @ Import 简介 元信息 元注解 属性 @Import 注解 value 属性取值范围 ImportSelector I ...

  6. 跟着小马哥学系列之 Spring IoC(进阶篇:Environment)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  7. 跟着小马哥学系列之 Spring IoC(进阶篇:类型转换)

    学成路更宽,吊打面试官. --小马哥 简介 大家好,我是小马哥成千上万粉丝中的一员!2019年8月有幸在叩丁狼教育举办的猿圈活动中知道有这么一位大咖,从此结下了不解之缘!此系列在多次学习极客时间< ...

  8. Spring AOP知识详解

    本文来详细说下spring中的aop内容 文章目录 Spring AOP概述 Spring AOP一代 Pointcut Jointpoint Advice Advisor 织入 Spring AOP ...

  9. Spring AOP 切点详解

    本文内容 Spring 10种切点表达式详解 切点的组合使用 公共切点的定义 声明切点@Poincut @Poincut 的使用格式如下: @Poincut("PCD") // 切 ...

最新文章

  1. windows下安装Python virtualenvwrapper-win
  2. SAP UI5 setModel of scFld Controller
  3. 帮助你成为高手的视频和资料
  4. 不拘一格!清华将致力于培养顶尖数学家
  5. 解密flash播放器
  6. 数据泵避免个别表数据的导出
  7. 爬虫提交form表单中含有(unable to decode value)解决方法
  8. UNIX-LINUX编程实践教程-第五章-实例代码注解-echostate.c
  9. 我来做百科(第七天)
  10. ax 2012 随笔
  11. The Happy Prince
  12. 樊登读书搞定读后感_《读懂一本书:樊登读书法》_精选读后感_500字
  13. 单词数(HDU 2072)
  14. 充分统计量 因子分解定理证明 (转自维基)
  15. PVE7.2-3直通独显 nvidia 1080ti
  16. 自动分析黑名单及白名单的iptables脚本
  17. 005永磁同步电机的数学模型:写了很久,非常适合零基础的同学学习参考
  18. APP测试面试题汇总,面试必考一定要看
  19. LayaAir之制作迷宫
  20. 【树莓派4B深度学习 垃圾分类】Chap.3 树莓派安装opencv并测试视频接口实时视频流的垃圾分类【深度学习 招式篇】

热门文章

  1. markdown编辑器——文字颜色、大小、字体、背景色、图片大小与居中对齐的设置方法
  2. 把VBA转换成JAVA_请教,我用Excel中生成了Java代码,用VBA生成Jav
  3. 哪一款iPhone拍照效果最好?拍照最好用的苹果手机是哪款
  4. 程序linux培训,马哥-51CTO-Linux培训-0910-程序包管理
  5. Linux下查看网络设备类型
  6. C# 操作Excel加水印
  7. Fabric中的私有数据
  8. 2019/9/6工学结合周记
  9. ceph-deploy的calamari命令
  10. 汇编语言-用Si和Di实现字符串复制到其他的区域