Spring 框架学习7 - Spring 中的新注解

Spring 中的新注解

首先,看一下上个案例所写的 Spring 的配置文件:

<?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 在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.selflearning.spring"></context:component-scan>

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的必备的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
</beans>

这里有几个必须写的配置:

  1. 使用 context:component 标签告知 Spring 创建容器时必须扫描注解的包
  2. 由于 dbutils 框架是额外引入的,我们无法在其类上写上注解,所以必须在配置文件中写上创建 bean 的配置。
  3. 配置必须的数据源。

这就意味着,我们在使用 Spring 时,如果想要使用注解,必须要注解和配置文件同时使用。不能脱离 xml 配置文件。

那么我们怎么拿掉这些配置文件呢?我们就需要和配置文件中的这些内容有相同功能的注解。

1. Configuration 和 ComponentScan

Configuration 和 ComponentScan 可以解决以上必须配置 context:component-scan 标签

1.1 Configuration

首先,我们在源包下创建一个 config 类:

package config;


/**
 * 该类是一个配置类,它的作用和 bean.xml 是一样的
*/

public class SpringConfiguration {

}

这个类是一个配置类,我们想让它用于和 xml 文件一样的配置功能。此时,我们就需要 Configuration 注解。

这个注解,是 Spring 中的一个新的注解,它的作用是指定当前类为一个配置类。

package config;
import org.springframework.context.annotation.Configuration;

/**
 * 该类是一个配置类,它的作用和 bean.xml 是一样的
*/

@Configuration
public class SpringConfiguration {

}

这样,这个类,便是 Spring 的配置类了,它的功能应该和 Spring 中的 xml 配置文件一样。

这里有一个细节:当配置类作为 AnnotationConfigContext 对象创建的参数时,该注解可以不写。但并不绝对。

1.2 ComponentScan

ComponentScan 的作用:

通过注解注定 Spring 在创建容器时要扫描的包

属性:

  • value:它和 basePackage 的作用是一样的,都是用于指定创建容器时要扫描的包。我们使用次注解,就等同于在 xml 中配置了 context:component-scan 标签。

    /**
    	 * Alias for {@link #basePackages}.
    	 * <p>Allows for more concise annotation declarations if no other attributes
    	 * are needed &mdash; for example, {@code @ComponentScan("org.my.pkg")}
    	 * instead of {@code @ComponentScan(basePackages = "org.my.pkg")}.
    	 */
    	@AliasFor("basePackages")
    	String[] value() default {};
    
    	/**
    	 * Base packages to scan for annotated components.
    	 * <p>{@link #value} is an alias for (and mutually exclusive with) this
    	 * attribute.
    	 * <p>Use {@link #basePackageClasses} for a type-safe alternative to
    	 * String-based package names.
    	 */
    	@AliasFor("value")
    	String[] basePackages() default {};
    

    原因是因为,两者互为别名(AliasFor, 别名)

然后,我们在该类中使用这个注解就可以了:

package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
/**
 * 该类是一个配置类,它的作用和 bean.xml 是一样的
*/
@Configuration
@ComponentScan(basePackages = {"com.selflearning"})
public class SpringConfiguration {

}

这样, 就相当于在配置文件中配置了 context:component-scan 标签。

2. Bean 注解

我们来看以下的 Spring 配置文件:

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的必备的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

这里的 Spring 配置文件创建了两个 bean 对象,并放入了 Spring 的 ioc 容器中。分别是使用带参构造函数创建的 QueryRunner bean 对象和使用 set 构建方式创建的 ComboPooledDataSource 对象。

我们将这些配置信息抽取出来,可以得到以下的两个方法:

    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

createQueryRunner 方法通过 QueryRunner 类的构造函数返回一个 QueryRunner 对象。而 createDataSource 方法通过 ComboPooledDataSource 对象的 set 方法创建一个ComboPooledDataSource 对象并返回。这两个方法看似和 Spring 配置文件的创建 bean 对象的过程相同,但实际上少了一步,将创建的 bean 对象翻入 Spring 的 ioc 容器而这一步,正是 Bean 注解的工作。

