Spring框架-第二篇

第一章:Spring的IOC实现CRUD

1.1-需求和技术要求

  • 需求:实现账户的 CRUD 操作
  • 技术:
    • 使用 spring 的 IoC 实现对象的管理
    • 使用 dbutils作为持久层解决方案
    • 使用 c3p0 数据源

1.2-环境搭建

1.2.1-Maven工程导入依赖

  <dependencies>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--dbUtils-->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!--c3p0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.26</version>
    </dependency>
  </dependencies>

1.2.2-数据库脚本

CREATE DATABASE  IF NOT EXISTS db1
USE db1
CREATE TABLE account(
id INT PRIMARY KEY AUTO_INCREMENT,
NAME VARCHAR(40),
money FLOAT
)CHARACTER SET utf8 COLLATE utf8_general_ci;
INSERT INTO account(NAME,money) VALUES('aaa',1000);
INSERT INTO account(NAME,money) VALUES('bbb',1000);
INSERT INTO account(NAME,money) VALUES('ccc',1000);

1.2.3-创建实体类

public class Account {
  private int id;
  private String name;
  private float money;

  public int getId() {
    return id;
  }

  public void setId(int id) {
    this.id = id;
  }

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }

  public float getMoney() {
    return money;
  }

  public void setMoney(float money) {
    this.money = money;
  }

  @Override
  public String toString() {
    return "Account{" +
            "id=" + id +
            ", name='" + name + '\'' +
            ", money=" + money +
            '}';
  }
}

1.2.4-创建持久层

接口

public interface IAccountDao {
  /**
   * 查询所有账户信息
   *
   * @return
   */
  List<Account> findAll();

  /**
   * 根据id查询一个数据
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * 添加一个新数据
   * @param account
   */
  void save(Account account);

  /**
   * 更新
   * @param account
   */
  void update(Account account);

  /**
   * 删除
   * @param id
   */
  void del(int id);
}

实现类

public class AccountDaoImpl implements IAccountDao {
  private QueryRunner runner = null;
  public void setRunner(QueryRunner runner) {
    this.runner = runner;
  }

