欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例

程序员文章站 2022-04-21 10:33:44
经过了第一天的学习,我们对spring框架应该有了一定的了解。对于使用xml配置的方式也应该会使用了。今天我们就来学习spring基于注解的IOC以及IOC的案例。1 Spring中IOC的常用注解在我们上一天的学习中我们使用的是xml对ioc进行配置,配置的方式如下

经过了第一天的学习,我们对spring框架应该有了一定的了解。对于使用xml配置的方式也应该会使用了。今天我们就来学习spring基于注解的IOC以及IOC的案例。

1 Spring中IOC的常用注解

在我们上一天的学习中我们使用的是xml对ioc进行配置,配置的方式如下

<bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope="" init-method="" destroy-method="">
    <property name="" value="" ref=""></property>
</bean>

今天学习的注解配置IOC主要有四种注解

1. 用于创建对象的注解
他们的作用和在xml中配置文件中编写一个 标签实现的功能是一样的。
2. 用于注入数据的注解
他们的作用就和在xml配置文件中 标签中写一个标签的作用是一样的。
3. 用户改变作用范围的注解

	他们的作用就和在bean标签中使用scope属性实现的功能是一样的

4. 和生命周期相关的注解

	他们的作用就和在bean标签中使用 init-method 标签和 destroy-method 标签的作用是一样的

下面我们就来分别演示一下各种注解的使用,我们在原来的工程基础上新建一个Module命名为day02_01anno_ioc,然后我们把上一天里创建的Module名为 day01_03spring里src/main/里的所有内容拷贝到我们新建的Module的src/main里。我们新建的Module结构如下
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例

1.1 用于创建对象的注解

他们的作用和在xml中配置文件中编写一个 标签实现的功能是一样的。

Component:当我们要创建的对象不属于三层架构时,我们就可以使用这个注解。
	作用:用于把当前类对象存入到spring容器中
	属性:value:用于指定bean的id,。当我们不写时,它的默认值时当前类名,且首字母改小写

Controller:一般用在表现层
Service:一般用于业务层
Repository:一般用于持久层

以上三个注解Controller、Service、Repository他们的作用和属性与Component是一模一样的。他们三个是Spring框架为我们提供明确的三层架构使用的注解,使我们的三层对象更加清晰

然后我们来修改一下 AccountServiceImpl.java

/*
 * 账户的业务层实现类
 * 用于创建对象的注解
 *      他们的作用和在xml中配置文件中编写一个 <bean></bean> 标签实现的功能是一样的
 *      @Component
 *          作用:用于把当前类对象存入到spring容器中
 *          属性:
 *              value:用于指定bean的id,。当我们不写时,它的默认值时当前类名,且首字母改小写
 * 用于注入数据的
 *      他们的作用就和在xml配置文件中 <bean>标签中写一个<property>标签的作用是一样的
 * 用户改变作用范围的
 *      他们的作用就和在bean标签中使用scope属性实现的功能是一样的
 * 和生命周期相关的
 *      他们的作用就和在bean标签中使用 init-method 标签和 destroy-method 标签的作用是一样的
 */
@Component
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao = new AccountDaoImpl();
    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

现在还不能实现数据的注入,还需要修改bean.xml,因为告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名为context名称空间和约束中。首先我们登录spring的官网,找到spring官方说明文档的网页,然后找到对应spring框架版本的说明文档,我这里使用的5.0.2 的版本该版本的说明文档地址为 https://docs.spring.io/spring/docs/5.0.2.RELEASE/spring-framework-reference/core.html#spring-core 打开该网页,然后Ctrl + F 搜索 “xmlns:context” 找到含有 “context” 的命名空间和约束,如下
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
将其复制到我们的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"
       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
        http://www.springframework.org/schema/context/spring-context.xsd">
    <!--告知spring在创建容器时要扫描的包,配置所需要的标签不是在beans的约束中,而是一个名为context 名称空间和约束中。-->
	<!-- 告知spring在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.itheima"></context:component-scan>
</beans>

然后我们在修改一下Client.java里的main方法,

