Spring(二)

  • Spring中关于IoC配置的常用注解
  • 使用xml方式和注解方式实现单表的CRUD操作(持久层技术选择dbutils)
  • 改造基于注解的IoC案例,使用纯注解的方式实现(Spring的一些新注解使用)
  • Spring和Junit整合

史上最全的java spring注解

Spring常用注解【经典总结】

Spring注解大全

Spring 注解大全与详解

Spring基于注解的IOC配置

廖雪峰Spring教程:使用Annotation配置

Spring注解大全

Spring 注解大全与详解

在学习基于注解的IoC配置之前,要先有一个认识:注解配置和xml配置要实现的功能都是一样的,都是要降低程序间的耦合。只是配置的形式不一样

关于实际的开发中到底使用xml还是注解,每家公司有着不同的使用习惯。所以这两种配置方式我们都需要掌握。

在之前的笔记中,我们已经完成了Spring基于Xml配置的IOC

1
2
3
<!--把对象的创建通过Spring来管理-->
<bean id="accountService" class="top.qing.service.impl.AccountServiceImpl"></bean>
<bean id="accountDao" class="top.qing.dao.impl.AccountDaoImpl"></bean>

之前的笔记也已经提到过了,在Spring中我们有三种方法配置IoC:

  • 基于 XML 的配置文件
  • 基于注解的配置
  • 基于 Java 的配置

这里我们基于注解来配置IoC,删除掉bean.xml中的<bean>标签。

相关注解介绍

