Spring IOC 学习笔记

389 阅读14分钟

Spring IOC

第一章 引言

1.什么是Spring

Spring是一个轻量级的JavaEE解决方案,整合众多优秀的设计模式
  • 轻量级

    1.对于运行环境没有额外要求
    
    2.代码移植性高
      不需要实现额外接口
    
  • JavaEE的解决方案

    Controller Service DAO 一站式服务
    
  • 整合了设计模式

    1.工厂模式
    2.代理模式
    3.模板模式
    4.策略模式
    

2.设计模式

1.广义概念
	面向对象设计中,解决特定问题的经典代码
2.狭义概念
	GOF4人帮定义的23种设计模式:工厂,适配器,装饰器,门面,代理....

3.工厂设计模式

3.1什么是工厂模式

1.概念:通过工厂类,创建对象
	   User user = new User();
2.好处:解耦合
  耦合:指代码间的强关联关系,一方的改变会影响另一方
  问题:不利于代码维护
  简单:把接口的实现类,硬编码在程序中
       UserService userService = new UserServiceImpl();

3.2简单工厂设计

public class BeanFactory {

    private static Properties properties = new Properties();

    static {
        try{
            //获取IO输入流
            InputStream in = BeanFactory.class.getResourceAsStream("/applicationContext.properties");
            //文件内容封装到Properties集合中
            properties.load(in);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static UserService getUserService(){
        UserService userService = null;
        try {
            userService =(UserService) Class.forName(properties.getProperty("userService")).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userService;
    }

    public static UserDao getUserDao(){
        UserDao userDao =null;
        try {
            userDao= (UserDao)Class.forName(properties.getProperty("userDao")).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return userDao;
    }
}

3.3通用工厂的设计

  • 问题

    简单工厂会产生大量的冗余
    
  • 通用工厂的代码

    //创建一切想要的对象
    public static Object getBean(String key){
        Object object = null;
        try {
            object=Class.forName(properties.getProperty(key)).newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return object;
    }
    

3.4通用工厂的使用方式

1. 定义类型(类)
2. 通过配置文件的配置告知工厂
	key=value
3. 通过工厂获得类的对象(反射)

4.总结

Spring本质:工厂 ApplicationContext(applicationContext.xml)

第二章 第一个Spring程序

1.环境搭建

  • Spring的jar包

    设置maven依赖
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-context -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.1.9.RELEASE</version>
    </dependency>
    
  • Spring的配置文件

    1.配置文件的放置位置:任意位置,没有硬性要求
    2.配置文件的命名:没有硬性要求,建议:applicationContext.xml
    
    思考:日后应用Spring框架时,需要进行配置文件的路径的设置
    

2.Spring的核心API

  • ApplicationContext

    作用:Spring提供的ApplicationContext这个工厂,用于对象的创建
    好处:解耦合
    
    • ApplicationContext接口类型

      接口:屏蔽实现二点差异
      非web环境:ClassPathXmlApplicationContext (main junit)
      web环境:XmlWebApplicationContext
      
    • 重量级资源

      ApplicationContext工厂的对象占用大量内存
      不会频繁的创建对象:一个应用只会创建一个工厂对象
      ApplicationContext工厂:一定是线程安全的(多线程并发访问)
      

3.程序开发

1.创建类型
2.配置文件的配置 applicationCOntext.xml
<bean id="person" class="com.hyy.basic.Person"></bean>
3.通过工厂类,获得对象
	ApplicationContext
	 	|- ClassPathApplicationContext
    //创建工厂
    ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
	//获取对象
	Person person = (Person)context.getBean("person");
	

4.细节分析

  • 名词解释

    Spring工厂创建的对象,叫做bean或者组件(componet)
    
  • Spring工厂的相关方法

    @Test
    public void test3(){
        //创建工厂
        ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
        //根据名字和类型取bean
        Person person = context.getBean("person",Person.class);
        System.out.println(person);
    
        //配置文件必须只能有一个bean
        Person person = context.getBean(Person.class);
        System.out.println(person);
    
        //获取Spring工厂配置的所有的bean标签的id值
        String[] beanDefinitionNames = context.getBeanDefinitionNames();
        for(String st:beanDefinitionNames){
            System.out.println(st);
        }
        //根据类的类型获取Spring工厂配置的所有的bean标签的id值
        String[] beanNamesForType = context.getBeanNamesForType(Person.class);
        for(String st:beanNamesForType){
            System.out.println(st);
        }
    
        //判断是否存在指定id的bean 只能判断id,不能判断name
        boolean person = context.containsBeanDefinition("person");
        
         //判断是否存在指定id的bean 能判断id和name
        boolean person = context.containsBean("person");
    }
    
  • 配置文件中需要注意的细节

    1.只配置class属性,没有id值
    <bean  class="com.hyy.basic.Person"></bean>
    a) 上述这种配置,Spring给我们创建了id值 com.hyy.basic.Person#0
    b) 应用场景:如果这个bean只需要使用一次,就可以省略id值
    			如果这个bean会使用多次,或者被其他bean引用则需要设置id值
    2.name属性
    作用:用于在Spring的配置文件中,为bean对象定义别名
    相同:
    	1.context.getBean("id|name") ---> object
    区别:
    	1.别名可以定义多个,id只能由一个值
    	2.XML的id属性的值,命名要求:必须以字母开头,数字,字母,下划线,连字符
    		   name属性的值,命名没有要求
    	  name属性会应用在特殊命名的场景下:/person
    

5.Spring工厂的底层实现原理(简易版)

Spring工厂是可以调用对象私有的构造方法创建对象

通过反射创建对象

6.思考

问题:未来在开发过程中,是不是所有的对象,都交给Spring来创建呢?
回答:理论上是,但是有特例 实体对象(entity)是由持久层框架来进行创建的。

第三章 Spring5与日志框架的整合

Spring与日志框架的整合,日志就可以在控制台中,输出Spring框架运行中的一些重要信息
好处:便于了解Spring框架的运行过程,利于程序的调试
  • Spring如何整合日志框架

    默认
        Spring1.2.3 早前都是commons-logging.jar
        Spring5.x 默认整合的日志框架 logback log4j2
    Spring5.x整合log4j
    	1.引入log4j.jar包
    	2.引入log4.properties配置文件
    
    • pom

      <!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-log4j12 -->
      <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>1.7.25</version>
          <scope>test</scope>
      </dependency>
      
      <!-- https://mvnrepository.com/artifact/log4j/log4j -->
      <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>1.2.17</version>
      </dependency>
      
    • log4j.properties

      #配置根
      log4j.rootLogger = debug,console
      ###日志输出到控制台
      log4j.appender.console=org.apache.log4j.ConsoleAppender
      log4j.appender.console.Target=System.out
      log4j.appender.console.layout=org.apache.log4j.PatternLayout
      log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n
      

第四章 注入(Injection)

1.什么是注入

通过Spring工厂及配置文件,为所创建对象的成员变量赋值

1.1 为什么需要注入

通过编码的方式,为成员变量进行赋值,存在耦合

/*
    *  用于测试属性注入
    * */
@Test
public void test6(){
    //创建工厂
    ApplicationContext context = new ClassPathXmlApplicationContext("/applicationContext.xml");
    //获取对象
    Person person = (Person) context.getBean("person");
    person.setId(1);
    person.setName("迷雾神");
    System.out.println(person);
}

1.2 如何进行注入[开发步骤]

  • 类成员变量提供set/get方法

  • 配置Spring的配置文件

    <bean id="person" class="com.hyy.basic.Person">
        <property name="id" value="10"></property>
        <property name="name" value="迷雾小神"></property>
    </bean>
    

1.3 注入好处

解耦合

2.Spring注入的原理分析(简易版)

Spring通过底层调用对象属性对应的set方法,完成成员变量的赋值,称之为set注入

第五章 Set注入详解

针对于不同类型的成员变量,在<property>标签,需要嵌套其他的标签
<property>
	<xxx>
</property>

1.JDK内置类型

1.1 Sting+8钟基本类型

<value>迷雾神</value>

1.2 数组类型

<list>
    <value>hu@qq.com.cn</value>
    <value>yang@qq.com.cn</value>
    <value>li@qq.com.cn</value>
</list>

1.3 set集合

<set>
    <value>133111</value> //根据属性字段的泛型类型
    <value>133222</value>
    <value>133444</value>
</set>

<set>
	<ref></ref>
    <set></set>
</set>

1.4 list集合

<list>
    <value>chengdu</value>
    <value>beijing</value>
    <value>shanghai</value>
</list>

1.5 Map集合

注意:map --- entry -- key有特定的标签
					 值根据类型选择对应的标签
<map>
    <entry>
        <key><value>miwushen</value></key>
        <value>1111</value>
    </entry>
    <entry>
        <key><value>miwuxiaoshen</value></key>
        <value>2222</value>
    </entry>
</map>

1.6 Properites

Properites类型 特殊的Map key=String value=String
<props>
    <prop key="key1">value1</prop>
    <prop key="key2">value2</prop>
</props>

1.7 复杂的JDK类型(Date)

需要通过自定义转换器,处理

2.用户自定义类型

2.1 第一种方式

  • 为成员变量提供set 方法

  • 配置文件中进行注入

    <bean id="userService" class="com.hyy.basic.UserServiceImpl">
        <property name="userDao" >
            <bean class="com.hyy.basic.UserDaoImpl"></bean>
        </property>
    </bean>
    

2.2 第二种方式

  • 第一种赋值方式存在的问题

    1.配置文件代码冗余
    2.被注入的对象多次创建,浪费内存资源
    
  • 为成员变量提供set 方法

  • 配置文件中进行注入

<bean id="userService" class="com.hyy.basic.UserServiceImpl">
    <property name="userDao" ref="userDao"></property>
</bean>
<bean id="userDao" class="com.hyy.basic.UserDaoImpl"></bean>

3.Set注入的简化写法

3.1 基于属性简化

JDK类型注入
<property name="name">
	<value>miwushen</value>
</property>

<property name="name" value="miwushen"></property>

3.2 基于p命名空间简化

<bean id ="person" class="com.hyy.basic.Person" p:name="迷雾神" p:id="100"></bean>
//自定义类型
<bean id="userService" class="com.hyy.basic.UserServiceImpl" p:userDao-ref="userDao"></bean>

第六章 构造注入

注入:通过Spring的配置文件,为成员变量赋值
Set注入:Spring调用Set方法,通过配置文件,为成员变量赋值
构造注入:Spring调用构造方法,通过配置文件,为成员变量赋值

1.开发步骤

  • 提供有参的构造方法

    public class Customer {
        private String name;
        private int age;
        
        public Customer(String name, int age) {
            this.name = name;
            this.age = age;
        }
    }
    
  • Spring的配置文件

    //constructor-arg的个数和顺序要与给定构造方法的参数一致
    <bean id="customer" class="com.hyy.basic.constructor.Customer">
        <constructor-arg>
            <value>迷雾神</value>
        </constructor-arg>
        <constructor-arg>
            <value>18</value>
        </constructor-arg>
    </bean>
    

2.构造方法重载

2.1 参数个数不同时

public Customer(String name) {
    this.name = name;
}
public Customer(String name, int age) {
    this.name = name;
    this.age = age;
}
通过控制<constructor-arg>标签的数量进行区分

2.2 参数个数相同时

public Customer(String name) {
    this.name = name;
}
public Customer(int age) {
    this.age = age;
}
通过在标签引入 type属性 进行类型的区分<constructor-arg type="int">标签的数量进行区分

3.注入的总结

未来的实战,应用set注入还是构造注入?
答案:set注入更多
	1.构造注入麻烦(重置)
	2.Spring底层框架底层 大量应用了set注入

第七章 反转控制与依赖注入

1.反转控制(IOC Inverse of Control)

控制:对于成员变量赋值的控制权
反转控制:把对于成员变量赋值的控制权,从代码中反转到Spring工厂和配置文件中。
	好处:解耦合
底层实现:工厂设计模式

2.依赖注入(Dependency Injection DI)

注入:通过Spring的工厂及配置文件,为对象(bean,组件)的成员变量赋值
依赖注入:当一个类需要另一个类时,就意味着依赖,一旦出现依赖,就可以把另一个作为本类的成员变量,
		最终通过Spring配置文件进行注入(赋值)

第八章 Spring工厂创建复杂对象

1.什么是复杂对象

复杂对象:指的就是不能直接通过new构造方法创建的对象
	Connection
	SqlSessionFactory

2.Spring工厂创建复杂对象的3种方式

2.1 FactoryBean接口

  • 开发步骤

    • 实现FactoryBean接口

    • Spring配置文件的配置

      # 如果Class中指定的类型 是FactoryBean接口的实现类,那么通过id值获取的是这个,类所创建的复杂类对象 Connection
      <bean id ="conn" class="com.hyy.factoryBean.ConnectionFactoryBean"/>
      

  • 细节

    • 如果就想获取FactoryBean类型的对象 加 “&”

      context.getBean("&conn")
      
    • isSingleton方法

      返回true只会创建一个对象

      返回false每一次都会创建一个对象

      问题:根据对象的特点,决定返回true(SqlSessionFactory)还是false(Connection)
      
    • 依赖注入的体会(DI)

      <bean id ="conn" class="com.hyy.factoryBean.ConnectionFactoryBean">
          <property name="drive" value="com.mysql.jdbc.Driver"></property>
          <property name="url" value="jdbc:mysql://localhost:3306/testspring?serverTimezone=UTC"></property>
          <property name="user" value="root"></property>
          <property name="password" value="root"></property>
      </bean>
      
  • FactoryBean的实现原理(简易版)

    接口回调
    1.为什么Spring要规定FactoryBean接口实现,并且getObject()?
    2.context.getBean("conn")获得是复杂对象Connection 而没有获得ConnectionFactoryBean(&)
    
    Spring内部运行流程
    1.通过conn获得ConnectionFactoryBean类的对象,通过instanof判断出是FactoryBean接口实现类
    2.Spring按照规定 getObject() ----> Connection
    3.返回Connection
    

  • FactoryBean总结

    Spring中用于创建复杂对象的一种方式,也是Spring原生提供。整合其他框架,大量应用FactoryBean
    

2.2 实例工厂

1.避免Spring框架的侵入
2.整合遗留系统
  • 开发步骤

    <bean id ="connFactory" class="com.hyy.factoryBean.ConnectionFactory"></bean>
    
    <bean id ="conn" factory-bean="connFactory" factory-method="getConnection">
    

2.3 静态工厂

  • 开发步骤

    <bean id ="conn" class="com.hyy.factoryBean.StaticConnectionFactory factory-method="getConnection">
    

3.Spring工厂创建对象的总结

第九章 控制Spring工厂创建对象的次数

1.如何控制简单对象的创建次数

<bean id="account" class="com.hyy.scope.Account" scope="prototype"></bean>
scope="prototype" :每一次都会创建新的对象
scope="singleton" :只会创建一次对象 (默认)

2.如何控制复杂对象的创建次数

通过FactoryBean的isSingleton()方法来控制

3.为什么要控制对象的创建次数

好处:节省不必要的内存浪费
  • 什么样的对象只创建一次

    1.SqlSessionFactory
    2.DAO
    3.Service
    
  • 什么样的对象每一次都要创建

    1.Connection
    2.SqlSession | Session
    3.Struts Action
    

第十章 对象的生命周期

1.什么是对象的生命周期

指的一个对象创建,存活,消亡的一个完整过程

2.为什么要学习对象的生命周期

由Spring负责对象的创建,存活,销毁,了解生命周期,有利于我们使用好Spring创建的对象

3.生命周期的三个阶段

  • 创建阶段

    Spring工厂何时创建对象
    
    • scope="singleton"

      Spring工厂创建的同时,创建对象
      
      注意:获取对象的时候,再创建对象
      加入 lazy-init="true"
      <bean id="product" class="com.hyy.life.Product" lazy-init="true" scope="singleton"/>
      
    • scope="prototype"

      Spring工厂在获取对象同时,创建对象
      context.getBean("")
      
  • 初始化阶段

    Spring工厂再创建完对象后,调用对象的初始化方法
    1.初始化方法提供:通过需求,用代码提供初始化方法,完成初始化操作
    2.初始化方法调用:Spring工厂来进行调用
    
    • InitializingBean接口

      public void afterPropertiesSet() throws Exception {
          System.out.println("Product.afterPropertiesSet");
      }
      
    • 对象中提供普通方法

      public void myInit(){
          
      }
      //配置文件
      <bean id="product" class="com.hyy.life.Product" init-method="myInit"/>
      
    • 细节分析

      1.如果一个对象即实现了InitializingBean接口,又提供了普通的初始化方法

      1.先执行InitializingBean
      2.在执行普通初始化方法
      

      2.注入一定发生在初始化之前

      3.什么叫做初始化操作

      1.资源的初始化:数据库 IO 网络...
      
  • 销毁阶段

    Spring销毁对象前,会调用对象的销毁方法,完成销毁操作
    
    1.Spring什么时候销毁所创建的对象
     context.close();
    2.销毁方法:程序员根据自己的需求,定义销毁方法,完成销毁操作
    	  调用:Spring工厂完成调用
    
    • DisposableBean

      public void destroy() throws Exception {
          System.out.println("Product销毁了");
      }
      
    • 定义一个普通的销毁方法

      public void myDestory(){
          
      }
      //配置文件
      <bean id="product" class="com.hyy.life.Product" destory-method="myDestory"/>
      
    • 细节分析

      1. 销毁方法的操作只适用于scope="singleton"的情况

      2. 什么叫做销毁操作

        主要指的是 资源的释放操作 io.close()  connection.close()
        

4.总结

第十一章 配置文件参数化

把Sping配置文件中需要经常修改的字符串信息,转移到一个更小的配置文件中

1.Spring配置文件中存在需要经常修改的字符串?
	存在  以数据库连接相关的参数 代表
2.经常变化的字符串,在Spring的配置中,直接修改,不利于项目维护
3.转移到更小的配置文件中(.properties)中
  利于项目维护

1.配置文件参数的开发步骤

  • 提供一个小的配置文件(.properties)

    名字:随便
    放置位置:随便
    
    jdbc.driverClassName=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/testspring?serverTimezone=UTC
    jdbc.userName=root
    jdbc.password=root
    
  • Spring配置文件与小的配置文件整合

    <context:property-placeholder location="classpath:db.properties"/>
    
  • Spring配置文件中通过${key}获取小配置文件对应的值

    <context:property-placeholder location="classpath:db.properties"/>
    
    <bean id ="conn" class="com.hyy.factoryBean.ConnectionFactoryBean">
        <property name="drive" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}}"></property>
        <property name="user" value="${jdbc.userName}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>
    

第十二章 自定义类型转换器

1.类型转换器

作用:Spring通过类型转换器把配置文件中字符串类型的数据,转换成了对象中成员变量对应类型的数据

2.自定义类型转换器

原因:当Spring内部没有提供特定类型的转换器,而还需要使用的时候,那么就需要自己定义类型转换器
  • 类 implements Converter接口