public class Client {
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new AnnotationConfigApplicationContext();

        //2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountServiceImpl");
        System.out.println(as);
//        System.out.println(as);
    }
}

注意我们在 AccountServiceImpl 类上使用的 Component 注解且使用的是默认值,所以我们在获取对象的时候要使用这个类的名称并且把首字母改成小写accountServiceImpl,下面我们来运行一下main方法,控制台输出如下,说明数据注入成功。
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
下面我们来修改一下 AccountServiceImpl .java,我们不使用 Component 注解的默认值,

//@Component(value = "accountService")
@Component("accountService") //当注解中我们只用一个value属性,那这个value是可以不写的,如果同时有两个活以上的属性赋值就必须要写
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }

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

注意:
当注解中我们只使用一个value属性,那这个value是可以不写的,如果同时有两个或者以上的属性赋值就必须要写。
然后这个Client.java里的main方法也要改一下,因为这个 AccountServiceImpl 类上的 Component 注解不在使用默认值了,这个Client.java修改如下

public class Client {

    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new AnnotationConfigApplicationContext();
        //2.根据id获取bean对象
//        IAccountService as = (IAccountService) ac.getBean("accountServiceImpl");
        IAccountService as = (IAccountService) ac.getBean("accountService");
        System.out.println(as);
    }
}

运行一下main方法,控制台输出如下,说明数据注入成功。
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
因为 Controller、Service、Repository这个三个注解和Component注解的作用和属性是一模一样的,所以,这个四个注解可以相互替换。比如我们先=现在把 AccountServiceImpl的注解换成 Controller 注解。其他的不用修改。

//@Controller(value = "accountService")
@Controller("accountService")
public class AccountServiceImpl implements IAccountService {
	......
}

然后我们来运行一下Client.java里的main方法,控制台输出如下,可以看出这样也是可以正常运行的。
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
注意虽然这四个注解的作用个属性都是一样的,但是为了代码的已读我们在写代码的时候最好还是根据三层架构来使用不同的注解
现在我们把 AccountServiceImpl 的注解改成 Service 注解,AccountDaoImpl 的注解改成 Repository。然后修改一下Client.java里的main方法。

public class Client {
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new AnnotationConfigApplicationContext();
        //2.根据id获取bean对象
//        IAccountService as = (IAccountService) ac.getBean("accountServiceImpl");
        IAccountService as = (IAccountService) ac.getBean("accountService");
        System.out.println(as);

        IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
        System.out.println(adao);
    }
}

运行一下main,控制台输出如下
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例

1.2 用于注入数据的注解

他们的作用就和在xml配置文件中 标签中写一个标签的作用是一样的。

Autowired:
	作用:
		自动按照类型注入.只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
		如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,则报错。
		如果ioc容器中有多个类型匹配时:
			spring会把变量名当做id去容器中查找有没有对应类型并且id为变量名的对象,如果找到,就直接注入,否则就报错。
		出现位置:
			可以是成员变量上,也可以是方法上
		细节:
    		使用注解注入时,set方法就不是必须的了。
Qualifier:
	作用:在按照类型注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以(稍后讲)
	属性:
    	value:用于指定注入bean的id。
Resource
	作用:直接按照bean的id注入。它可以独立使用
	属性:
    	name:用于指定bean的id

以上三个注解Autowired、Qualifier、Resource都只能注入其他bean类型的数据,而基本数据类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现

1.2.1 Autowired

下面就来演示一下,我们首先修改一下 AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired // 自动按照类型注入.只要容器中有唯一的一个bean对象类型和要注入的变量类型匹配,就可以注入成功。
    private IAccountDao accountDao = null;
    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

然后修改一下Client.java

public class Client {
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
//        ApplicationContext ac = new AnnotationConfigApplicationContext();
        //2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        as.saveAccount();
    }
}

运行一下main方法,控制台输出如下,我们发现 dao里的saveAccount方法被正常调用了,
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
在AccountServiceImpl里是怎么给accountDao进行注入的呢,这里我们使用的 Autolifier 注解,框架首先在容器中查找 IAccountDao 这个类型的bean对象,如果容器中只有一个就直接注入成功。如果容器中有多个 IAccountDao 类型的数据呢?我在com.itheima.dao.impl包里在创建一个 AccountDaoImpl2.java。

