黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置

  • Spring的学习内容
spring共四天
	第一天:spring框架的概述以及spring中基于XML的IOC配置
	第二天:spring中基于注解的IOC和IOC的案例
	第三天:spring中的aop和基于XML以及注解的AOP配置
	第四天:spring中的JdbcTemlate以及Spring事务控制
  • 本文参考文档:《Spring5第一天.pdf》
  • 本文代码项目:spring_day01_jdbc、spring_day01_spring_introduce、spring_day01_factory、spring_day01_bean、spring_day01_di(路径:G:\idea_java_project\)

今日内容

1、spring的概述
	1)spring是什么
	2)spring的两大核心
	3)spring的发展历程和优势
	4)spring体系结构
	
2. 程序的耦合及解耦
	1)曾经案例中问题
	2)工厂模式解耦

3. IOC概念和spring中的IOC
	1) spring中基于XML的IOC环境搭建

4. 依赖注入(Dependency Injection)

1、spring的概述以及体系结构
  参考文档以及视频的介绍。
  注意,我们使用Maven来构建我们的工程,因此我们不需要Spring的开发jar包,只需要在Maven中导入Spring的坐标即可。
  下面是Spring的开发包目录
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第1张图片

  下面是Spring的体系结构
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第2张图片

2、IOC 的概念和作用
2.1 程序的耦合和解耦
  什么是程序耦合?(见文档—2.1.1 什么是程序的耦合
  编写jdbc的代码用于程序间的耦合(见视频7,项目spring_day01_jdbc 的分析)。
  耦合的分析如下代码:

package com.lkj.jdbc;

/**
 * 程序的耦合
 *      耦合:程序间的依赖关系,包括:
 *          1)类之间的依赖
 *          2)方法间的依赖
 *
 *      解耦: 降低程序间的依赖关系
 *      实际开发中: 应该做到,编译期不依赖,运行时才依赖。
 *
 *      解耦的思路:
 *          第一步:使用反射来创建对象,而避免使用new关键字。
 *              如下分析,new创建对象就会依赖于某一个类或者jar包,会在编译期间就产生依赖,因为编译期间就会检查使用的类;
 *              而通过反射的方式,则只是依赖某个字符串,编译的时候不检测,运行的时候才会产生依赖
 *
 *          第二步:通过读取配置文件来获取要创建的对象全限定类名
 *              如下,我们的 Class.forName("com.mysql.jdbc.Driver"); 如果想使用其他数据库,仍然需要修改字符串的内容。
 *              而这一需要修改代码。如果我们通过配置文件的方式读取类型,就只需要修改配置文件即可!
 *
 */
public class JdbcTest1
{
    public static void main(String[] args) throws Exception
    {
        /**
         * 如果我们将mysql的依赖删除,那么如果使用 DriverManager.registerDriver(new com.mysql.jdbc.Driver());在编译期间就会出异常。
         * 如果使用:Class.forName("com.mysql.jdbc.Driver"); 此时不需要依赖于具体的类或者jar包,即使没有mysql的驱动,
         * 也只是报运行时异常:ClassNotFoundException , 而不会在编译期间出错,这样可以解耦。
         */
        //1.注册驱动
//        DriverManager.registerDriver(new com.mysql.jdbc.Driver());//这种方法在编译期就依赖于某个类或者某个jar包
        //在这里,“com.mysql.jdbc.Driver”只是一个字符串,我们不需要像上面一样依赖于具体的某个驱动类,这个类就可以相对独立出来
        Class.forName("com.mysql.jdbc.Driver");

        // 2.获取连接
        Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/lkj_spring", "root", "root");

        //3.获取操作数据库的预处理对象
        PreparedStatement ps = conn.prepareStatement("select * from account");

        //4.执行SQL,得到结果集
        ResultSet resultSet = ps.executeQuery();

        //5.遍历结果集
        while(resultSet.next())
        {
            System.out.println(resultSet.getString("name"));
        }

        //6.释放资源
        resultSet.close();
        ps.close();
        conn.close();

    }
}

  耦合指的就是程序间的依赖关系,是对程序模块间关联程度的度量。降低耦合的方式:

 解耦的思路:
        第一步:使用反射来创建对象,而避免使用new关键字。
        第二步:通过读取配置文件来获取要创建的对象全限定类名。

2.2 曾经代码中的问题分析
  这部分见视频10、项目spring_day01_factory 分析。

//问题如下
		//表现层在调用业务层方法的时候,需要new创建业务层对象
        AccountService accountService = new AccountServiceImpl();
        //业务层在创建持久层方法的时候,需要new创建持久层的对象
    	private AccountDao accountDao = new AccountDaoImpl();
   就像前面说的,凡是设置new对象的,编译期间类之间就会产生依赖关系,这样耦合性太强。

  前面提到使用工厂模式可以实现解耦,工厂模式是如何运行的?(视频11,12)
  我们使用一个创建Bean对象的工厂BeanFactory类,用来生成service以及dao的Bean对象。这个BeanFactory中使用反射创建service以及dao的对象,避免了编译期的依赖;同时通过配置文件来获取类的全限定类名,使得我们想获得不同对象的时候可以不需要修改代码,而修改配置文件即可。这样便减少了类之间依赖,降低了程序的耦合性。代码如下:

package com.lkj.factory;

/**
 * 一个创建Bean对象的工厂
 *
 * Bean:在计算机英语中,有可重用组件的含义。
 * JavaBean:用java语言编写的可重用组件。
 * javabean的范围远大于实体类,实体类只是java可重用组件的一部分。
 * 我们的业务层service以及持久层dao的类都可以看作可重用组件,也就是javabean。
 *
 * 这里这个创建Bean对象的工厂,实际上就是创建我们的service和dao对象的。
 * -------如何创建service以及dao的对象?
 *   第一个:需要一个配置文件来配置我们的service和dao
 *           配置的内容:唯一标识=全限定类名(key=value)
 *   第二个:通过读取配置文件中配置的内容,反射创建对象
 *
 *   我的配置文件可以是xml也可以是properties
 */
public class BeanFactory
{
    /*
    首先,由于我们要创建的是AccountServiceImpl以及AccountDaoImpl的对象,因此我们的配置文件bean.properties的内容就是这2个类的全限定类名。
    accountService=com.lkj.service.impl.AccountServiceImpl
    accountDao=com.lkj.dao.impl.AccountDaoImpl

    下面我们读取 bean.properties 文件
     */

    //定义一个Properties对象的引用(先不实例化,等到加载类的时候再通过静态代码块实例化)
    private static Properties pro;//这个引用要在静态代码块中使用,将其设置为static

    //使用静态代码块为Properties对象赋值
    static{
        try
        {
            //实例化对象——这里new就产生编译期的依赖,但是这是不可避免的,我们只能减少耦合而无法消除耦合
            pro = new Properties();
            /*
            获取properties文件的流对象
                这个时候我们不要使用FileInputStream等读取流来读取properties文件,因为这个时候我们不知道文件名应该写什么。
                比如我们写为项目的文件路径 src/bean.properties ,如果是web项目,编译后只有classes目录,没有src,无法使用;
                如果我们写为磁盘的绝对路径,这个项目可以找到,但是我们不能保证工程部属后,部属的机器都有相应的盘。

                因此,参照之前说的,我们读取文件,要么使用类加载器,要么使用ServletContext对象的getRealPath方法。
             */
            //这里使用类加载器读取bean.properties
            InputStream rs = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            pro.load(rs);//将流加载到Properties中
        }
        catch (IOException e)
        {
            //没有bean.properties文件,任何对象都无法创建,直接抛出错误
            throw new ExceptionInInitializerError("初始化Properties失败");
        }
    }

    /**
     * 根据bean的名称获取对象——返回Object对象,因为我们不知道要获取哪一个Bean对象,返回Object才能兼容所有类型
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName)
    {
        Object bean = null;
        try
        {
            //获取Bean对象的全限定类名
            String beanPath = pro.getProperty(beanName);
            //通过反射获取Bean对象
            bean = Class.forName(beanPath).newInstance();
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        //如果获取Bean对象失败,那么只会返回null,成功则返回相应对象
        return bean;
    }
}

  当我们使用工厂类BeanFactory来生产Bean对象(AccountService或者AccountDao),此时依赖变为运行时的依赖。比如此时将AccountDaoImpl删除,出现运行时异常:ClassNotFoundException: com.lkj.dao.impl.AccountDaoImpl (这是因为我们没有直接new创建AccountDaoImpl对象 ,而是通过其全限定类名反射创建其对象,在创建的过程中没有使用到这个AccountDaoImpl 的名字,因此缺少这个类的问题会在运行时抛出)。而之前使用new创建Bean对象的时候,我们发现编译期间就会出错(程序直接报错)。

  对于上面的代码,我们如果使用工厂多次生产对象,每次生产的对象都不同,也就是生产Bean对象的过程是多例的。
  重要!这种多例的模式会出现问题(如视频12所指出的问题):由于每次都生成不同的对象,每个对象都有独立的实例,因此类对象在创建的时候,都会重新初始化成员变量。因此问题中,AccountServiceImpl类的成员变量 i,每次生成AccountServiceImpl对象的时候,都会重新初始化这个i ,因此即使我们每个AccountServiceImpl类的对象都执行 i++ ,每个对象对应的i都是1。
  如果我们生产的过程是单例的,既每次生产同一个AccountServiceImpl类对象,那 i 只会初始化一次,并且每次执行saveAccount方法都会 i+1。
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第3张图片
  我们之前说过,如果使用的是单例对象,当多个线程访问操作类的成员变量的时候,所有的线程都会操作同一个对象的成员变量,比如A线程给成员变量+1,而B线程同时-1,这样便出现线程安全问题。因此我们之前讲Servlet的时候说过,尽量不要定义成员变量。而多例对象则没有这个问题,因为多例对象每个对象都有自己对应的初始化的成员变量。

  但是,我们的service与dao中没有需要在方法中进行操作的成员变量,因此我们的service与dao中不存在线程安全问题,他们在创建对象的时候,可以直接创建单例对象。比如我们将上面的 i 创建到saveAccount中,即使是单例对象,多线程调用这个方法,每次 i 都会重新初始化,不会出现多个线程同时操作同一个变量的问题。

  我们知道,对象被创建多次,执行效率没有单例对象高。因此我们试图创建service与dao的单例对象。但是BeanFactory中,使用newInstance()创建对象,表示每次都会调用默认构造函数创建对象,因此我们需要对代码进行调整,使得BeanFactory只能newInstance() 一次。这个时候,我们只newInstance一次,如果我们不将这个对象存储,JVM可能将这个对象作为垃圾回收,下次无法使用这个对象,即使创建新的对象,也不是同一个对象。因此newInstance后,应该将这个对象存储起来。如下代码

package com.lkj.factory;

import java.io.IOException;
import java.io.InputStream;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 一个创建Bean对象的工厂
 *
 * Bean:在计算机英语中,有可重用组件的含义。
 * JavaBean:用java语言编写的可重用组件。
 * javabean的范围远大于实体类,实体类只是java可重用组件的一部分。
 * 我们的业务层service以及持久层dao的类都可以看作可重用组件,也就是javabean。
 *
 * 这里这个创建Bean对象的工厂,实际上就是创建我们的service和dao对象的。
 * -------如何创建service以及dao的对象?
 *   第一个:需要一个配置文件来配置我们的service和dao
 *           配置的内容:唯一标识=全限定类名(key=value)
 *   第二个:通过读取配置文件中配置的内容,反射创建对象
 *
 *   我的配置文件可以是xml也可以是properties
 */
public class BeanFactory
{
    /*
    首先,由于我们要创建的是AccountServiceImpl以及AccountDaoImpl的对象,因此我们的配置文件bean.properties的内容就是这2个类的全限定类名。
    accountService=com.lkj.service.impl.AccountServiceImpl
    accountDao=com.lkj.dao.impl.AccountDaoImpl

    下面我们读取 bean.properties 文件
     */

    //定义一个Properties对象的引用(先不实例化,等到加载类的时候再通过静态代码块实例化)
    private static Properties pro;//这个引用要在静态代码块中使用,将其设置为static

    //定义一个Map,用于存放我们要创建的对象。我们把它称之为容器(同样先不实例化)
    private static Map<String,Object> beans;

    //使用静态代码块为Properties对象赋值
    static{
        try
        {
            //实例化对象——这里new就产生编译期的依赖,但是这是不可避免的,我们只能减少耦合而无法消除耦合
            pro = new Properties();
            /*
            获取properties文件的流对象
                这个时候我们不要使用FileInputStream等读取流来读取properties文件,因为这个时候我们不知道文件名应该写什么。
                比如我们写为项目的文件路径 src/bean.properties ,如果是web项目,编译后只有classes目录,没有src,无法使用;
                如果我们写为磁盘的绝对路径,这个项目可以找到,但是我们不能保证工程部属后,部属的机器都有相应的盘。

                因此,参照之前说的,我们读取文件,要么使用类加载器,要么使用ServletContext对象的getRealPath方法。
             */
            //这里使用类加载器读取bean.properties
            InputStream rs = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
            pro.load(rs);//将流加载到Properties中

            //实例化容器beans
            beans = new HashMap<String, Object>();
            //取出配置文件中所有的Key——使用Properties的keys()方法
            Enumeration<Object> keys = pro.keys();
            //遍历枚举,取出bean.properties中的每一个键,通过键取出值,值既要创建的Bean类的全限定类名
            while (keys.hasMoreElements())
            {
                //nextElemrnt()方法返回Object类型,将其转换为String类型
                String key = keys.nextElement().toString();
                String beanPath = pro.getProperty(key);
                Object value = Class.forName(beanPath).newInstance();//创建Bean类的对象

                //把key和value存入容器中——key同样是原来bean.properties中的key,而值是key对应的Bean类的对象
                beans.put(key,value);
            }

        }
        catch (Exception e)
        {
            //没有bean.properties文件,任何对象都无法创建,直接抛出错误
            throw new ExceptionInInitializerError("初始化Properties失败");
        }
    }

    /**
     * 根据bean的名称获取对象
     * 此时,由于静态代码块在初始化的时候,已经将bean.properties中要创建的Bean对象创建并存储到集合beans中,
     * 我们只需从beans集合中获取即可。而且由于静态代码块只在BeanFactory进入内存的时候初始化一次,
     * 既我们即使多次通过getBean获取相应的对象,也只会获取同一个对象(单例)
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName)
    {
        return beans.get(beanName);
    }


    /**
     * 根据bean的名称获取对象——返回Object对象,因为我们不知道要获取哪一个Bean对象,返回Object才能兼容所有类型
     * @param beanName
     * @return
     */
//    public static Object getBean(String beanName)
//    {
//        Object bean = null;
//        try
//        {
//            //获取Bean对象的全限定类名
//            String beanPath = pro.getProperty(beanName);
//            //通过反射获取Bean对象
//            bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造函数创建对象
//        }
//        catch (Exception e)
//        {
//            e.printStackTrace();
//        }
//        //如果获取Bean对象失败,那么只会返回null,成功则返回相应对象
//        return bean;
//    }
}

  在这种情况下,每次获取的都是同一个AccountServiceImpl对象,因此 i 只加载一次。(i定义为类成员)此时多线程访问可能出现线程安全问题(多个线程访问同一个对象)。
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第4张图片
  如果将 i 定义到saveAccount方法中,线程安全问题便解决。
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第5张图片
  在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候, 让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。在接下来的使用的时候,直接拿过来用就好了。那么,这个读取配置文件, 创建和获取三层对象的类就是工厂。


2.3 IOC的概念和作用
  IOC控制反转的涵义(见视频15,文档-2.1.4 控制反转-Inversion Of Control):我们在使用工厂创建对象的时候,将控制权从自己new一个对象,转换给了工厂,工厂获得控制权,为我们生成一个对象,因此这个过程称之为IOC控制反转。这种方式减低了程序间的依赖关系,削减程序间的耦合。
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第6张图片
  控制反转(Inversion of Control,英文缩写为lOC)把创建对象的权利交给框架(工厂),是框架的重要特征,并非面向对象编程的专用术语。它包括依赖注入(Dependency Injection,简称Dl)和依赖查找(Dependency Lookup)。
  明确 ioc 的作用:削减计算机程序的耦合(解除我们代码中的依赖关系)


3、使用 spring 的 IOC 解决程序耦合
  明确:IOC不能实现数据库的CRUD,也不能实现表现层的请求参数封装,IOC的作用是削减计算机程序的耦合,必须明确这个概念。
  前面已经实现工厂模式解耦,Spring就是使用配置的方式来实现我们刚刚的BeanFactory的代码操作。(准备过程见视频16的解析)

Spring要查看的文档:Spring Framework Documentation,在Spring开发包下面的路径为:spring-framework-5.0.2.RELEASE-dist\spring-framework-5.0.2.RELEASE\docs\spring-framework-reference\index.html
我们已经将其添加至浏览器的移动设备书签。

3.1 Spring环境搭建和入门
  具体过程见视频17的解析,我们这里只对重要的细节作记录。(Maven工程只需添加Spring的坐标,不需要导入真正的jar包。)
  我们Maven工程中Spring的jar包与使用jar包导入方式Spring的jar包的区别(视频17-3.40)
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第7张图片
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第8张图片
黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第9张图片
  这部分的注释都在spring_day01_spring_introduce项目(1-3) 以及 spring_day01_bean项目(4),注意查看项目的代码。

3.2 需要注意的细节

1、使用Spring,我们只需要创建配置文件,将要创建对象的类的 全限定类名 以及类名的 唯一标志 存储到配置文件中。使用的时候通过 new ClassPathXmlApplicationContext("配置文件") 获取Spring的核心容器 ApplicationContext ,我们通过唯一标志就可以来获取核心容器中相应的类的对象。

//---------------------------------------------------------

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

//---------------------------------------------------------

3、核心容器的两个接口引发出的问题:
     ApplicationContext:     单例对象适用              采用此接口
     它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。
     
     BeanFactory:            多例对象使用
     它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候根据id获取对象了,什么时候才真正的创建对象。

	注意:BeanFactory 才是 Spring 容器中的顶层接口。ApplicationContext 是它的子接口。

	1)我们的service以及dao没有类成员,因此他们不存在线程安全问题,这样我们可以使用单例模式来创建他们的对象。 如果是单例模式创建对象,什么时候创建,都只会创建一个对象,因此当容器ApplicationContext一创建,我们选择立即创建对象。
	
	2)在多例模式中,我们会创建多个对象,如果一开始就创建对象,后面还会创建对象,那我们还不如什么时候使用对象就什么时候创建。
	