2.1 Bean 的作用:

用于把当前方法的返回值作为 bean 对象,存入 Spring 的 IOC 容器中。

2.2 Bean 的属性:

  • name:用于指定 bean 的 id。默认值:当前方法的名称。

  • 当我们在使用注解配置方法时,如果方法有参数,Spring 框架会去容器中查找有没有可用的 bean 对象。查找的方式和 Autowired 注解的方式是一样。

2.3 Bean 的使用:

	/**
     * 用于创建一个 QueryRunner 对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

3. AnnotationConfigApplicationContext 类

我们打开测试类:

package com.selflearning.test;

import com.selflearning.spring.domain.Account;
import com.selflearning.spring.service.IAccountService;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import java.util.List;

public class AccountServiceTest {
    @Test
    public void testFindAll() {
        // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 得到业务层对象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        // 3. 执行方法
        List<Account> list = as.findAllAccount();

        for(Account account : list) {
            System.out.println(account);
        }
    }
}

发现,所有的测试类,都是使用的配置文件的信息。

嗯?那么我们还使用注解干什么?而且,我已经将配置文件删除了,这样,就会报错。

所以,AnnotationConfigApplicationContext 这位兄弟出场了。它用来读取被 Configuration 注解注解过的配置类

    @Test
    public void testFindAll() {
        // 1. 获取容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        // 2. 得到业务层对象
        IAccountService as = ac.getBean("accountService", IAccountService.class);
        // 3. 执行方法
        List<Account> list = as.findAllAccount();

        for(Account account : list) {
            System.out.println(account);
        }
    }

这样,就读取了配置类的信息,就可以正常使用了。

4. Import 注解

如果,我们有几个配置类,并且配置类之间是父子关系,我们已经怎么办呢?

这个时候 Import 注解就出现了。

  • 作用:用于导入其他的配置类

  • 属性

    • value:用于指定其他配置类的字节码
  • 细节:当我们使用 Import 注解之后,有 Import 注解的类就是父配置类,而导入的都是子配置类。

  • Import 注解的使用:

    • 首先是子配置类,用来配置 jdbc 的属性:

      package config;
      
      import com.mchange.v2.c3p0.ComboPooledDataSource;
      import org.apache.commons.dbutils.QueryRunner;
      import org.springframework.context.annotation.Bean;
      import org.springframework.context.annotation.ComponentScan;
      import org.springframework.context.annotation.Configuration;
      import org.springframework.context.annotation.Scope;
      
      import javax.sql.DataSource;
      import java.beans.PropertyVetoException;
      
      /**
       * 连接数据库
       */
      public class JdbcConfig {
      
          /**
           * 用于创建一个 QueryRunner 对象
           *
           * @param dataSource
           * @return
           */
          @Bean(name = "runner")
          @Scope("prototype")
          public QueryRunner createQueryRunner(DataSource dataSource) {
              return new QueryRunner(dataSource);
          }
      
          /**
           * 创建数据源对象
           *
           * @return
           */
          @Bean(name = "dataSource")
          public DataSource createDataSource() {
              ComboPooledDataSource ds = new ComboPooledDataSource();
              try {
                  ds.setDriverClass("com.mysql.cj.jdbc.Driver");
                  ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
                  ds.setUser("root");
                  ds.setPassword("root");
                  return ds;
              } catch (PropertyVetoException e) {
                  throw new RuntimeException(e);
              }
      
          }
      
      }
      
    • 然后是父配置类,这个里面放的应该是一些主要的配置:

      package config;
      
      import com.mchange.v2.c3p0.ComboPooledDataSource;
      import org.apache.commons.dbutils.QueryRunner;
      import org.springframework.context.annotation.*;
      
      import javax.sql.DataSource;
      import java.beans.PropertyVetoException;
      
      /**
       * 该类是一个配置类,它的作用和 bean.xml 是一样的
       */
      
      @Configuration
      @ComponentScan(basePackages = {"com.selflearning"})
      @Import(value = JdbcConfig.class)
      public class SpringConfiguration {
      
      
      }
      
    • 然后就是持久层的 dao 接口:

      package com.selflearning.spring.dao;
      
      import com.selflearning.spring.domain.Account;
      
      import java.util.List;
      
      /**
       * 账户的持久层接口
       */
      public interface IAccountDao {
      
          /**
           * 查询所有
           * @return
           */
          List<Account> findAllAccount();
      
          /**
           * 查询一个
           * @return accountId
           */
          Account findAccountById(Integer accountId);
      
          /**
           * 保存
           * @param account
           */
          void saveAccount(Account account);
      
          /**
           * 更新
           * @param account
           */
          void updateAccount(Account account);
      
          /**
           * 删除
           * @param accountId
           */
          void deleteAccount(Integer accountId);
      
      }
      
    • 其实现类:

      package com.selflearning.spring.dao.impl;
      
      import com.selflearning.spring.dao.IAccountDao;
      import com.selflearning.spring.domain.Account;
      import org.apache.commons.dbutils.QueryRunner;
      import org.apache.commons.dbutils.handlers.BeanHandler;
      import org.apache.commons.dbutils.handlers.BeanListHandler;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Repository;
      
      import java.sql.SQLException;
      import java.util.List;
      
      /**
       * 账户的持久层实现类
       */
      @Repository("accountDao")
      public class AccountDaoImpl implements IAccountDao {
      
          @Autowired
          private QueryRunner runner;
      
          @Override
          public List<Account> findAllAccount() {
              try {
                  return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
              } catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public Account findAccountById(Integer accountId) {
              try {
                  return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), accountId);
              } catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void saveAccount(Account account) {
              try{
                  runner.update("insert into account (name, money) values(?,?)", account.getName(), account.getMoney());
              }catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void updateAccount(Account account) {
              try{
                  runner.update("update account set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
              }catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      
          @Override
          public void deleteAccount(Integer accountId) {
              try{
                  runner.update("delete from account where id = ?", accountId);
              }catch (SQLException e) {
                  throw new RuntimeException(e);
              }
          }
      }
      
    • 业务层 dao 接口代码:

      package com.selflearning.spring.service;
      
      import com.selflearning.spring.domain.Account;
      
      import java.util.List;
      
      /**
       * 账户的业务层接口
       */
      public interface IAccountService {
      
          /**
           * 查询所有
           * @return
           */
          List<Account> findAllAccount();
      
          /**
           * 查询一个
           * @return accountId
           */
          Account findAccountById(Integer accountId);
      
          /**
           * 保存
           * @param account
           */
          void saveAccount(Account account);
      
          /**
           * 更新
           * @param account
           */
          void updateAccount(Account account);
      
          /**
           * 删除
           * @param accountId
           */
          void deleteAccount(Integer accountId);
      }
      
    • 业务层 dao 接口的实现类:

      package com.selflearning.spring.service.impl;
      
      import com.selflearning.spring.domain.Account;
      import com.selflearning.spring.service.IAccountService;
      import com.selflearning.spring.dao.IAccountDao;
      import org.springframework.beans.factory.annotation.Autowired;
      import org.springframework.stereotype.Service;
      
      import javax.annotation.Resource;
      import java.util.List;
      
      @Service("accountService")
      public class AccountServiceImpl implements IAccountService {
      
          @Autowired
          private IAccountDao accountDao;
      
          @Override
          public List<Account> findAllAccount() {
              return accountDao.findAllAccount();
          }
      
          @Override
          public Account findAccountById(Integer accountId) {
              return accountDao.findAccountById(accountId);
          }
      
          @Override
          public void saveAccount(Account account) {
              accountDao.saveAccount(account);
          }
      
          @Override
          public void updateAccount(Account account) {
              accountDao.updateAccount(account);
          }
      
          @Override
          public void deleteAccount(Integer accountId) {
              accountDao.deleteAccount(accountId);
          }
      }
      
    • 最后就是测试类:

      @Test
          public void testFindAll() {
              // 1. 获取容器
              ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
              // 2. 得到业务层对象
              IAccountService as = ac.getBean("accountService", IAccountService.class);
              // 3. 执行方法
              List<Account> list = as.findAllAccount();
      
              for(Account account : list) {
                  System.out.println(account);
              }
          }
      
    • 运行结果:

      
      Account{id=1, name='aaa', money=1000.0}
      Account{id=2, name='bbb', money=1000.0}
      Account{id=3, name='ccc', money=1000.0}
      Account{id=5, name='test', money=2000.0}
      

当然,如果你使用的是并列关系的配置类,直接在 AnnotationConfigApplicationContext 将配置类的字节码传入即可。

5. PropertySource

我们来看一下之前的数据库配置类:

    /**
     * 创建数据源对象
     *
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

我们发现,这里的属性,用字符串写死。如果如果数据库进行了迁移或者升级,数据库配置和以前不一样了,那么我们就要进入到 java 文件内部进行修改,这无疑是非常巨大的耦合。所以,Spring 提供了 PropertySource 和 PropertiesSources 这个两个注解来读取 Properties 配置文件。

PropertySource 注解:

  • 作用:用于指定 properties 文件的文职
  • 属性:
    • value:指定文件的名称和路径
    • 关键字:classpath:表示类路径下

PropertySource 注解的使用:

主配置类:

@Configuration
@ComponentScan(basePackages = {"com.selflearning"})
@Import(value = JdbcConfig.class)
// 读取 properties 文件
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {


}

数据库配置类:

package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * 连接数据库
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

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

    /**
     * 创建数据源对象
     *
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

}

测试类结果:

Account{id=1, name='aaa', money=1000.0}
Account{id=2, name='bbb', money=1000.0}
Account{id=3, name='ccc', money=1000.0}
Account{id=5, name='test', money=2000.0}

我们发现,这样进行配置,好像比之前使用 xml 文件进行配置还要更加复杂一些:

    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的必备的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

所以,这里有一个原则:

我们在进行 Spring 的配置的时候,首先要根据企业的编写习惯,这个是没有办法更改的(除非你换个公司。。。)。然后,在自己有选择的时候,我建议,自己编写的类直接使用注解。而当时别人的写好的类或者框架的时候,我们最好还是选择 xml 文件进行配置。这两种进行搭配,应该是最好的。

6. Querylifier 注解的另一种用法

如果当一个对象有多个实现的情况,这种情况下应该怎么办呢?

这个时候,Querylifier 这个注解就又出现了。

我们知道,之前 Querylifier 是和 Autowired 进行搭配使用的。当一个 bean 对象有多个相同类型的注入 bean 时,可以使用 Autowired 和 Querylifier 的搭配来实现 bean 参数的注入。而这里的 Querylifier 是用来指定需注入的 bean 对象的对象名称。

这里的 Querylifier 也是这种用法,用来给参数进行注解。

我们来看以下的例子:

package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

/**
 * 连接数据库
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

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

    /**
     * 创建数据源对象
     *
     * @return
     */
    @Bean(name = "ds1")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }
    
    /**
     * 创建数据源对象
     *
     * @return
     */
    @Bean(name = "ds2")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

}

这里有两个 DataSource 类型的 bean 对象。我们知道使用注解进行 bean 对象的创建,其中的 bean 类型的注入是自动注入。也就是首先通过类型进行判断注入变量是否和唯一一个 bean 对象的类型相同,如果相同注入。如果有两个或和注入变量相同类型的 bean 对象,会根据变量名称来进行注入。如果都没有,便会报错。

这个时候,我们可以使用 Querylifier 注解,给参数进行注解,通过 Bean 对象名称进行注入:

    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Querylifier(
"d1") DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     *
     * @return
     */
    @Bean(name = "ds1")
    public DataSource createDataSource() {
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring?serverTimezone=Asia/Shanghai");
            ds.setUser("root");
            ds.setPassword("root");
            return ds;
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }

    }