    public class MydateConvert implements Converter<String ,Date> {
        /*
        * convert 作用:String ---> Date
        * param :代表配置文件中的日期的字符串 value="2020-04-01"
        * return:把转换好的Date作为convert方法的返回值后,Spring自动的为birthday属性进行注入
        * */
        public Date convert(String s) {
            Date date=null;
            try {
                SimpleDateFormat format =new SimpleDateFormat("yyyy-MM-dd");
                date = format.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
        }
    }
    
  • Spring的配置文件进行配置

    • MydateConvert对象创建出来

      <bean id="myDateCOnvert" class="com.hyy.convert.MydateConvert" />
      
    • 类型转换器的注册

      目的:告知Spring框架,我们所创建的MydateConvert是一个类型转换器
      <!--用于注册类型转换器-->
      <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
          <property name="converters">
              <set>
                  <ref bean="myDateCOnvert"></ref>
              </set>
          </property>
      </bean>
      

3.细节

  • MydateConvert 中的日期的格式,通过依赖注入的方式,由配置文件来完成

    public class MydateConvert implements Converter<String ,Date> {
        private String pattern;
        /*
        * convert 作用:String ---> Date
        * param :代表配置文件中的日期的字符串 value="2020-04-01"
        * return:把转换好的Date作为convert方法的返回值后,Spring自动的为birthday属性进行注入
        * */
        public Date convert(String s) {
            Date date=null;
            try {
                SimpleDateFormat format =new SimpleDateFormat(pattern);
                date = format.parse(s);
            } catch (ParseException e) {
                e.printStackTrace();
            }
            return date;
        }
    