//---------------------------------------------------------

4、spring对bean的管理细节(项目spring_day01_bean)
   1) 创建bean的三种方式
   	第一种方式:使用默认构造函数创建。
    在spring的配置文件中使用bean标签,配以id和class属性之后,且没有其他属性和标签时。采用的就是默认构造函数创建bean对象,此时如果类中没有默认构造函数,则对象无法创建。
    <bean id="accountService" class="com.lkj.service.impl.AccountServiceImpl"></bean>
   
   
   	第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
    我们在实际开发过程中,有可能使用到别人写好的类,他们可能存在于jar包中,我们无法对其进行修改,这些类中可能就没有默认构造函数。这种情况下,我们就没办法使用第一种方式来创建jar包中类的对象。
    同时,存在这样的的情况,我们要使用普通工厂类中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器)
	如下:我们模拟一个工厂类
	//-------------------------------------------------------
	
//模拟一个工厂类(该类可能是存在于jar包中的,我们无法通过修改源码的方式来提供默认构造函数)
public class InstanceFactory
{
  /*
  此时这个InstanceFactory是jar包中的类,我们如何获取AccountServiceImpl的对象?

    如果我们使用前面的方法创建对象,我们创建的是 InstanceFactory 的对象,而我们想获取的是 InstanceFactory 中getAccountService方法所获取的返回值对象,因此不能用第一种方式创建对象。
    第一种方式创建 —— 
   */
    public AccountService getAccountService(){
        return new AccountServiceImpl();
    }
}
	//--------------------------------------------------------
    <bean id="instanceFactory" class="com.lkj.factory.InstanceFactory"></bean>     先获取工厂类的对象
    <bean id="accountService" factory-bean="instanceFactory" factory-method="getAccountService"></bean>

    这种方式下,我们通过 ApplicationContext 的 getBean("accountService") 方法,通过id="accountService"找到工厂类的名称 factory-bean 以及获取相应对象的方法factory-method,此时第一个<bean>标签已经创建工厂类 factory-bean 的对象,我们通过这个对象就可以调用相应的方法factory-method