这样就可以了,Spring 就会根据 bean 的名称进行注入。

7. Spring 整合 junit

7.1 问题的分析

在之前编写的测试类中,会重复出现获取 Spring 容器,然后通过容器传进参数获取 bean 对象的操作。

 // 1. 获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 得到业务层对象
        IAccountService as = ac.getBean("accountService", IAccountService.class);

虽然可以通过使用 junit 中的 before 和 after 标签来确定一部分代码在测试代码执行前还是执行后执行:

     @Before
    public void init() {
        // 1. 获取容器
        ac = new ClassPathXmlApplicationContext("bean.xml");
        // 2. 得到业务层对象
        as = ac.getBean("accountService", IAccountService.class);
    }

执行结果并没有发生变化。

但是,各位要清楚,测试和编写程序是两个部门或者说是两种程序员干的事,一个测试工程师,一个开发工程师。而这个测试工程师,有可能懂 Spring 框架,也有可能不懂 Spring 框架。所以,测试工程师有可能写不出来上面那一行代码。

测试工程师应该关注的是你的方法能不能通过,然后生成测试报告,不应该关系你的测试类的代码应该如何去写。

那么从哪里进行入手并且解决呢?

那么接下来我们进行分析:

  1. 应用程序的入口
    • main 方法
  2. junit 单元测试中,没有 main 方法也能执行
    • junit 继承了一个 main 方法,该方法就会判断当前测试类中哪些方法有 @Test 注解。如果有,junit 就会让有 test 注解的方法执行。
    • 执行,也就是调用一下 method.invoke
  3. junit 不会管我们是否采用 Spring 框架。
    • 在执行测试方法时,junit 根本不知道我们是不是使用了 Spring 框架。所以也就不会为我们读取配置文件或者配置类,创建核心容器。
  4. 由以上三点可知,当测试方法执行时,没有 ioc 容器,就算写了 Autowired 注解,也无法实现注入。

