文章目录
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>
这里有几个必须写的配置:
- 使用 context:component 标签告知 Spring 创建容器时必须扫描注解的包
- 由于 dbutils 框架是额外引入的,我们无法在其类上写上注解,所以必须在配置文件中写上创建 bean 的配置。
- 配置必须的数据源。
这就意味着,我们在使用 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 — 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 框架。所以,测试工程师有可能写不出来上面那一行代码。
测试工程师应该关注的是你的方法能不能通过,然后生成测试报告,不应该关系你的测试类的代码应该如何去写。
那么从哪里进行入手并且解决呢?
那么接下来我们进行分析:
- 应用程序的入口
- main 方法
- junit 单元测试中,没有 main 方法也能执行
- junit 继承了一个 main 方法,该方法就会判断当前测试类中哪些方法有 @Test 注解。如果有,junit 就会让有 test 注解的方法执行。
- 执行,也就是调用一下 method.invoke
- junit 不会管我们是否采用 Spring 框架。
- 在执行测试方法时,junit 根本不知道我们是不是使用了 Spring 框架。所以也就不会为我们读取配置文件或者配置类,创建核心容器。
- 由以上三点可知,当测试方法执行时,没有 ioc 容器,就算写了 Autowired 注解,也无法实现注入。
7.2 问题的解决
-
导入 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>
-
使用 Junit 提供的一个注解,把原有的 main 方法替换,替换成 Spring 提供的 Runwith 注解:
@RunWith(SpringJUnit4ClassRunner.class) public class AccountServiceTest {
-
告知 Spring 的运行器,Spring 和 ioc 创建是 基于 xml 的,还是基于配置类的。
- ContextConfiguration 注解:
- Location 属性:指定 xml 文件的位置,加上 classpath 关键字,表示在类路径下
- classes:指定类注解所在位置
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class AccountServiceTest {
- ContextConfiguration 注解:
-
当我们使用 Spring5 的时候,要求使用的 junit 的版本在 4.12 以上。
-
当进行以上操作的时候,我们就可以使用测试类了。
@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); } }