来获取这个方法所创建的对象。
    实际上,在加载 bean.xml 配置文件的时候,相应的instanceFactory对象已经创建,并且调用getAccountService方法获取 AccountService 对象存放到Spring的容器中。
    注意,下面的 factory-bean="instanceFactory"  用来找到上面 <bean> 的 id="instanceFactory",他们的值要相同。
	
	
	第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入spring容器)
    对于第二种方法,如果工厂类中获取对象的方法是静态方法,则应如下设置:
    <bean id="accountService" class="com.itheima.factory.StaticFactory" factory-method="getAccountService"></bean>
    这种方式下,我们通过 ApplicationContext 的 getBean("accountService") 方法,通过id="accountService"找到工厂类的全限定类名 class="com.itheima.factory.StaticFactory",由于这个工厂类中的 getAccountService 方法是静态的,因此我们没有必要创建工厂类的对象,而是直接通过工厂类的全限定类名,直接调用 getAccountService 方法来获取相应的对象。
	
   	
   2) bean对象的作用范围	调整
		首先明确,Spring所创建的Bean对象,默认情况下是单例的。
        bean标签的scope属性:
            作用:用于指定bean的作用范围     取值: 常用的就是单例的和多例的
                singleton:单例的(默认值)
                prototype:多例的
                request:作用于web应用的请求范围 (既在web项目的请求范围内,才能通过这个<bean>标签来创建Bean类的对象)
                session:作用于web应用的会话范围
                global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是session(见视频21-4.40解析,如下图13) bean对象的生命周期
  	 bean对象的生命周期
       单例对象
           出生:当容器 ApplicationContext 创建时对象出生
           活着:只要容器还在,对象一直活着
           死亡:容器销毁,对象消亡
           总结:单例对象的生命周期和容器相同

       多例对象
           出生:当我们使用对象时spring框架为我们创建
           活着:对象只要是在使用过程中就一直活着。
           死亡:当对象长时间不用,且没有别的对象引用时,由Java的垃圾回收器回收(Spring不知道我们上面时候用完,只能等待JVM的CG回收)