我们将相关注解分为四类:

  • 用于创建对象的注解(相当于之前基于xml配置中的<bean id="" class="">
  • 用于注入数据的注解(相当于:<property name="" ref=""><property name="" value="">
  • 用于改变作用范围的注解(相当于:<bean id="" class="" scope="">
  • 和生命周期相关的注解(了解)(相当于:<bean id="" class="" init-method="" destroy-method="" />

用于创建对象的注解

  • @Component

    • 作用:用于把当前类对象存入Spring容器中
    • 属性:value:用于指定bean的id。当我们不写value属性时,它的默认值是当前类名,且首字母改小写
  • @Controller:一般用在表现层

  • @Service:一般用在业务层

  • @Repository:一般用在持久层

    @Controller@Service@Repository这三个注解的作用和属性和@Component是一样的,细分了三个注解,是Spring框架为我们提供明确的三层架构使用的注解,使我们的三层对象更加清晰

例子:首先要在bean.xml配置文件中添加如下代码

1
2
3
4
<!--告知Spring在创建容器时要扫描的包(注意是包而不是类)-->
<!--配置所需要的标签不是在beans的约束中,而是一个名称为context名称空间和约束中-->
<context:component-scan base-package="top.qing.service.impl"></context:component-scan>
<context:component-scan base-package="top.qing.dao.impl"></context:component-scan>

然后我们在AccountDaoImpl.java和AccountServiceImpl.java中使用@Component注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.qing.dao.impl;


import org.springframework.stereotype.Component;
import top.qing.dao.AccountDao;

@Component
public class AccountDaoImpl implements AccountDao {

@Override
public void saveAccount() {
System.out.println("保存了账户");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package top.qing.service.impl;


import org.springframework.stereotype.Component;
import top.qing.dao.AccountDao;
import top.qing.dao.impl.AccountDaoImpl;
import top.qing.service.AccountService;

@Component
public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

@Override
public void saveAccount() {
accountDao.saveAccount();
}
}

模拟客户端的代码如下:

1
2
3
4
5
6
7
8
9
10
@Test
public void test1(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountServiceImpl");
System.out.println(accountService);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDaoImpl");
System.out.println(accountDao);
}

结果如下:

对象创建成功

而另外三个注解@Controller@Service@Repository@Controller注解的用法是一样的

我们在持久层AccountDaoImpl中:

1
@Repository("accountDao")

在业务层AccountServiceImpl中:

1
@Service(value = "accountService")

当注解的属性只有一个,且属性是value是,可以省略value=

执行代码如下:

1
2
3
4
5
6
7
8
9
10
@Test
public void test2(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
System.out.println(accountService);
AccountDao accountDao = (AccountDao) applicationContext.getBean("accountDao");
System.out.println(accountDao);
}

结果同样可以创建出对象:

用于注入数据的注解

  • @Autowired
  • @Qualifier
  • @Resource
  • @Value

注意@Autowired@Qualifier@Resource这三个注解都只能注入其他bean类型的数据,而**基本类型String类型无法使用上述注解实现,而是需要@Value注解**。另外,集合类型的注入只能通过XML来实现

(在之前的依赖注入笔记中,也已经提到了依赖注入中能注入的三类数据

@Autowired

Spring @Autowired 注释

@Autowired

  • 作用:自动按照类型注入。只要容器中唯一一个bean对象类型和要注入的变量类型匹配,就可以注入成功
    • 如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错
    • 如果Ioc容器中有多个类型匹配时:首先按照数据类型匹配出多个对象,然后再以变量名作为bean的id在刚刚匹配出的多个id中继续查找,如果根据id没有查找到结果则报错
  • 出现位置:可以在变量上,也可以在方法上
  • 注意:在使用注解注入时,set方法就不是必须的了

自动按照类型注入

视频讲解

比如说:在加上@Autowired注解之前,我们执行代码:

1
2
3
4
5
6
7
8
@Test
public void test3(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.saveAccount();
}

会报空指针异常的错误,这是因为我们在AccountServiceImpl中并没有给accountDao赋值:

1
private AccountDao accountDao;

而加上@Autowired注解后就可以自动注入数据了:

1
2
@Autowired
private AccountDao accountDao;

结果如下:

@Qualifier

Spring @Qualifier 注释

@Qualifier

  • 作用:在按照类型注入(@Autowired的基础之上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以

  • 属性:

    • value:用于指定注入bean的id(按照名称注入)
  • 注意:@Qualifier注解不能单独使用,只能和@Autowired一起使用

    (这里有一种特殊情况可以单独使用@Qualifier注解,具体见@Qualifier的另一种用法

例子:这里我们有两个AccountDao的实现类,代码分别为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.qing.dao.impl;


import org.springframework.stereotype.Repository;
import top.qing.dao.AccountDao;

@Repository("accountDao1")
public class AccountDaoImpl implements AccountDao {

@Override
public void saveAccount() {
System.out.println("保存了账户111");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.qing.dao.impl;


import org.springframework.stereotype.Repository;
import top.qing.dao.AccountDao;

@Repository("accountDao2")
public class AccountDaoImpl2 implements AccountDao {

@Override
public void saveAccount() {
System.out.println("保存了账户222");
}
}

然后在AccountServiceImpl中添加@Qualifier注解,重要部分代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
@Service(value = "accountService")

public class AccountServiceImpl implements AccountService {

@Autowired
@Qualifier(value = "accountDao2")
private AccountDao accountDao;

@Override
public void saveAccount() {
accountDao.saveAccount();
}
}

注意@Qualifier注解不能单独使用,只能和@Autowired一起使用

还是执行上述的saveAccount方法,结果如下:

分析:由于我们@Qualifier注解中指定的value为accountDao2,所以会去找id为accountDao2的bean对象,而AccountDaoImpl2中的注解@Repository("accountDao2")指定了该bean的id,所以会将AccountDaoImpl2注入到accountDao中。

@Resource

@Resource

  • 作用:直接按照bean的id注入可以独立使用
  • 属性:
    • name:用于指定bean的id

例子,还是基于上述的例子,只不过我们在AccountServiceImpl中修改代码如下:

1
2
@Resource(name = "accountDao1")
private AccountDao accountDao;

结果如下:

@Value

@Value

  • 作用:用于注入基本类型和String类型的数据
  • 属性:
    • value:用于指定数据的值。它可以使用spring中SpEL(也就是Spring的el表达式)
      • SpEL的写法:${表达式} (之前的笔记中已经提到过EL表达式了)

例子:首先在AccountServiceImpl中指定:@Resource(name = "accountDao2")

然后在AccountDaoImpl2中修改代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package top.qing.dao.impl;


import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Repository;
import top.qing.dao.AccountDao;

@Repository("accountDao2")
public class AccountDaoImpl2 implements AccountDao {

@Value("jay")
private String name;

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

@Override
public void saveAccount() {
System.out.println("姓名为:"+this.name);
System.out.println("保存了账户222");
}
}

执行代码:

1
2
3
4
5
6
7
8
@Test
public void test3(){
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
accountService.saveAccount();
}

结果如下:

用于改变作用范围的注解

@Scope

  • 作用:用于指定bean的作用范围
  • 属性:
    • value:指定范围的取值(常用取值:singleton(单例),prototype(多例),默认是singleton)

(Bean对象的作用范围和生命周期笔记

视频讲解

和生命周期相关的注解(了解)

  • @PreDestroy
    • 作用:用于指定销毁方法
  • @PostConstruct
    • 作用:用于指定初始化方法

视频讲解


Spring案例:实现单表的CRUD

这里我们持久层技术选择dbutils(为了避免引入后面的相关知识)(JAVA 基础 DBUtils的使用DbUtils的使用

  • 基于Xml方式
  • 基于注解方式

基于Xml方式

环境准备

首先和创建一个Maven过程,在pom.xml中:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.qing</groupId>
<artifactId>Spring02_crud_XmlIoC</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>

<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>


</project>

然后开始建表,SQL如下:

1
2
3
4
5
6
7
8
9
create table account2(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;

insert into account2(name,money) values('aaa',1000);
insert into account2(name,money) values('bbb',1000);
insert into account2(name,money) values('ccc',1000);

account2表

接下来我们依次创建业务层和持久层

首先创建业务层,在service包下创建AccountService接口和其实现类如下:

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
package top.qing.service;

import top.qing.domain.Account;

import java.util.List;

/**
* 账户的业务层接口
*/
public interface AccountService {
/**
* 查询所有account记录
* @return
*/
List<Account> findAll();

/**
* 根据id查询一条account
* @param id
* @return
*/
Account findById(Integer id);

/**
* 保存
* @param account
*/
void saveAccount(Account account);

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

/**
* 根据id删除account
* @param id
*/
void deleteAccountById(Integer id);
}
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
package top.qing.service.impl;

import top.qing.dao.AccountDao;
import top.qing.domain.Account;
import top.qing.service.AccountService;

import java.util.List;

public class AccountServiceImpl implements AccountService {

private AccountDao accountDao;

// accountDao的set方法,用于后续Spring基于Xml的注入
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}

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

@Override
public Account findById(Integer id) {
return accountDao.findById(id);
}

@Override
public void saveAccount(Account account) {
accountDao.saveAccount(account);
}

@Override
public void updateAccount(Account account) {
accountDao.updateAccount(account);
}

@Override
public void deleteAccountById(Integer id) {
accountDao.deleteAccountById(id);
}
}

然后创建持久层,在dao包下创建AccountDao接口和其实现类如下:

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
package top.qing.dao;

import top.qing.domain.Account;

import java.util.List;

/**
* 账户的持久层接口
*/
public interface AccountDao {
/**
* 查询所有account记录
* @return
*/
List<Account> findAll();

/**
* 根据id查询一条account
* @param id
* @return
*/
Account findById(Integer id);

/**
* 保存
* @param account
*/
void saveAccount(Account account);

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

/**
* 根据id删除account
* @param id
*/
void deleteAccountById(Integer id);
}
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
package top.qing.dao.impl;

import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import top.qing.dao.AccountDao;
import top.qing.domain.Account;

import java.sql.SQLException;
import java.util.List;

public class AccountDaoImpl implements AccountDao {

private QueryRunner queryRunner;

// queryRunner的set方法,用于后续Spring基于Xml的注入
public void setQueryRunner(QueryRunner queryRunner) {
this.queryRunner = queryRunner;
}

@Override
public List<Account> findAll() {
List<Account> accountList = null;
try {
accountList = queryRunner.query("select * from account2", new BeanListHandler<Account>(Account.class));
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return accountList;
}

@Override
public Account findById(Integer id) {
Account account = null;
try {
account = queryRunner.query("select * from account2 where id = ?", new BeanHandler<Account>(Account.class), id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return account;
}

@Override
public void saveAccount(Account account) {
try {
// 主键在SQL语句中设置为自增长,所以这里只需要插入name和money字段即可
queryRunner.update("insert into account2(name,money) values(?,?) ", account.getName(), account.getMoney());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}

@Override
public void updateAccount(Account account) {
try {
// 主键在SQL语句中设置为自增长,所以这里只需要插入name和money字段即可
queryRunner.update("update account2 set name = ?, money = ? where id = ?", account.getName(), account.getMoney(), account.getId());
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}

@Override
public void deleteAccountById(Integer id) {
try {
// 主键在SQL语句中设置为自增长,所以这里只需要插入name和money字段即可
queryRunner.update("drop from account2 where id = ?", id);
} catch (SQLException throwables) {
throwables.printStackTrace();
}

}
}

最后别忘了在domain包下创建Account类:

1
2
3
4
5
6
7
8
9
10
package top.qing.domain;

import lombok.Data;

@Data
public class Account {
private Integer id;
private String name;
private Float money;
}

基于Xml的IoC配置

首先还是要在resources文件夹下创建bean.xml如下:

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
<?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 http://www.springframework.org/schema/beans/spring-beans.xsd">

<!--配置accountService-->
<bean id="accountService" class="top.qing.service.impl.AccountServiceImpl">
<!--set方法注入-->
<property name="accountDao" ref="accountDao"></property>
</bean>

<!--配置accountDao对象-->
<bean id="accountDao" class="top.qing.dao.impl.AccountDaoImpl">
<!--set方法注入-->
<property name="queryRunner" ref="queryRunner"></property>
</bean>

<!--配置queryRunner对象(要配置为多例对象,防止线程间互相干扰)-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入DataSource(使用构造函数注入)-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源ds对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库的必须信息(set方法注入)-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>

</beans>

这里可以注意一下,我们这里的配置几乎复习了之前笔记中的大部分内容。

配置好bean.xml之后,就可以测试代码了:

1
2
3
4
5
6
7
8
9
10
11
12
@Test
void testFindAll() {
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
// 3.调用accountService中的方法
List<Account> accounts = accountService.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}

结果如下:

成功查询出account2表中的记录

而剩下的方法进过测试后也没有问题,完整的test代码如下:

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
package top.qing;


import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import top.qing.domain.Account;
import top.qing.service.AccountService;

import java.util.List;

public class TestAccountServiceImpl {
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");

@Test
void testFindAll() {
// 3.调用accountService中的方法
List<Account> accounts = accountService.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}

@Test
void testFindById() {
// 3.调用accountService中的方法
Account account = accountService.findById(2);
System.out.println(account);
}

@Test
void testSaveAccount() {
// 3.调用accountService中的方法
Account account = new Account();
account.setName("ddd");
account.setMoney(1234.5f);
try {
accountService.saveAccount(account);
System.out.println("保存成功!");
} catch (Exception e) {
e.printStackTrace();
}
}

@Test
void testUpdateAccount() {
// 3.调用accountService中的方法
Account account = new Account();
account.setId(2);
account.setName("fff");
account.setMoney(2222.5f);
try {
accountService.updateAccount(account);
System.out.println("更新成功!");
} catch (Exception e) {
e.printStackTrace();
}
}

@Test
void testDeleteAccountById() {
// 3.调用accountService中的方法
try {
accountService.deleteAccountById(3);
System.out.println("删除成功!");
} catch (Exception e) {
e.printStackTrace();
}
}

}

在这个案例中,我们无需太过关注dbutils的相关知识,只需要注意在bean.xml中的依赖注入即可,后续的持久层技术我们会有别的选择

基于注解方式

使用上面讲过的注解

环境准备大致同上,这里只说一下不同的地方。

首先bean.xml修改如下:

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
<?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 http://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="top.qing.service.impl"></context:component-scan>
<context:component-scan base-package="top.qing.dao.impl"></context:component-scan>


<!--配置queryRunner对象(要配置为多例对象,防止线程间互相干扰)-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入DataSource(使用构造函数注入)-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源ds对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库的必须信息(set方法注入)-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>
</beans>

关键部分:

1
2
3
<!--告知Spring在创建容器时要扫描的包(注意是包而不是类)-->
<context:component-scan base-package="top.qing.service.impl"></context:component-scan>
<context:component-scan base-package="top.qing.dao.impl"></context:component-scan>

然后在AccountServiceImpl中添加注解如下:

1
2
3
4
5
6
7
@Service("accountService")
public class AccountServiceImpl implements AccountService {

@Autowired
private AccountDao accountDao;
// 代码同上这里就省略了(使用@Autowired注解,set方法可以删除)
}

在AccountDaoImpl中添加注解如下:

1
2
3
4
5
6
7
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

@Autowired
private QueryRunner queryRunner;
// 代码同上这里就省略了(使用@Autowired注解,set方法可以删除)
}

测试代码也同上,以findAll为例:

其他方法经测试也可以成功

Spring的新注解

在上述方法中,我们依然必须要有bean.xml中的相关配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!--告知Spring在创建容器时要扫描的包(注意是包而不是类)-->
<context:component-scan base-package="top.qing.service.impl"></context:component-scan>
<context:component-scan base-package="top.qing.dao.impl"></context:component-scan>

<!--配置queryRunner对象(要配置为多例对象,防止线程间互相干扰)-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入DataSource(使用构造函数注入)-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源ds对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库的必须信息(set方法注入)-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>

想要完全去除Xml文件,就需要用到新的Spring注解了:

  • @Configuration
  • @ComponentScan
  • @Bean
  • @Scope(在**@Bean**笔记中使用了一下)

@Configuration和@ComponentScan

@Configuration

  • 作用:指定当前类是一个配置类
  • 注意:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写视频讲解

@ComponentScan

  • 作用:通过注解指定Spring在创建容器时要扫描的包

  • 属性:

    • value用于指定创建容器时要扫描的包

    • basePackages用于指定创建容器时要扫描的包

      (这两个属性的作用是一样的)

    我们使用此注解就等同于在xml中配置了:<context:component-scan base-package=""></context:component-scan>

我们在config包下创建SpringConfiguration类,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
package config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

/**
* 配置类,作用和bean.xml文件是一样的
*/
@Configuration
@ComponentScan(basePackages = "top.qing")
public class SpringConfiguration {
}

这个时候bean.xml就可以去掉context:component-scan部分了,还剩下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<!--配置queryRunner对象(要配置为多例对象,防止线程间互相干扰)-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入DataSource(使用构造函数注入)-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

<!--配置数据源ds对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库的必须信息(set方法注入)-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>

@Bean

@Bean

  • 作用:把当前方法的返回值作为bean对象存入Spring的IoC容器中
  • 属性:
    • name:用于指定bean的id。当不写时,默认值是当前方法的名称
  • 注意:当我们使用注解配置方法(即有一个返回值的方法)时,如果方法有参数,Spring框架会去容器中查找有没有可用的bean对象。查找的方式和Autowired注解的作用是一样的(@Autowired

首先我们在SpringConfiguration类中添加代码如下:

1
2
3
4
5
6
7
8
9
10
/**
* 创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "queryRunner")
@Scope("prototype")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}

这一段代码对应着bean.xml文件中的:

1
2
3
4
5
<!--配置queryRunner对象(要配置为多例对象,防止线程间互相干扰)-->
<bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--注入DataSource(使用构造函数注入)-->
<constructor-arg name="ds" ref="dataSource"></constructor-arg>
</bean>

注意:在上面的注意点中已经提到过了,由于我们的createQueryRunner方法需要一个dataSource的参数,而我们的dataSource还没有加载到Spring容器中,所以目前是会报错的:

dataSource还没有加载到Spring容器中

接着我们在SpringConfiguration类中添加代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* 创建数据源DataSource对象
* @return
*/
@Bean(name = "dataSource")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");
return dataSource;
}

这一段代码对应着bean.xml文件中的:

1
2
3
4
5
6
7
8
<!--配置数据源ds对象-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!--注入连接数据库的必须信息(set方法注入)-->
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8"></property>
<property name="user" value="root"></property>
<property name="password" value="123456"></property>
</bean>

这时由于dataSource已经被加载到Spring容器中了,上面的报错就自然消失了

这时我们就可以删掉bean.xml文件了

AnnotationConfigApplicationContext类

在删除bean.xml文件后,我们要去运行测试文件,但是在这之前我们要先修改一下测试类代码

在之前的测试类中代码是这样的:

1
2
3
4
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");

而由于我们已经完全用注解来配置IoC了,删除bean.xml后就不能再用ClassPathXmlApplicationContext来读取bean.xml了

我们使用AnnotationConfigApplicationContext来读取配置类SpringConfiguration,代码如下:

1
2
3
4
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");

经过测试,方法都可以正常运行:

Spring注解补充

  • @Import
  • @PropertySource

@Import

视频讲解

@Import

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

在上述代码中,我们将注入Spring容器的代码都放在SpringConfiguration类中了。但是后续我们打算将SpringConfiguration类作为主配置类,而将其中关于数据源DataSource相关的注入写到DBConfig中,这时就要用到**@Import**注解了

首先在config包下创建DBConfig类,代码如下:

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
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.Configuration;
import org.springframework.context.annotation.Scope;

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

/**
* 用于配置数据库连接相关的类
*/
@Configuration
public class DBConfig {
/**
* 创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name = "queryRunner")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}

/**
* 创建数据源DataSource对象
* @return
*/
@Bean(name = "dataSource")
@Scope("prototype")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");
return dataSource;
}
}

这时我们的SpringConfiguration类就可以修改为:

1
2
3
4
5
6
7
8
9
10
11
12
13
package config;

import org.springframework.context.annotation.*;

/**
* 主配置类,作用和bean.xml文件是一样的
*/
@Configuration
@ComponentScan(basePackages = "top.qing")
@Import(DBConfig.class)
public class SpringConfiguration {

}

@PropertySource

@PropertySource

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

我们可以发现在上述代码中和数据库连接的一些参数我们是直接写死在程序中的:

1
2
3
4
dataSource.setDriverClass("com.mysql.jdbc.Driver");
dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8");
dataSource.setUser("root");
dataSource.setPassword("123456");

这显然不是我们想要的,那么我们可以和以前一样,将数据抽取到一个properties文件中

在resources文件夹下创建dbConfig.properties文件如下:

1
2
3
4
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8
jdbc.username = root
jdbc.password = 123456

然后修改配置类DBConfig如下:

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
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.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.context.annotation.Scope;

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

/**
* 用于配置数据库连接相关的类
*/
@Configuration
@PropertySource("classpath:dbConfig.properties")
public class DBConfig {

@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 = "queryRunner")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}

/**
* 创建数据源DataSource对象
* @return
*/
@Bean(name = "dataSource")
// @Scope("prototype")
public DataSource createDataSource() throws PropertyVetoException {
ComboPooledDataSource dataSource = new ComboPooledDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(username);
dataSource.setPassword(password);
return dataSource;
}
}

注意@PropertySource注解和@Value注解的使用,其中**@PropertySource("classpath:dbConfig.properties")中的classpath表示类路径下**:

经过测试,方法都可以正常运行:

@Qualifier的另一种用法

上面我们已经提到过**@Qualifier注解了,这里再介绍@Qualifier**的另一种用法

其实和上面说的配合@Autowired注解类似,当我们有多个类型相同的要注入到IoC容器的对象时(@Bean注解),比如说我们要注入两个DataSource对象:

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
   @Bean(name="ds1")
public DataSource createDataSource1(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl("jdbc:mysql://localhost:3306/mybatis02?characterEncoding=utf-8");
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}

@Bean(name="ds2")
public DataSource createDataSource(){
try {
ComboPooledDataSource ds = new ComboPooledDataSource();
ds.setDriverClass(driver);
ds.setJdbcUrl(url);
ds.setUser(username);
ds.setPassword(password);
return ds;
}catch (Exception e){
throw new RuntimeException(e);
}
}

这两个要注入的DataSource对象类型相同只是name属性不同(id不同),那么在原来的代码:

1
2
3
4
@Bean(name = "queryRunner")
public QueryRunner createQueryRunner(DataSource dataSource){
return new QueryRunner(dataSource);
}

哪怕是id默认取变量名(dataSource),也不能匹配到上面要注入的两个DataSource对象(上面两个bean对象一个id为ds1,一个为ds2),这时运行就会报错

这个时候就需要使用**@Qualifier**注解来指定我们要注入的DataSource对象了,代码如下:

1
2
3
4
5
6
7
8
9
10
/**
* 用于创建一个QueryRunner对象
* @param dataSource
* @return
*/
@Bean(name="runner")
@Scope("prototype")
public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
return new QueryRunner(dataSource);
}

指定创建name为ds2的DataSource对象(id为ds2的Bean对象)

视频讲解


Spring整合Junit

问题分析

视频讲解

  • 应用程序的入口:main方法(主启动类)
  • junit单元测试中,没有main方法也能执行
    • junit集成了一个main方法,该方法就会判断当前测试类中哪些方法有@Test注解,junit就让有@Test注解的方法执行
  • junit不会管我们是否采用spring框架
    • 在执行测试方法时,junit根本不知道我们是不是使用了spring框架,所以也就不会为我们读取配置文件/配置类创建spring核心容器

由以上三点可知,当测试方法执行时,没有Ioc容器,就算写了@Autowired注解,也无法实现注入

Spring整合Junit实现

Spring整合junit的配置:

  1. 在pom.xml中导入Spring整合junit的相关依赖

  2. 使用Junit提供的**@Runwith注解**把原有的main方法替换了,替换成Spring提供的

  3. 告知Spring的运行器(Runner),Spring的IoC创建是基于xml还是注解的,并且说明位置

    • @ContextConfiguration注解,属性有:

      • locations(value):指定xml文件的位置(加上classpath关键字,表示在类路径下)
      • classes:指定注解类所在的位置

      和属性相关的部分源码:

      1
      2
      3
      4
      5
      6
      7
      @AliasFor("locations")
      String[] value() default {};

      @AliasFor("value")
      String[] locations() default {};

      Class<?>[] classes() default {};

注意:当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上

按照上面这个步骤,首先我们的pom.xml要修改为以下内容:

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
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>top.qing</groupId>
<artifactId>Spring02_crud_Annotation_IoC_without</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging>

<dependencies>
<!--Spring核心容器-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<!--Spring整合Junit-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>

<!--log4j-->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.4</version>
</dependency>

<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>

<dependency>
<groupId>c3p0</groupId>
<artifactId>c3p0</artifactId>
<version>0.9.1.2</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.18</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>6</source>
<target>6</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

然后我们的测试类中的AccountService对象就可以通过IoC来为我们自动创建了(当然要加上@Autowired注解)。完整的测试代码如下:

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
package top.qing;

import config.SpringConfiguration;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.qing.domain.Account;
import top.qing.service.AccountService;

import java.util.List;

/**
* 测试account表CRUD
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class TestAccountServiceImpl {

@Autowired
private AccountService accountService;

@Test
public void testFindAll() {
// 3.调用accountService中的方法
List<Account> accounts = accountService.findAll();
for (Account account : accounts) {
System.out.println(account);
}
}

@Test
public void testFindById() {
// 3.调用accountService中的方法
Account account = accountService.findById(2);
System.out.println(account);
}

@Test
public void testDeleteAccountById() {
// 3.调用accountService中的方法
try {
accountService.deleteAccountById(3);
System.out.println("删除成功!");
} catch (Exception e) {
e.printStackTrace();
}
}

}

我们通过@RunWith注解来为我们的测试类提供运行器,通过@ContextConfiguration来指定我们配置IoC的方式及对应的Xml文件(或配置类)所在路径,有了这两个注解后,我们就可以在类中通过@Autowired注解让Spring为我们创建AccountService对象,而不用想之前那样手动创建:

1
2
3
4
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");

其中@ContextConfiguration(classes = SpringConfiguration.class)对应:

1
2
// 1.使用ApplicationContext接口,获取Spring核心容器对象
ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);

1
2
@Autowired
private AccountService accountService;

则对应:

1
2
// 2.根据bean.xml文件中<bean>标签的的id来获取对象
AccountService accountService = (AccountService) applicationContext.getBean("accountService");