spring 框架学习-1

spring 框架学习安排

目录结构

  • spring 框架学习安排
    • 1. 第一天:
      • 1. spring 框架概述
        • 1. spring 是什么
        • 2. spring 的两大核心
        • 3. spring 的发展历程和优势(了解)
        • 4. spring 的优势(由浅如深,慢慢体会)
        • 5. spring 的体系结构
      • 2. 程序的耦合及解耦
        • 1. 曾经案例中存在的问题
        • 2. 工厂模式解耦
      • 3. IOC解耦和spring中的IOC
        • 1. IOC(控制反转)
        • 2.spring中基于XML的IOC环境搭建
        • 3.spring中三种创建bean对象的方法
      • 4. 依赖注入(Dependency Injection)

1. 第一天:

1. spring 框架概述

1. spring 是什么

spring是一个分层的,开源的,全栈式的(full-stack )、JavaEE轻量级框架,以 IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程为内核,提供了展现层 Spring MVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多 著名的第三方框架和类库,逐渐成为使用最多的Java EE 企业应用开源框架。

2. spring 的两大核心

IOC(Inverse Of Control: 反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)

3. spring 的发展历程和优势(了解)

1997 年 IBM提出了EJB 的思想
1998 年,SUN制定开发标准规范 EJB1.0
1999 年,EJB1.1 发布
2001 年,EJB2.0 发布
2003 年,EJB2.1 发布
2006 年,EJB3.0 发布 Rod Johnson(spring之父)
Expert One-to-One J2EE Design and Development(2002) 阐述了 J2EE 使用EJB 开发设计的优点及解决方案
Expert One-to-One J2EE Development without EJB(2004) 阐述了 J2EE 开发不使用 EJB的解决方式(Spring 雏形)
2017 年 9 月份发布了 spring 的最新版本 spring 5.0 通用版(GA)

4. spring 的优势(由浅如深,慢慢体会)

  1. 方便解耦,简化开发
    通过 Spring提供的 IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造 成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可 以更专注于上层的应用。
  2. AOP编程的支持
    通过 Spring的 AOP 功能,方便进行面向切面的编程,许多不容易用传统OOP 实现的功能可以
    通过 AOP 轻松应付。
  3. 声明式事务的支持
    可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理, 提高开发效率和质量。
  4. 方便程序的测试
    可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可 做的事情。
  5. 方便集成各种优秀框架
    Spring可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持
  6. 降低 JavaEE API的使用难度
    Spring对 JavaEE API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的 使用难度大为降低。
  7. Java源码是经典学习范例
    Spring的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对Java 设计模式灵活运用以 及对 Java技术的高深造诣。它的源代码无意是 Java 技术的最佳实践的范例。

5. spring 的体系结构

spring 框架学习-1_第1张图片

2. 程序的耦合及解耦

1. 曾经案例中存在的问题

public class TestJdbc {
    public static void main(String[] args) throws SQLException, ClassNotFoundException {
        //1.注册驱动
       // DriverManager.registerDriver(new Driver());//编译时和驱动类关联
        Class.forName("com.mysql.jdbc.Driver");  // 常用:运行时和驱动类关联,降低类之间的耦合度
        //2.获取连接
        Connection conn = DriverManager.getConnection("jdbc:mysql:///spring_eesy", "root", "809080");
        //3.获取执行SQL预处理对象
        PreparedStatement pstm = conn.prepareStatement("select * from account");
        //4.执行SQL
        ResultSet rst = pstm.executeQuery();
        //遍历
        while(rst.next()){
            String name = rst.getString("name");
            System.out.println(name);
        }
        rst.close();
        conn.close();
        pstm.close();
    }
}
  • 程序的耦合:程序间的依赖关系
    • 包括
      • 类之间的依赖关系
      • 方法之间的依赖关系
  • 解耦:降低程序间的依赖关系
  • 实际开发中
    • 应该做到:编译时不依赖,运行时依赖
    • 解耦的思路:
      • 通过读取配置文件的过程来创建对象的全限定类名,使用反射到创建对象,避免使用new关键字

上述代码是一个简单的JDBC连接数据库的小案例,存在的问题在注册驱动的时候,我们平时都是用第二种Class.forName(“com.mysql.jdbc.Driver”); ,这是由于反射的作用,可以在程序运行的时候新建处驱动类,而不用在编译时就开始新建出这个类,这样做的好处就是解耦了类和类之间的关系,减低耦合度。像第二种建立驱动类的方法,在编译时,驱动类Driver和DriverManager就会相关联起来,如果一方缺少jiar包,就会导致编译期过不去,也就不能达到减低耦合度的效果。

