200 Success
200 Success 博客
200 Success
常用标签
组员

一、写在前面

主要有三种模式:纯XML,不完全注解,纯注解。

二、案例主体程序

  • Dao层(实现基本的增删改查)
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner queryRunner;

    @Override
    public List<Account> findAllAccounts() {
        try {
            return queryRunner.query("select * from account",
                                     new BeanListHandler<Account>(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>(Account.class),id);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public int addAccount(Account account) {
        try {
            return queryRunner.update("insert into account(name,money) values(?,?)",
                                      account.getName(),account.getMoney());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public int deleteAccount(Integer id) {
        try {
            return queryRunner.update("delete from account where id = ?",id);
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Override
    public int updateAccount(Account account) {
        try {
            return queryRunner.update("update account set name = ? , 
    money = ? where id =?",account.getName(),account.getMoney(),account.getId());
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}
  • Service层
public class AccountServiceImpl implements IAccoutService {

    private IAccountDao accountDao;

    @Override
    public List<Account> findAllAccounts() {
        return accountDao.findAllAccounts();
    }

    @Override
    public Account findAccountByID(Integer id) {
        return accountDao.findAccountByID(id);
    }

    @Override
    public int addAccount(Account account) {
        int flag = 0;
        Account res = accountDao.findAccountByID(account.getId());
        if (res == null){
            flag = accountDao.addAccount(account);
        }
        return flag;
    }

    @Override
    public int deleteAccount(Integer id) {
        int flag = 0;
        Account res = accountDao.findAccountByID(id);
        if(res != null){
            flag = accountDao.deleteAccount(id);
        }
        return flag;
    }

    @Override
    public int updateAccount(Account account) {
        int flag = 0;
        Account res = accountDao.findAccountByID(account.getId());
        if(res != null){
            flag = accountDao.updateAccount(account);
        }
        return flag;
    }
}
  • Junit单元测试类
public class AccountServiceTest {

    @Test
    public void testFindAll() {
        //1.获取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取业务层对象
        IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
        //3.执行方法
        List<Account> accounts = accountService.findAllAccounts();
        //4.遍历
        for (Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        //1.获取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取业务层对象
        IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
        //3.执行方法
        Account account = accountService.findAccountByID(1);
        System.out.println(account);
    }

    @Test
    public void testAdd() {
        Account account = new Account();
        account.setMoney(123456f);
        account.setName("test");
        //1.获取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取业务层对象
        IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
        //3.执行方法
        int res = accountService.addAccount(account);
        System.out.println(res);
    }

    @Test
    public void testUpdate() {
        Account account = new Account();
        account.setId(4);
        account.setMoney(1000f);
        account.setName("ddd");
        //1.获取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取业务层对象
        IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
        //3.执行方法
        int res = accountService.updateAccount(account);
        System.out.println(res);
    }

    @Test
    public void testDelete() {
        //1.获取容器
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取业务层对象
        IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);
        //3.执行方法
        int res = accountService.deleteAccount(5);
        System.out.println(res);
    }
}

三、IoC和DI实战

3.1 纯XML模式

在纯XML模式中,通过配置<bean>标签让Spring框架用反射的方式创建对象,并存入IoC容器中。而类中的成员变量通过构造方法或者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">

    <!--配置accountService-->
    <bean id="accountService" class="org.leslie.service.impl.AccountServiceImpl">
        <!--注入accountDao set方法注入-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置accountDao-->
    <bean id="accountDao" class="org.leslie.dao.impl.AccountDaoImpl">
        <!--注入queryRunner set方法注入-->
        <property name="queryRunner" ref="queryRunner"></property>
    </bean>

    <!--配置queryRunner--> <!--如果是单例,当面对多个DAO同时使用有可能产生的线程安全-->
    <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">
        <!--连接数据库的必备信息 set方法注入-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ioc?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456789"></property>
    </bean>
</beans>

在这里,我们导入了DBUtils来简化JDBC操作,DBUtils封装了对JDBC的操作。

QueryRunner核心类中的QueryRunner类底层自动维护数据库连接Connection,因此为了防止多个Dao同时使用QueryRunner对象产生的线程安全问题,我们需要把他设置为多例对象。即在<bean>标签中加入属性scope="prototype"。

3.2 不完全注解

在不完全注解模式中,往往是注解与XML配置共存。对于自己写的类,可以使用注解模式来交给IoC管理,类中的属性也可以被IoC容器通过注解模式来注入。但是对于外部Jar包中的类,我们需要通过XML来配置,因为我们无法直接操作这些类的源码。

注意,注解模式(有注解就要)需要改变xml配置文件的相关约束

  • 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">

    <!--告诉要扫描的包-->
    <context:component-scan base-package="org.leslie" ></context:component-scan>

    <!--配置queryRunner--> <!--如果是单例,当面对多个DAO同时使用有可能产生的线程安全-->
    <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">
        <!--连接数据库的必备信息 set方法注入-->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ioc?useUnicode=true&amp;characterEncoding=utf8&amp;useSSL=false&amp;serverTimezone=GMT%2B8"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456789"></property>
    </bean>
</beans>
  • Dao层
@Repository("accountDao")      //使用@Component的别名
public class AccountDaoImpl implements IAccountDao {

    @Autowired        
    //先在IoC容器中找同类型的,即QueryRunner。如果没有,报错;如果有一个,没问题;
    //如果有多个,则根据变量名匹配;如果变量名也匹配不到,报错,此时需要配合@Qualifier("bean的id")使用。
    private QueryRunner queryRunner;

    @Override
    public List<Account> findAllAccounts() { //同上,不再赘述 }

    @Override
    public Account findAccountByID(Integer id) { //同上,不再赘述 }

    @Override
    public int addAccount(Account account) { //同上,不再赘述 }

    @Override
    public int deleteAccount(Integer id) { //同上,不再赘述 }

    @Override
    public int updateAccount(Account account) { //同上,不再赘述 }
}
  • Service层
//Service层和Dao层一样的道理,不再赘述
@Service("accountService") //value = "accountService"
public class AccountServiceImpl implements IAccoutService {

    @Autowired
    private IAccountDao accountDao;

    @Override
    public List<Account> findAllAccounts() { //同上,不再赘述 }

    @Override
    public Account findAccountByID(Integer id) { //同上,不再赘述 }

    @Override
    public int addAccount(Account account) { //同上,不再赘述 }

    @Override
    public int deleteAccount(Integer id) { //同上,不再赘述 }

    @Override
    public int updateAccount(Account account) { //同上,不再赘述 }
}

3.3 纯注解

何谓纯注解,意思就是没有XML配置。我们要完全拿掉XML配置就要通过其他方法来实现和XML配置一样的功能。但是在我看来,纯注解并不是一件很方便的事,他并没有简化配置,反而更麻烦了。

首先来看几个注解。

@Configuration

作用:指定当前类是一个配置类
细节:当配置类作为在获取容器时,AnnotationConfigApplicationContext的参数时候可以省略
也就说,要么注解,要么把这个类的字节码当参数传入进去。

当我们指定完配置类后,这时候这个类就和XML文件是一样的。接下来完成的就是在类中写内容,使其和XML中的内容完全等价。

先来替换掉扫描包的标签。

<context:component-scan base-package="org.leslie" ></context:component-scan>

@ComponentScan

作用:用户通过注解指定spring在创建容器的时候要扫描的包
属性:value:和basePackages的作用是一样的,都是指定创建容器要扫描的包

接下来,要替换的就是外部Jar包中的类的对象的创建。

<bean id="xxx" class="xxx" scope="xxx"></bean>

@Bean

作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器
属性:name/value:用于指定bean的id。 默认值是当前方法的名称
细节:
1.当我们使用注解配置方法时,如果方法有参数,spring回去容器中找,看看有没有可用的
2.查找的方式和Autowired注解的作用是一样的,
3.先找类型一样的,如果没有则报错
4.如果类型一样的有多个,就看变量名

这时候彻底换完了,但是还有不足。我希望不是所有配置都集中在一起,而是可以分模块,按照不同的功能分开。

这时候就产生了主配置类,和子配置类的概念。

@Import

作用:用于导入其他的配置类
属性:value:其他配置类的字节码
用了@import后 有Import注解的是主配置类, 导入的都是子配置类

这时,完成了模块的分离,但是在JDBC配置类中,连接数据库的数据源被写死了,我希望他能读取prop配置文件,这样修改会更方便,不需要翻找源码。

@PropertySource

作用:用于指定properties文件的位置,放在子类和主类都可以,但是一般放主类
属性:value:指定文件的名称和路径,关键字:classpath:表示类路径下

好了,终于完成了,来看一下最终代码。

  • 主配置类
//@Configuration  当配置类作为在获取容器时,AnnotationConfigApplicationContext的参数时候可以省略
@ComponentScan({"org.leslie","config"})
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
public class SpringConfig {
  • JDBC子配置类
//@Configuration    当主配置类使用@Import注解后  这个类就是子配置类了  不在是平级的关系 也不需要配置@Configuration
public class JdbcConfig {

    @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对象
     * @param dataSource
     * @return
     */
    @Bean(name = "queryRunner")
    @Scope("prototype")         
    //如果有ioc容器有多个DataSource对象时,并且不能通过变量名和Id对应匹配,需要用@Qualifier匹配
    public QueryRunner createQueryRunner(@Qualifier("dataSource")DataSource dataSource){
        return new QueryRunner(dataSource);
    }

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

四、Spring和Junit整合

回顾最开始的测试类代码,会发现有两行代码一直在重复。

//1.获取容器
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
//2.获取业务层对象
IAccoutService accountService = applicationContext.getBean("accountService", AccountServiceImpl.class);

而且当我们这样写的时候,测试工程师不一定会Spring,因此这些不应该在测试方法中出现。

然后可能会这样想,我把他们抽出来,封装成一个方法,然后在测试类中调用可以吗?

@Before
public void init(){
  //1.获取容器
  applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
  //2.获取业务层对象
  accountService = applicationContext.getBean("accountService",AccountServiceImpl.class);
}

软件测试工程师不应该关心这一段,只需要关注test方法有没有通过。因此,我们这样写也行不通。可不可以开发者通过注解的方式直接注入给accoutService对象直接注入呢,这样测试工程师就可以直接写测试方法了。

首先,我们需要了解,

  • 应用程序的入口是main方法。
  • 而Junit单元测试中,没有main方法也能执行。因为junit集成了一个main方法,该方法会判断哪些方法有@Test注解, junit就会让哪些方法执行。
  • Junit不会管是否采用Spring框架,也就是说执行测试方法的时候,Junit不知道是不是使用了Spring框架,所以也就不会为我们读取xml配置文件或者配置类创建Spring核心容器。
  • 由上可知,当测试方法执行时,没有ioc容器,就算写了Autowired注解也无法注入。

因此我们需要让Spring给我们创建Spring的IoC容器,具体步骤如下:

  • 导入spring整合junit的jar (坐标)
  • 使用Junit提供的一个注解把原有的main方法替换,换成spring提供的。用@RunWith(SpringJUnit4ClassRunner.class)
  • 告知spring的运行器,spring的ioc创建是基于xml还是注解,且说明位置

@ContextConfiguration

属性1:locations:指定xml文件的位置,加上classpath:,表示在类路径下
属性2:classes:指定注解类所在的位置
注意:使用spring5以上,Junit需要>=4.12

到此,就完成了Spring和Junit的整合,最终结果后如下。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class AccountServiceTest {

    @Autowired
    private IAccoutService accountService;

    @Test
    public void testFindAll() { //同上,不再赘述 }

    @Test
    public void testFindOne() { //同上,不再赘述 }
        
    @Test
    public void testAdd() { //同上,不再赘述 }

    @Test
    public void testUpdate() { //同上,不再赘述 }

    @Test
    public void testDelete() { //同上,不再赘述 }
}
Java
访问: 106 次
    Hokori
    February 14th, 2020 at 03:14 pm

    icon_cry.png 这都是啥啊 大佬学的东西好高深

添加新评论

icon_biggrin.pngicon_neutral.pngicon_twisted.pngicon_arrow.pngicon_eek.pngicon_smile.pngicon_sad.pngicon_cool.pngicon_evil.pngicon_mrgreen.pngicon_exclaim.pngicon_surprised.pngicon_razz.pngicon_rolleyes.pngicon_wink.pngicon_cry.pngicon_confused.pngicon_lol.pngicon_mad.pngicon_question.pngicon_idea.pngicon_redface.png