SSM框架学习----Spring(2)

1. Spring的常用注解

1.1. xml配置的回顾

 <bean id=""  class=""
              scope=""  init-method="init" destroy-method="destroy">
              <property>
                  name="" value="" | ref=""
              </property>
              </bean>

将以上出现的标签和属性划分为4类:

  • 用于创建对象:与在xml配置bean标签的作用是一样的
  • 用于注入数据:与xml配置中bean标签中的property标签的作用是一样的
  • 用于改变作用范围:在bean标签中使用scope属性的作用一致
  • 用于生命周期使用:在bean标签中使用init_method和destroy_method的作用一致

1.2. Component注解

@Component注解(String value() default “”)

细节:如果注解中有且只有一个属性要赋值时,且名称是 value,value 在赋值是可以不写。

作用:

把资源让 spring 来管理,相当于在 xml 中配置一个 bean。

属性:

value:指定 bean 的 id,如果不指定 value 属性,默认 bean 的 id 是当前类的类名,首字母为小写。

注意:

直接在某个实现类上加Component注解,是不会被Spring执行的,因为并没有告知Spring创建容器时需要扫描的包或者类是哪些,因此需要在xml中进行配置:

<context:component-scan base-package="com.huzhen"></context:component-scan>

注意是在beans标签内配置,而不是bean约束中进行配置,在context名称空间约束中指定package之后,spring会扫描该包下及其子包中的所有类和接口中的注解

@Component("accountService")
@Scope("singleton")
public class AccountServiceImpl implements AccountService {
    ...

另外,如果没有在Component注解中执行value的值,也就是容器的key,那么会使用默认key,当前类的name,并且首字母是小写,在测试类中演示:

