文章目录
一、基于XML的IOC案例
1.1、创建数据表和数据
create table account(
id int primary key auto_increment,
name varchar(40),
money float
)character set utf8 collate utf8_general_ci;
insert into account(name,money) values('Tom',1200);
insert into account(name,money) values('Bob',1300);
insert into account(name,money) values('Alise',1400);
1.2、项目准备工作
1.新建Maven项目:
2.环境搭建pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>commons-dbutils</groupId>
<artifactId>commons-dbutils</artifactId>
<version>1.7</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>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
3.创建domain.Account实体类:
package com.it.domain;
import java.io.Serializable;
/**
* 账户的实体类
*/
public class Account implements Serializable {
private Integer id;
private String name;
private Float money;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Float getMoney() {
return money;
}
public void setMoney(Float money) {
this.money = money;
}
@Override
public String toString() {
return "Account{" +
"id=" + id +
", name='" + name + '\'' +
", money=" + money +
'}';
}
}
4.创建service.IAccountService.java:
package com.it.service;
import com.it.domain.Account;
import java.util.List;
/**
* 账户的业务层接口
*/
public interface IAccountService {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
}
5.创建dao.IAccountDao.java:
package com.it.dao;
import com.it.domain.Account;
import java.util.List;
/**
* 账户的持久层接口
*/
public interface IAccountDao {
/**
* 查询所有
* @return
*/
List<Account> findAllAccount();
/**
* 查询一个
* @return
*/
Account findAccountById(Integer accountId);
/**
* 保存
* @param account
*/
void saveAccount(Account account);
/**
* 更新
* @param account
*/
void updateAccount(Account account);
/**
* 删除
* @param acccountId
*/
void deleteAccount(Integer acccountId);
}
6.创建service.AccountServiceImpl.java:
package com.it.service.impl;
import com.it.dao.IAccountDao;
import com.it.domain.Account;
import com.it.service.IAccountService;
import java.util.List;
/**
* 账户的业务层实现类
*/
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = 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 acccountId) {
accountDao.deleteAccount(acccountId);
}
}
7.创建service.AccountDaoImpl.java:
package com.it.dao.impl;
import com.it.dao.IAccountDao;
import com.it.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.util.List;
/**
* 账户的持久层实现类
*/
public class AccountDaoImpl implements IAccountDao {
private QueryRunner runner;
public void setRunner(QueryRunner runner) {
this.runner = runner;
}
@Override
public List<Account> findAllAccount() {
try{
return runner.query("select * from account",new BeanListHandler<Account>(Account.class));
}catch (Exception 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 (Exception 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 (Exception 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 (Exception e) {
throw new RuntimeException(e);
}
}
@Override
public void deleteAccount(Integer accountId) {
try{
runner.update("delete from account where id=?",accountId);
}catch (Exception e) {
throw new RuntimeException(e);
}
}
}
1.3、搭建基于XML的IOC框架
1、新建bean.xml文件,并添加内容:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
内容:
2、使用Junit进行单元测试:
package com.it.test;
import com.it.domain.Account;
import com.it.service.IAccountService;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import java.util.List;
/**
* 使用Junit单元测试:测试我们的配置
*/
public class AccountServiceTest {
@Test
public void testFindAll() {
//1.获取容器,创建容器中所有Bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取指定的Bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
//3.执行方法
List<Account> accounts = as.findAllAccount();
for(Account account : accounts){
System.out.println(account);
}
}
@Test
public void testFindOne() {
//1.获取容器,创建容器中所有Bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取指定的Bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
//3.执行方法
Account account = as.findAccountById(1);
System.out.println(account);
}
@Test
public void testSave() {
//1.获取容器,创建容器中所有Bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取指定的Bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
Account account = new Account();
account.setName("test");
account.setMoney(12345f);
//3.执行方法
as.saveAccount(account);
}
@Test
public void testUpdate() {
//1.获取容器,创建容器中所有Bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取指定的Bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
//3.执行方法
Account account = as.findAccountById(4);
account.setMoney(23456f);
as.updateAccount(account);
}
@Test
public void testDelete() {
//1.获取容器,创建容器中所有Bean对象
ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//2.根据id获取指定的Bean对象
IAccountService as = ac.getBean("accountService", IAccountService.class);
//3.执行方法
as.deleteAccount(4);
}
}
1.4、搭建基于注解的IOC框架
1、将 accountServiceImpl 注入容器,给成员变量注入值:
2、将 accountDaoImpl 注入容器,给成员变量注入值:
3、在bean.xml指定需要扫描注解的包:
4、测试:
二、Spring新注解
2.1、@Configuration和@ComponentScan注解
知识:
@Configuration
作用:指定当前类是一个配置类。
注意:当配置类作为AnnotationConfigAppApplicationContent对象创建的参数时,该注解可以不写。
@ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性:
value
:它和besePackages的作用是一样的。都是用于指定创建容器时,要扫描的包。
相当于bean.xml中的:
用法:
1、在一个类的头部加上@Configuration
注解,指定是个配置类。
2、然后再添加@ComponentScan
注解,该注解内传入包的路径代表要扫描的包,因为这个标签是允许传入数组的,所以有多重写法:
@ComponentScan(basePackages= {"com.it","com.config"})
不常用
@ComponentScan(value = {"com.it","com.config"})
不常用
@ComponentScan({"com.it","com.config"})
常用
@ComponentScan("com.it")
常用:数组只中只有一个元素时可以这样写
例如:
1、新建一个类作为配置类:
写入代码:
然后在里面创建一个QueryRunner
对象并返回:
问题:图1中红框代码是不是等于图2中的红框代码?
图1:
图2:
答案:不等。
因为在图2中不仅创建了QueryRubber
对象,还能将该对象(bean)放到了spring容器中。但是图一只是创建了QueryRubber
对象对象,并没有放入到容器中。
那么如何使用代码将bean放到容器中呢?需要用到一个注解@Bean
2.2、@Bean注解
知识:
@Bean
作用:用于把当前方法的返回值作为bean对象存入spring容器中
属性:
name
:用于指定bean的id。当不写时,默认值是当前方法的名称就是id,方法回执值就是value。
注意:当我们使用注解方法时,如果方法有参数,spring框架爱会去容器中查找有没有可用的bean对象。查找的方式和@Autowired
是一样的。
例如:
献上完整代码:
到这里,我们已经把bean.xml中所有的xml配置都换成了使用java代码类配置。此时有两处地方也需要修改:
(1)
(2)将QueryRunner
单例使用注解修改成多例
然后再测试运行即可。
2.3、@Import注解
复习:
@Configuration
作用:指定当前类是一个配置类
注意:当配置类作为AnnotationConfigAppApplicationContent对象形式参数时,该注解可以不写。
这句话怎么理解?看图:
问:如果现在有两个配置类,其中SpringConfiguration类没
@Configuration
注解,而jdbcConfig类有@Configuration
注解,此时测试代码这样写会不会报错?
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
答案:会。
原因:因为如果是两个类,AnnotationConfigApplicationContext会查找该类中有没有哦@Configuration
注解,如果没有,则不会继续往下扫描代码,此时代码会报找不到注解的错。就算JdbcCondif中有该注解是这样写的
此时如下来两个配置类:
其中SpringCofiguration.java:
而JdbcConfig.java
:
问题:此时运行代码会报错嘛?
答案:会。
原因:在多个
配置类存在的时候,AnnotationConfigApplicationContext参数中的配置类,结果发现该类中没有@Configuration
注解,所以认为该类不是配置类,不再往下扫描类中的注解。所以这个时候想要让它加载到两个配置类有两种做法:
方法1:在SpringConfiguration
类中加上@Configuration
注解。然后就会往下执行@ComponentScan(basePackages = {"com.it"})
注解,此时就能找到JdbcConfig,java
方法2:这样写。
以上的写法是两个配置类一起加载的方法。那如果我只想加载一个配置类,且两个配置类的关系是包含于被包含
的关系。那么如何用父配置类调到子配置呢?
知识:
@Import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
例如:
此时,无论是JdbcConfig.java
中有没有@Configuration
注解都能正常运行,因为此时相当于只调用了一个父配置类,然后父配置类再去调用子配置类。
2.4、@PropertySource注解
以上例子我们的数据库配置都是硬编码,那么现在我要修改成软编码,需要把数据库配置放到一个properties配置文件中,然后去读配置文件。现在我们注重关心下“读”这个环节,除了使用传统的方法外。我们也可以通过注解的方式来读。
知识:
@PropertySource
作用:用于指定properties文件的位置
属性:
value
:指定文件的名称和路径。关键字:classpath,表示类路径下。其中classpath
表示去类路径下找
例如:
@PropertySource("classpath:JdbcConfig.properties")
如果有包,也可以这样写:
`@PropertySource(“classpath:com/xiaolang/JdbcConfig.properties”)``
1、新建一个配置文件:
2、定义变量:
3、使用@PropertySource
注解读取配置文件:
其中classpath
表示去类路径下找
4、运行测试代码即可:
2.5、@Qualifier注解的另外一种用法
知识:
@Qualifier
一般和@Autowired
注解一起使用,用于给成员变量提供选择注入。当然也可以单独使用,单独使用放在成员方法中的形参前面。
原理:
@Qualifier
常用于Spring容器中存在多个同类型
的bean对象。因为类型相同,往往只能通过变量的名称去和容器中的id匹配。一旦变量名称和容器中的bean不同,则无法匹配到,此时就需要@Qualifier
来指定需要匹配的bean。
如下代码:
问:在图中只是将数据源dataSource
注册到了容器中,凭什么在createQueryRunner()
方法中能作为参数注入值呢?
答案:因为Spring会自动匹配到一个容器中的数据库,然后进行连接。那么如果容器中同时存在多个数据库bean怎么办?这个时候运行测试代码就会报错。此时就可以使用到@Qualifier
来指定注入哪个数据库的bean对象。
其实在代码里面一开始就有个默认的@Qualifier
注解,只是被隐藏了,没写出来,所以能够匹配到:
如果现在库中同时存在两个数据库类型的bean,比如都是mysql,一个连接访问的是employee表,一个访问的是user表,同时两个都注册了bean到容器中,如图:
所以这个错可以这么修改:
方法一:
方法二:
2.6、Junit及新注解
首先我们再修改一下测试类代码,使代码提高复用性。如下:
知识
Junit运行原理
: 应用程序的入口是main()方法,但是在Junit中,没有main方法也能执行?在Junit其实是集成了main()方法的,该方法会判断当前测试类中哪些方法有@Test
注解,Junit就让@Test注解的方法执行。
重要:
1、Junit不会管我们是否采用spring框架。在执行测试方法时,junit根本不知道程序是否使用了spring框架,所以就不会为我们读取配置文件,读取配置类,更不会去获取spring容器。
总结:在执行测试方法时候,没有IOC容器,就算写了@Autowired注解,也无法实现注入。
问题:如果我们想在Junit中使用注解,就需要在Junit中告诉Spring的存在。那么该如何做呢?
答案:
1、导入spring整合junit的jar(坐标) 2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 @Runwith
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置
@ContextConfiguration
locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置
例如:
@ContextConfiguration(locations = “classpath:bean.xml”)
注意:
当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上
步骤:
1、导入spring整合junit的jar(坐标)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.0.2.RELEASE</version>
</dependency>
2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的 @Runwith
:
3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置:
4、既然使用了@RunWith(SpringJUnit4ClassRunner.class)
和@ContextConfiguration(classes = SpringConfiguration.class)
,那么此时容器已经创建,所以此时容器里面就存在已经注入的service和dao的bean对象,此时只需要修改代码为:
或者改为:
此时就大大精简了Junit测试类的代码。
三、案例总结
总结:
1、如果是调用配置文件的读取,或调用到外部jar包对象,推荐使用XML的方式,
2、如果是自己写的代码,推荐使用注解的方式。
3、最好就是XML和注解灵活使用开发项目。