@Repository("accountDao2")
public class AccountDaoImpl2 implements IAccountDao {
    public void saveAccount() {
        System.out.println("保存了账户222");
    }
}

然后我们把 AccountDaoImpl 的注解的属性改成 accountDao1

@Repository("accountDao1")
public class AccountDaoImpl implements IAccountDao {
    public void saveAccount() {
        System.out.println("保存了账户111");
    }
}

我们在次运行main方法,发现就会报错了,因为现在ioc容器中有多个 IAccountDao 这个类型的对象时Spring框架不知道要使用是哪一个进行数据注入,但是我们把变量 accountDao 的名字改成accountDao1之后重新运行main方法,控制台输出如下,可以看出,数据注入成功了,但是为什么注入的是 AccountDaoImpl 这个类型的对象呢?
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
我们尝试把变量名改成accountDao2之后再一次运行main方法,控制台输出如下,
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
从这上面的现象可以看出ioc容器中有多个 IAccountDao 这个类型的对象时,Spring框架会在用变量名做为id来查询IAccountDao 这个类型的对象进行数据注入。

1.2.2 Qualifier

如果不想根据变量名称来进行对属性的数据注入怎么办呢?这时我们可以使用 Qualifier 注解,Qualifier注解的作用是在按照类型注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用要结合Autowired一起使用。但是在给方法参数注入时可以(稍后讲)。属性 value:用于指定注入bean的id。
修改一下AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    @Qualifier("accountDao1") // 这个就是要注入bean对象的id
    private IAccountDao accountDao = null;
    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

这样在数据注入的时候就会按照 Qualifier 注解的value属性值来进行数据注入。

1.2.3 Resource

上面的两种对属性的数据注入都有点麻烦,有没有在简单的办法呢?答案是有的,就是使用 Resource 注解。Resource 注解的作用:直接按照bean的id注入。它可以独立使用属性:name:用于指定bean的id。修改一下AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    // @Autowired
    // @Qualifier("accountDao1")
	@Resource(name = "accountDao1") // name 用于指定bean的id
    private IAccountDao accountDao = null;
    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    public void saveAccount() {
        accountDao.saveAccount();
    }
}

然后我们运行一下main方法,从控制台可以看出这样也是可以的。

以上三个注解都只能注入其他bean类型的数据,而基本数据类型和String类型无法使用上述注解实现。另外,集合类型的注入只能通过xml来实现。

1.3 用户改变作用范围的

他们的作用就和使用xml配置中的bean标签中使用scope属性实现的功能是一样的

Scope
    作用:指定bean的作用范围
    属性:value:指定范围的取值,常用取值:singleton(对应单例)默认值、prototype(对应多例)

下面我们来演示一下,我们修改一下 AccountServiceImpl。java

@Service("accountService")
@Scope("prototype")// singleton 是默认值
public class AccountServiceImpl implements IAccountService {
    // @Autowired
    // @Qualifier("accountDao1")
    @Resource(name = "accountDao1")
    private IAccountDao accountDao2 = null;

    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    public void saveAccount() {
        accountDao2.saveAccount();
    }
}

然后在修改一下Client.java里的main方法

public class Client {
    public static void main(String[] args) {
        //1. 获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountService as2 = ac.getBean("accountService", IAccountService.class);

        System.out.println(as == as2);
    }
}

运行一下main方法,控制台输出如下,说明 as和as2是两个对象。
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例

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

他们的作用就和使用xml配置中在bean标签中使用 init-method 标签和 destroy-method 标签的作用是一样的。

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

下面我们来演示一下,修改一下 AccountServiceImpl.java

