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
| <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="" />
)
用于创建对象的注解
例子:首先要在bean.xml配置文件中添加如下代码:
1 2 3 4
|
<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(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); 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(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); 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(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); 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注入,可以独立使用
- 属性:
例子,还是基于上述的例子,只不过我们在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(){ ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); accountService.saveAccount(); }
|
结果如下:
用于改变作用范围的注解
@Scope
:
- 作用:用于指定bean的作用范围
- 属性:
- value:指定范围的取值(常用取值:singleton(单例),prototype(多例),默认是singleton)
(Bean对象的作用范围和生命周期笔记)
(视频讲解)
和生命周期相关的注解(了解)
@PreDestroy
:
@PostConstruct
:
(视频讲解)
Spring案例:实现单表的CRUD
这里我们持久层技术选择dbutils(为了避免引入后面的相关知识)(JAVA 基础 DBUtils的使用,DbUtils的使用)
基于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> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency>
<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);
|
接下来我们依次创建业务层和持久层
首先创建业务层,在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 {
List<Account> findAll();
Account findById(Integer id);
void saveAccount(Account account);
void updateAccount(Account account);
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;
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 {
List<Account> findAll();
Account findById(Integer id);
void saveAccount(Account account);
void updateAccount(Account account);
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;
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 { queryRunner.update("insert into account2(name,money) values(?,?) ", account.getName(), account.getMoney()); } catch (SQLException throwables) { throwables.printStackTrace(); } }
@Override public void updateAccount(Account account) { try { 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 { 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">
<bean id="accountService" class="top.qing.service.impl.AccountServiceImpl"> <property name="accountDao" ref="accountDao"></property> </bean>
<bean id="accountDao" class="top.qing.dao.impl.AccountDaoImpl"> <property name="queryRunner" ref="queryRunner"></property> </bean>
<bean id="queryRunner" 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.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() { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); AccountService accountService = (AccountService) applicationContext.getBean("accountService"); List<Account> accounts = accountService.findAll(); for (Account account : accounts) { System.out.println(account); } }
|
结果如下:
而剩下的方法进过测试后也没有问题,完整的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 { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml"); AccountService accountService = (AccountService) applicationContext.getBean("accountService");
@Test void testFindAll() { List<Account> accounts = accountService.findAll(); for (Account account : accounts) { System.out.println(account); } }
@Test void testFindById() { Account account = accountService.findById(2); System.out.println(account); }
@Test void testSaveAccount() { 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() { 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() { 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">
<context:component-scan base-package="top.qing.service.impl"></context:component-scan> <context:component-scan base-package="top.qing.dao.impl"></context:component-scan>
<bean id="queryRunner" 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.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
| <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; }
|
在AccountDaoImpl中添加注解如下:
1 2 3 4 5 6 7
| @Repository("accountDao") public class AccountDaoImpl implements AccountDao {
@Autowired private QueryRunner queryRunner; }
|
测试代码也同上,以findAll为例:
其他方法经测试也可以成功
Spring的新注解
在上述方法中,我们依然必须要有bean.xml中的相关配置:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| <context:component-scan base-package="top.qing.service.impl"></context:component-scan> <context:component-scan base-package="top.qing.dao.impl"></context:component-scan>
<bean id="queryRunner" 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.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
:
我们在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;
@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
| <bean id="queryRunner" 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.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
|
@Bean(name = "queryRunner") @Scope("prototype") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); }
|
这一段代码对应着bean.xml文件中的:
1 2 3 4 5
| <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean>
|
注意:在上面的注意点中已经提到过了,由于我们的createQueryRunner方法需要一个dataSource的参数,而我们的dataSource还没有加载到Spring容器中,所以目前是会报错的:
接着我们在SpringConfiguration类中添加代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
|
@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
| <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <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
| ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
|
而由于我们已经完全用注解来配置IoC了,删除bean.xml后就不能再用ClassPathXmlApplicationContext来读取bean.xml了
我们使用AnnotationConfigApplicationContext来读取配置类SpringConfiguration,代码如下:
1 2 3 4
| ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
|
经过测试,方法都可以正常运行:
Spring注解补充
@Import
视频讲解
@Import
:
- 作用:用于导入其他的配置类
- 属性:
- 注意:当我们使用
@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 {
@Bean(name = "queryRunner") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); }
@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.*;
@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;
@Bean(name = "queryRunner") public QueryRunner createQueryRunner(DataSource dataSource){ return new QueryRunner(dataSource); }
@Bean(name = "dataSource")
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
|
@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的配置:
-
在pom.xml中导入Spring整合junit的相关依赖
-
使用Junit提供的**@Runwith
注解**把原有的main方法替换了,替换成Spring提供的
-
告知Spring的运行器(Runner),Spring的IoC创建是基于xml还是注解的,并且说明位置
注意:当我们使用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> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.10.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>5.2.10.RELEASE</version> </dependency>
<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;
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = SpringConfiguration.class) public class TestAccountServiceImpl {
@Autowired private AccountService accountService;
@Test public void testFindAll() { List<Account> accounts = accountService.findAll(); for (Account account : accounts) { System.out.println(account); } }
@Test public void testFindById() { Account account = accountService.findById(2); System.out.println(account); }
@Test public void testDeleteAccountById() { try { accountService.deleteAccountById(3); System.out.println("删除成功!"); } catch (Exception e) { e.printStackTrace(); } }
}
|
我们通过@RunWith
注解来为我们的测试类提供运行器,通过@ContextConfiguration
来指定我们配置IoC的方式及对应的Xml文件(或配置类)所在路径,有了这两个注解后,我们就可以在类中通过@Autowired
注解让Spring为我们创建AccountService对象,而不用想之前那样手动创建:
1 2 3 4
| ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
AccountService accountService = (AccountService) applicationContext.getBean("accountService");
|
其中@ContextConfiguration(classes = SpringConfiguration.class)
对应:
1 2
| ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
|
而
1 2
| @Autowired private AccountService accountService;
|
则对应:
1 2
| AccountService accountService = (AccountService) applicationContext.getBean("accountService");
|