zoukankan      html  css  js  c++  java
  • 【SpringFramework】Spring 入门

    Spring 入门

    文章源码

    Spring 概述

    Spring

    Spring 是分层的 Java SE/EE 应用全栈式轻量级开源框架,以 IOC(Inverse Of Control,反转控制)和 AOP(Aspect Oriented Programming,面向切面编程)为内核,提供了 表现层 Spring MVC 和 持久层 Spring JDBC 以及 业务层事务管理等众多技术。而且可以方便的整合其他开源框架和类库。

    Spring 优势

    • 方便解耦,简化开发:通过 IOC 容器可以将对象间的依赖关系交由 Spring 进行控制,可以避免过度的程序耦合。而且也不需要再为单例模式、属性解析等底层需求编写代码。
    • 面向切面:通过 AOP,可以方便进行面向切面的编程,弥补面向对象的一切缺陷。
    • 声明式事务:通过 声明式方式灵活的进行事务管理,可以提高开发效率和质量。
    • 源码学习典范:Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。它的源代码是 Java 技术的最佳实践的范例。

    Spring 体系结构

    Spring 体系结构

    程序的耦合和解耦

    耦合性是对模块间关联程度的度量。耦合的强弱取决于模块间接口的复杂性、调用模块的方式以及通过界面传送数据的多少。

    耦合的分类

    • 内容耦合:当一个模块直接修改或操作另一个模块的数据时,或一个模块不通过正常入口而转入另一个模块时。内容耦合是最高程度的耦合,应该避免使用。
    • 公共耦合:两个或两个以上的模块共同引用一个全局数据项,这种耦合被称为公共耦合。在具有大量公共耦合的结构中,确定究竟是哪个模块给全局变量赋了一个特定的值是十分困难的。
    • 外部耦合:一组模块都访问同一全局简单变量而不是同一全局数据结构,而且不是通过参数表传递该全局变量的信息,则称之为外部耦合。
    • 控制耦合:一个模块通过接口向另一个模块传递一个控制信号,接受信号的模块根据信号值而进行适当的动作,这种耦合被称为控制耦合。
    • 标记耦合:若一个模块 A 通过接口向两个模块 B 和 C 传递一个公共参数,那么称模块 B 和 C 之间存在一个标记耦合。
    • 数据耦合:模块之间通过参数来传递数据,那么被称为数据耦合。数据耦合是最低的一种耦合形式,系统中一般都存在这种类型的耦合,因为为了完成一些有意义的功能,往往需要将某些模块的输出数据作为另一些模块的输入数据。
    • 非直接耦合:两个模块之间没有直接关系,它们之间的联系完全是通过主模块的控制和调用来实现的。

    总结起来,就是如果模块间必须存在耦合,就尽量使用数据耦合,少用控制耦合,限制公共耦合的范围,尽量避免使用内容耦合。

    程序耦合举例

    • AccountDAOImpl.java

      package cn.parzulpan.dao;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 账户持久层接口的实现类
      */
      
      public class AccountDAOImpl implements AccountDAO{
          /**
          * 模拟保存账户
          */
          public void saveAccount() {
              System.out.println("保存了账户...");
          }
      }
      
      
    • AccountServiceImpl.java

      package cn.parzulpan.service;
      
      import cn.parzulpan.dao.AccountDAO;
      import cn.parzulpan.dao.AccountDAOImpl;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 账户业务层接口的实现类
      */
      
      public class AccountServiceImpl implements AccountService{
          private AccountDAO accountDAO = new AccountDAOImpl();   // 这里发生了耦合
      
          /**
          * 模拟保存账户
          */
          public void saveAccount() {
              accountDAO.saveAccount();
          }
      }
      
      
    • Client.java

      package cn.parzulpan.ui;
      
      import cn.parzulpan.service.AccountServiceImpl;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 模拟一个表现层,用于调用业务层,实际开发中应该是一个 Servlet 等
      */
      
      public class Client {
          public static void main(String[] args) {
              AccountServiceImpl accountService = new AccountServiceImpl();   // 这里发生了耦合
              accountService.saveAccount();
          }
      }
      
      

    Factory 解耦

    在实际开发中可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并且存起来(用容器存储)。在接下来的使用的时候,直接拿过来用就行。

    那么,这个读取配置文件,创建和获取三层对象的类就是工厂类。即两个步骤:

    • 通过读取配置文件来获取创建对象的全限定类名。
    • 使用反射来创建对象,避免使用 new 关键字。

    工厂就是负责给从容器中获取指定对象的类,这时候获取对象的方式发生了改变。之前,在获取对象时,采用 new 的方式是主动的。现在,在获取对象时,采用跟工厂要的方式,工厂会查找或者创建对象,是被动的

    之前

    new

    现在

    factory

    • bean.properties

      accountService=cn.parzulpan.service.AccountServiceImpl
      accountDAO = cn.parzulpan.dao.AccountDAOImpl
      
    • BeanFactory.java

      package cn.parzulpan.factory;
      
      import java.io.InputStream;
      import java.util.*;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 工厂类,负责给从容器中获取指定对象的类
      */
      
      public class BeanFactory {
          private static Properties properties;
      
          private static Map<String, Object> beans;   // Factory 解耦的优化,存放创建的对象,称为容器
      
          static {
              try {
                  // 实例化对象
                  properties = new Properties();
                  // 获取文件流对象,使用类加载器
                  InputStream is = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                  properties.load(is);
      
                  beans = new HashMap<>();
                  Enumeration<Object> keys = properties.keys();
                  while (keys.hasMoreElements()) {
                      String key = keys.nextElement().toString();
                      String beanPath = properties.getProperty(key);
                      Object instance = Class.forName(beanPath).newInstance();
                      beans.put(key, instance);
                  }
              } catch (Exception e) {
                  e.printStackTrace();
                  throw new ExceptionInInitializerError("初始化 Properties 失败!");
              }
          }
      
          /**
          * 获取指定对象的类
          * @param beanName
          * @return
          */
          public static Object getBean(String beanName){
              try {
      //            return Class.forName(properties.getProperty(beanName)).newInstance(); // 两个步骤
                  System.out.println(beanName + " " + beans.get(beanName));
                  return beans.get(beanName); // 两个步骤
              } catch (Exception e) {
                  e.printStackTrace();
              }
              return null;
          }
      }
      
      
    • AccountServiceImpl.java

      package cn.parzulpan.service;
      
      import cn.parzulpan.dao.AccountDAO;
      import cn.parzulpan.dao.AccountDAOImpl;
      import cn.parzulpan.factory.BeanFactory;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 账户业务层接口的实现类
      */
      
      public class AccountServiceImpl implements AccountService{
      //    private AccountDAO accountDAO = new AccountDAOImpl();   // 这里发生了耦合
      
          /**
          * 模拟保存账户
          */
          public void saveAccount() {
              AccountDAO accountDAO = (AccountDAO) BeanFactory.getBean("accountDAO"); // 通过 Factory 解耦
              if (accountDAO != null) {
                  accountDAO.saveAccount();
              }
          }
      }
      
      
    • Client.java

      package cn.parzulpan.ui;
      
      import cn.parzulpan.factory.BeanFactory;
      import cn.parzulpan.service.AccountService;
      import cn.parzulpan.service.AccountServiceImpl;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 模拟一个表现层,用于调用业务层,实际开发中应该是一个 Servlet 等
      */
      
      public class Client {
          public static void main(String[] args) {
      //        AccountServiceImpl accountService = new AccountServiceImpl();   // 这里发生了耦合
      
              AccountService accountService = (AccountService) BeanFactory.getBean("accountService"); // 通过 Factory 解耦
      
              if (accountService != null) {
                  accountService.saveAccount();
              }
      
              // 通过 Factory 解耦存在的问题
              for (int i = 0; i < 5; ++i) {
                  System.out.println(BeanFactory.getBean("accountService"));  // 对象被创建多次
              }
          }
      }
      
      

    IOC 解耦

    • 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
              https://www.springframework.org/schema/beans/spring-beans.xsd">
      
          <!-- 把对象的创建交给 Spring 来管理-->
          <bean id="accountService" class="cn.parzulpan.service.AccountServiceImpl"/>
          <bean id="accountDAO" class="cn.parzulpan.dao.AccountDAOImpl"/>
      
      </beans>
      
    • ClientIOC.java

      package cn.parzulpan.ui;
      
      import cn.parzulpan.dao.AccountDAO;
      import cn.parzulpan.service.AccountService;
      import org.springframework.context.ApplicationContext;
      import org.springframework.context.support.ClassPathXmlApplicationContext;
      
      /**
      * @Author : parzulpan
      * @Time : 2020-12
      * @Desc : 使用 IOC
      */
      
      public class ClientIOC {
          public static void main(String[] args) {
              // 使用 ApplicationContext 接口,获取 Spring 核心容器
              ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
              // 根据 id 获取 Bean 对象
              AccountService as = ac.getBean("accountService", AccountService.class);
              System.out.println(as);
              AccountDAO ad = ac.getBean("accountDAO", AccountDAO.class);
              System.out.println(ad);
          }
      
      }
      

    IOC

    IOC(Inverse Of Control,反转控制),把创建对象的权利交给 Spring 框架,它包括 DI(Dependency Injection,依赖注入)和 DL(Dependency Lookup,依赖查找)。

    简单的说,IOC 是一种以被动接收的方式获取对象的思想,它主要是为了降低程序的耦合

    bean 标签

    • 作用:用于配置对象让 Spring 来创建。默认情况下它调用的是类中的无参构造函数,如果没有无参构造函数则不能创建成功。
    • 属性
      • id:给对象在容器中提供一个唯一标识,用于获取对象
      • class:指定类的全限定类名,用于反射创建对象,默认情况下调用无参构造函数。
      • scope:指定对象的作用范围
        • singleton 默认值,单例的
        • prototype 多例的
        • request WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 request 域中
        • session WEB 项目中,Spring 创建一个 Bean 的对象,将对象存入到 session 域中
        • global session WEB 项目中,应用在集群环境,如果没有集群环境那么 globalSession 相当于 session
      • init-method:指定类中的初始化方法名称。
      • destroy-method:指定类中销毁方法名称。

    bean 的三种创建方式

    第一种方式:使用默认无参构造函数。它会根据默认无参构造函数来创建类对象。如果 bean 中没有默认无参构造函数,将会创建失败。

        <bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC"/>
        <bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>
    

    第二种方式:使用实例工厂的方法创建对象。先把工厂的创建交给 Spring 来管理,然后在使用工厂的 bean 来调用里面的方法。

    • factory-bean 属性:用于指定实例工厂 bean 的 id
    • factory-method 属性:用于指定实例工厂中创建对象的方法
    package cn.parzulpan.factory;
    
    import cn.parzulpan.service.AccountService;
    import cn.parzulpan.service.AccountServiceImplIOC;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : Spring 管理实例工厂。模拟一个工厂类,该类可能存在于 jar 包中,无法通过修改源码来提供默认构造函数
     */
    
    public class InstanceFactory {
        public AccountService getAccountService() {
            return new AccountServiceImplIOC();
        }
    }
    
        <bean id="instanceFactory" class="cn.parzulpan.factory.InstanceFactory"/>
        <bean id="accountServiceIOC" factory-bean="instanceFactory" factory-method="getAccountService"/>
        <bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>
    

    第三种方式:使用静态工厂的方法创建对象。使用某个类中的静态方法创建对象,并存入 Spring 核心容器。

    • id 属性:指定 bean 的 id,用于从容器中获取
    • class 属性:指定静态工厂的全限定类名
    • factory-method 属性:指定生产对象的静态方法
    package cn.parzulpan.factory;
    
    import cn.parzulpan.service.AccountService;
    import cn.parzulpan.service.AccountServiceImplIOC;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc : Spring 管理静态工厂。模拟一个工厂类,该类可能存在于 jar 包中,无法通过修改源码来提供默认构造函数
     */
    
    public class StaticFactory {
        public static AccountService getAccountService() {
            return new AccountServiceImplIOC();
        }
    }
    
    
        <bean id="accountServiceIOC" class="cn.parzulpan.factory.StaticFactory" factory-method="getAccountService"/>
        <bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC"/>
    

    测试 ClientIOC.java:

    public class ClientIOC {
        public static void main(String[] args) {
            // 使用 ApplicationContext 接口,获取 Spring 核心容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    
            System.out.println("------");
    
            //
            AccountService asi = ac.getBean("accountServiceIOC", AccountService.class);
            System.out.println(asi);
            AccountDAO adi = ac.getBean("accountDAOIOC", AccountDAO.class);
            System.out.println(adi);
            adi.saveAccount();
    
        }
    
    }
    

    bean 的作用范围和生命周期

    对于单例对象scope="singleton"

    一个应用只有一个对象的实例,它的作用范围就是整个引用。

    生命周期

    • 对象出生:当应用加载,创建容器时,对象就被创建了。
    • 对象活着:只要容器在,对象一直活着。
    • 对象死亡:当应用卸载,销毁容器时,对象就被销毁了。
        <!-- bean 的作用范围和生命周期 -->
        <bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC" scope="singleton"
              init-method="init" destroy-method="destroy"/>
        <bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC" scope="singleton"
              init-method="init" destroy-method="destroy"/>
    

    对于多例对象scope="prototype"

    每次访问对象时,都会重新创建对象实例。

    生命周期

    • 对象出生:当使用对象时,创建新的对象实例。
    • 对象活着:只要对象在使用中,就一直活着。
    • 对象死亡:当对象长时间不用时,被 java 的垃圾回收器回收了。
        <bean id="accountServiceIOC" class="cn.parzulpan.service.AccountServiceImplIOC" scope="prototype"
              init-method="init" destroy-method="destroy"/>
        <bean id="accountDAOIOC" class="cn.parzulpan.dao.AccountDAOImplIOC" scope="prototype"
              init-method="init" destroy-method="destroy"/>
    

    测试

            ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            AccountService asi = ac.getBean("accountServiceIOC", AccountService.class);
            System.out.println(asi);
            AccountDAO adi = ac.getBean("accountDAOIOC", AccountDAO.class);
            System.out.println(adi);
            adi.saveAccount();
    
            ac.close(); // 手动关闭容器
    

    DI

    DI(Dependency Injection,依赖注入),它是 Spring IOC 的具体实现。因为 IOC 作用是降低耦合,那么依赖关系的维护都交给了 Spring,依赖关系的维护就称之为依赖注入。

    能依赖注入的数据,有三类:

    • 基本数据类型和 String
    • 其他 Bean 类型,在配置文件中或者其他注解配置过的 Bean
    • 集合类型

    依赖注入的方法,有三种:

    • 使用构造函数注入
    • 使用 set 方法注入
    • 使用注解注入

    使用构造函数注入

    类中需要提供一个对应参数列表的构造函数。

    属性:

    • index 指定参数在构造函数参数列表的索引位置
    • type 指定参数在构造函数中的数据类型
    • name 指定参数在构造函数中的名称
    • value 它能赋的值是基本数据类型和 String 类型
    • ref 它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
    • 前三个都是找给谁赋值,后两个指的是赋什么值的
        <!-- 构造函数注入
             类中需要提供一个对应参数列表的构造函数
             属性:
                index 指定参数在构造函数参数列表的索引位置
                type 指定参数在构造函数中的数据类型
                name 指定参数在构造函数中的名称
                value 它能赋的值是基本数据类型和 String 类型
                ref 它能赋的值是其他 bean 类型,也就是说,必须得是在配置文件中配置过的 bean
                前三个都是找给谁赋值,后两个指的是赋什么值的
        -->
        <bean id="accountServiceDI" class="cn.parzulpan.service.AccountServiceImplDI">
            <constructor-arg name="name" value="parzulpan"/>
            <constructor-arg name="age" value="100"/>
            <constructor-arg name="birthday" ref="now"/>
        </bean>
        <bean id="now" class="java.util.Date"/>
    

    ClientDI.java

    package cn.parzulpan.ui;
    
    import cn.parzulpan.service.AccountService;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * @Author : parzulpan
     * @Time : 2020-12
     * @Desc :
     */
    
    public class ClientDI {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            AccountService asi = ac.getBean("accountServiceDI", AccountService.class);
            System.out.println(asi);
            asi.saveAccount();  // call saveAccount() parzulpan 100 Sun Dec 20 19:27:49 CST 2020
        }
    }
    
    

    这种注入方式的优点:在获取 bean 对象时,注入数据是必须的操作,否则无法创建成功。

    缺点:改变了 bean 对象的实例化方式,在创建对象时,如果用不到这些属性,也必须提供。

    使用 set 方法注入

    类中需要提供属性的 set 方法。

    属性:

    • name:找的是类中 set 方法后面的部分
    • ref:给属性赋值是其他 bean 类型的
    • value:给属性赋值是基本数据类型和 string 类型的
        <!-- set 方法 注入
             类中需要提供属性的 set 方法
             属性:
                name:找的是类中 set 方法后面的部分
                ref:给属性赋值是其他 bean 类型的
                value:给属性赋值是基本数据类型和 string 类型的
        -->
        <bean id="accountServiceDI2" class="cn.parzulpan.service.AccountServiceImplDI2">
            <property name="name" value="库里"/>
            <property name="age" value="30"/>
            <property name="birthday" ref="nowSet"/>
        </bean>
        <bean id="nowSet" class="java.util.Date"/>
    

    ClientDI.java

            AccountService asi2 = ac.getBean("accountServiceDI2", AccountService.class);
            System.out.println(asi2);
            asi2.saveAccount();  // call saveAccount() 库里 30 Sun Dec 20 20:11:01 CST 2020
    

    这种注入方式的优点:创建对象时没有明确的限制,可以直接使用默认构造函数。

    缺点:如果某个成员必须有值,则 set 方法无法保证一定执行。

    但是,set 方式是更常用的方式。

    注入集合属性

    注入集合属性,在注入集合数据时,只要结构相同,标签可以互换。

    List 结构的:array, list, set

    Map 结构的:map, entry, props, prop

        <!-- 注入集合属性
             在注入集合数据时,只要结构相同,标签可以互换
             List 结构的:array, list, set
             Map 结构的:map, entry, props, prop
        -->
        <bean id="accountServiceDI3" class="cn.parzulpan.service.AccountServiceImplDI3">
            <property name="myStr">
                <set>
                    <value>AAA</value>
                    <value>BBB</value>
                    <value>CCC</value>
                </set>
            </property>
            <property name="myList">
                <list>
                    <value>AAA</value>
                    <value>BBB</value>
                    <value>CCC</value>
                </list>
            </property>
            <property name="mySet">
                <set>
                    <value>AAA</value>
                    <value>BBB</value>
                    <value>CCC</value>
                </set>
            </property>
            <property name="myMap">
                <map>
                    <entry key="testA" value="aaa"/>
                    <entry key="testB" value="bbb"/>
                </map>
            </property>
            <property name="myProps">
                <props>
                    <prop key="testA">aaa</prop>
                    <prop key="testB">bbb</prop>
                </props>
            </property>
        </bean>
    

    练习和总结


    ApplicationContext 接口的三个实现类?

    • ClassPathXmlApplicationContext 它是从类的根路径下加载配置文件,推荐使用这种
    • FileSystemXmlApplicationContext 它是从磁盘路径上加载配置文件,配置文件可以在磁盘的任意位置,不推荐使用这种
    • AnnotationConfigApplicationContext 使用注解配置容器对象时,需要使用此类来创建 Spring 核心容器,它用来读取注解

    BeanFactory 和 ApplicationContext 的区别?

    • BeanFactory 是 Spring 核心容器中的顶层接口
    • ApplicationContext 是 BeanFactory 的子接口
    • 它们两者创建对象的时间点不一样
      • ApplicationContext 立即加载,只要读取了配置文件,默认情况下就会创建对象,适用于单例对象,推荐使用这种
      • BeanFactory 延迟加载,什么时候使用什么时候创建对象,适用于多例对象

  • 相关阅读:
    Windows 下安装 Python + Django
    asp.net core 一个中小型项目实战的起手式——Swagger配置
    asp.net core 一个中小型项目实战的起手式——项目搭建与仓储模式下的持久层创建(1)
    c#一些常用知识点
    ADO.NET中的5个主要对象
    JavaScript的闭包特性如何给循环中的对象添加事件(一)
    在什么情况下会用到虚方法?它与接口有什么不同?
    一般处理程序Session
    什么是code-Behind技术?
    什么是事务?
  • 原文地址:https://www.cnblogs.com/parzulpan/p/14164990.html
Copyright © 2011-2022 走看看