@Service("accountService")
@Scope("singleton") // 这里使用的是单例,因为当使用多例时,spring不会主动去销毁这个对象,是有jvm的gc来管理
public class AccountServiceImpl implements IAccountService {
    // @Autowired
    // @Qualifier("accountDao1")
    @Resource(name = "accountDao1")
    private IAccountDao accountDao2 = null;
    public AccountServiceImpl() {
        System.out.println("对象被创建了...");
    }
    @PostConstruct
    public void init() {
        System.out.println("AccountServiceImpl init ...");
    }
    @PreDestroy
    public void destroy() {
        System.out.println("AccountServiceImpl destroy ...");
    }
    public void saveAccount() {
        accountDao2.saveAccount();
    }
}

然后在修改一下Client.java里的main方法

public class Client {
    public static void main(String[] args) {
        //1. 获取核心容器对象
        // ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

        //2.根据id获取bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        as.saveAccount();

        ac.close();
    }
}

然后运行mian方法,控制台输出如下。
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
注意在main方法里,变量 ac 的类型,不能是 ApplicationContext,因为后面的调用 ac 的 close()没有定义在接口 ApplicationContext 中是定义在子类ClassPathXmlApplicationContext中,父类引用对象只能调用父类方法。所以这里把ac的类型改成了 ClassPathXmlApplicationContext 。

2 案例使用xml方式和注解的方式实现单表的CRUD操作

2.1 使用xml方式实现单表的CRUD操作

经过上面的学习,现在我们使用spring框架来做一个小案例,使用junit来测试 AccountService 里的方法。我们重新创建一个Module命名为day02_02account_xml_ioc。在这个案例中我们使用xml配置的方式来配置ioc。
Module建成之后的结构
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
首先修改pom.xml文件

<?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>com.itheima</groupId>
    <artifactId>day02_02account_xml_ioc</artifactId>
    <version>1.0-SNAPSHOT</version>

    <packaging>jar</packaging>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--使用dbutils来进行持久化-->
        <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>
        <!--jdbc使用c3p0-->
        <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>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

创建domain类Account.java

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 +
                '}';
    }
}

创建dao接口IAccountDao.java

/**
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 查询所有的Account
     * @return
     */
    List<Account> findAllAccount();
    /**
     * 根据id查询Account
     * @param accountId
     * @return
     */
    Account findAccountById(Integer accountId);
    /**
     * 保存Account
     * @param account
     */
    void saveAccount(Account account);
    /**
     * 更新Account
     * @param account
     */
    void updateAccount(Account account);
    /**
     * 删除
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}

然后创建dao的实现类AccountDaoImpl.java

/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl implements IAccountDao {
    private QueryRunner runner;
    public void setRunner(QueryRunner runner) { //用于数据的注入
        this.runner = runner;
    }
    
    public List<Account> findAllAccount() {
        try {
            return runner.query("select * from account;", new BeanListHandler<Account>(Account.class));
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    
    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);
        }
    }

    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);
        }
    }

    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);
        }
    }

    public void deleteAccount(Integer accountId) {
        try {
            runner.update("delete from account where id = ?;", accountId);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

创建 Service接口IAccountService.java

/**
 * 账户业务层接口
 */
public interface IAccountService {
    /**
     * 查询所有的Account
     * @return
     */
    List<Account> findAllAccount();
    /**
     * 根据id查询Account
     * @param accountId
     * @return
     */
    Account findAccountById(Integer accountId);
    /**
     * 保存Account
     * @param account
     */
    void saveAccount(Account account);
    /**
     * 更新Account
     * @param account
     */
    void updateAccount(Account account);
    /**
     * 删除
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}

创建service接口的实现类AccountServiceImpl.java

/**
 * 账户的业务层实现类
 */
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    public List<Account> findAllAccount() {
        return this.accountDao.findAllAccount();
    }

    public Account findAccountById(Integer accountId) {
        return this.accountDao.findAccountById(accountId);
    }

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

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

    public void deleteAccount(Integer accountId) {
        this.accountDao.deleteAccount(accountId);
    }
}

然后创建spring的配置文件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">
    <!-- 配置Service -->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
        <!-- 使用set方法注入 dao对象-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl">
        <!-- 使用set方法注入 QueryRunner -->
        <property name="runner" ref="runner"></property>
    </bean>

    <!--配置 QueryRunner 对象,因为 bean 对象 默认是单例对象,所以有可能发生线程安全问题。所以让scope设置为prototype-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源,使用c3p0的数据库连接池-->
    <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/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>