7.2 问题的解决

  1. 导入 Spring 整合 Junit 的 jar 包(坐标):

    <!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.1.9.RELEASE</version>
        <scope>test</scope>
    </dependency>
    
  2. 使用 Junit 提供的一个注解,把原有的 main 方法替换,替换成 Spring 提供的 Runwith 注解:

    @RunWith(SpringJUnit4ClassRunner.class)
    public class AccountServiceTest {
    
  3. 告知 Spring 的运行器,Spring 和 ioc 创建是 基于 xml 的,还是基于配置类的。

    • ContextConfiguration 注解:
      • Location 属性:指定 xml 文件的位置,加上 classpath 关键字,表示在类路径下
      • classes:指定类注解所在位置
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    public class AccountServiceTest {
    
  4. 当我们使用 Spring5 的时候,要求使用的 junit 的版本在 4.12 以上。

  5. 当进行以上操作的时候,我们就可以使用测试类了。

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    public class AccountServiceTest {
    
    
        @Resource(name = "accountService")
        private IAccountService as;
    
    
        @Test
        public void testFindAll() {
    
            // 3. 执行方法
            List<Account> list = as.findAllAccount();
    
            for(Account account : list) {
                System.out.println(account);
            }
        }
    
        @Test
        public void testFindOne() {
            // 3. 执行方法
            System.out.println(as.findAccountById(1));
        }
    
        @Test
        public void testSave() {
            // 3. 创建对象
            Account account = new Account();
            account.setMoney(2000.0f);
            account.setName("test");
            as.saveAccount(account);
        }
    
        @Test
        public void testUpdate() {
            // 3. 创建需要更新的对象
            Account account = new Account();
            account.setId(4);
            account.setName("Mr.Wang");
            account.setMoney(52580f);
            // 4. 调用方法
            as.updateAccount(account);
        }
    
        @Test
        public void testDelete() {
            // 3. 调用方法
            as.deleteAccount(4);
        }
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值