  @Override
  public List<Account> findAll() {
    try{
      String sql = "select * from account";
      return runner.query(sql,new BeanListHandler<Account>(Account.class));
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void save(Account account) {
    try{
      String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
      runner.update(sql,account.getName(),account.getMoney());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void del(int id) {
    try{
      String sql = "DELETE FROM account WHERE id=?";
      runner.update(sql,id);
    }catch (Exception e){
      e.printStackTrace();
    }
  }

}

1.2.5-创建业务层

接口

public interface IAccountServices {
  /**
   * 查询所有账户信息
   *
   * @return
   */
  List<Account> findAll();

  /**
   * 根据id查询一个数据
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * 添加一个新数据
   * @param account
   */
  void save(Account account);

  /**
   * 更新
   * @param account
   */
  void update(Account account);

  /**
   * 删除
   * @param id
   */
  void del(int id);
}

实现类

public class AccountServicesImpl implements IAccountServices {
  private IAccountDao dao = null;
  public void setDao(IAccountDao dao) {
    this.dao = dao;
  }

  @Override
  public List<Account> findAll() {
    return dao.findAll();
  }

  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }

  @Override
  public void save(Account account) {
    dao.save(account);
  }

  @Override
  public void update(Account account) {
    dao.update(account);
  }

  @Override
  public void del(int id) {
    dao.del(id);
  }
}

1.2.6-创建配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
</beans>

1.3-配置步骤

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置IAccountService对象-->
    <bean id="accountService" class="cn.lpl666.services.impl.AccountServicesImpl">
        <property name="dao" ref="accountDao"></property>
    </bean>
    <!--配置IAccountDao对象-->
    <bean id="accountDao" class="cn.lpl666.dao.impl.AccountDaoImpl">
        <property name="runner" ref="queryRunner"></property>
    </bean>
    <!--配置QueryRunner-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="c3p0"></constructor-arg>
    </bean>
    <!--配置数据源-->
    <bean id="c3p0" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--注入连接数据库必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/db1"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

1.4-测试

public class ClientTest {
  // 创建Spring容器
  private ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
  // 获取IAccountServices对象
  private IAccountServices services = ac.getBean("accountService",IAccountServices.class);
  /**
   * 查询所有
   */
  @Test
  public void findAll(){
    List<Account> all = services.findAll();
    System.out.println(all);
  }

  /**
   * 查询一个
   */
  @Test
  public void findOne(){
    Account account = services.findOne(1);
    System.out.println(account);
  }

  /**
   * 添加
   */
  @Test
  public void save(){
    Account account = new Account();
    account.setName("test2");
    account.setMoney(40000);
    services.save(account);
  }

  /**
   * 修改
   */
  @Test
  public void update(){
    Account account = services.findOne(1);
    account.setMoney(9999);
    services.update(account);
  }

  /**
   * 删除
   */
  @Test
  public void del(){
    services.del(6);
  }

}

第二章:基于注解的IOC配置

2.1-环境搭建

Maven引入依赖包

  <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>

持久层接口和实现类

接口

public interface IAccountDao {
  void save();
}

实现类

@Component("accountDao")
public class AccountDaoImpl implements IAccountDao {
  @Override
  public void save() {
    System.out.println("保存了账户");
  }
}

服务层接口和实现类

接口

public interface IAccountServices {
  void save();
}

实现类

@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;
  @Value("10")
  private int number;
  @Override
  public void save() {
    dao.save();
    System.out.println(number);
  }
}

配置文件bean.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 
        告知 spring 创建容器时要扫描的包 
        cn.lpl666包下所有的dao层、服务层、实体类都会被扫描
    -->
    <context:component-scan base-package="cn.lpl666"/>
</beans>

测试类

public class ClientUI {
  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    IAccountServices services =(IAccountServices) ac.getBean("accountService");
    services.save();
  }
}

2.2-常用注解

2.2.1-用于创建对象的

相当于:<bean id="" class="">

@Component

作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。

属性:value,指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小写

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {}

@Controller@Service@Repository

他们三个注解都是针对一个的(@Component)衍生注解,他们的作用及属性都是一模一样的。

他们只不过是提供了更加明确的语义化。

@Controller:一般用于表现层的注解。

@Service:一般用于业务层的注解。

@Repository:一般用于持久层的注解。

细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

2.2.2-用于注入数据的

相当于: <property name="" value=""><property name="" ref="">

@Autowired

作用:自动按照类型注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个 类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到 就报错。

@Qualifier

作用:在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowire 一起使用;但是给方法参数注入时,可以独立使用。

属性:value:指定 bean 的 id。

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;
)

@Resource

作用: 直接按照 Bean 的 id 注入。它也只能注入其他 bean 类型。

属性: name,指定 bean 的 id。

@Component("accountService")
public class AccountServicesImpl implements IAccountServices {
  @Resource(name="accountDao")
  private IAccountDao dao = null;
)

@Value

作用: 注入基本数据类型和 String 类型数据的

属性: value,用于指定值

  @Value("10")
  private int number;

2.2.3-用于改变作用范围的

相当于:<bean id="" class="" scope="">

@Scope

作用: 指定 bean 的作用范围。

属性: value,指定范围的值。

  • 取值:singleton prototype request session globalsession
