SpringIOC框架详解(二)

一、基于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和注解灵活使用开发项目。

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值