最后创建测试类 AccountServiceTest.java

/**
 * 使用Junit单元测试,测试我们的配置
 */
public class AccountServiceTest {
    // private ApplicationContext ac;
    private ClassPathXmlApplicationContext ac;
    IAccountService as;

    @Before
    public void init() {
        //1. 获取核心容器对象
        ac = new ClassPathXmlApplicationContext("bean.xml");
        as = ac.getBean("accountService", IAccountService.class);
    }

    @After
    public void destroy() {
        ac.close();
    }

    @Test
    public void testFindAll() {
        List<Account> accounts = as.findAllAccount();
        for (Account account : accounts) {
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        Account account = new Account();
        account.setName("test");
        account.setMoney(4000f);
        as.saveAccount(account);
    }

    @Test
    public void testUpdate() {
        Account account = as.findAccountById(4);
        account.setName("test_update");
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
        as.deleteAccount(4);
    }
}

这些测试方法这里就不一个个运行了,感兴趣的同学可以自行运行一下。经过这个案例的练习对第一天的学习的内容怎么使用应该有了大致的了解。

2.2 使用注解方式实现单表的CRUD操作

我们重新创建一个Module命名为day02_03account_anno_ioc,然后修改pom.xml文件,文件内容参考上一个Module里的pom.xml。这个Module和上一个Module的差别不大我们就复制一下上一个Module的文件,我们把上一个Module里src/main里的所有的文件都拷贝到,我们新建的这个Module里src/main里。下面我们做一下修改。
修改AccountDaoImpl.java

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired //ioc容器中只有一个QueryRunner 对象,所以使用这个注解就行
    private QueryRunner runner;
	//删除掉 setRunner 方法
	...
}

修改AccountServiceImpl.java

@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired// 因为ioc容器中只有一个 IAccountDao 对象,所以这里使用只这个注解也可以
	// @Resource(name = "accountDao") //使用这个注解也可以哦
    private IAccountDao accountDao;

}

然后修改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"
       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
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- 告知spring在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.itheima"></context:component-scan>

	<!-- 下面的这些内容暂时不能删除,因为这些都是引用的其他包里的内容,暂时没法使用注解进行配置 -->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="ds"></constructor-arg>
    </bean>

    <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"/>
        <property name="password" value="123456"/>
    </bean>
</beans>

然后我们运行各自的测试方法,通过查看控制台的输出,可以发现,可以正常运行。但是刚刚也有注意的问题就是在bean.xml中使用其他包里的内容无法使用的注解的问题所以暂时这个bean.xml配置文件还不能删除。下面就来解决它。

2.3 完全使用注解的方式实现单表的CRUD操作

首先我们再创建一个新的Moduel命名为day02_04account_ioc_withoutXml,由于这个Module和上一个Moduel的结构变化不大,我们还是直接拷贝上一个Module(day02_03account_anno_ioc)里src/main里的所有文件复制到我们新建的Module的src/main里,然后修改pom.xml文件参考上一个Module里的。最后这个Module的结构如下
Spring学习笔记第二天,Spring基于xml或注解的IOC以及IOC的案例
首先我们的目的是删除掉bean.xml后项目仍然可以正常运行,怎么办呢?下面spring的新注解就出场了。

spring中的新注解
	作用:指定当前类是一个配置类
ComponentScan
	作用:用于通过注解指定spring在创建容器时要扫描的包
	属性:
		value:它和 basePackages 的作用是一样的,都是用于指定创建容器时要扫描的包,我们使用此注解就等同于在xml中配置了要扫描的包。
			<context:component-scan base-package="com.itheima"></context:component-scan>
Bean
	作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
	属性:用于指定bean的id。当不写时,默认值就是当前方法的名称
	细节:当使用注解配置方法方法时,如果方法有参数,spring框架会去容器中查找有没有可用的Bean对象,查找的方式和Autowired注解的方式是一样的。