        public String getPattern() {
            return pattern;
        }
    
        public void setPattern(String pattern) {
            this.pattern = pattern;
        }
    }
    
    <bean id="myDateCOnvert" class="com.hyy.convert.MydateConvert" >
        <property name="pattern" value="yyyy-MM-dd"></property>
    </bean>
    
  • ConversionServiceFactoryBean定义id属性时 值必须为:conversionService

  • Spring框架内置了日期类型的转换器

    日期格式:2020/05/01  (不支持:2020-05-01)
    

第十三章 后置处理Bean

BeanPostProcessor作用:对Spring工厂所创建的对象,进行再加工

注意:BeanPostProcessor作用接口

工厂创建对象过程

BeanPostProcessor实现原理

我们要实现BeanPostProcessor接口中规定的方法

Object postProcessBeforeInitialization(Object bean, String beanName)
作用:Spring创建完对象后,进入注入后,可以运行Before方法进行加工
获得Spring创建好的对象:通过方法的参数
最终通过返回值交给Spring框架

Object postProcessAfterInitialization(Object bean, String beanName)
作用:Spring执行完对象的初始化操作后,可以运行After方法进行加工
获得Spring创建好的对象:通过方法的参数
最终返回值交给Spring框架

实战中:
很少处理初始化操作:没必要区分Before After。只需实现其中一个方法就可以
  • BeanPostProcessor开发步骤

    1. 类实现BeanPostProcessor接口

      public class MyBeanPostProcessor implements BeanPostProcessor {
      
          @Override
          public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
              return bean;
          }
      
          @Override
          public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
              Categroy categroy = (Categroy) bean;
              categroy.setName("迷雾小神");
              return bean;
          }
      }
      
    2. Spring配置文件中进行配置

      <bean id="myBeanPostProcessor" class="com.hyy.beanpost.MyBeanPostProcessor"/>
      
    3. BeanPostProcessor细节

      BeanPostProcessor会对Spring工厂中所有创建的对象进行加工