spring 框架学习

spring 框架学习安排

1. 第二天(学习前最好能复习一下注解)

1. spring中IOC的常用注解

曾经的xml配置

<bean id="accountService" class="com.liuzeyu.service.impl.IAccountServiceImpl
init-method="" destory-method="" scope=""/>
<property name=""  value="" | ref="">
</property>

</bean>

上述配置可以用于创建对象,用于注入数据,用于改变作用范围和生命周期
<mark>换做注解实现:</mark>

  1. 用于创建对象

    1. dao层实现类:
      @Component("accountDao")
      public class AccountDaoImpl implements AccountDao{
      
          public void save() {
              System.out.println("保存了数据库");
          }
      }
      
    2. service实现类:
      @Component("accountService")
      public class IAccountServiceImpl implements IAccountService{
      
          public void save() {
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              AccountDao adao = (AccountDao) ac.getBean("accountDao");
              adao.save();
          }
      }
      
    3. 测试函数
      public class UITest {
          public static void main(String[] args) {
              //1.获取核心容器对象
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.根据id获取bean对象
              //2.1方式1获取
              IAccountService aservice = (IAccountService) ac.getBean("accountService");
              aservice.save();
          }
      }	
      

    其中@Component

    注解的作用是把当前类存入spring的IOC容器中
    属性:value用于指定bean的id
    最后需要再bean.xml配置

    <?xml version="1.0" encoding="UTF-8"?>
    <!--</beans>-->
    <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="com.liuzeyu"></context:component-scan>
    
    </beans>
    
    <context:component-scan base-package="com.liuzeyu"></context:component-scan>
    

    作用怎是告知spring创建容器时要扫描的包,配置所需要的标签不是再bean约束里面,而是一个名称为context名称空间和约束里面。
    <mark>补充三个注解:</mark>
    1)Controller:一般用于表现层
    2)Service:一般用于业务逻辑层
    3)Repository:一般用于持久层
    以上三个注解的属性和作用和@Component相同,它们三个是spring框架为我们提供三层架构使用的注解,使我们三次架构更加清晰。

  2. 用于注入数据

    1. 在1的基础上,修改service实现类获取AccountDao 对象的方法,使用注入数据的方式
      @Service("accountService")
      public class IAccountServiceImpl implements IAccountService{
      
          @Autowired
          private AccountDao adao = null;
          public void save() {
              adao.save();
          }
      }
      

    分析:@Autowired会干一件事,就是用AccountDao 去spring的IOC容器中寻找value值为AccountDao的实现类(不会去寻找key),如果找到,则反射生成实现类实现的接口对象,即AccountDao 对象。

    1. 如果此时遇到有多个AccountDao的实现类,那么Autowired第一次寻找就找到了两个,此时如何区分呢?该使用哪一个接口对象呢?退而求其次,Autowired会再寻找注入的变量名称和spring的IOC容器中bean对象的被Component标记的vlaue值,也就是bean对象的id值是否相等,如果不等则报错,如果相等创建其对象。
    2. 为什么解决上面出现的两个dao层实现类实现同一接口使用,@Qualifier指定特定的key值

      缺陷就是必须配置@Autowired使用,如何解决这一个捆绑的烦恼呢?
    3. 为了解决上述捆绑的烦恼,可以使用为Resource的name属性配置
  3. 用于改变作用范围和生命周期(了解)

    1. 单例和多例的配置

    2. 生命周期的问题
      public class UITest {
          public static void main(String[] args) {
              //1.获取核心容器对象
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.根据id获取bean对象
              //2.1方式1获取
              IAccountService aservice = (IAccountService) ac.getBean("accountService");
              aservice.save();
              ((ClassPathXmlApplicationContext) ac).close();
          }
      }
      

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

  1. 准备dao层接口和实现类

    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除
         * @param acccountId
         */
        void deleteAccount(Integer acccountId);
    }
    
    /**
     * 账户的持久层实现类
     */
    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);
            }
        }
    }
    
    
  2. 准备service接口和实现类

    package com.liuzeyu.service;
    
    import com.liuzeyu.domain.Account;
    
    import java.util.List;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @return
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 保存
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除
         * @param acccountId
         */
        void deleteAccount(Integer acccountId);
    
    
    }
    
    
    /**
     * 账户的业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService{
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
    
        public Account findAccountById(Integer accountId) {
            return accountDao.findAccountById(accountId);
        }
    
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
    
        public void deleteAccount(Integer acccountId) {
            accountDao.deleteAccount(acccountId);
        }
    }
    
    
  3. 准备实体类

    package com.liuzeyu.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    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 +
                    '}';
        }
    }
    
    
  4. 准备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.liuzeyu.service.impl.AccountServiceImpl">
            <!-- 注入dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置Dao对象-->
        <bean id="accountDao" class="com.liuzeyu.dao.impl.AccountDaoImpl">
            <!-- 注入QueryRunner -->
            <property name="runner" ref="runner"></property>
        </bean>
    
        <!--配置QueryRunner-->
        <bean id="runner" 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.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy"></property>
            <property name="user" value="root"></property>
            <property name="password" value="809080"></property>
        </bean>
    </beans>
    
  5. 准备测试函数

    /**
     * Created by liuzeyu on 2020/4/20.
     */
    public class AnnotationTest {
    
        @Test
        public void testFindAll(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
    
        @Test
        public void testFindById(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = ias.findAccountById(1);
            System.out.println(account);
        }
    
    
        @Test
        public void testInsert(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = new Account();
            account.setName("liuzeyu");
            account.setMoney(50000f);
            ias.saveAccount(account);
            testFindAll();  //测试
        }
    
        @Test
        public void testUpdate(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            Account account = ias.findAccountById(4);
            account.setName("wahaha");
            account.setMoney(88888f);
            ias.updateAccount(account);
            testFindAll();  //测试
        }
    
        @Test
        public void testDelete(){
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountService ias = ac.getBean("accountService", IAccountService.class);
            ias.deleteAccount(4);
            testFindAll();  //测试
        }
    }
    
    
    1. 依赖pom.xml
     <packaging>jar</packaging>
        <dependencies>
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-context</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
    
            <dependency>
                <groupId>junit</groupId>
                <artifactId>junit</artifactId>
                <version>4.12</version>
            </dependency>
    
            <dependency>
                <groupId>org.springframework</groupId>
                <artifactId>spring-test</artifactId>
                <version>5.0.2.RELEASE</version>
            </dependency>
            <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>
    
            <dependency>
                <groupId>c3p0</groupId>
                <artifactId>c3p0</artifactId>
                <version>0.9.1.2</version>
            </dependency>
    
        </dependencies>
    

3. 改造基于xml的IOC案例,使用注解和注入的方式实现

  1. 在2的基础上修改使用注解的方式:
    1. 修改bean,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="com.liuzeyu"></context:component-scan>
      
          <!--配置QureyRunner-->
          <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
              <constructor-arg name="ds" ref="dataSource"/>
          </bean>
      
          <!-- 配置数据源 -->
          <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/eesy"></property>
              <property name="user" value="root"></property>
              <property name="password" value="809080"></property>
          </bean>
      </beans>
      
    2. 持久层

    3. 业务层

  2. 在上面1的基础上继续优化,bean.xml使用注解替换
    1. bean.xml哪些东西需要使用注解替换掉
    2. 如上图,需要使用注解替换的地方有1,2,3处
      1. 1处的替换使用一个配置类
        /**
         * Created by liuzeyu on 2020/4/21.
         * spring的配置文件类。使用注解方式用于替换bean.xml的配置信息
         * spring用到的注解:
         *      1.@Configuration用于指定当前类是一个注解类
         *      2.@ComponentScan("com.liuzeyu")指定spring创建IOC容器是需要扫描的包
         */
        	@Configuration
        	@ComponentScan("com.liuzeyu")
        	public class SpringConfig {
        
        	}
        
        
      2. 2处是根据构造函数来注入数据,3处是根据setXXX来注入数据所以我们也可以自己定义构造出函数来注入数据

        @Bean注解:
        作用:把当前方法的返回值作为bean对象存入到spring的IOC容器中。
        属性:name用于指定bean的id,如果不写默认为当前方法的名称
        <mark>细节:</mark>
        当我们使用注解配置方法是,如果方法有参数,spring框架会去容器中寻找有没有可用的bean对象,查找的方式和Autowired的方式一样,自动按照类型注入,如果有唯一匹配,则注入,否则报错。如果出现多个类型匹配,在进而匹配它的变量名称。
      3. 可以将QueryRunner改为多例
        /**
         * Created by liuzeyu on 2020/4/21.
         * 测试QueryRunner是单例还是多例
         */
        public class QueryRunnerTest {
        
            @Test
            public void testQueryRunner(){
                ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
                QueryRunner runner1 = ac.getBean("runner", QueryRunner.class);
                QueryRunner runner2 = ac.getBean("runner", QueryRunner.class);
                System.out.println(runner1 == runner2); //true :单例
            }
        }
        
        只需要在QueryRunner注入处添加注解@Scope(“prototype”)即可
      4. @Import注解的使用
        1. 发现一个问题,配置类缺少@Configuration程序依然可以运行
          这是因为在测试类中加载类配置文件的字节码

          ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
          
        2. 如果出现多个配置文件,并且将SpringConfig配置文件的信息写入到JDBCConfig中

          @ComponentScan("com.liuzeyu")
          public class SpringConfig {
          }
          
          @Configuration
          @ComponentScan("com.liuzeyu")
          public class JDBCConfig {
          
              @Bean(name = "runner")
              @Scope(value = "prototype")
              public QueryRunner createQueryRunner(DataSource dataSource){
                  return new QueryRunner(dataSource);
              }
          
          
              @Bean("dataSource")
              public DataSource createDataSource(){
                  try{
                      ComboPooledDataSource source = new ComboPooledDataSource();
                      source.setUser("root");
                      source.setPassword("809080");
                      source.setDriverClass("com.mysql.jdbc.Driver");
                      source.setJdbcUrl("jdbc:mysql://localhost:3306/eesy");
                      return source;
                  }catch (Exception e){
                      throw new RuntimeException(e);
                  }
              }
          }
          
          

          此时如果再次缺少了JDBCConfig的@Configuration,将会报错。因为创建ApplicationContext对象时会加载配置类SpringConfig(默认已经当作配置类),开启扫描包,未发现@Configuration标识的配置类,就会报错。此时解决办法有两个:
          第一种:加载JDBCConfig.class字节码

          ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class, JDBCConfig.class);
          

          第二种:在使用Import注解

          @ComponentScan("com.liuzeyu")
          @Import(JDBCConfig.class)
          public class SpringConfig {			
          }
          

          Import:
          作用:用于导入其它的配置类
          属性:value用于指定其它的配置类的字节码,当我们使用Import注解后,有Import的类就是父配置类,而导入的就是子配置类

      5. @PropertySource的使用
        1. 准备配置文件
        2. 删除创建DataSource方法中的jdbc连接信息
        3. 添加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;
          
              @Bean(name = "runner")
              @Scope(value = "prototype")
          
          DataSource方法中的jdbc连接信息
        4. 但是现在配置文件中的信息就会被加载进来吗,不可以,需要在父配类上加上注解@PropertySource
          @ComponentScan("com.liuzeyu")
          @Import(JDBCConfig.class)
          @PropertySource("classpath:jdbc.properties")
          public class SpringConfig {
          
          }
          
          因为放到resource资源文件下的配置文件部署后都会在类路径下,这样就可以从@Value获取到值
      6. @Qualifier注解在方法上的使用
        解决方案:

4. spring和JUnit的整合

  1. 发现测试方法存在的问题

    public class AnnotationTest {
        private IAccountService ias = null;
    	
        @Before
        public void  init(){
            ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfig.class);
            ias = ac.getBean("accountService", IAccountService.class);
        }
    
        @Test
        public void testFindAll(){
    
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
        
    }
    

    上述测试案例可以运行,但是存在一个问题,就是如果测试人员来用的话万一在init函数里面出现问题,则测试人员将无法作报告分析,因为这是开发人员留下的坑,一般的测试人员只对测试方法进行分析。

  2. 进行分析问题
    可以使用注解的方式注入IAccountService对象,如下:

    public class AnnotationTest {
    
        @Autowired
        private IAccountService ias = null;
    
        @Test
        public void testFindAll(){
    
            List<Account> accounts = ias.findAllAccount();
            for (Account account : accounts) {
                System.out.println(account);
            }
    
        }
    
    }
    

    运行会出现空指针异常,为什么呢?
    要从应用程序的入口main函数开始分析,其实在junit中默认集成了一个main方法,该方***判断当前测试类哪些方法有@Test注解,junit就可以让这些方法执行。
    为什么出现空指针?
    junit是不管我们是否采用spring框架的,它只会使用自己的main入口函数,所以它也就不会为我们读取配置然后创建IOC容器了,因此会注入失败,出现空指针。

  3. 解决方案
    整合junit和spring框架

    1. 导入相关的jar包
    2. 使用juint提供的一个注解将原来的main方法替换掉,替换层spring提供的 @Runwith
    3. 告知spring运行器,spring和IOC的创建是基于xml还是注解,并说明位置 @ContextConfiguration(),属性有:
      1. locations:指定xml的文件位置,加上classpath关键字,表示在类路径下
      2. classes:指定注解所在的位置,这里要注意:注解配置类无论是否使用@Import的父配置类,都要加上@Configuration表明它是一个配置类,否则无法获取容器中的bean对象
        @RunWith(SpringJUnit4ClassRunner.class)
        @ContextConfiguration(classes = SpringConfig.class)
        public class AnnotationTest {
        
            @Autowired
            private IAccountService ias;
        
            @Test
            public void testFindAll(){
        
                List<Account> accounts = ias.findAllAccount();
                for (Account account : accounts) {
                    System.out.println(account);
                }
        
            }
        
        }