    public static void main(String[] args) {

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 如果注解中没有指定value,传递进去的参数是当前需要创建对象的类名(默认首字母小写 accountServiceImpl)
        // AccountService serviceDao = ac.getBean("accountServiceImpl", AccountService.class);
        AccountService serviceDao = ac.getBean("accountService", AccountService.class);
        System.out.println(serviceDao);

1.2. Controller Service Repository注解

他们三个注解都是Component的衍生注解,他们的作用及属性都是一模一样的,只不过是提供了更加明确的语义化。Spring框架提供的明确的三层使用的注解,使得三层对象更加清晰。

使用范围

@Controller:一般用于表现层的注解。

@Service:一般用于业务层的注解。

@Repository:一般用于持久层的注解。

注意:

如果每个层的实现类的注解都使用Component以及三个衍生注解中任何一个注解,都是没有问题的,只是为了区分开每层之间的实体类对象

1.3. 注入数据的注解

1.3.1. Autowired

作用

自动按照类型注入,只要容器中有唯一一个bean对象类型和待注入的变量类型匹配,即可注入。当使用注解注入属性时,set 方法可以省略。它只能注入其他 bean 类型。当有多个类型匹配时,使用要注入的对象变量名称作为 bean 的 id,在 spring 容器查找,找到了也可以注入成功。找不到就报错。

出现位置:

可以作用在类,方法也可以在某个变量上

注意:

在使用注解注入时,set方法就不是必须的了。比如说下面,对于AccountServiceImpl类中的AccountDao对象,不需要使用set方法使用注解可以进行自动注入

如果仅使用Component注解,而不使用注入数据的注解,在调用dao接口对象时依旧报错,此时的错误是空指针异常,表示下面的accounDao对象没有找到
请添加图片描述

@Component("accountService")
public class AccountServiceImpl implements AccountService {

    @Autowired
    private AccountInterface accountDao1;

    public AccountServiceImpl(){
        System.out.println("业务层的对象创建...");
    }

    @Override
    public void saveAccount() {
        System.out.println("Service业务层中的方法... ");
        accountDao1.saveAccount();
    }

    public void init(){
        System.out.println("对象initial...");
    }

    public void destroy(){
        System.out.println("对象destroy...");
    }
}
public static void main(String[] args) {

        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 如果注解中没有指定value,传递进去的参数是当前需要创建对象的类名(默认首字母小写 accountServiceImpl)
        // AccountService serviceDao = ac.getBean("accountServiceImpl", AccountService.class);
        AccountService serviceDao = ac.getBean("accountService", AccountService.class);
        System.out.println(serviceDao);

        AccountInterface accountDao = ac.getBean("accountDao", AccountInterface.class);
        System.out.println(accountDao);
        // 如果不添加注入数据的注解,报空指针异常
        serviceDao.saveAccount();

    }

在AccountDao接口对象上配上注解Autowired之后:

请添加图片描述
那么Spring是如何去寻找并且注入数据的?具体可以看下面的图示:
请添加图片描述
自动按照类型注入:此时待注入的数据类型为IAccountDao,从容器中寻找是否存在改类型的对象,发现存在那么就可以将IAccountDao对象成功注入到accountDao变量中去。相反,如果容器没有一个bean类型与待注入的变量类型匹配,会报错

如果IoC容器中存在多个类型可以匹配到,首先在类型匹配到的对象范围内(上图中的红框),其次根据变量名匹配bean对象的id也就是map中的key,如果能匹配到,则注入成功,否则报错。

为了演示多个bean对象可以匹配到的情况,定义两个dao接口的实现类AccountDaoImpl1和AccountDaoImpl2

@Repository("accountDao1")
public class AccountDaoImpl implements AccountInterface {
    @Override
    public void saveAccount() {
        System.out.println("First Saved Account...");
    }

    public AccountDaoImpl(){
        System.out.println("持久层的对象创建...");
    }
}
@Repository("accountDao2")
public class AccountDaoImpl2 implements AccountInterface {
    @Override
    public void saveAccount() {
        System.out.println("Second Save Account...");
    }
}

测试报错的情况,当dao下存在两个impl,一个命名为accountDao1,一个命名为accountDao2,而待注入的变量名为AccountDao,此时匹配不到对象,报错
请添加图片描述
当ServiceImpl中的成员变量修改为accountDao1或者accountDao2时,此时可以匹配成功:
请添加图片描述
而修改成员变量的方法并不是很友好,那么就引入下面一个注解@Qualifier

1.3.2. Qualifier

作用:

在按照类中注入的基础上再按照名称注入。在给类成员注入数据时不能单独使用,需要与Autowired注解一起使用;在给方法参数注入数据时可以单独使用

属性:

value指定bean的id。

在ServiceImpl类的类成员变量AccountDao中增加Qualifier注解指明bean对象的id,即可注入成功,但不能去掉Autowired注解

    @Autowired
    @Qualifier("accountDao1")
    private AccountInterface accountDao;

1.3.3. Resource

作用:

直接按照bean的id进行注入,可以独立使用

属性:

name用于指定bean对象的id。

注意:

Resource注解不是Spring提供的,是javax.annotation下的注解,需要在pom.xml中导入javax.annotation的依赖:

        <!-- https://mvnrepository.com/artifact/javax.annotation/javax.annotation-api -->
        <dependency>
            <groupId>javax.annotation</groupId>
            <artifactId>javax.annotation-api</artifactId>
            <version>1.3.2</version>
        </dependency>
    @Resource(name = "accountDao1")
    private AccountInterface accountDao;

使用Resource注解直接指定bean对象的id,可以直接进行注入


以上三个注解只能注入bean类型的数据,对于基本类型和String类型无法使用以上三种注解,另外,集合类型只能通过xml配置注入

1.3.4. Value

作用:

用于注入基本类型和String类型的数据

属性:

value指定数据的值,可以使用Spring中的SpEL(Spring中的EL表达式)

SpEL的写法:${表达式}

    @Value("大司马")
    private String name;
    @Value("35")
    private Integer age;

1.4. 改变作用范围的注解----Scope

作用:

指定bean对象的作用范围

属性:

value指定范围的取值,常用取值两个:singleton单例对象;prototype多例对象(默认为singleton)

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

1.5.1. PreDestroy

用于指定销毁方法

1.5.2. PostConstructor

用于指定初始化方法


对于Scope和生命周期相关的注解演示:

@Component("accountService")
@Scope("singleton")
public class AccountServiceImpl implements AccountService {

//    @Autowired
//    @Qualifier("accountDao1")
    @Resource(name = "accountDao1")
    private AccountInterface accountDao;

    @Value("大司马")
    private String name;
    @Value("35")
    private Integer age;

    public AccountServiceImpl(){
        System.out.println("业务层的对象创建...");
    }

    @Override
    public void saveAccount() {
        System.out.println("Service业务层中的方法... " + "," + name + "," + age);
        accountDao.saveAccount();
    }

    @PostConstruct
    public void init(){
        System.out.println("对象initial...");
    }
    @PreDestroy
    public void destroy(){
        System.out.println("对象destroy...");
    }
}

测试类中输出结果:

    public static void main(String[] args) {

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        // 如果注解中没有指定value,传递进去的参数是当前需要创建对象的类名(默认首字母小写 accountServiceImpl)
        // AccountService serviceDao = ac.getBean("accountServiceImpl", AccountService.class);
        AccountService serviceDao = ac.getBean("accountService", AccountService.class);
        AccountService serviceDao2 = ac.getBean("accountService", AccountService.class);
        System.out.println(serviceDao);
        System.out.println(serviceDao == serviceDao2);
        // 如果不添加注入数据的注解,报空指针异常
        serviceDao.saveAccount();
        ac.close(); // 调用子类的对象方法,并且只能是单例对象才能被Spring管理
    }

请添加图片描述

  • 由于是设置为singleton为单例对象,那么创建对象是立即加载,在读取配置文件结束就会立刻创建对象
  • 然后执行初始化方法,打印该对象,由于这里是单例对象,所以这里创建的两次对象其实是同一个,返回true
  • 最后执行dao接口的方法,销毁方法执行。

另外,对于Scope使用prototype属性,是不会执行销毁方法的,因为多例对象和容器不相关,输出结果:
请添加图片描述
不仅会输出两次对象的创建,而且也打印出false,证明创建的是多例对象。

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

需求:使用xml方式完成单表account的CRUD操作

Account表数据如下:
请添加图片描述

2.1.1. 导入依赖

由于不涉及Spring和Mybatis的整合,所以使用工具类DBUtils来帮助执行sql语句。因此导入的依赖除了Spring之外,还包括:

  • MySQL
  • DBUtils
  • c3p0
  • JUnit(用于单元测试)
<?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.huzhen</groupId>
    <artifactId>Spring_xml_Instance</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.14</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/commons-dbutils/commons-dbutils -->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.7</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.27</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/com.mchange/c3p0 -->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
</project>

2.2.2. Dao层,Service层以及实体类

  • 账户的持久层接口:AccountDao
package com.huzhen.dao;

import com.huzhen.domain.Account;
import java.util.List;

/**
 * 账户的持久层接口
 */
public interface AccountDao {

    /**
     * 查询所有账户信息
     * @return
     */
    List<Account> findAll();

    /**
     * 根据id查询账户信息
     */
    Account findAccountById(Integer id);

    /**
     * 增加账户信息
     */
    void saveAccount(Account account);

    /**
     * 更新账户信息
     */
    void updateAccount(Account account);

    /**
     * 根据id删除账户信息
     */
    void deleteAccount(Integer id);
}
  • 持久层接口的实现类:AccountDaoImpl
package com.huzhen.dao.impl;

import com.huzhen.dao.AccountDao;
import com.huzhen.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import java.sql.SQLException;
import java.util.List;

/**
 * 账户的持久层接口实现类
 */
public class AccountDaoImpl implements AccountDao {
    // 使用DBUtils工具类来帮助Dao层的开发,简化操作,不需要再使用JDBC那一套冗余的工作:
    // 注册驱动,获取连接,获取代理对象,代理对象执行sql,释放资源
    private QueryRunner runner;

    public void setRunner(QueryRunner runner) {
        this.runner = runner;
    }

    public List<Account> findAll() {
        try {
            return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
          throw new RuntimeException();
        }
    }

    public Account findAccountById(Integer id) {
        try {
            return runner.query("select * from account where id=?", new BeanHandler<Account>(Account.class), id);
        } catch (SQLException e) {
           throw new RuntimeException();
        }
    }

    public void saveAccount(Account account) {
        try {
            runner.update("insert  into account(name, revenue) values(?,?)", account.getName(), account.getRevenue());
        } catch (SQLException e) {
            throw new RuntimeException();
        }
    }

    public void updateAccount(Account account) {
        try {
            runner.update("update account set name=?, revenue=? where id=?", account.getName(), account.getRevenue(), account.getId());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    public void deleteAccount(Integer id) {
        try {
            runner.update("delete from account where id=?", id);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

这里使用DBUtils工具类来执行SQL语句,需要定义一个QueryRunner对象,并且对象该变量的set方法,用于使用xml配置来注入数据,使用runner对象的query方法来进行查询,update方法进行增删改操作。其中第一个参数为String类型的sql语句;如果存在单个返回值,使用BeanHandler类,指定泛型反射出一个Account对象;如果存在多个返回值,使用BeanListHandler,返回一个Account类型的集合;没有返回值则忽略,对于SQL语句中需要输入值的,使用占位符来解决,并且在参数列表中加入输入值,输入值的顺序与占位符的顺序保持一致

  • 业务层接口:AccountService
package com.huzhen.service;

import com.huzhen.domain.Account;
import java.util.List;

/**
 * 账户的业务层接口
 */
public interface AccountService {

    /**
     * 查询所有账户信息
     * @return
     */
    List<Account> findAll();

    /**
     * 根据id查询账户信息
     */
    Account findAccountById(Integer id);

    /**
     * 增加账户信息
     */
    void saveAccount(Account account);
    
    /**
     * 更新账户信息
     */
    void updateAccount(Account account);

    /**
     * 根据id删除账户信息
     */
    void deleteAccount(Integer id);
}
  • 业务层接口的实现类:AccountServiceImpl
package com.huzhen.service.impl;

import com.huzhen.dao.AccountDao;
import com.huzhen.domain.Account;
import com.huzhen.service.AccountService;
import java.util.List;

/**
 * 账户的业务层接口的实现类
 */
public class AccountServiceImpl implements AccountService {
    // 业务层调用持久层的对象
    private AccountDao accountDao;
    // 由于使用xml进行配置来注入数据,需添加set方法
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

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

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

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

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

    public void deleteAccount(Integer id) {
        accountDao.deleteAccount(id);
    }
}

业务层实现类中需要定义一个AccountDao接口的对象,并且增加其set方法,用来给accountDao对象注入数据,来执行accountDao对象中的方法。

  • 实体类Account
package com.huzhen.domain;
import java.io.Serializable;

public class Account implements Serializable {
    private Integer id;
    private String name;
    private Double revenue;
    
    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 Double getRevenue() {
        return revenue;
    }

    public void setRevenue(Double revenue) {
        this.revenue = revenue;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", revenue=" + revenue +
                '}';
    }
}

2.3.3. 配置xml

四步走逐个配置:

  • 配置service对象,并且在service对象中注入dao对象
  • 配置dao对象,并且在dao对象中注入runner对象
  • 配置runner对象,使用构造函数进行注入dataSource对象
  • 配置dataSource对象,使用set方法注入数据
<?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.huzhen.service.impl.AccountServiceImpl">
        <!--在Service中注入dao对象-->
        <property name="accountDao" ref="accountDao"></property>
     </bean>
    <!--配置dao对象-->
    <bean id="accountDao" class="com.huzhen.dao.impl.AccountDaoImpl">
        <!--在Dao中注入queryRunner对象-->
        <property name="runner" ref="runner"></property>
    </bean>

    <!--配置queryRunner对象 为了防止多个dao使用runner对象发生线程安全问题,因此配置成多例对象-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源 使用构造函数注入-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源 使用c3p0来连接数据库 使用set方法注入数据-->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///springIoC?serverTimezone=UTC&amp;useSSL=false"></property>
        <property name="user" value="root"></property>
        <property name="password" value="*****"></property>
      </bean>
</beans>

这里在配置runner对象时,为了防止多个dao对象使用runner对象引发的线程安全问题,所以使用scope属性配置成多例对象prototype

2.3.4. 单元测试

三步走:

  • 获取IoC容器
  • 得到业务层对象
  • 执行方法
mport java.util.List;

/**
 * 使用JUnit单元测试 xml配置
 * 步骤:
 *      1. 获取容器
 *      2. 得到业务层对象
 *      3. 执行方法
 */
public class accountServiceTest {

    AccountService accountService = null;

    @Before
    public void init(){
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        accountService = ac.getBean("accountService", AccountService.class);
    }

    @Test
    public void testFindAll(){
        List<Account> list = accountService.findAll();
        for(Account account : list){
            System.out.println(account);
        }
    }

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

    @Test
    public void testSave(){
        Account account = new Account();
        account.setName("小超梦");
        account.setRevenue(517812.00);
        accountService.saveAccount(account);
    }
    
    @Test
    public void testUpdate(){
        Account account = accountService.findAccountById(20201006);
        account.setName("胖子");
        account.setRevenue(8000.00);
        accountService.updateAccount(account);
    }

    @Test
    public void testDelete(){
        accountService.deleteAccount(20201006);
    }
}

举findAll和insert方法的结果进行展示:
请添加图片描述
请添加图片描述

3. 使用纯注解的方式实现

3.1.1 简单注解开发

  • 账户的业务层实现类
/**
 * 账户的业务层接口的实现类
 */
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    // 业务层调用持久层的对象 由于使用注解进行注入数据,set方法可以删除掉
    @Autowired
    private AccountDao accountDao;
// 针对于xml防方式注入数据,需要使用set方法注入数据
//    public void setAccountDao(AccountDao accountDao) {
//        this.accountDao = accountDao;
//    }
    

由于是单表的CRUD操作,不需要指明对应的变量名,使用Autowired注解即可

  • 账户的持久层实现类
/**
 * 账户的持久层接口实现类
 */
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    // 使用DBUtils工具类来帮助Dao层的开发,简化操作,不需要再使用JDBC那一套冗余的工作:
    // 注册驱动,获取连接,获取代理对象,代理对象执行sql,释放资源
    @Autowired
    private QueryRunner runner;

//    public void setRunner(QueryRunner runner) {
//        this.runner = runner;
//    }

此时就可以将xml配置中的accountService和accountDao配置删除了,但是runner和数据源的配置以及Component-scan的配置不可删除。


单元测试类中的操作与xml配置时保持一致,不需要改变。

3.2.2. 使用Spring新注解进行开发

3.2.2.1. 简单注解存在的问题思考

基于注解的 IoC 配置已经完成,但是存在一个问题:这里依然离不开 spring 的 xml 配置文件,那么能不能不写这个 bean.xml,所有配置都用注解来实现呢?当然,需要注意的是,我们选择哪种配置的原则是简化开发和配置方便,而非追求某种技术,不是为了实现而实现。


之所以离不开注解,原因是存在xml有一句很重要的配置也就是:

<context:component-scan base-package="com.huzhen"/>

component-scan用来告Spring需要扫描注解的包在哪里? 然后Spring才能帮助我们去读取配置文件,创建容器时,扫描注解,依据注解创建对象,并存入容器中

另外,对于其他的bean对象(如存在jar包的某个类中反射出来的对象):数据源和DBUtils的配置

    <!--配置queryRunner对象 为了防止多个dao使用runner对象发生线程安全问题,因此配置成多例对象-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!--注入数据源 使用构造函数注入-->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

    <!--配置数据源 使用c3p0来连接数据库 使用set方法注入数据-->
    <bean name="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql:///springIoC?serverTimezone=UTC&amp;useSSL=false"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
      </bean>

如何去掉这两块配置,需要使用到另外两个Spring中的新注解:@Configuration和@ComponentScan

3.2.2.2. Configuration注解

作用:

指定当前类是一个配置类,当创建容器时会从该类上加载注解,获取容器时需要使用AnnotationApplicationContext(有@Configuration 注解的类.class);属性value用于指定配置类的字节码

属性:

value用于指定配置类的字节码

细节:

当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。但是存在多个配置类的情况下,并且在获取容器时在AnnotationConfigApplicationContext方法中只传入了某一个配置类的字节码,那么需要在其他配置类上写上注解Configuration,否则Spring将不能加载这些配置类。

3.2.2.3. ComponentScan注解

作用:

通过注解来指定创建容器时需要扫描的包,起到的作用与xml中:

<context:component-scan base-package="com.huzhen"/>

是一样的

属性:

  • basePackages
  • value

两者的作用一致,都是指定需要扫描的包名

@Configuration()
@ComponentScan("com.huzhen")
public class SpringConfiguration {
3.2.2.4. Bean注解

作用:

只能作用在方法上,用于将当前方法的返回值作为bean对象存到spring的IoC容器中。

属性:

name用于指定当前bean对象的id,当不指定name时,默认值为当前方法名作为key,返回的bean对象则为value

观察数据源获取runner对象的配置可以发现,其中的class传递进去的是QueryRunner的全限定类名,然后Spring通过反射创建一个runner对象存到IoC容器中,那么可以在配置中定义一个方法,使用构造函数进行创建runner对象,根据DataSource来获取一个runner对象。如下图:

请添加图片描述

此时两者得到的对象一样的,通过构造函数new出来的runner对象仅仅是一个独立的对象,而配置文件创建的对象是存在容器中的,因此需要使用@Bean注解来使得runner对象存入到容器中。

细节:

当使用注解去配置方法时,如果某个方法有输入参数,spring会去容器中查找是否存在可用的bean对象,查找的方式和Autowired注解是一样的
请添加图片描述
在没有获取DataSource对象的方法时,这里的形参ds会报错,表明在IoC容器中不存在DataSource对象,所以我们必须再定义一个获取DataSource对象的方法

    /**
     * 创建数据源对象
     * @return dataSource
     */
    @Bean("ds")
    public DataSource createDataSource(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass("com.mysql.cj.jdbc.Driver");
            ds.setJdbcUrl("jdbc:mysql:///springIoC?serverTimezone=UTC&amp;useSSL=false");
            ds.setUser("root");
            ds.setPassword("123456");
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return ds;
    }

此时不再需要xml配置文件中了,然后在单元测试类中需要读取这个配置类中的数据源和DBUtils的配置,使用ApplicationContext接口的另一个实现类AnnotationConfigApplicationContext,通过源码可以知道传入的参数为配置类SpringConfiguration的字节码

public class accountServiceTest {

    AccountService accountService = null;
    @Before
    public void init(){
      //  ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        accountService = ac.getBean("accountService", AccountService.class);
    }

    @Test
    public void testFindAll(){
        List<Account> list = accountService.findAll();
        for(Account account : list){
            System.out.println(account);
        }
    }
3.2.2.5. Import

作用:

用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解

属性:

value指定其他配置类的字节码,当使用Import注解之后,带有Import注解的类即为父配置类,而导入其他的均为子配置类

3.2.2.6. PropertySource

作用:

用于加载.properties 文件中的配置。例如在配置数据源时,可以把连接数据库的信息写到properties 配置文件中,就可以使用此注解指定 properties 配置文件的位置

属性:

value用于指定properties文件的路径。如果是在类路径下,需要在文件名之前加上classpath:


以上两个注解联合演示:

  • jdbcConfig配置类
@SuppressWarnings("all")
public class JdbcConfig {
    // 使用value注解注入数据
    @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的构造函数创建一个runner对象
     * 如果Bean注解中不指定name,则将方法名作为key,对象作为value保存到IoC容器中
     * @param ds
     * @return QueryRunner
     */
    @Bean(name = "runner")
    @Scope("prototype")
    public QueryRunner createRunner(@Qualifier("ds1") DataSource ds){
        return new QueryRunner(ds);
    }

    /**
     * 创建数据源对象
     * @return dataSource
     */
    @Bean("ds1")
    public DataSource createDataSource1(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return ds;
    }

    /**
     * 当匹配相同数据类型的bean对象,会根据bean的id去寻找,解决方法有:
     * 1. 将输入参数的变量名修改成某一个bean对象的key
     * 2. 对于Qualifier注解,对于类成员变量不能单独使用,但是对于方法可以直接使用,在形参之前使用Qualifier注解
     * 并且传入对应的bean对象的key
     */
    @Bean("ds2")
    public DataSource createDataSource2(){
        ComboPooledDataSource ds = new ComboPooledDataSource();
        try {
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return ds;
    }
}
  • 主配置类SpringConfiguration
//@Configuration()
@ComponentScan("com.huzhen")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfiguration {
    

}

  • 由于在测试类中已经将主配置类的字节码读入,所以在SpringConfiguration类上的@Configuration可以省略
  • 可以在获取容器时读取多个配置类,那么需要传入多个配置类的字节码,如下:
 ApplicationContext ac = new AnnotationConfigApplicationContext(配置类1.class, 配置类2.class,...)

​ 也可以在主配置类上使用@Import来指明其他配置类的字节码,就不再需要传入多个配置类的字节码,也不需要在其他子配置类上标注@Configuration

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql:///SpringIoC?serverTimezone=UTC&useSSL=false
jdbc.username=root
jdbc.password=******
  • 使用外部配置文件来传入连接数据源的信息,在主配置类上使用==@PropertySource并指明properties的文件位置==,在子配置类中定义4个类成员变量,并且通过@Value注解来注入数据,注意此时需要使用SpEL表达式也就是==${}==来传递参数,其中的参数即为外部文件properties中的key
  • 对于容器存在多个bean对象与某个方法的输入参数的数据类型匹配,那么此时根据该方法的形参名来进行匹配,如果存在,则匹配成功,否则报错。
    • 可以将形参名修改成某个需要的bean对象的key
    • 由于Qualifier注解是可以在方法和参数上单独使用的,因此可以使用@Qualifier注解来指明此时需要传入的bean对象具体是哪一个(Qualifier中传入的是bean对象的key)

4. Spring和Junit整合

4.1. 问题引入

在测试类中,每个测试方法都有以下两行代码:

ApplicationContext ac = **new** ClassPathXmlApplicationContext("bean.xml");
IAccountService as = ac.getBean("accountService",IAccountService.**class**);

这两行代码的作用是获取容器,如果不写的话,直接会提示空指针异常。所以又不能轻易删掉。即使使用Junit的Before注解将其定义到init函数中,依旧是不合理的,因为测试人员并不需要关心容器是如何获取以及如何获取对象的。

分析:

  • 应用程序的入口是main方法,
  • JUnit单元测试中没有main方法也能执行,因为JUnit继集成了一个main方法,该方法会判断当前测试类中哪些方法有==@Test注解==,JUnit会让带有JUnit注解的方法执行。
  • JUnit不会管是否采用了什么框架,在执行测试时,JUint也就不会去读取配置文件或者配置类去获取容器,来创建对象
  • 因此,当测试方法执行时,根本没有IoC容器,即使在类变量上写上Autowired注解,也无法实现注入

4.2. 解决方法

junit 给我们暴露了一个注解,可以让我们替换掉它的运行器。这时,需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容器。我们只需要告诉它配置文件在哪就行了。

  • 导入Spring整合JUnit的jar包
  • 使用JUnit提供的注解==@RunWith将原有的运行器(main)替换成Spring提供的SpringJUnit4ClassRunner==

阅读源码,发现RunWith接口传入的是一个字节码:

请添加图片描述

SpringJUnit4ClassRunner运行器的字节码传入,因为是Spring提供的运行器,才能主动去获取容器

  • 告知Spring的运行器,Spring的IoC创建是基于xml(告知xml的类路径)还是基于注解(告知配置类的字节码),使用==@ContextConfiguration==来指明。通过阅读ContextConfiguration的源码:
    请添加图片描述

可以发现,如果基于xml配置的,使用locations来指定xml的位置:classpath:类路径

如果基于注解的,使用value来指定配置类的字节码:xxxx.class

另外,当使用Spring 5.x版本时,要求JUnit的jar包必须是4.12及以上,不然会报错


JUnit单元测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class accountServiceTest {

    @Autowired
    AccountService accountService = null;
    
    @Test
    public void testFindAll(){
        List<Account> list = accountService.findAll();
        for(Account account : list){
            System.out.println(account);
        }
    }
    
    @Test
    public void testFindOne(){
        Account account = accountService.findAccountById(20201001);
        System.out.println(account);
    }

    @Test
    public void testSave(){
        Account account = new Account();
        account.setName("我兄弟");
        account.setRevenue(1000.00);
        accountService.saveAccount(account);
    }

    @Test
    public void testUpdate(){
        Account account = accountService.findAccountById(20201006);
        account.setName("胖子");
        account.setRevenue(8000.00);
        accountService.updateAccount(account);
    }

    @Test
    public void testDelete(){
        accountService.deleteAccount(20201006);
    }
}

使用@RunWith来使用Spring提供的运行器,才能让Spring去获取容器

使用@ContextConfiguration来告知Spring的配置文件,才能告知Spring去找xml配置文件还是去找配置类

最后只需要在AccountService接口实现类的对象上加上@Autowired,Spring才能完成自动注入数据


请添加图片描述
输出结果完全没有问题,合理完成spring和JUnit的整合。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

从现在开始壹并超

你的鼓励,我们就是hxd

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值