20201124 王维

学习总结

1 使用 spring 的 IoC 的实现账户的CRUD

使用 spring 的 IoC 实现对象的管理

使用 DBUtils 作为持久层解决方案

使用 c3p0 数据源

2 基于注解的 IOC 配置

2.1 环境搭建

2.2.1 添加 jar 包

注意:在基于注解的配置中,我们还要多一个 aop 的 jar 包

<!-- mysql驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
    </dependency>

    <!-- spring -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>

    <!-- 数据源 -->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.2.1</version>
    </dependency>

    <!-- 数据库工具类 -->
    <dependency>
      <groupId>commons-dbutils</groupId>
      <artifactId>commons-dbutils</artifactId>
      <version>1.6</version>
    </dependency>

    <!-- 日志 -->
    <dependency>
      <groupId>log4j</groupId>
      <artifactId>log4j</artifactId>
      <version>1.2.12</version>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <version>1.18.10</version>
    </dependency>

    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

2.1.2 使用@Component 注解配置管理的资源

package com.itlaobing.spring.service.impl;

import com.itlaobing.spring.dao.IAccountDao;
import com.itlaobing.spring.model.Account;
import com.itlaobing.spring.service.IAccountService;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;

@Service("service")
@Scope("prototype")
public class AccountServiceImpl implements IAccountService {

//    @Autowired
//    @Qualifier("accountDaoImpl")
    @Resource
    private IAccountDao accountDao;

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

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

    @Override
    public void deleteAccount(Integer accountId) {
        accountDao.delete(accountId);
    }

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findById(accountId);
    }

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

package com.itlaobing.spring.dao.impl;

import com.itlaobing.spring.dao.IAccountDao;
import com.itlaobing.spring.model.Account;
import lombok.Data;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;

import java.sql.SQLException;
import java.util.List;