创建SpringConfiguration.java,这个类就相当于我们使用xml配置时的bean.xml。

/**
 * 该类是一个配置类,它的作用就和bean.xml的作用是一样的
 * spring中的新注解
 * Configuration
 *      作用:指定当前类是一个配置类
 * ComponentScan
 *      作用:用于通过注解指定spring在创建容器时要扫描的包
 *      属性:
 *          value:它和 basePackages 的作用是一样的,都是用于指定创建容器时要扫描的包
 *                 我们使用此注解就等同于在xml中配置了
 *                      <context:component-scan base-package="com.itheima"></context:component-scan>
 * Bean
 *      作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
 *      属性:用于指定bean的id。当不写时,默认值就是当前方法的名称
 *      细节:当使用注解配置方法方法时,如果方法有参数,spring框架会去容器中查找有没有可用的Bean对象,
 *           查找的方式和Autowired注解的方式是一样的。
 */
@Configuration
//@ComponentScan(value = "com.itheima")
//@ComponentScan(basePackages = "com.itheima")
@ComponentScan("com.itheima")
public class SpringConfiguration {

    /**
     * 用于创建一个 QueryRunner 对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name = "dataSource")
    public DataSource createDataSource() {
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource("");
            ds.setDriverClass("com.mysql.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/spring");
            ds.setUser("root");
            ds.setPassword("123456");
            return ds;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

然后我们修改一下 AccountServiceTest.java。注意ApplicationContext的子类 AnnotationConfigApplicationContext 的一个构造参数为一个 Class 类型,但是这个Class的类要用 Configuration 注解修饰

/**
 * 使用Junit单元测试,测试我们的配置
 */
public class AccountServiceTest {
    // private ApplicationContext ac;
    // private ClassPathXmlApplicationContext ac;
    private AnnotationConfigApplicationContext ac;
    IAccountService as;

    @Before
    public void init() {
        //1. 获取核心容器对象
//        ac = new ClassPathXmlApplicationContext("bean.xml");
        ac = new AnnotationConfigApplicationContext(SpringConfiguration.class); // 参数是用 Configuration 注解修饰的类的 class
        as = ac.getBean("accountService", IAccountService.class);
    }
    ...

然后就可以删除bean.xml了,删除之后我们运行一下测试类 AccountServiceTest 里的测试方法是可以正常运行的。
还有一个问题需要注意,就是ioc容器里的bean对象在不配置Scope的情况下都是单例,我们在练习使用xml配置的时候说过了,runner是单例的可能会有线程安全问题。下面我们来测试下runner这个对象是不是单例的。在测试文件夹里的com.itheima.test包下新建一个测试类 QueryRunnerTest.java

public class QueryRunnerTest {
    @Test
    public void testQueryRunner() {
        //1.获取ioc容器
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        //2.获取QueryRunner对象
        QueryRunner runner1 = ac.getBean("runner", QueryRunner.class);
        QueryRunner runner2 = ac.getBean("runner", QueryRunner.class);
        System.out.println(runner1 == runner2);
    }
}

我们运行下这个测试方法控制台输出为 true,说明这是单例的,我们怎么解决呢,你是否还记得 Scope这个注解,下面我们就修改一下SpringConfiguration.java

@Configuration
@ComponentScan("com.itheima")
public class SpringConfiguration {
    /**
     * 用于创建一个 QueryRunner 对象
     * @param dataSource
     * @return
     */
    @Bean(name = "runner")
    @Scope("prototype") // 让runner对象多例
    public QueryRunner createQueryRunner(DataSource dataSource) {
        return new QueryRunner(dataSource);
    }
    ...
}

下面我们在运行一下QueryRunnerTest 里的测试方法testQueryRunner,控制台输出就是false了。

持久层技术选型 dbutils

3 改造基于注解的IOC案例,使用纯注解的方式实现

Spring中一些新注解的使用

4 Spring和Junit整合

持续更新中…

参考文档
Spring 5.0.2 的说明文档

本文地址:https://blog.csdn.net/Code_Boy_Code/article/details/107593774