黑马就业班——Spring框架:part1 -- Spring框架的概述以及Spring中基于XML的IOC配置_第10张图片

3.3 spring 的依赖注入(参考项目 spring_day01_di)

spring中的依赖注入:Dependency Injection
   1、IOC的作用:降低程序间的耦合(依赖关系)。但是程序间的依赖不可能消除,程序间必然存在一些依赖。比如web层调用service,service调用dao。

    2、什么是依赖关系:在当前类需要用到其他类的对象。
    依赖关系的管理:以后都交给spring来维护。
        依赖关系由spring为我们提供,我们只需要在配置文件中说明,既在当前类中告诉Spring我们要使用哪一个类的对象,spring就会为我们提供。

    依赖关系的维护:就称之为依赖注入。
        能注入的数据:有三类
            基本类型和String
            其他bean类型(在配置文件中或者注解配置过的bean)
            复杂类型/集合类型
         注入的方式:有三种
            第一种:使用构造函数提供
            第二种:使用set方法提供
            第三种:使用注解提供

  • 注意区分spring创建bean对象的3种方式与依赖注入的3种方式。其实依赖注入也是在创建bean对象的时候执行的(使用第一种方式创建对象的时候,给对象内的属性进行依赖注入),在创建bean对象的时候,通过依赖注入,给bean对象中的成员变量赋值(成员变量可以是基本数据类型,也可以是bean、集合类型!)
      如我们创建AccountServiceImpl对象,而AccountServiceImpl中又有一个AccountDao类型的属性,那么通过依赖注入,可以在创建AccountServiceImpl对象的时候,给这个AccountDao类型的属性赋值!

  依赖注入: Dependency Injection。 它是 spring 框架核心 ioc 的具体实现。我们的程序在编写时, 通过控制反转, 把对象的创建交给了 spring,但是代码中不可能出现没有依赖的情况。ioc 解耦只是降低他们的依赖关系,但不会消除。 例如:我们的业务层仍会调用持久层的方法。