2. 工厂模式解耦

  1. 模拟三层架构
    1. dao层
      spring 框架学习-1_第2张图片

      /**
       * 持久层接口
       */
      public interface IAccountDao {
          public void save();
      }
      
      public class IAccountImpl  implements IAccountDao{
          public void save() {
              System.out.println("保存了账户...");
          }
      }
      
    2. service层
      spring 框架学习-1_第3张图片

      public class IAccountServiceImpl implements IAccountService{
          private IAccountDao dao = new IAccountImpl();
          public void save() {
              dao.save();
          }
      }
      
      /**
       * 业务层接口
       */
      public interface IAccountService {
          public void save();
      }
      
    3. 视图层
      spring 框架学习-1_第4张图片

    4. 测试结果
      在这里插入图片描述
      问题:
      发现类和类之间的依赖关系非常密切,如service依赖dao层,如 private IAccountDao dao = new IAccountImpl();
      视图层依赖service层,如 IAccountService service = new IAccountServiceImpl();程序的耦合度较高。
      解决方案:
      将类和类之间的依赖关系使用配置文件进行存放,运用反射原理,在运行期,将依赖建立起来,这样就可以降低了类之间在编译器的依赖关系。

  2. 降低三层架构的耦合度(反射)
    1. 准备配置文件account.properties,存放dao层的实现类和service层的实现类的全限定类名

      accountService=com.liuzeyu.service.impl.IAccountServiceImpl
      accountDao=com.liuzeyu.dao.impl.IAccountImpl
      
    2. 准备工具类BeanFactory,用户加载配置文件中的信息进内存

      public class BeanFactory {
          private static  Properties properties;
          static {
              try {
                  //1.加载配置文件
                  InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("account.properties");
                  //2.建立Properties对象
                  properties = new Properties();
                  //加载配置文件进内存
                  properties.load(is);
              } catch (IOException e) {
                  throw  new ExceptionInInitializerError("加载配置文件失败");
              }
          }
      
          public static Object getBean(String beanName){
              Object bean = null;
              try {
                  String beanPath = properties.getProperty(beanName);
                  bean = Class.forName(beanPath).newInstance();
      
              } catch (InstantiationException e) {
                  e.printStackTrace();
              } catch (IllegalAccessException e) {
                  e.printStackTrace();
              } catch (ClassNotFoundException e) {
                  e.printStackTrace();
              }
              return bean;
          }
      }
      
      
    3. 改造service层,视图层建立对象的方法
      视图层:

      public class UITest {
          public static void main(String[] args) {
              IAccountService service = (IAccountService)BeanFactory.getBean("accountService");
              service.save();
          }
      }
      

      service层:

      public class IAccountServiceImpl implements IAccountService{
          private IAccountDao dao = (IAccountDao)BeanFactory.getBean("accountDao");
          public void save() {
              dao.save();
          }
      }
      
    4. 测试结果
      在这里插入图片描述

  3. 仔细分析上述案例存在的缺陷
    经测试可以分析出来,这是一个多例(多个对象被创建)的案例。如何分析呢?
    1. 视图层多次创建service实现类,发现创建出来的都是不同的对象
      spring 框架学习-1_第5张图片
      spring 框架学习-1_第6张图片
      存在的问题:但是由于service层是线程安全,如何看出是线程安全的?

    2. 假设方法里面有可以修改类属性的操作 ,i
      spring 框架学习-1_第7张图片
      spring 框架学习-1_第8张图片
      可以发现每次创建的service是同一个,多例的使用场景一般就在这,是为了防止多个线程操作同一个属性,导致类的属性发生多次改变。

    3. 但是我们案例中并没有多例场景的,因此并不需要创建多个对象,所以可将多例改为单例。步骤如下:

      1. 将类BeanFactory中的bean对象创建一次后存起来
        bean = Class.forName(beanPath).newInstance();
        
      2. 操作静态代码块
        public class BeanFactory {
            private static  Properties properties;
            //定义一个Map容器,存放创建的对象
            private static Map beans;
            static {
                try {
                    //1.加载配置文件
                    InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("account.properties");
                    //2.建立Properties对象
                    properties = new Properties();
                    //加载配置文件进内存
                    properties.load(is);
        //-----------------------多例--->单例-----------------------------
                    //实例化容器
                    beans = new HashMap();
                    //获取配置对象所有的key
                    Enumeration keys = properties.keys();
                    //遍历枚举
                    while(keys.hasMoreElements()){
                        //取出每个key
                        String key = keys.nextElement().toString();
                        //根据key获取value值
                        String beanPath = properties.getProperty(key);
                        //反射创建对象
                        Object value = Class.forName(beanPath).newInstance();
                        beans.put(key,value);
                    }
                } catch (IOException e) {
                    throw  new ExceptionInInitializerError("加载配置文件失败");
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        
        //    public static Object getBean(String beanName){
        //        Object bean = null;
        //        try {
        //            String beanPath = properties.getProperty(beanName);
        //            bean = Class.forName(beanPath).newInstance();
        //
        //        } catch (InstantiationException e) {
        //            e.printStackTrace();
        //        } catch (IllegalAccessException e) {
        //            e.printStackTrace();
        //        } catch (ClassNotFoundException e) {
        //            e.printStackTrace();
        //        }
        //        return bean;
        //    }
        
        //根据bean的名称获取对象
        public static Object getBean(String beanName){
                return beans.get(beanName);
            }
        }
        
        配置文件:
        spring 框架学习-1_第9张图片
        这样就可以加载一次Class就创建对象存入准备好容器,实现单例模式。

3. IOC解耦和spring中的IOC

1. IOC(控制反转)

引入IOC(控制反转)的概念:
通过一张图来理解:
spring 框架学习-1_第10张图片
左上角是通过new 关键字来创建对象,右下角是通过一个工厂通过反射来创建对象,他们之间的区别可以从上图就可以看出来:

  • 如果使用new关键字则是程序通过new直接访问内存中的对象资源
  • 如果使用工厂,想要获取内存中的资源,就必须经过工厂提供资源,即这个工厂通过加载配置文件反射创建出对象

那这个和控制反转又有什么区别呢?
解释:如果程序选择了第一种方式创建对象,则程序就拥有了主动的控制权,可以主动的向内存申请资源。如果使用第二种工厂方式创建对象,则程序就将自己的主动权交予工厂,由工厂来向内存申请资源创建对象,这种主动权的转移又称为控制反转。

百度百科解释:控制反转(Inversion of Control,英文缩写为loC)把创建对象的权利交给框架,是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称Dl)和依赖查找(Dependency Lookup)。

明确IOC的作用:
削减计算机之间的耦合,降低程序代码中的依赖关系。

2.spring中基于XML的IOC环境搭建

  1. 环境搭建

    1. 在2的工厂模式解耦的基础上修改,使用将使用spring提供的工厂进行反射创建对象,首先删除BeanFactory工厂类,保留dao层,service层和ui层即可。开始导入依赖:

      jar
      
          
              org.springframework
              spring-context
              5.0.2.RELEASE
          
      
      
    2. 接着在resource下准备配置文件bean.xml(可随意命名)

      
      
          
          
          
      
      
    3. UI中开始测试工厂创建的对象

      public class UITest {
          public static void main(String[] args) {
              IAccountService service = new IAccountServiceImpl();
              //1.获取spring核心容器对象
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              //2.根据id获取bean对象
              //2.1方式1获取
              IAccountService aservice = (IAccountService) ac.getBean("accountService");
              //2.2方式2获取
              IAccountDao adao = ac.getBean("accountDao", IAccountDao.class);
      
              System.out.println(aservice);
              System.out.println(adao);
          }
      }
      
  2. ApplicationContext 的三个实现类

    1. ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话是加载不了的(更常用)。
    2. FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件,但必须拥有访问权限。
    3. AnnotationConfigApplicationContext:用于读取注解创建容器的。

    查看实现类:
    spring 框架学习-1_第11张图片

  3. 核心容器引发出的问题
    spring 框架学习-1_第12张图片
    由继承树可以发现我们上面使用的工厂解耦的工厂类BeanFactory是一个顶级的接口,我们上面使用的AppcationContext创建的核心容器和BeanFactory又有什么区别呢?

    • AppcationContext:它在构建核心容器时,创建对象的策略采用立即加载的方式,只要配置文件已加载成功,就马上创建配置文件的配置对象,为避免多线程带来的安全问题,通常适用于单例模式。
    • BeanFactory,它在构建核心容器时,创建对象的方式采用延迟加载的方式,也就是说,什么时候根据id获取对象,什么时候才真正创建对象,该方法避免了多线程带来的安全问题,所以适用于多例模式。
    1. debug方式测试立即加载和延迟加载:
      1. 在service的实习类中添加一个构造函数:
        在这里插入图片描述
        开始debug在这里插入图片描述
        发现是执行完创建核心容器就立即输出了 “IAccountService创建了”
      2. BeanFactory创建对象
        在这里插入图片描述
        spring 框架学习-1_第13张图片
        发现加载配置文件后并未输出“IAccountService创建了”,而是运行到getBean函数后才创建的IAccountService对象

3.spring中三种创建bean对象的方法

  1. bean对象的三种创建方式,在2的基础上修改

    1. 方式1:删除dao层及相关的代码
      spring 框架学习-1_第14张图片

    2. 选择配置方式1创建bean对象

      
      
          
      
          
          
      
      

      如果IAccountServiceImpl类缺少默认的构造函数,则会爆出异常:

      No default constructor found;
      
    3. 方式2:在方式1的基础上注释bean.xml的配置信息。思考,如果想要从某个jar包的类中获取方法返回值的信息,进行对象创建,前提:该类没有默认的构造函数bean对象又该如何创建,并且jar包不可修改源码?

      1. 模拟一个工厂类,返回IAccountService
        /**
         * Created by liuzeyu on 2020/4/19.
         * 模拟一个工厂类,该类可能存在于jar包中,我们无法通过修改源码的方式来对其提供默认构造函数
         */
        public class InstanceFactory {
        
            public IAccountService getAccountService(){
                return new IAccountServiceImpl();
            }
        }
        
      2. bean.xml配置
        
        
        
        
    4. 方式3:如果工厂类静态方法返回值应当如何获取?

      1. 为模拟工厂类添加静态方法
        
            public static  IAccountService getStaticAccountService(){
                return new IAccountServiceImpl();
            }
        
      2. bean.xml配置
        
        	
        
  2. bean对象的作用范围
    bean的作用范围由bean标签的scope所指定,单例和多例较常用

    1. singleton:单例(默认)
    2. prototype:多例
    3. request:作用与web应用的请求范围
    4. session:作用与web应用的会话范围
    5. global-session:作用于集群服务器的session会话范围,当不是集群时,它就是单纯的session范围

    通过代码演示:
    spring 框架学习-1_第15张图片
    可见bean对象就被创建了一次,bean的作用范围默认就是单例的。
    修改bean的scope属性,使得创建的bean对象是多例的。

    
    

    再次运行上面图片中的代码
    spring 框架学习-1_第16张图片
    最后什么是global-session呢?请看图:
    spring 框架学习-1_第17张图片
    由于用户访问流量过大,公司服务器采用集群方式,上述则是用户访问集群服务器请求登录,用户首先访问到服务器192.168.0.1,发现其是空闲状态,其余都在忙碌,则将用户的验证码存入session域中,第二次再次访问输入验证码(如果没过期),此时服务器1被无情占用了,但是服务器6已经空闲可使用,但是6上面已经不存在session内的验证码,此时又该怎么办?
    答:global-session出场,所有的服务器共享一个session域,这样就不会出现上述的情况了。
    spring 框架学习-1_第18张图片

  3. bean对象的生命周期

    1. 单例对象

      1. 出生:随着配置文件加载后,容器创建bean对象也就创建
      2. 活着:只要容器还在,bean对象就一直都在
      3. 死亡:容器销毁,bean对象销毁

      总结:对象的使用周期和容器相同

    2. 多例对象

      1. 出生:什么时候使用对象,spring框架就为我们创建
      2. 活着:对象只要在使用过程中就一直活着
      3. 死亡:当对象长时间不用,并没有对象引用时,又Java的GC回收

    测试:

    1. 为service的实现类添加init(),destory()方法
      spring 框架学习-1_第19张图片
    2. 添加bean属性init-method,destroy-method
      
      
    3. 测试UITest函数
      1. 多例:
        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");
                ClassPathXmlApplicationContext ca= (ClassPathXmlApplicationContext)ac;
                ca.close();
            }
        }
        
        spring 框架学习-1_第20张图片
        发现destory函数并未执行,所以bean对象也并未销毁,最后由GC回收。
      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");
                ClassPathXmlApplicationContext ca= (ClassPathXmlApplicationContext)ac;
                ca.close();
            }
        }
        
        在这里插入图片描述
        可见单例对象是可以通过close关闭的。