@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {

    @Autowired
    private QueryRunner runner;

    @Override
    public void save(Account account) {
        String sql = "insert into tb_account(name , money) values( ? , ? )";
        try {
            runner.update(sql , account.getName() , account.getMoney());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void update(Account account){
        String sql = "update tb_account set name = ? , money = ?  where id = ?";
        try {
            runner.update(sql , account.getName() , account.getMoney() , account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public void delete(Integer accountId) {
        String sql = "delete from tb_account where id = ?";
        try {
            runner.update(sql , accountId);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    @Override
    public Account findById(Integer accountId) {
        String sql = "select * from tb_account where id = ?";
        try {
            return runner.query(sql , new BeanHandler<Account>(Account.class) , accountId);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    @Override
    public List<Account> findAll() {
        String sql = "select * from tb_account";
        try {
            return runner.query(sql , new BeanListHandler<Account>(Account.class) );
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }
}

注意:当我们使用注解注入时,set 方法可以不用写,使用注解导入

2.1.3 创建 spring 的 xml 配置文件并开启对注解的支持

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://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.itlaobing.spring" />

<!--    &lt;!&ndash; 配置service &ndash;&gt;-->
<!--    <bean id="accountService" class="com.itlaobing.spring.service.impl.AccountServiceImpl">-->
<!--        <property name="accountDao" ref="accountDao" />-->
<!--    </bean>-->

<!--    &lt;!&ndash; 配置Dao &ndash;&gt;-->
<!--    <bean id="accountDao" class="com.itlaobing.spring.dao.impl.AccountDaoImpl">-->
<!--        <property name="runner" ref="runner" />-->
<!--    </bean>-->

    <!-- 配置runner 此处我们只注入数据源,多例模式-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype" >
        <constructor-arg index="0" ref="dataSource" />
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" >
        <property name="driverClass" value="com.mysql.jdbc.Driver" />
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf8" />
        <property name="user" value="root" />
        <property name="password" value="root" />
    </bean>

</beans>

context:component-scan: 告知spring在初始化容器时扫描包 base-package:要扫描的包名,多个包之间使用逗号隔开

2.1.4 测试

package com.itlaobing.spring.service.impl;

import com.itlaobing.spring.config.SpringConfig;
import com.itlaobing.spring.model.Account;
import com.itlaobing.spring.service.IAccountService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

public class AccountServiceImplTest {

    private ApplicationContext context;

    private IAccountService service;

    @Before
    public void setUp() throws Exception {

        context = new ClassPathXmlApplicationContext("spring.xml");
        service = context.getBean("accountServiceImpl" , IAccountService.class);
    }

    @Test
    public void saveAccount() {
        Account account = new Account();
        account.setName("www");
        account.setMoney(2000.5f);
        service.saveAccount(account);
    }

    @Test
    public void updateAccount() {
        Account account = service.findAccountById(1);
        account.setMoney(2020f);
        service.updateAccount(account);
    }

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

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

    @Test
    public void findAllAccount() {
        List<Account> list = service.findAllAccount();
        if (list == null){
            System.out.println("空");
            return;
        }
        for (Account account : list){
            System.out.println(account);
        }
    }
}

2.2 常用注解

2.2.1 创建对象

相当于: <bean id="" class="">

@Component

作用:把资源让 spring 来管理。相当于在 xml 中配置一个 bean。 属性: value:指定 bean 的 id。如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小 写。

语义化衍生:@Controller @Service @Repository

它们三个注解都是针对一个 @Component 的衍生注解,他们的作用及属性都是一模一样的。

他们只不过是提供了更加明确的语义化。

  • @Controller:一般用于表现层的注解。
  • @Service:一般用于业务层的注解。
  • @Repository:一般用于持久层的注解。

2.2.2 注入数据

相当于:<property name="" ref="" />

@Autowired

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

@Qualifier

作用: 在自动按照类型注入的基础之上,再按照 Bean 的 id 注入。它在给字段注入时不能独立使用,必须和 @Autowired 一起使用,@Autowired 多个类型时不再使用要注入的对象变量名称作为 bean 的 id,只使用@Qualifier指定的id;但是给方法参数注入时,可以独立使用。

属性: value:指定 bean 的 id。

@Resource

作用: 默认按 byName自动注入。@Resource有两个属性是比较重要的,分是name和type,Spring将 @Resource注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以如果使用 name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果既不指定name也不指定type属性,这时将通过反射机制使用byName要注入的对象变量名称自动注入策略。

属性: name:指定 bean 的 id。type:指定bean的类型

装配顺序

  1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛 出异常
  2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
  3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
  4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则按照byType方式进行匹配,如果没有匹配则回退为一个原始类型进行匹配,如果匹配则自动装配
@Value

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

属性: value:用于指定值

@Autowired和@Resource的区别

@Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。

@Autowired默认按类型装配(这个注解是属于spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果 我们想使用名称装配可以结合@Qualifier注解进行使用

@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指 定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在 setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注 意的是,如果name属性一旦指定,就只会按照名称进行装配。

推荐使用:@Resource注解在字段上,这样就不用写setter方法了,并且这个注解是属于J2EE的,减少了与spring的耦合。这样代码看起就比较优雅。

2.2.3 改变作用范围

相当于: <bean id="" scope="">

@Scope

作用: 指定 bean 的作用范围。

属性: value:指定范围的值。

取值:singleton prototype request session globalsession

2.2.4 生命周期相关(了解)

相当于: <bean id="" class="" init-method="" destroy-method="">

@PostConstruct

作用: 用于指定初始化方法

@PreDestroy

作用: 用于指定销毁方法

2.3 关于 Spring 注解和 XML 的选择问题

注解的优势: 配置简单,维护方便(我们找到类,就相当于找到了对应的配置)。

XML 的优势: 修改时,不用改源码。不涉及重新编译和部署。

Spring 管理 Bean 方式的比较:

基于xml配置 基于注解配置
bean定义 <bean id="" class=""> @Component语义化衍生:@Controller @Service @Repository
bean名称 通过id或name指定 @Component("id")如果不指定 value 属性,默认 bean 的 id 是当前类的类名。首字母小 写。
bean注入 <property name="" ref="" />或者p命名空间 @Autowired按类型注入@Qualifier按名称注入
生命周期 <bean id="" class="" init-method="" destroy-method=""> @PostConstruct @PreDestroy
适合场景 bean的实现类来自第三方 bean的实现类由用户自己开发

2.4 spring 的纯注解配置

package com.itlaobing.spring.config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;

@Configuration
@ComponentScan("com.itlaobing.spring")
@PropertySource("classpath:db.properties")
public class SpringConfig {

    @Value("${driver}")
    private String driver;

    @Value("${url}")
    private String url;

    @Value("${user}")
    private String username;

    @Value("${pass}")
    private String password;

    @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 (PropertyVetoException e) {
            e.printStackTrace();
        }
        return null;
    }

    @Bean(name = "runner")
    public QueryRunner createDBAssit(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

}

@Configuration

作用: 用于指定当前类是一个 spring 配置类,当创建容器时会从该类上加载注解。获取容器时需要使用 AnnotationConfigApplicationContext(有@Configuration 注解的类.class)。

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

@ComponentScan

作用: 用于指定 spring 在初始化容器时要扫描的包。作用和在 spring 的 xml 配置文件中的: context:component-scan 是一样的

属性: basePackages:用于指定要扫描的包。和该注解中的 value 属性作用一样。

@Bean

作用: 该注解只能写在方法上,表明使用此方法创建一个对象,并且放入 spring 容器。

属性: name:给当前@Bean 注解方法创建的对象指定一个名称(即 bean 的 id)。

@PropertySource

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

属性: value[]:用于指定 properties 文件位置。如果是在类路径下,需要写上 classpath:

@Import

作用: 用于导入其他配置类,在引入其他配置类时,可以不用再写@Configuration 注解。当然,写上也没问 题。

属性: value[]:用于指定其他配置类的字节码。

3 Spring 整合 Junit[掌握]

3.1 测试类中的问题和解决思路

问题:在测试类中,每个测试方法都要获取容器和获取实例

ApplicationContext contextnew = AnnotationConfigApplicationContext(SpringConfig.class);
IAccountService service = context.getBean("accountServiceImpl" , IAccountService.class);

解决思路分析:

针对上述问题,我们需要的是程序能自动帮我们创建容器。一旦程序能自动为我们创建 spring 容器, 我们就无须手动创建了,问题也就解决了。

我们都知道,junit 单元测试的原理,但显然,junit 是无法实现的,因为它自己都无法知晓我们是否使 用了 spring 框架,更不用说帮我们创建 spring 容器了。不过好在,junit 给我们提供了一个注解,可以 让我们替换掉它的运行器。 这时,我们需要依靠 spring 框架,因为它提供了一个运行器,可以读取配置文件(或注解)来创建容 器。我们只需要告诉它配置文件在哪就行了。

3.2 配置步骤

第一步:导入jar包

注意,JUnit包的版本要是4.12及以上才可以

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.2.RELEASE</version>
    <scope>test</scope>
</dependency>

第二步:使用@RunWith 注解替换原有运行器

第三步:使用@ContextConfiguration 指定 spring 配置文件 的位置

@ContextConfiguration

作用:加载spring配置类

属性:locations:用于指定配置文件的位置。如果是类路径下,需要用

classpath:表明 classes 属性:用于指定注解的类。当不使用 xml 配置时,需要用此属性指定注解类的位置。

第四步:使用@Autowired 给测试类中的变量注入数据

package com.itlaobing.spring.service.impl;

import com.itlaobing.spring.config.SpringConfig;
import com.itlaobing.spring.model.Account;
import com.itlaobing.spring.service.IAccountService;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.List;

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

    @Autowired
    private ApplicationContext context;

    @Autowired
    private IAccountService service;

//    @Before
//    public void setUp() throws Exception {
//
////        context = new ClassPathXmlApplicationContext("spring.xml");
//        context = new AnnotationConfigApplicationContext(SpringConfig.class);
//        service = context.getBean("accountServiceImpl" , IAccountService.class);
//    }

    @Test
    public void saveAccount() {
        Account account = new Account();
        account.setName("www");
        account.setMoney(2000.5f);
        service.saveAccount(account);
    }

    @Test
    public void updateAccount() {
        Account account = service.findAccountById(1);
        account.setMoney(2020f);
        service.updateAccount(account);
    }

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

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

    @Test
    public void findAllAccount() {
        List<Account> list = service.findAllAccount();
        if (list == null){
            System.out.println("空");
            return;
        }
        for (Account account : list){
            System.out.println(account);
        }
    }
}

心得体会

今天将spring的基础操作基本学完了,对他的实际开发中的使用也有了一定的了解,使用起来还是比较的简单的,不过需要记忆的注解确实有点多,还是需要多熟悉。

标签

评论

© 2021 成都云创动力科技有限公司 蜀ICP备20006351号-1