那这种业务层和持久层的依赖关系, 在使用 spring 之后, 就让 spring 来维护了。简单的说,就是坐等框架把持久层对象传入业务层,而不用我们自己去获取

  • 依赖注入就是依赖关系的维护
构造函数注入

  顾名思义,就是使用类中的构造函数,给成员变量赋值。注意,赋值的操作不是我们自己做的,而是通过配置的方式,让 spring 框架来为我们注入。既,我们在创建一个类对象的时候,同时通过这个类的构造函数,可以给这个类中的成员变量赋值。我们将创建对象并通过构造函数给成员变量赋值的过程交给Spring来完成。创建对象的过程中,当前类与创建对象的类之间形成依赖,这就是依赖注入
  要创建对象的AccountServiceImpl 类

public class AccountServiceImpl implements AccountService
{
    /*
    1、注入的数据有:name(String)、age(基本数据类型)、Date(Bean类型)
    2、如果是经常变化的数据,并不适用于注入的方式。
        如果这些数据不常变化,当2个类之间产生依赖关系,可以使用spring的依赖注入,来维护这些数据。
     */
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl(String name,Integer age,Date birthday){
        this.name = name;
        this.age = age;
        this.birthday = birthday;
    }

    public void  saveAccount(){
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }
}

  构造函数注入


    <bean id="accountService" class="com.lkj.service.impl.AccountServiceImpl">
        <constructor-arg name="name" value="张三">constructor-arg>
        <constructor-arg name="age" value="18">constructor-arg>
        <constructor-arg name="birthday" ref="now">constructor-arg>
    bean>

    
    
    <bean id="now" class="java.util.Date">bean>
