• Spring 最全Bean的加载和获取方式整理


    一、 前言

    Spring Framework是一个强大且广泛使用的Java应用程序框架,它提供了众多功能和工具,其中之一就是Spring容器。Spring容器是Spring应用程序的核心,它负责管理和维护对象(通常称为"Bean")的生命周期。在Spring中,Bean的加载和获取是常见的操作,本文将总结Spring Bean的加载和获取方式,以帮助开发者更好地理解和使用Spring框架。

    二、Bean加载的九种方式

    1. XML配置方式

    在Spring中,最传统的方式是使用XML配置文件定义Bean。通过在XML配置文件中声明Bean的定义,Spring容器会在应用程序启动时加载这些Bean并管理它们的生命周期。以下是一个简单的XML配置示例:

    
    <context:component-scan base-package="com.fd.spring"/>
    
    
    <bean class="com.fd.spring.domain.User" id="user"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试:

    public static void main( String[] args )
        {
    
            ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    
            System.out.println(applicationContext.getBean(User.class));
    
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    2. XML+注解配置方式

    Bean类添加自动注入注解

    @Component // 自动注入注解
    public class User {
    }
    
    • 1
    • 2
    • 3

    spring.xml配置文件

    
    <context:component-scan base-package="com.fd.spring"/>
    
    • 1
    • 2

    测试结果:
    在这里插入图片描述

    3. 注解方式

    创建配置类

    @Configuration
    @ComponentScan(basePackages = "com.fd.spring") // 开启注解扫描
    public class SpringConfig {
    }
    
    • 1
    • 2
    • 3
    • 4

    测试方法:

    AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    
    System.out.println(applicationContext.getBean(User.class));
    
    • 1
    • 2
    • 3

    测试结果:
    在这里插入图片描述

    4. 使用@Bean方式

    使用@Bean加载第三方bean,并将所在类定义为配置类或Bean

    @Configuration
    public class SpringConfig1 {
    
        @Bean
        public Book book() {
            return new Book();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    测试方法:

    AnnotationConfigApplicationContext applicationContext1 = new AnnotationConfigApplicationContext(SpringConfig1.class);
    
    for (String name : applicationContext1.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    在这里插入图片描述
    扩展:

    根据条件确认是否加载Bean
    引入依赖:

    <dependency>
        <groupId>org.springframework.bootgroupId>
        <artifactId>spring-boot-autoconfigureartifactId>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    @Configuration
    @Import(User.class)
    public class SpringConfig1 {
    
        @Bean
        public Book book() {
            return new Book();
        }
    
        @Bean
        @ConditionalOnClass(Mouse.class) // 存在Mouse类就加载Cat
        public Cat tom() {
            return new Cat();
        }
    
        @Bean
        @ConditionalOnMissingClass("com.fd.spring.domain.Mouse") // 不存在Mouse类就加载Dog
        public Dog dog() {
            return new Dog();
        }
    
        @Bean
        @ConditionalOnBean(User.class) // 容器存在User bean则加载Food
        public Food food() {
            return new Food();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    测试结果:
    在这里插入图片描述

    类似注解:
    @ConditionalOnBean(仅仅在当前上下文中存在某个对象时,才会实例化一个Bean)
    @ConditionalOnClass(某个class位于类路径上,才会实例化一个Bean)
    @ConditionalOnExpression(当表达式为true的时候,才会实例化一个Bean)
    @ConditionalOnMissingBean(仅仅在当前上下文中不存在某个对象时,才会实例化一个Bean)
    @ConditionalOnMissingClass(某个class类路径上不存在的时候,才会实例化一个Bean)
    @ConditionalOnNotWebApplication(不是web应用)

    5. 使用@Import方式

    使用@Import注解导入要注入的bean对应的字节码,

    @Import(User.class)
    public class SpringConfig2 {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    被导入的bean无需使用注解声明为bean,此形式可以有效的降低源代码与Spring技术的耦合度,在spring技术底层及诸多框架的整合中大量使用

    public class User {
    }
    
    • 1
    • 2

    测试:

    AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig2.class);
    
    for (String name : applicationContext2.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试结果:
    在这里插入图片描述

    使用@Import注解导入配置类,会把配置类内的bean一起加载

    @Import(SpringConfig1.class)
    public class SpringConfig2 {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    测试结果:
    在这里插入图片描述

    6. 容器初始化完毕后注入bean

    使用上下文对象在容器初始化完毕后注入bean

    AnnotationConfigApplicationContext applicationContext2 = new AnnotationConfigApplicationContext(SpringConfig2.class);
    // 手动注册
    applicationContext2.register(Dog.class);
    
    for (String name : applicationContext2.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    测试结果:
    在这里插入图片描述

    7. 实现ImportSelector接口

    导入实现了ImportSelector接口的类,实现对导入源的编程式处理(动态加载)

    public class MySelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            // 判断导入类元数据是否存在XX注解
            boolean flag = importingClassMetadata.hasAnnotation("com.fd.spring.annotation.MyAnnotation");
            // 如果存在就导入User类,否则导入Book类
            if (flag) {
                return new String[] {"com.fd.spring.domain.User"};
            } else {
                return new String[] {"com.fd.spring.domain.Book"};
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    自定义注解

    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotation {
    }
    
    • 1
    • 2
    • 3
    @MyAnnotation
    @Import(MySelector.class)
    public class SpringConfig3 {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试方法:

    AnnotationConfigApplicationContext applicationContext3 = new AnnotationConfigApplicationContext(SpringConfig3.class);
    
    for (String name : applicationContext3.getBeanDefinitionNames()) {
        System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试结果:
    在这里插入图片描述

    取消自定义注解

    // @MyAnnotation
    @Import(MySelector.class)
    public class SpringConfig3 {
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试结果:
    在这里插入图片描述

    扩展:

    通过ImportSelector接口,我们就可以根据条件判断确认是否加载Bean

    public class MySelector implements ImportSelector {
        @Override
        public String[] selectImports(AnnotationMetadata importingClassMetadata) {
            try {
                // 如果存在Mouse类则加载Cat类
                Class<?> clazz = Class.forName("com.fd.spring.domain.Mouse");
    
                return new String[] {"com.fd.spring.domain.Cat"};
    
            } catch (ClassNotFoundException e) {
                return new String[]{};
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    8. 实现ImportBeanDefinitionRegistrar接口

    导入实现了ImportBeanDefinitionRegistrar接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的裁定,例如对现有bean的覆盖,进而达成不修改源代码的情况下更换实现的效果

    public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
    
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Book.class).getBeanDefinition();
    
            registry.registerBeanDefinition("book1",beanDefinition);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    配置类

    @Import(MyImportBeanDefinitionRegistrar.class)
    public class SpringConfig4 {
        
    }
    
    • 1
    • 2
    • 3
    • 4

    测试方法:

    AnnotationConfigApplicationContext applicationContext4 = new AnnotationConfigApplicationContext(SpringConfig4.class);
    
    for (String name : applicationContext4.getBeanDefinitionNames()) {
      	System.out.println(name);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    测试结果:
    在这里插入图片描述

    9. 实现BeanDefinitionRegistryPostProcessor接口

    导入实现了BeanDefinitionRegistryPostProcessor接口的类,通过BeanDefinition的注册器注册实名bean,实现对容器中bean的最终裁定

    public class MyBeanDefinitionRegisterPostProcessor implements BeanDefinitionRegistryPostProcessor {
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
        }
    
        @Override
        public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
    
            AbstractBeanDefinition beanDefinition = BeanDefinitionBuilder.rootBeanDefinition(Dog.class).getBeanDefinition();
            beanDefinitionRegistry.registerBeanDefinition("dog", beanDefinition);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    配置类

    @Import(MyBeanDefinitionRegisterPostProcessor.class)
    public class SpringConfig5 {
    
    }
    
    • 1
    • 2
    • 3
    • 4

    测试结果:
    在这里插入图片描述

    三、Bean获取的九种方式

    在Spring中,Bean的实例化、定位、配置应用程序中的对象及建立对象间的依赖关系,都是在IoC容器中进行的。因此,要在Spring中获取Bean,本质上就是从IoC容器当中获取Bean。

    在Spring中,BeanFactory是IoC容器的实际代表者,该接口提供了IoC容器最基本功能。同时,Spring还提供了另外一种类型的容器:ApplicationContext容器。

    ApplicationContext容器包括BeanFactory容器的所有功能(BeanFactory的子接口),提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。

    一般情况,我们称BeanFactory为IoC容器,称ApplicationContext为应用上下文。但有时为了方便,也将ApplicationContext称为Spring容器。

    通常不建议使用BeanFactory,但BeanFactory 仍然可以用于轻量级的应用程序,如移动设备或基于applet的应用程序,其中它的数据量和速度是显著。

    1. 通过BeanFactory获取

    通过BeanFactory来获取Bean。基于xml配置文件的时代,可以通过如下方式获得BeanFactory,再通过BeanFactory来获得对应的Bean。这种写法估计也只会出现在古老的项目当中。

    BeanFactory beanFactory = new XmlBeanFactory(new ClassPathResource("spring.xml"));
    
    beanFactory.getBean(User.class);
    
    • 1
    • 2
    • 3

    2. 启动获取ApplicationContext

    在项目启动时先获取ApplicationContext对象,然后将其存储在一个地方,以便后续用到时进行使用。这里只介绍基于SpringBoot启动实现:

    @SpringBootApplication
    public class Application {
    
        public static void main(String[] args) {
            // 启动时,保存上下文,并保存为静态
            ConfigurableApplicationContext ac = SpringApplication.run(Application.class, args);
            SpringContextUtil.setApplicationContext(ac);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    对应的SpringContextUtil类如下:

    public class SpringContextUtil {
    
        private static ApplicationContext ac;
    
        public static <T>  T getBean(String beanName, Class<T> clazz) {
            T bean = ac.getBean(beanName, clazz);
            return bean;
        }
    
        public static void setApplicationContext(ApplicationContext applicationContext){
            ac = applicationContext;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    启动Spring项目时,直接获取到ApplicationContext的引用,然后将其存储到工具类当中。在使用时,则从工具类中获取ApplicationContext容器,进而从中获得Bean对象。

    3. 实现BeanFactoryAware接口

    在上面的方式中,XmlBeanFactory已经被废弃,但可以通过其他方式来获得BeanFactory,然后再从BeanFactory中获得指定的Bean。获取BeanFactory实例最简单的方式就是实现BeanFactoryAware接口。

    @Component
    public class BeanFactoryHelper implements BeanFactoryAware {
    
     private static BeanFactory beanFactory;
    
     /**
      * 重写 BeanFactoryAware 接口的方法
      * @param beanFactory :参数赋值给本地属性之后即可使用 BeanFactory
      * @throws BeansException BeansException
      */
     @Override
     public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
      BeanFactoryHelper.beanFactory = beanFactory;
     }
     /**
      * 根据名称获取容器中的对象实例
      * @param beanName :注入的实例必须已经存在容器中,否则抛异常:NoSuchBeanDefinitionException
      * @return Object
      */
     public static Object getBean(String beanName) {
      return beanFactory.getBean(beanName);
     }
     /**
      * 根据 class 获取容器中的对象实例
      * @param requiredType :被注入的必须已经存在容器中,否则抛异常:NoSuchBeanDefinitionException
      * @param  Class
      * @return 对象
      */
     public static <T> T getBean(Class<T> requiredType) {
      return beanFactory.getBean(requiredType);
     }
     /**
      * 判断 spring 容器中是否包含指定名称的对象
      * @param beanName bean名称
      * @return 是否存在
      */
     public static boolean containsBean(String beanName) {
      return beanFactory.containsBean(beanName);
     }
     //其它需求皆可参考 BeanFactory 接口和它的实现类
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    在上述工具类中,便是基于BeanFactoryAware的特性,获得了BeanFactory,然后再通过BeanFactory来获得指定的Bean。

    4. 实现ApplicationContextAware接口

    通过实现ApplicationContextAware接口,在Spring容器启动时将ApplicationContext注入进去,从而获取ApplicationContext对象,这种方法也是常见的获取Bean的一种方式,推荐使用。

    @Component
    public class SpringContextUtils implements ApplicationContextAware {
    
    	 private static ApplicationContext ac;
    	
    	 @Override
    	 public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
    	  ac = applicationContext;
    	 }
    	
    	 public static <T> T getBean(Class<T> clazz) {
    	  T bean = ac.getBean(clazz);
    	  return bean;
    	 }
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    5. 通过继承ApplicationObjectSupport

    此种方式依旧是先获得ApplicationContext容器,然后从中获取Bean对象,只不过是基于继承ApplicationObjectSupport类实现的。

    @Component
    public class SpringContextUtils extends ApplicationObjectSupport {
    
        private ApplicationContext applicationContext;
    
        public <T> T getBean(Class<T> clazz) {
    
            if (applicationContext == null) {
                applicationContext = getApplicationContext();
            }
    
            if (applicationContext == null) {
                return null;
            } else {
                return applicationContext.getBean(clazz);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    ApplicationObjectSupport类图如下,我们看到它实现了ApplicationContextAware接口,在Spring容器初始化过程中回调方法setApplicationContext来完成ApplicationContext的赋值。
    在这里插入图片描述

    6. 通过继承WebApplicationObjectSupport

    WebApplicationObjectSupport是ApplicationObjectSupport的一个实现类,提供了Web相关的支持。实现原理与ApplicationObjectSupport一样。

    @Component
    public class SpringContextUtils extends WebApplicationObjectSupport {
    
    	private ApplicationContext applicationContext;
    
        public <T> T getBean(Class<T> clazz) {
    
            if (applicationContext == null) {
                applicationContext = getApplicationContext();
            }
    
            if (applicationContext == null) {
                return null;
            } else {
                return applicationContext.getBean(clazz);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    类图如下:
    在这里插入图片描述

    7. 通过BeanFactoryPostProcessor

    Spring工具类,方便在非Spring管理环境中获取Bean。

    @Component
    public final class SpringUtils implements BeanFactoryPostProcessor{
        
        /** Spring应用上下文环境 */
        private static ConfigurableListableBeanFactory beanFactory;
    
        @Override
        public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException{
            SpringUtilsS.beanFactory = beanFactory;
        }
    
        /**
         * 获取对象
         *
         * @param name
         * @return Object 一个以所给名字注册的bean的实例
         * @throws BeansException
         *
         */
        @SuppressWarnings("unchecked")
        public static <T> T getBean(String name) throws BeansException{
            return (T) beanFactory.getBean(name);
        }
    
        /**
         * 获取类型为requiredType的对象
         *
         * @param clz
         * @return
         * @throws BeansException
         *
         */
        public static <T> T getBean(Class<T> clz) throws BeansException{
            T result = (T) beanFactory.getBean(clz);
            return result;
        }
    
        /**
         * 如果BeanFactory包含一个与所给名称匹配的bean定义,则返回true
         *
         * @param name
         * @return boolean
         */
        public static boolean containsBean(String name){
            return beanFactory.containsBean(name);
        }
    
        /**
         * 判断以给定名字注册的bean定义是一个singleton还是一个prototype。 如果与给定名字相应的bean定义没有被找到,将会抛出一个异常(NoSuchBeanDefinitionException)
         *
         * @param name
         * @return boolean
         * @throws NoSuchBeanDefinitionException
         *
         */
        public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException{
            return beanFactory.isSingleton(name);
        }
    
        /**
         * @param name
         * @return Class 注册对象的类型
         * @throws NoSuchBeanDefinitionException
         *
         */
        public static Class<?> getType(String name) throws NoSuchBeanDefinitionException{
            return beanFactory.getType(name);
        }
    
        /**
         * 如果给定的bean名字在bean定义中有别名,则返回这些别名
         *
         * @param name
         * @return
         * @throws NoSuchBeanDefinitionException
         *
         */
        public static String[] getAliases(String name) throws NoSuchBeanDefinitionException{
            return beanFactory.getAliases(name);
        }
    
        /**
         * 获取aop代理对象
         * 
         * @param invoker
         * @return
         */
        @SuppressWarnings("unchecked")
        public static <T> T getAopProxy(T invoker){
            return (T) AopContext.currentProxy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92

    8. 通过WebApplicationContextUtils

    Spring提供了工具类WebApplicationContextUtils,通过该类可获取WebApplicationContext对象。
    这个方法很常见于SpringMVC构建的Web项目中,适用于Web项目的B/S结构。下面两个工具方式的区别是,前者在获取失败时抛出异常,后者返回null。

    public class SpringContextUtils2  {
    
        private ApplicationContext applicationContext;
    
        public static <T> T getBean(ServletContext request, String name, Class<T> clazz){
    
            WebApplicationContext webApplicationContext = WebApplicationContextUtils.getRequiredWebApplicationContext(request);
            // 或者
            WebApplicationContext webApplicationContext1 = WebApplicationContextUtils.getWebApplicationContext(request);
            
           //webApplicationContext1.getBean(name, clazz);
    
            return webApplicationContext.getBean(name, clazz);
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    9. 通过ContextLoader

    使用ContextLoader提供的getCurrentWebApplicationContext方法,也是常用的获取WebApplicationContext的一种方法。

    具体实现代码如下:

    WebApplicationContext wac = ContextLoader.getCurrentWebApplicationContext();
    wac.getBean(beanID);
    
    • 1
    • 2

    该方法常见于SpringMVC实现的Web项目中。该方式是一种不依赖于Servlet,不需要注入的方式。但是需要注意一点,在服务器启动时和Spring容器初始化时,不能通过该方法获取Spring容器。

    总结:

    虽然,spring提供了好几种方法(3、4、5、6、7)可以实现在普通的类中继承或实现相应的类或接口来获取spring 的ApplicationContext对象,但是在使用是一定要注意实现了这些类或接口的普通java类一定要在Spring 的配置文件application-context.xml文件中进行配置或注解配置(注入容器)。否则获取的ApplicationContext对象将为null。

  • 相关阅读:
    机器学习必修课 - 交叉验证 Cross-Validation
    【论文极速读】Prompt Tuning——一种高效的LLM模型下游任务适配方式
    一个奇葩的线上问题,导致我排查了一天
    数据可视化引领智慧工业新时代
    超前进位加法器
    阿里云今年有双十一活动吗?不好说
    -bash: ifconfig: command not found
    浅层砂过滤器 全自动浅层介质过滤系统
    python实现命令tree的效果
    PaddleOCR学习笔记1-初步尝试
  • 原文地址:https://blog.csdn.net/weixin_44863237/article/details/133323127