@Component("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {}

2.2.4-和生命周期相关的

相当于:<bean id="" class="" init-method="" destroy-method="">

@PostConstruct

作用:用于指定初始化方法。

@PreDestroy

作用:用于指定销毁方法

2.2.5-注解和 XML 的选择问题

注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。

XML 的优势: 修改时,不用改源码。不涉及重新编译和部署

Spring 管理 Bean 方式的比较:

2.3-注解IOC实现CRUD

2.3.1-需求和技术要求

同上第一章:1.1-需求和技术要求

2.3.2-Maven工程导入依赖

<dependencies>
    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--dbUtils-->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.4</version>
    </dependency>
    <!--c3p0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.2</version>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--spring-test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.3.RELEASE</version>
      <scope>test</scope>
    </dependency>

    <!--mysql-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.26</version>
    </dependency>

  </dependencies>

2.3.3-数据库脚本

同上第一章:1.2.2-数据库脚本

2.3.4-创建实体类

同上第一章:1.2.3-创建实体类

2.3.5-创建持久层

接口:

public interface IAccountDao {
  /**
   * 查询所有账户信息
   *
   * @return
   */
  List<Account> findAll();

  /**
   * 根据id查询一个数据
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * 添加一个新数据
   * @param account
   */
  void save(Account account);

  /**
   * 更新
   * @param account
   */
  void update(Account account);

  /**
   * 删除
   * @param id
   */
  void del(int id);
}

实现类:

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
  @Autowired
  private QueryRunner runner = null;

  @Override
  public List<Account> findAll() {
    try{
      String sql = "select * from account";
      return runner.query(sql,new BeanListHandler<Account>(Account.class));
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public Account findOne(int id) {
    try{
      String sql = "select * from account where id=?";
      return runner.query(sql,new BeanHandler<>(Account.class),id);
    }catch (Exception e){
      e.printStackTrace();
    }
    return null;
  }

  @Override
  public void save(Account account) {
    try{
      String sql = "INSERT INTO account(NAME,money) VALUES(?,?)";
      runner.update(sql,account.getName(),account.getMoney());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void update(Account account) {
    try{
      String sql = "UPDATE account SET NAME=?,money=? WHERE id=?";
      runner.update(sql,account.getName(),account.getMoney(),account.getId());
    }catch (Exception e){
      e.printStackTrace();
    }
  }

  @Override
  public void del(int id) {
    try{
      String sql = "DELETE FROM account WHERE id=?";
      runner.update(sql,id);
    }catch (Exception e){
      e.printStackTrace();
    }
  }

}

2.3.6-创建业务层

接口:

public interface IAccountServices {
  /**
   * 查询所有账户信息
   *
   * @return
   */
  List<Account> findAll();

  /**
   * 根据id查询一个数据
   * @param id
   * @return
   */
  Account findOne(int id);

  /**
   * 添加一个新数据
   * @param account
   */
  void save(Account account);

  /**
   * 更新
   * @param account
   */
  void update(Account account);

  /**
   * 删除
   * @param id
   */
  void del(int id);
}

实现类:

@Service("accountService")
@Scope("prototype")
public class AccountServicesImpl implements IAccountServices {
  @Autowired
  @Qualifier("accountDao")
  private IAccountDao dao = null;

  @Override
  public List<Account> findAll() {
    return dao.findAll();
  }

  @Override
  public Account findOne(int id) {
    return dao.findOne(id);
  }

  @Override
  public void save(Account account) {
    dao.save(account);
  }

  @Override
  public void update(Account account) {
    dao.update(account);
  }

  @Override
  public void del(int id) {
    dao.del(id);
  }
}

2.3.7-创建jdbc配置文件

jdbc.properties

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/db1
jdbc.user=root
jdbc.password=root

2.3.8-创建jdbc配置类

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {
  @Value("${jdbc.driver}")
  private String driver;
  @Value("${jdbc.url}")
  private String url;
  @Value("${jdbc.user}")
  private String username;
  @Value("${jdbc.password}")
  private String password;

  /**
   * 创建QueryRunner对象
   * @param ds
   * @return
   */
  @Bean(name="runner")
  @Scope("prototype")
  public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
    return new QueryRunner(ds);
  }

  /**
   * 创建数据源对象
   * @return
   */
  @Bean(name = "dataSource")
  public DataSource createDataSource() {
    try {
      ComboPooledDataSource ds = new ComboPooledDataSource();
      ds.setUser(username);
      ds.setPassword(password);
      ds.setDriverClass(driver);
      ds.setJdbcUrl(url);
      return ds;
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }
}

2.3.9-创建Spring配置类

/**
    【Configuration】
        标识该类是配置类,相当于bean.xml配置文件
    【ComponentScan】
        用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:
        <context:component-scan base-package="cn.lpl666"/>是一样的。
 */
@Configuration    // 表示该类是配置类
@ComponentScan("cn.lpl666")  // spring容器要扫描的包
@Import({JdbcConfig.class})  // 导入其他配置类
public class SpringConfiguration {
}

2.3.10-测试

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class ClientTest {
  // 创建Spring容器
  private ApplicationContext as = null;
  // 获取IAccountServices对象
  @Autowired
  private IAccountServices services = null;
  /**
   * 查询所有
   */
  @Test
  public void findAll(){
    List<Account> all = services.findAll();
    System.out.println(all);
  }

  /**
   * 查询一个
   */
  @Test
  public void findOne(){
    Account account = services.findOne(1);
    System.out.println(account);
  }

  /**
   * 添加
   */
  @Test
  public void save(){
    Account account = new Account();
    account.setName("test3");
    account.setMoney(40000);
    services.save(account);
  }

  /**
   * 修改
   */
  @Test
  public void update(){
    Account account = services.findOne(1);
    account.setMoney(9999);
    services.update(account);
  }

  /**
   * 删除
   */
  @Test
  public void del(){
    services.del(6);
  }

}

2.4-新注解

@Configuration

作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationApplicationContext(有@Configuration 注解的类.class)。

属性: value:用于指定配置类的字节码

@Configuration    // 表示该类是配置类
public class SpringConfiguration {
}

@ComponentScan

作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的:<context:component-scan base-package="cn.lpl666"/> 是一样的。

属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

@Configuration    // 表示该类是配置类
@ComponentScan("cn.lpl666")  // spring容器要扫描的包
public class SpringConfiguration {
}

@Bean

作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。

属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。

public class JdbcConfig {
  /**
   * 创建QueryRunner对象
   * @param ds
   * @return
   */
  @Bean(name="runner")
  @Scope("prototype")
  public QueryRunner createQueryRunner(@Qualifier("dataSource") DataSource ds){
    return new QueryRunner(ds);
  }
  
}

@PropertySource

作用:用于加载.properties 文件中的配置。例如我们配置数据源时,可以把连接数据库的信息写到 properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置。

属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

@PropertySource("classpath:jdbc.properties")
public class JdbcConfig {}

@Import

作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。 属性: value[]:用于指定其他配置类的字节码。

@Import({JdbcConfig.class})  // 导入其他配置类
public class SpringConfiguration {
}

2.5-通过注解获取容器

ApplicationContext ac =
new AnnotationConfigApplicationContext(SpringConfiguration.class);

2.6- Spring 整合 Junit

2.6.1-问题

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac =new AnnotationConfigApplicationContext(SpringConfiguration.class);

IAccountService as = ac.getBean("accountService",IAccountService.class); 

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。

2.6.2-解决思路分析

针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器,我们就
无须手动创建了,问题也就解决了。
junit 是无法实现的,因为它自己都无法知晓我们是否使用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。
这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我
们只需要告诉它配置文件在哪就行了。

2.6.3-配置步骤

第一步: Maven引入依赖包,并且junit的版本要在4.12或以上版本

    <!--junit-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
    <!--spring-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.3.RELEASE</version>
    </dependency>
    <!--spring-test-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.3.RELEASE</version>
      <scope>test</scope>
    </dependency>

第二步:使用@RunWith 注解替换原有运行器

@RunWith(SpringJUnit4ClassRunner.class)
public class ClientTest {}

第三步:使用@ContextConfiguration 指定 spring 配置文件的位置

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
// 若是xml配置,则使用:@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {}

@ContextConfiguration 注解:

  • locations 属性:用于指定配置文件的位置。
  • 如果是类路径下,需要用 classpath:表明 classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置

第四步:使用@Autowired 给测试类中的变量注入数据

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class ClientTest {
  // 获取IAccountServices对象
  @Autowired
  private IAccountServices services = null;
}

2.6.4-为什么不把测试类配置到xml中

在解释这个问题之前,先解除大家的疑虑,配到 XML 中能不能用呢?
答案是肯定的,没问题,可以使用。
那么为什么不采用配置到 xml 中的方式呢?
这个原因是这样的:
    第一:当我们在 xml 中配置了一个 bean,spring 加载配置文件创建容器时,就会创建对象。
    第二:测试类只是我们在测试功能时使用,而在项目中它并不参与程序逻辑,也不会解决需求上的问
    题,所以创建完了,并没有使用。那么存在容器中就会造成资源的浪费。
    所以,基于以上两点,我们不应该把测试配置到 xml 文件中。

猜你喜欢

转载自www.cnblogs.com/lpl666/p/12307403.html