• 【深入浅出Spring6】第四期——实例化Bean和Bean的生命周期


    一、获取 Bean

    • Spring 提供了多种实例化Bean的方式:【只是表现形式不同,底层都是通过构造方法创建对象的】
      • 通过构造方法实例化 【最简单的方式直接声明bean
      • 通过简单工厂模式实例化 【定义一个简单模式工厂,然后通过工厂的静态方法获得Bean
      • 通过factory-bean实例化 【定义一个方法工厂,通过实例方法(需要创建对象才能调用)获取Bean
      • 通过FactoryBean接口实例化 【我们工厂类实现了FactoryBean 接口,声明工厂类的bean就能返回特定Bean的实例】

    $ 通过构造方法实例化

    • 需求:我们创建一个普通的Bean,然后在配置文件中声明一下,最后测试是否可以成功获得Bean

    编写一个Bean类 SpringBean.java

    package com.powernode.spring6.bean;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class SpringBean {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编写我们的配置文件 spring.xml

    <!--
    Bean对象实例化方法一: 通过声明bean,给出全限定类名,spring会自动调用该bean的无参构造方法来实例化Bean
    -->
    <bean id="sb" class="com.powernode.spring6.bean.SpringBean"/>
    
    • 1
    • 2
    • 3
    • 4

    编写测试方法 SpringBeanTest.java

    @Test
        public void testInstantiation1(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            SpringBean sb = applicationContext.getBean("sb", SpringBean.class);
            System.out.println(sb);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    $ 通过简单工厂实例化

    • 在之前工厂模式专题我们可以知道,简单工厂就是通过一个静态方法来获取Bean对象

    编写我们的测试类 Gun.java

    package com.powernode.spring6.bean;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Star {
        public Star() {
            System.out.println("调用了Star的无参构造方法。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    编写我们的简单工厂类 StarFactory.java

    package com.powernode.spring6.bean;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class StarFactory {
        // 在简单工厂中,通过静态方法获取我们的产品对象
        public static Star get(){
            return new Star();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编写我们的配置文件

    <!--方法二:通过简单工厂模式实例化Bean
                   我们需要指定使用哪个工厂,指定调用工厂的哪个静态方法获取Bean
                   这个Bean实际还是自己 new 的,只不过是通过Spring的bean来获取
                   底层还是调用的构造方法,只是外在的展示形式不同
                   -->
        <bean id="starBean" class="com.powernode.spring6.bean.StarFactory" factory-method="get" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    编写我们的测试方法

    @Test
        public void testInstantiation2(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            Star star = applicationContext.getBean("starBean", Star.class);
            System.out.println(star);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    $ 通过 factory-bean 实例化

    • 利用工厂方法模式来获取Bean对象,与简单工厂模式的区别在于 获取对象的方法方法不是静态的 【需要创建这个工厂的Bean对象】
    • 需求:我们创建一个类和对应的工厂,在配置文件中演示如果通过工厂方法模式实例化我们的Bean

    编写我们的 Gun.java

    package com.powernode.spring6.bean;
    
    /**
     * 具体产品角色
     * @author Bonbons
     * @version 1.0
     */
    public class Gun {
        public Gun() {
            System.out.println("调用了Gun的无参构造方法。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    编写我们的工厂方法GunFactory.java

    package com.powernode.spring6.bean;
    
    /**
     * 具体工厂角色
     * @author Bonbons
     * @version 1.0
     */
    public class GunFactory {
        // 方法是实例的 >> 需要创建对象才能调用
        public Gun get(){
            return new Gun();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    配置我们的 bean

    <!--
            方法三:通过工厂方法模式实例化Bean
            因为需要创建工厂的Bean,
            通过 factory-bean 属性告诉Spring使用哪个工厂的对象,
            然后通过 factory-method告诉Spring调用哪个方法获取Bean
        -->
        <bean id="factory" class="com.powernode.spring6.bean.GunFactory" />
        <bean id="gunBean" factory-bean="factory" factory-method="get"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 第一步,我们要声明我们工厂的bean,因为要用到这个对象
    • 第二步,不需要指定class属性,通过 factory-beanfactory-method 两个属性指定了使用哪个对象的什么方法获取Bean

    编写测试方法:

    @Test
        public void testInstantiation3(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            Gun gun = applicationContext.getBean("gunBean", Gun.class);
            System.out.println(gun);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    • 为什么此处还打印了 调用了Star的无参构造方法。
      • 因为我们生命Bean的时候scope使用了默认的 singleton >> 单例模式
      • 创建 Bean 对象的时机是初始化上下文的时候,也就是解析XML文件时就给XML文件中的所有Bean创建了对象。

    $ 通过 FactoryBean 实例化

    • 属于第三种方法的简化版,我们只要将工厂实现了FactoryBean接口,就不用去配置 factory-beanfactory-method 两个属性
    • 在我们声明工厂Bean的时候,就会返回一个我们指定的普通Bean的实例
    • factory-bean会自动指向实现FactoryBean接口的类,factory-method会自动指向**getObject()**方法
    • 需求:通过一个类和对应的工厂类来演示

    定义我们的具体角色类 Person.java

    package com.powernode.spring6.bean;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Person {
        public Person() {
            System.out.println("调用了Person的无参构造方法。");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    定义我们的具体工厂类 PersonFactory.java

    package com.powernode.spring6.bean;
    
    import org.springframework.beans.factory.FactoryBean;
    
    /**
     * 工厂Bean >> 可以获得普通Bean
     * @author Bonbons
     * @version 1.0
     */
    public class PersonFactoryBean implements FactoryBean<Person> {
        @Override
        public Person getObject() throws Exception {
            // 获取我们的Bean对象
            return new Person();
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    
        @Override
        public boolean isSingleton() {
            // 在接口中就实现了这个方法,默认为 true >> 单例
            return FactoryBean.super.isSingleton();
        }
    }
    
    • 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

    编写配置文件

    <!--
            方法四:通过FactoryBean接口来实例化我们的Bean
                属于方法三的简化形式,我们的工厂方法模式实现了这个接口,在配置的时候就不需要
                指定factory-bean、factory-bean两个属性
                直接通过创建工厂Bean就能直接返回一个具体的普通Bean的对象
        -->
        <bean id="person" class="com.powernode.spring6.bean.PersonFactoryBean" />
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    编写我们的测试文件

    @Test
        public void testInstantiation4(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            Person person = applicationContext.getBean("person", Person.class);
            System.out.println(person);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    $ BeanFactory 和 FactoryBean 的区别

    • BeanFactory

      • 工厂
      • Spring IoC 容器的顶级对象,被称为“Bean工厂”,负责创建Bean对象
    • FactoryBean

      • Bean
      • 能够辅助Spring实例化其它Bean对象的一个Bean。
    • 在Spring中,Bean可以分为两类:

      • 普通的Bean
      • 工厂Bean

    $ 注入 Date 针对方法四的实现案例分析

    • 我们知道 Date 可以作为简单类型也可以作为非简单类型使用
      • 作为简单类型时,通过 value 传递参数值要采用特定的语法格式
      • 作为非简单类型时,我们通过 ref 传入指定日期的 Bean
    • 需求:接下来我们演示如何通过工厂类实现非简单类型日期的注入

    编写我们的普通角色类 Person.java

    package com.powernode.spring6.bean;
    package com.powernode.spring6.bean;
    
    import java.util.Date;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Student {
        // 为了演示如何注入Date类型 >> 定义一个私有日期类型的birth
        private Date birth;
    
        public void setBirth(Date birth) {
            this.birth = birth;
        }
    
        @Override
        public String toString() {
            return "Student{" +
                    "birth=" + birth +
                    '}';
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    编写我们的工厂类 DateFactory.java

    package com.powernode.spring6.bean;
    
    import org.springframework.beans.factory.FactoryBean;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    /**
     * 使用工厂类获取我们的日期对象
     * @author Bonbons
     * @version 1.0
     */
    public class DataFactoryBean implements FactoryBean<Date> {
        // 通过构造方法传递我们要生成的日期
        private final String strDate;
    
        public DataFactoryBean(String srtDate) {
            this.strDate = srtDate;
        }
    
        @Override
        public Date getObject() throws Exception {
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
            // 格式化我们的日期字符串
            return sdf.parse(strDate);
        }
    
        @Override
        public Class<?> getObjectType() {
            return null;
        }
    }
    
    • 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

    编写我们配置文件

    <bean id="date" class="com.powernode.spring6.bean.DataFactoryBean">
            <!--通过它的构造方法传递我们的日期字符串-->
            <constructor-arg index="0" value="2022-11-15" />
        </bean>
        <bean id="student2" class="com.powernode.spring6.bean.Student">
            <property name="birth" ref="date" />
    </bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 我们通过构造注入传入指定的日期,在工厂类中通过实例方法将字符串类型的日期转化为对应的格式
    • 再创建我们Student的bean的时候,将属性值注入我们工厂类获得的Bean

    编写我们测试方法

    @Test
        public void testStudent2(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            Student student2 = applicationContext.getBean("student2", Student.class);
            System.out.println(student2);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    可以看到我们输入的日期还是那个格式,只是通过工厂类把输入日期的格式变成了我们习惯的格式。

    二、Bean的生命周期

    🌔 1、什么是Bean的生命周期?

    • 从对象创建到销毁的一个过程
    • Spring 框架就是一个Bean的工厂,负责Bean对象的创建和销毁

    🌔 2、那么一个Bean的一个完整生命周期都包括哪些部分?

    • 粗略的分,可以将生命周期分为五步
    • 考虑Bean后处理器,可以将生命周期分为七步
    • 再考虑接口的识别,可以将生命周期分为十步

    $ 分为五步的生命周期

    • 都包含哪五步:
      • 第一步:实例化Bean
      • 第二步:Bean属性赋值
      • 第三步:初始化Bean
      • 第四步:使用Bean
      • 第五步:销毁Bean
    • 需求:需要我们自己编写初始化和销毁方法,并在配置文件中进行配置

    编写我们的 User 类,我们通过User类演示Bean的这五步生命周期

    package com.powernode.spring6.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.*;
    
    /**
     * Bean的生命周期被分为五步的情况
     * @author Bonbons
     * @version 1.0
     */
    public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean {
        public User(){
            System.out.println("第一步,无参构造方法被调用");
        }
        private String name;
    
        public void setName(String name) {
            System.out.println("第二步,set方法被调用 >> 属性赋值");
            this.name = name;
        }
    
        // 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
        public void initBean(){
            System.out.println("第三步,初始化Bean");
        }
    
        public void destroyBean(){
            System.out.println("第五步,销毁Bean");
        }
    }
    
    • 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

    在配置文件中声明一下我们的Bean

    <!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
        <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean">
            <property name="name" value="白居易" />
        </bean>
    
    • 1
    • 2
    • 3
    • 4
    • 通过 init-method 属性指定我们的初始化方法
    • 通过 destroy-method 属性指定我们的销毁方法 【销毁不会自动执行,需要我们在测试方法中手动完成】

    编写我们的测试方法

    @Test
        public void testBeanLifecycleFive(){
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
            User user = applicationContext.getBean("user", User.class);
            System.out.println("第四步,使用Bean" + user);
            // 需要我们手动销毁Bean,但是这个方法属于ClassPathXMLApplicationContext的,所以我们需要强转
            ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
            context.close();
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 只有正常关闭spring容器,bean的销毁方法才会被调用。
    • ClassPathXmlApplicationContext类才有close()方法,所以我们需要强制类型转换
      在这里插入图片描述

    $ 分为七步的生命周期

    • 我们使用了Bean后处理器,就会在初始化Bean前后添加两个方法
      • 一个方法为Bean后处理器的 before 方法
      • 另一个方法为Bean后处理器的after方法
    • 我们这个Bean后处理器需要实现 BeanPostProcessor 接口中的方法,才能作为Bean后处理器使用
    • 而且这个Bean后处理器的作用范围是整个XML文件,配置后对整个XML文件中的bean都生效
    • 需求:我们写一个Bean后处理器,查看是否生命周期变成了七步

    编写我们的Bean后处理器:LogBeanPostProcessor

    package com.powernode.spring6.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.config.BeanPostProcessor;
    import org.springframework.lang.Nullable;
    
    /**
     * 日志Bean后处理器 >> 用来演示初始化前后插入代码的
     * @author Bonbons
     * @version 1.0
     */
    public class LogBeanPostProcessor implements BeanPostProcessor {
        // bean 我们的bean对象、beanName 我们bean的名字
        @Nullable
        public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("执行Bean后处理器的before方法");
            return bean;
        }
    
        @Nullable
        public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
            System.out.println("执行Bean后处理器的after方法");
            return bean;
        }
    }
    
    • 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

    在配置文件中配置我们的Bean后处理器

    <!--配置Bean后处理器,作用范围是整个配置文件-->
    <bean class="com.powernode.spring6.bean.LogBeanPostProcessor" />
    
    • 1
    • 2

    测试方法我们依旧使用我们上面五步生命周期的测试方法 >> 我们可以很清楚的看到生命周期变成了七步

    在这里插入图片描述

    $ 分为十步的生命周期

    • 在七步的基础上,增添了识别Bean是否实现了某些特定的接口
    • 如果实现了特定的接口,就调用接口中的方法,给我们传递一些与Bean相关的一些参数信息
    • 重新梳理一下,生命周期这十步都包括什么?
      • 第一步,实例化Bean
      • 第二步,Bean属性赋值
      • 第三步,检查Bean是否实现了Aware的相关接口,并设置相关依赖
        • BeanNameAware:通过setBeanName方法Spring会将Bean的名字传递给Bean
        • BeanClassLoaderAware:通过setBeanClassLoader方法Spring会将加载该Bean的类加载器传递给Bean
        • BeanFactoryAware :通过setBeanFactory方法Spring会将Bean工厂对象传递给Bean
      • 第四步,Bean后处理器的before执行
      • 第五步,检查Bean是否实现了InitializingBean接口,井调用接口方法 【afterPropertiesSet
      • 第六步,初始化Bean
      • 第七步,Bean后处理器的after执行
      • 第八步,使用Bean
      • 第九步,检查Bean是否实现了DisposableBean接口,井调用接口方法【destroy
      • 第十步,销毁Bean
    • 需求:让我们的User实现这三种接口中的方法,查看一下这十步的具体执行情况

    让我们User类实现这五个接口 【第一种三个、第二种一个、第三种一个】

    package com.powernode.spring6.bean;
    
    import org.springframework.beans.BeansException;
    import org.springframework.beans.factory.*;
    
    /**
     * Bean的生命周期被分为五步的情况
     * @author Bonbons
     * @version 1.0
     */
    public class User implements BeanNameAware, BeanClassLoaderAware, BeanFactoryAware, InitializingBean, DisposableBean{
    
        public User(){
            System.out.println("第一步,无参构造方法被调用");
        }
        private String name;
    
        public void setName(String name) {
            System.out.println("第二步,set方法被调用 >> 属性赋值");
            this.name = name;
        }
    
        // 让我们的User类去实现这三个接口 >> 用于在执行Bean后处理器的before方法之前调用
        public void initBean(){
            System.out.println("第三步,初始化Bean");
        }
    
        public void destroyBean(){
            System.out.println("第五步,销毁Bean");
        }
    
        @Override
        public void setBeanClassLoader(ClassLoader classLoader) {
            System.out.println("传递了类加载器" + classLoader);
        }
    
        @Override
        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            System.out.println("传递了创建这个Bean的工厂" + beanFactory);
        }
    
        @Override
        public void setBeanName(String s) {
            System.out.println("传递了Bean的名字" + s);
        }
    
        @Override
        public void destroy() throws Exception {
            System.out.println("DisposableBean's destroy method");
        }
    
        @Override
        public void afterPropertiesSet() throws Exception {
            System.out.println("InitializingBean's afterPropertiesSet method");
        }
    }
    
    • 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

    配置文件、测试文件都不需要改变

    在这里插入图片描述

    $ 不同的作用域的管理方式不同

    • 对于上面的生命周期,是针对我们采用默认的作用域 singleton 而言的
    • 如果我们让 scope = prototype,那么spring的工作内容只负责到使用Bean,之后的工作内容交给我们的客户端
    • 需求:我们将上面十步生命周期的配置文件进行修改,将scope设置为多例模式
    <!--将bean的生命周期分为五步:需要配置我们的初始化方法和销毁方法-->
        <bean id="user" class="com.powernode.spring6.bean.User" init-method="initBean" destroy-method="destroyBean" scope="prototype">
            <property name="name" value="白居易" />
        </bean>
    
    • 1
    • 2
    • 3
    • 4

    执行测试方法 >> Spring在完成Bean对象初始化之后,就不再追踪其生命周期了

    在这里插入图片描述

    $ 将我们自己创建的对象添加到Spring容器中

    • 需求:
      • 我们定义一个 Student 类,然后通过测试方法将这个类的实例添加到Spring容器中
      • 通过getBean再获取一下,看是否添加成功【返回同一个对象说明我们注入成功】

    编写我们的Student

    package com.powernode.spring6.bean;
    
    /**
     * @author Bonbons
     * @version 1.0
     */
    public class Student {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    编写我们的测试方法:

    @Test
        public void testRegisterBean(){
            // 我们自己创建一个对象
            Student student = new Student();
            System.out.println(student);
    
            // 创建一个可以将对象加入到Bean中的工厂
            DefaultListableBeanFactory defaultListableBeanFactory = new DefaultListableBeanFactory();
            defaultListableBeanFactory.registerSingleton("studentBean", student);
    
            // 通过getBean方法获取我们的Bean
            Student studentBean = defaultListableBeanFactory.getBean("studentBean", Student.class);
            System.out.println(studentBean);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    通过测试结果我们可以得出结论 >> 注入我们自己new的对象成功
    在这里插入图片描述

  • 相关阅读:
    IDEA-集成VisualVM插件,启动Java VisualVM
    【阿旭机器学习实战】【23】特征降维实战---人脸识别降维建模,并选出最有模型进行未知图片预测
    msm8953 LK通过cmdline向Kernel传递LCD参数过程分析
    XSC63-300-S-CB、XSC80-400-S-CA、XSC100-700-S-LB方型气缸
    前端常用布局方式大全——细致讲解
    【陕西理工大学-数学软件实训】数学实验报告(8)(数值微积分与方程数值求解)
    基于微服务(eureka)的优雅发布设计说明
    mac 竖屏显示屏鼠标无法从显示器移到mbp上
    【Selenium】WebDriverPool让动态爬虫变得更简单高效稳定
    Java泛型详解,史上最全图文详解!
  • 原文地址:https://blog.csdn.net/qq_61323055/article/details/127860204