set方法注入

  要创建对象的AccountServiceImpl2类(我们需要生成每一个成员变量的setter方法,通过在标签配置这个setter方法来进行赋值)

public class AccountServiceImpl2 implements AccountService
{
    //如果是经常变化的数据,并不适用于注入的方式
    private String name;
    private Integer age;
    private Date birthday;

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public void  saveAccount(){
        System.out.println("service中的saveAccount方法执行了。。。"+name+","+age+","+birthday);
    }
}

  set方式注入


    <bean id="accountService2" class="com.lkj.service.impl.AccountServiceImpl2">
        <property name="name" value="李四">property>
        <property name="age" value="21">property>
        <property name="birthday" ref="now">property>    
    bean>
注入集合数据(复杂类型的注入)

  要创建对象的AccountServiceImpl3类

public class AccountServiceImpl3 implements AccountService
{
    //下面我们尝试注入复杂类型,采用set方法注入
    private String[] myArr;
    private List<String> myList;
    private Set<String> mySet;
    private Map<String,String> myMap;
    private Properties myProps;

    public void setMyArr(String[] myArr) {
        this.myArr = myArr;
    }

    public void setMyList(List<String> myList) {
        this.myList = myList;
    }

    public void setMySet(Set<String> mySet) {
        this.mySet = mySet;
    }

    public void setMyMap(Map<String, String> myMap) {
        this.myMap = myMap;
    }

    public void setMyProps(Properties myProps) {
        this.myProps = myProps;
    }

    public void  saveAccount(){
        System.out.println(Arrays.toString(myArr));
        System.out.println(myList);
        System.out.println(mySet);
        System.out.println(myMap);
        System.out.println(myProps);
    }
}

  set方式注入


    <bean id="accountService3" class="com.lkj.service.impl.AccountServiceImpl3">
        <property name="myArr">
            <array>
                <value>AAAvalue>
                <value>BBBvalue>
                <value>CCCvalue>
            array>
        property>

        <property name="myList">
            <list>
                <value>AAAvalue>
                <value>BBBvalue>
                <value>CCCvalue>
            list>
        property>

        <property name="mySet">
            <set>
                <value>AAAvalue>
                <value>BBBvalue>
                <value>CCCvalue>
            set>
        property>

        <property name="myMap">
            <map>     
               <entry key="tsetA" value="aaa">entry>
                <entry key="testB">
                    <value>bbbvalue>
                entry>
            map>
        property>

       <property name="myProps">
           <props>
               <prop key="testC">cccprop>
           props>
       property>
    bean>

你可能感兴趣的:(Spring,spring)