• Spring进阶案例之注解和IoC案例


    Spring进阶案例之注解和IoC案例

    一、常见的注解分类及其作用

    从此前的基于xml的IoC开发案例和依赖注入案例中,我们可以将xml配置归纳为:

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

    注解按照作用可以分为四类:

    1.用于创建对象的注解:相当于xml配置的bean标签

    创建对象的注解有如下几个:

    注解 作用 属性
    @Component 把当前类对象存入Spring容器中 value: 用于指定bean的id。当不写value属性时,默认值是当前类名,且首字母改小写
    @Controller 一般用在表现层创建bean 同上
    @Service 一般用在业务层创建bean 同上
    @Repository 一般用在持久层创建bean 同上

    最后三个注解的作用和属性与Component注解一模一样,他们是Spring框架为我们提供明确三层架构的注解,可以使三层架构更加清晰。

    如果我们在AccoutServiceImpl类上加上@Component("accountService")或者@Service("accountService"),都可以起到将AccountServiceImpl类的对象加入的IoC容器中的效果。此时,ui.Client类中的main方法还不能执行:

    public static void main(String[] args) {
        //验证依赖注入
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        System.out.println(accountService);
    }
    

    会显示No bean named 'accountService' ,这是因为我还没有对Spring要扫描的包进行配置,如果不配置,Spring是不知道哪些类需要扫描注解。

    <!-- 配置Spring在创建容器时要扫描的包,在一个名为context名称空间的约束中进行配置-->
    <context:component-scan base-package="service"></context:component-scan>
    <context:component-scan base-package="dao"></context:component-scan>
    

    此时,项目结构为:

    dao包:
    public interface IAccountDao
    dao.impl包:
    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao
    
    service包:
    public interface IAccountService
    service.impl包:
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService
    
    ui包:
    public class Client
    
    2.用于注入对象的注解:相当于xml配置的bean标签中property标签,使用注解进行注入时,不再需要set方法

    如果我们需要调用accountService中的saveAccount方法,就需要对AccountServiceImpl类中的accountDao成员变量进行注入。同时需要在AccountDaoImpl类的accountDao变量上加上@Autowired("accountDao")注解。用于注入数据的注解有如下几个:前三个注解只能注入其他bean类型的注入,基本类型和String类型的注入无法通过它们来实现。集合类型的注入只能通过xml配置来实现,不能通过注解来实现。

    注解 作用 属性
    @Autowired 自动按照类型注入,可以出现在变量上,也可以出现在在方法上。
    @Qualifier 按照类中注入的基础之上再按照名称注入。在给类成员注入时不能单独注入,在给方法参数注入时可以单独注入 value:用于指定注入bean的id
    @Resource 直接按照bean的id注入,可以单独使用 name:用于指定注入bean的id
    @Value 用于注入String类型和基本类型 value:用于指定注入数据的值,可以使用Spring中的el表达式(SpEL,写法为:${表达式})

    这个时候,我们在ui.Client类的main方法中,就可以执行saveAccount方法了。

    public static void main(String[] args) {
        //验证依赖注入
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        System.out.println(accountService);
        IAccountDao accountDao = applicationContext.getBean("accountDao", IAccountDao.class);
        System.out.println(accountDao);
        //调用saveAccount方法
        accountService.saveAccounts();
    }
    

    在使用@Autowired注解时,需要注意的是:

    1. 只要IoC容器中有唯一的一个bean对象和要注入的变量类型匹配,就可以注入成功。
    2. 如果IoC容器中任何bean对象和要注入的变量类型都不匹配,就会报错。
    3. 如果IoC容器中有多个bean对象和要注入的变量类型匹配,则按变量名称和bean类名进行匹配,若有唯一一个匹配,则注入成功,否则注入失败。

    例如,在dao.impl包下,有两个IAccountDao接口的实现类,分别是是AccountDaoImpl1和AccountDaoImpl2,在这两个类上分别加入注解@Repository("accountDao1")和@Repository("accountDao2")。

    此时,项目结构为:

    dao包:
    public interface IAccountDao
    dao.impl包:
    @Repository("accountDao1")
    public class AccountDaoImpl1 implements IAccountDao
    @Repository("accountDao2")
    public class AccountDaoImpl2 implements IAccountDao
    
    service包:
    public interface IAccountService
    service.impl包:
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService
    
    ui包:
    public class Client
    

    如果还使用Autowired注解对AccountServiceImpl类中的accountDao变量进行注入,就会报错。这个时候,有三种方式可以选择:

    • 只使用@Autowired注解,并修改accountDao变量名为accountDao1,此时注入的是dao.impl.AccountDaoImpl1

    • 同时使用@Autowired和@Qualifier("accountDao2")注解,变量名可以任意,注入的是dao.impl.AccountDaoImpl2

    • 只使用@Resource(name = "accountDao1")注解,变量名可以任意,注入的是dao.impl.AccountDaoImpl1

      //方法一
      @Autowired
      private IAccountDao accountDao1;
      
      //方法二
      @Autowired
      @Qualifier("accountDao2")
      private IAccountDao accountDao22;
      
      //方法三
      @Resource(name = "accountDao1")
      private IAccountDao accountDao;
      

    为了看的更清楚,我们进行如下改造:

    //AccountDaoImpl1和AccountDaoImpl2中的saveAccounts方法:
    public void saveAccounts() {
        System.out.println(this.getClass());
        System.out.println("向数据库写入账户数据!!!");
    }
    //AccountServiceImpl中的saveAccounts方法:
    public void saveAccounts() {
      	System.out.println("执行保存账户操作");
        //调用持久层接口函数
        accountDao.saveAccounts();
        accountDao1.saveAccounts();
        accountDao22.saveAccounts();
    }
    //Client类中的main方法:
    public static void main(String[] args) {
        //验证依赖注入
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
        IAccountService accountService = (IAccountService) applicationContext.getBean("accountService");
        //调用saveAccount方法
        accountService.saveAccounts();
    }
    

    输出为:

    执行保存账户操作
    class dao.impl.AccountDaoImpl1
    向数据库写入账户数据!!!
    class dao.impl.AccountDaoImpl1
    向数据库写入账户数据!!!
    class dao.impl.AccountDaoImpl2
    向数据库写入账户数据!!!
    
    3.用于改变对象作用范围的注解:相当于xml配置的bean标签中的scope属性

    如果我们要改变bean的作用范围,就需要用到scope属性:

    注解 作用 属性
    @Scope 用于指定bean的作用范围 value: 指定范围的取值。常用取值:singleton(单例,也是默认值)、prototype(多例)、

    例如,我们在AccountServiceImpl2类上加上注解@Scope("prototype"),然后在main方法中执行:

    IAccountDao accountDao11 = applicationContext.getBean("accountDao1", IAccountDao.class);
    System.out.println(accountDao11);
    IAccountDao accountDao12 = applicationContext.getBean("accountDao1", IAccountDao.class);
    System.out.println(accountDao12);
    IAccountDao accountDao21 = applicationContext.getBean("accountDao2", IAccountDao.class);
    System.out.println(accountDao21);
    IAccountDao accountDao22 = applicationContext.getBean("accountDao2", IAccountDao.class);
    System.out.println(accountDao22);
    

    可以看到输出中,前两个accountDao是同一个对象,后两个是不同对象:

    验证@Scope注解

    4.用于改变对象生命周期的注解:相当于xml配置的bean标签中的init-method属性和destroy-method属性
    注解 作用 使用位置
    @PostConstruct 用于指定初始化方法 用在方法上
    @PreDestroy 用于指定销毁方法 用在方法上

    注意:多例对象的销毁仍然由JVM执行,无法通过关闭容器来销毁

    二、基于XML的IoC案例
    1.准备工作

    这个IoC案例,主要演示对数据库的crud操作,所以首先需要创建数据库,sql文件如下:

    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('aaa',1000);
    insert into account(name,money) values('bbb',1000);
    insert into account(name,money) values('ccc',1000);
    

    整个项目的结构如下:

    基于xml的IoC案例的项目结构

    IAccountDao和IAccountService这两个接口类中的方法如下,因为只是演示,简单起见这两个接口中的方法一模一样:

        /** 查询所有 */
        List<Account> findAllAccounts();
    
        /** 根据id查询账户 */
        Account findAccountById(Integer id);
    
        /** 保存账户 */
        void saveAccount(Account account);
    
        /** 修改账户*/
        void updateAccount(Account account);
    
        /** 根据id删除账户 */
        void deleteAccountById(Integer id);
    

    我们还需要一个实体类用于封装查询结果,简单起见,省略了get和set方法,以及toString方法:

    public class Account implements Serializable {
        private Integer id;
        private String name;
        private float money;
      //还有set方法和get方法,以及toString方法没有显示
    }
    
    2.编写接口实现类
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner queryRunner;
    
      	//由于需要通过配置文件对QueryRunner进行注入,所以需要提供set方法
        public void setQueryRunner(QueryRunner queryRunner) {
            this.queryRunner = queryRunner;
        }
    
        @Override
        public List<Account> findAllAccounts() {
            try {
                return queryRunner.query("select * from account",
                        new BeanListHandler<>(Account.class));
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findAccountById(Integer id) {
            try {
                return queryRunner.query("select * from account where id = ?",
                        new BeanHandler<>(Account.class), id);
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void saveAccount(Account account) {
            try {
                queryRunner.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 {
                queryRunner.update("update account set name=?, money=? where id = ?",
                        account.getName(), account.getMoney(), account.getId());
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void deleteAccountById(Integer id) {
            try {
                queryRunner.update("delete from account s where id = ?", id);
            }catch (Exception e){
                throw new RuntimeException(e);
            }
        }
    }
    
    public class AccountServiceImpl implements IAccountService {
        
        private IAccountDao accountDao;
    		//set方法用于注入
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public List<Account> findAllAccounts() {
            return accountDao.findAllAccounts();
        }
    
        @Override
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(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);
        }
    }
    
    3.编写配置文件

    前面的部分都不用太关心,只需要了解即可。QueryRunner是java编程中的数据库操作实用工具类DBUtils中的一个核心类,可以用于连接数据库执行SQL语句。beans.xml文件是Spring的配置文件,重点应该放在配置文件上。

    <?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.crud.service.impl.AccountServiceImpl">
            <!-- 注入dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置dao -->
        <bean id = "accountDao" class="com.crud.dao.impl.AccountDaoImpl">
            <!-- 注入queryRunner -->
            <property name="queryRunner" ref="queryRunner"></property>
        </bean>
    
        <!-- 配置queryRunner,必须是多例对象,否则会出现线程安全问题 -->
        <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.cj.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="12345678"></property>
        </bean>
    </beans>
    

    配置文件中需要注意的是,因为每一个连接都会用到QueryRunner对象,所以必须是多例模式,否则就会出现线程安全问题。此外,在配置数据源时,property中的value可以从其他文件中读取。该配置文件,基本上综合了之前讲的用xml进行依赖注入。

    例如:

    首先在src/main/resources目录下编写:jdbc.properties文件

    jdbc.driver = com.mysql.cj.jdbc.Driver
    jdbc.url = jdbc:mysql://localhost:3306/spring
    jdbc.user = root
    jdbc.password = 12345678
    

    然后对beans.xml文件进行修改:

    <context:property-placeholder location="classpath:jdbc.properties"/>
    
    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 配置连接数据库的必备信息 -->
        <property name="driverClass" value="${jdbc.driver}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="user" value="${jdbc.user}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    

    对于maven的配置文件pom.xml,主要需要导入的包有:Spring、dbutils(用于执行sql语句)、mysql、c3p0(用于数据源)、junit(用于单元测试)

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.5.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>8.0.20</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.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    
    3.单元测试

    编写AccountServiceTest测试类用于测试,只测试能否正常执行crud操作,这里只演示了测试查询所有方法:

    public class AccountServiceTest {
    
        @Test
        public void testFinaAll() {
            //1.获取容器
            ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml");
            //2.从容器中获取业务层对象
            IAccountService accountService = applicationContext.getBean("accountService", IAccountService.class);
            //3.执行方法
            List<Account> accounts = accountService.findAllAccounts();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
    }    
    
    三、基于注解和xml的IoC案例
    1.修改AccountServiceImpl和AccountDaoImpl类

    在上述案例中,采用的xml进行依赖注入。本案例中,使用注解的方式进行依赖注入,所以不再需要main方法。这两个类仅进行如下修改,其余部分不变:

    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
    
        @Autowired
        private QueryRunner queryRunner;
    }
    
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
    
        @Autowired
        private IAccountDao accountDao;
    }
    
    2.修改beans.xml配置文件

    和之前一样,要告知Spring要扫描的包,所以约束空间要加入context名称空间,此外之前service和dao的xml配置就不再需要了,但是和连接数据库相关的QueryRunner的配置仍然需要。

    <?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
            https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            https://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 配置Spring在创建容器时要扫描的包,在一个名为context名称空间的约束中进行配置-->
        <context:component-scan base-package="com.crud"></context:component-scan>
    
        <!-- 配置queryRunner,必须是多例对象,否则会出现线程安全问题 -->
        <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.cj.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="12345678"></property>
        </bean>
    </beans>
    
    3.测试执行

    由于多个单元测试都需要用到accountService对象,所以这里抽取出来作类对象,这样就只需要初始化一次。

    public class AccountServiceTest {
    
      	private ApplicationContext applicationContext;
        private IAccountService accountService;
    
        //手动获取accountService对象
        @Before
        public void init(){
            //1.获取容器
            applicationContext = new ClassPathXmlApplicationContext("beans.xml");
            //2.从容器中获取业务层对象
            accountService = applicationContext.getBean("accountService", IAccountService.class);
        }
    
        @Test
        public void testFinaAll() {
           //3.执行方法
            List<Account> accounts = accountService.findAllAccounts();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
    }
    
    四、基于纯注解的IoC案例
    1.配置类

    如果不写xml配置文件,我们如何去指定Spring的相关配置呢?这个时候就需要一个配置类来代替xml配置文件。在src目录下,新建com.crud.conf包,该包下新建配置类SpringConfig。依照前面注解结合xml配置文件的案例。我们需要解决的问题有:

    1.告知Spring容器要扫描的包,对应<context:component-scan>标签
    2.配置queryRunner
    3.配置数据源
    
    2.Spring新注解
    注解 作用 属性
    @Configuration 指定当前类是一个配置类。
    @Import 用于导入其他的配置类 value:用于指定其他配置类的字节码
    @ComponentScan/@ComponentScans 指定Spring在创建容器时要扫描的包 value/basePackages:用于指定包
    @Bean 把当前方法的返回值作为Bean对象存入IoC容器 name:用于指定bean的id,不写是默认值就是当前方法的名称
    @PropertySource 用于指定要使用的properties文件的位置 value:指定文件的名称和路径,classpath关键字表示类路径
    3.新注解的使用

    整个项目的结构如下:

    纯注解的IoC案例项目结构

    接下来我们一个一个详细理解这些注解的作用:

    1. @Configuration注解用于指定配置类,一般所有的配置类都需要加上这个注解。但是也有例外:如果不写@Configuration注解,那么会将Spring的IoC容器会将AnnotationConfigApplicationContext构造方法中的参数作为配置类;因此,作为参数传入的配置类的@Configuration注解可用不写。
    2. @Import注解用于导入其他配置类。在实际开发过程中,可能不止一个配置类。例如和Spring相关的配置作为一个住配置类,和连接数据库相关的配置类作为一个子类。当使用@Import注解之后,使用@Import注解的配置类就是主配置类,导入的配置类就是子配置类。此时,子类就不需要再写@Configuration注解。
    3. @ComponentScan/@ComponentScans注解用于指定Spring在创建容器时要扫描的包,前者用于指定一个包,后者用于指定多个包。这就相当于xml配置文件中的<context:component-scan>中的basePackages属性。
    4. @Bean注解用于把当前方法的返回值作为Bean对象存入IoC容器。当方法上有@Bean注解时,Spring会在容器中查找有没有和参数对应的可用bean对象,查找方式和@Autowired注解的方式相同。这就相当于xml配置文件中的Bean标签。
    5. @PropertySource注解用于指定要使用的properties文件的位置。

    最终这些注解在代码中的使用如下:

    @Configuration
    @ComponentScan("com.crud")
    @Import(JdbcConfig.class)
    /** 主配置类 */
    public class SpringConfig { }
    
    /** jdbc配置类 */
    @PropertySource("classpath:jdbcConfig.properties")
    public class JdbcConfig {
    
        @Value("${jdbc.driver}")
        private String driver;
        @Value("${jdbc.url}")
        private String url;
        @Value("${jdbc.user}")
        private String user;
        @Value("${jdbc.password}")
        private String password;
    
        /**  根据数据源创建QueryRunner对象,必须是多例的,否则会出现线程安全问题 */
        @Bean("queryRunner")
        @Scope("prototype")
        public QueryRunner createQueryRunner(DataSource dataSource){
            return new QueryRunner(dataSource);
        }
    
        /** 创建数据源对象 */
        @Bean("dataSource")
        public DataSource createDataSource(){
            try{
                ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
                comboPooledDataSource.setDriverClass(driver);
                comboPooledDataSource.setJdbcUrl(url);
                comboPooledDataSource.setUser(user);
                comboPooledDataSource.setPassword(password);
                return comboPooledDataSource;
            }catch (Exception e){
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    }
    
    4.测试

    有一点需要修改,就是在获取容器时,不能再使用ClassPathXmlApplicationContext类,而应该使用AnnotationConfigApplicationContext,如下:

    //参数是类字节码,可以传入多个类的字节码,作为参数的配置类可以不写@Configuration注解
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
    
    5.@Bean注解的查找方式

    @Bean创建Bean对象的方式后的查找方法和@Autowired注解相同,如果有多个名称匹配,就需要用到@Qualifier注解来指定用哪个匹配。例如,jdbcConfig类可以修改为如下:

    /**
     * 根据数据源创建QueryRunner对象,必须是多例的,否则会出现线程安全问题
     * @Qualifier注解用于解决多个类型匹配时的问题
     * @param dataSource
     * @return
     */
    @Bean("queryRunner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(@Qualifier("ds2") DataSource dataSource){
        return new QueryRunner(dataSource);
    }
    
    /**
     * 创建数据源对象
     * @return
     */
    @Bean("ds1")
    public DataSource createDataSource(){
        try{
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl(url);
            comboPooledDataSource.setUser(user);
            comboPooledDataSource.setPassword(password);
            return comboPooledDataSource;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
    @Bean("ds2")
    public DataSource createDataSource1(){
        try{
            ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
            comboPooledDataSource.setDriverClass(driver);
            comboPooledDataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
            comboPooledDataSource.setUser(user);
            comboPooledDataSource.setPassword(password);
            return comboPooledDataSource;
        }catch (Exception e){
            e.printStackTrace();
            throw new RuntimeException(e);
        }
    }
    
    五、Spring整合Junit

    在测试类中,获取Spring的IoC容器和AccountService对象时,我们一直都是手动获取。有没有办法让Spring为我们自动注入呢?当然是有的,但是如果直接没有在accountService成员变量之上,加上@Autowired注解,运行就会报错。这是因为:

    1.Junit单元测试中,没有main方法也能执行。因为Junit集成了一个main方法,该方法会判断当前测试类中哪些方法有@Test注解,Junit就会执行有@Test注解的方法。
    2.Junit不会管用户使用何种框架,因此在执行测试方法是,junit不知道用户是否使用Spring框架,所以也就不会读取配置文件/配置来创建Spring的IoC核心容器。因此在测试类中,就算写了@Autowired注解,也无法实现注入。
    

    因此要在Junit中使用Spring,就要遵循一下步骤:

    1.导入Spring整合junit的配置

    在pom.xml配置文件中<dependencies>的导入:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.5.RELEASE</version>
    </dependency>
    
    2.使用Junit提供的@RunWith注解,将原有的main方法替换为Spring提供的main方法

    在AccountServiceTest类之上加上@RunWith(SpringJUnit4ClassRunner.class)注解

    3.告知Spring的运行器,是基于xml还是注解进行配置,并说明位置

    在AccountServiceTest类之上加上@ContextConfiguration(classes = SpringConfig.class)注解。对于@ContextConfiguration注解,属性locations用于指定xml配置文件的位置,要加上classpath关键字表示在类路径下,属性classes:指定配置类所在的位置。

    4.注意事项

    当Spring的版本是5.x时,junit的版本必须是4.12及以上版本。最终整个测试类代码如下:

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfig.class)
    public class AccountServiceTest {
    
        @Autowired
        private IAccountService accountService;
        
        @Test
        public void testFinaAll() {
           //3.执行方法
            List<Account> accounts = accountService.findAllAccounts();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
    }
    
  • 相关阅读:
    SEO分享:我为什么会有这么多的优质外链资源?
    执行shell脚本提示“syntax error near unexpected token for((i=0;i&lt;$length;i++))”
    Codeforces Round #254 (Div. 2)D(预计)
    自己写配置文件
    软件測试基本方法(二)之白盒測试
    hdu 4638 Group
    影视集结号--首页
    2015阿里巴巴秋招在线笔试题
    php 抓取天气情况 www.weather.com.cn
    C语言中的enum(枚举)使用方法
  • 原文地址:https://www.cnblogs.com/liyier/p/13405515.html
Copyright © 2020-2023  润新知