4. 依赖注入(Dependency Injection)

IOC的作用:降低程序间的耦合程度(依赖程度)
依赖关系的管理:以后都交给spring来维护,在当前类中我们需要用到其它类的对象,都由spring提供,我们只需要在配置文件中说明即可。
依赖注入:依赖关系的维护。
依赖注入的数据有三类:

  • 基本数据类型和String
  • 其它bean类型(在配置文件中或注解配置过的bean)
  • 复杂类型/集合类型

注入的方式有三种

  • 第一种:使用构造函数提供
  • 使用set方法提供
  • 使用注解提供
  1. 使用构造函数注入:

    1. 为service的实现类添加带参构造

      package com.liuzeyu.service.impl;
      
      import com.liuzeyu.service.IAccountService;
      
      import java.util.Date;
      
      public class IAccountServiceImpl implements IAccountService{
      
          //添加属性,如果是经常变化的数据,并不适用于注入的方式
          private String name;
          private Integer id;
          private Date birthday;
      
          public IAccountServiceImpl(String name, Integer id, Date birthday) {
              this.name = name;
              this.id = id;
              this.birthday = birthday;
          }
      
          public void save() {
              System.out.println("save()..."+"姓名:"+name+" id:"+id+" 生日:"+birthday);
          }
      
      }
      
      
    2. 此时bean.xml应如何修改才能创建对象呢?
      构造函数的注入:
      使用标签:construct-arg
      使用位置:bean标签内部
      标签中的属性:
      1)type:用户指定要注入的数据的数据类型,该数据类型也是构造函数中某些参数的数据类型
      2)index:用于指定要注入的数据给构造函数中指定索引位置的参数赋值,该索引从0开始
      3)name:用于给指定构造函数中指定的名称的参数赋值
      =以上三个用于指定给构造函数中那个参数赋值===
      4)value:用于提供基本数据类型和String类型数据
      5)ref:用于指定其它的bean类型数据,它是指就是在spring的IOC核心容器中出现过的bean对象
      优势
      在获取bean对象时,注入是必须的操作,因为已经没有了默认构造函数,因此注入时必须的,否则无法操作成功。
      劣势:
      改变了bean对象的实例方式,是我们在创建对象时,如果用不到这些数据,也必须提供。
      spring 框架学习-1_第21张图片
      3.输出结果
      在这里插入图片描述

  2. 使用set函数注入

    1. 修改service实现类
      /**
       * set注入方法
       */
      public class IAccountServiceImpl2 implements IAccountService {
      
          //添加属性,如果是经常变化的数据,并不适用于注入的方式
          private String name;
          private Integer id;
          private Date birthday;
      
          public void setName(String name) {
              this.name = name;
          }
      
          public void setId(Integer id) {
              this.id = id;
          }
      
          public void setBirthday(Date birthday) {
              this.birthday = birthday;
          }
      
          public void save() {
              System.out.println("save()..."+"姓名:"+name+" id:"+id+" 生日:"+birthday);
          }
      
      }
      
      
    2. 修改bean.xml文件
      涉及到的标签property
      出现的位置,bean标签内部
      标签的属性:
      1)name:注入所调用的set方法的名称,其实是所关联JavaBean的属性。
      2)value:提供基本类型和String类型。
      3)ref:其它的bean类型数据,用于指向spring的IOC创建出的bean对象 。
      优势:创建对象时没有明确的限制,可以直接使用默认构造函数
      劣势:如果要使某个成员必须有值,则获取对象时有可能没set进去,因为它没有了默认构造函数的约定
      
      
      
      
          
      
          
              
              
              
          
      
      
  3. 复杂类型/集合类型的注入问题

    1. 准备service的实现类
      package com.liuzeyu.service.impl;
      
      import com.liuzeyu.service.IAccountService;
      
      import java.util.*;
      
      /**
       * set注入方法
       */
      public class IAccountServiceImpl3 implements IAccountService {
      
          private String[] mystr;
          private List mylist;
          private Set myset;
          private Map myMap;
          private Properties myProps;
      
          public void setMystr(String[] mystr) {
              this.mystr = mystr;
          }
      
          public void setMylist(List mylist) {
              this.mylist = mylist;
          }
      
          public void setMyset(Set myset) {
              this.myset = myset;
          }
      
          public void setMyMap(Map myMap) {
              this.myMap = myMap;
          }
      
          public void setMyProps(Properties myProps) {
              this.myProps = myProps;
          }
      
          public void save() {
              System.out.println(Arrays.toString(mystr));
              System.out.println(mylist);
              System.out.println(myset);
              System.out.println(myMap);
              System.out.println(myProps);
          }
      
      }
      
      
    2. 修改bean.xml文件
      
      
      
          
              
                  
                      AAA
                      BBB
                      CCC
                  
              
      
              
                  
                      111
                      222
                      333
                  
              
      
              
                  
                      aaa
                      bbb
                      ccc
                  
              
      
              
                  
                      
                      
                      
                  
      
              
      
              
                  
                      三七
                      三八
                      三九
                  
              
          
      
      
      说明:其中list和set均属于数据类型,因此可以互相替换使用,如:
      spring 框架学习-1_第22张图片
      但是set和list却不可以互换。
      Map和Property也可以互换,如
      spring 框架学习-1_第23张图片

你可能感兴趣的:(SSM框架学习,java,spring,设计模式,编程语言)