03.Spring的 IOC 和 DI

IOC的概念和作用

这两行代码就明显的揭露出IOC的含义

//  private IAccountDao dao = new AccountDaoImpl();
    private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

这两种代码代表两种截然不同创建对象的方式
当我们用new创建对象的时候 我们APP直接和资源取得联系 他们直接有必然的联系 资源独立和应用独立变得很难 它有明显的依赖关系
当我们用第二种方式创建对象的时候 APP断开了与资源的联系 而是找工厂要资源 让工厂与资源取得联系 并把想要的对象转给应用 从而实现了资源与应用必然的依赖关系
基于这种思想 就是我们所说的IOC

03.Spring的 IOC 和 DI_第1张图片
IOC

为什么叫控制反转而不是降低依赖呢?
我们在编写Service的时候 用new 或者 工厂 都是自己决定的AccountServiceImpl 这个类可以有自主使用的权力 它在寻找Dao的时候可以自主选择自己想要的Dao的(通过 new)
但是它把自主选择Dao的权利交给了BeanFactory 然后由固定的名称找到bean对象 这个bean对象是不是我们能用的 我们就不得而知了 我们不能自主控制 通过 控制权发生了转移 所以是控制反转
带来的好处就是 降低耦合

Spring中IOC的前期准备

IOC只能解耦 降低程序间的依赖关系
前面讲了工厂模式来解耦 现在使用配置的方式来实现这些
准备Spring的开发包
下载地址: https://repo.spring.io/libs-release-local/org/springframework/spring/
打开它可以看见目录结构

03.Spring的 IOC 和 DI_第2张图片
spring的开发包

doc:API和开发规范
libs:jar包和源码
schema:约束
03.Spring的 IOC 和 DI_第3张图片
libs

spring基于XML的IOC环境搭建和入门

新建一个工程


03.Spring的 IOC 和 DI_第4张图片
新建一个工程

之前代码里的dao和service都能用,copy过来,删掉Factory,原来使用工厂创建对象 改为用new创建


03.Spring的 IOC 和 DI_第5张图片
copy之前的代码

搭建环境
03.Spring的 IOC 和 DI_第6张图片
pom.xml

导入denpendency以后 多了这些jar包 spring把你可能会用到的jar包都导进来了


03.Spring的 IOC 和 DI_第7张图片
image.png

03.Spring的 IOC 和 DI_第8张图片
show dependencies

通过 IDEA maven 的 show dependencies 功能 可以看到 beans core aop expreesion 这四个核心容器
简单来说核心容器就是个map 里面放着封装的对象
03.Spring的 IOC 和 DI_第9张图片
show dependencies

仍然需要一个配置文件,所以创建beans.xml,然后添加约束


接下来让对象的创建让spring来管理


03.Spring的 IOC 和 DI_第10张图片
beans.xml

接下来 在Client这个类中,之前的代码需要改造下.
这个类要做到两件事:
1.获取核心容器对象
2.根据ID获取Beans
获取核心容器的 ApplicationContext类 有两个实现类 分别是
①ClassPathXmlApplicationContext
②FileSystemXmlApplicationContext
这两个都是基于Xml配置的


03.Spring的 IOC 和 DI_第11张图片
Spring中工厂的类结构图
public class Client {
    /**
     * 获取核心容器对象并据ID获取对象
     * @param args
     */
    public static void main(String[] args) {
        //获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        //根据id获取Bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
       IAccountDao adao = ac.getBean("acccountDao",IAccountDao.class);
        System.out.println(as);
        System.out.println(adao);
    }
} 

getBean的另外一种方式,传入一字节码,根据字节码来获取对象类型,另外一种是Object类型 我们自己强转.


03.Spring的 IOC 和 DI_第12张图片
getBean

ApplicationContext的三个实现类

①ClassPathXmlApplicationContext:它可以加载类路径下的所有配置文件,要求配置文件必须在类路径下.不在的话加载不了
②FlieSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须要有访问权限)
③AnotationXmlApplicationContext:它是用注解创建容器的
所以用FileSystemXmlApplicationContext来获取核心容器的话,改动仅需把

ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");

这里改动即可

public class Client {
    /**
     * 获取核心容器对象并据ID获取对象
     * @param args
     */
    public static void main(String[] args) {
        //获取核心容器对象
        //ApplicationContext ac = new ClassPathXmlApplicationContext("beans.xml");
        ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\WorkSpace\\day01_eesy_03spring\\src\\main\\resources\\beans.xml");
        //根据id获取Bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountDao adao = ac.getBean("accountDao",IAccountDao.class);
        System.out.println(as);
        System.out.println(adao);
    }
}

BeanFactory和ApplicationContext的区别
核心容器的两个接口引发出的问题
ApplicationContext:它在构建核心容器的时候,采用的立即加载的方式,也就是说只要一读取完配置文件就马上加载.
BeanFactory:它在构建核心容器的时候,采用的是延迟加载的方式,也就是说什么时候根据id获取对象了,什么时候才创建.
验证 ApplicationContext 的加载:
在Service中增加一个构造函数

public class AccountServiceImpl implements IAccountService {
    /**
     * 模拟保存账户
     */
    private IAccountDao accountDao = new AccountDaoImpl();

    public AccountServiceImpl() {
        System.out.println("AccountServiceImpl创建了");
    }
    @Override
    public void saveAccount() {

    }
}

在Client中打上断点 可以看到刚读取完配置文件,对象就被创建了


03.Spring的 IOC 和 DI_第13张图片
ApplicationContext

BeanFactory接口:
首先 先定义BeanFactory factory = null; Alt + Ctrl + B 可以看见BeanFactory的实现类,我们选择了 XmlBeanFactory,虽然它已经过时了


03.Spring的 IOC 和 DI_第14张图片
XmlBeanFactory

于是 BeanFactory factory = new XmlBeanFactory();
XmlBeanFactory 需要一个参数对象 ,ctrl + 左键 可以看到 需要一个Resource对象
03.Spring的 IOC 和 DI_第15张图片

03.Spring的 IOC 和 DI_第16张图片
打开类视图

03.Spring的 IOC 和 DI_第17张图片
查看实现类,其实ctrl+alt+B 就能看到

03.Spring的 IOC 和 DI_第18张图片
ctrl+alt+B的查看方式

查看类的实现 是一个很好的思维方式,从老师这里学到的.
选择了 ClassPathResource 我们需要去类路径寻找我们的bean,xml
再往后的步骤都一样了

 public static void main(String[] args) {
      /*--------------------BeanFactory------------------------*/
        Resource resource = new ClassPathResource("beans.xml");
        BeanFactory factory = new XmlBeanFactory(resource);
        //通过id获取Bean对象
        IAccountService as = (IAccountService) factory.getBean("accountService");
        System.out.println(as);
    }

打断点 看执行的时候什么时候创建对象 可以看到 再读完配置文件 配置好工厂后 都没有创建好对象 通过id获取Bean对象的时候 才真正创建了对象


03.Spring的 IOC 和 DI_第19张图片
断点执行

所以两种创建对象的时间点是不一样的

立即加载 在工厂模式下 Service和Dao由于没有类成员,不存在线程安全的问题,所以在此基础上,可以直接选用单例模式创建对象,既然是单例模式,对象只会创建一次,所以可以使用立即加载的方式.只要容器创建,就马上创建对象,适用于单例对象.

延迟加载 什么时候用,什么时候才真正的创建对象 适用于多例模式

如果在一加载容器的时候就创建对象,第二次使用的时候又再创建一次对象,不如在什么时候用的时候什么时候创建更合适

Spring 可以根据配置上的不同 可以改变对象创建的方式
BeanFactory 是个顶层接口 功能不是那么完善 实现类和子接口会在BeanFactory的功能上进行拓展,所以实际开发中使用applicationContext比较多

applicationContext 如何判定单例还是多例

spring中bean的细节之三种创建Bean对象的方式

准备一个新的工程


03.Spring的 IOC 和 DI_第20张图片
创建new Project

找到03里的一些源码直接复制到04工程中,bean.xml也复制一份.删掉dao.(为了更简洁)

Spring对Bean的管理细节

  • 创建Bean的三种方式
    • 使用默认构造函数创建
      在Spring的配置文件中 使用bean标签中 配以id和class属性以后 没有其他的属性和标签时 采用的就是使用默认构造函数创建对象 此时如果类中没有默认的构造函数 则对象无法创建.
      在原来的代码中 已存在 AccountServiceImpl类的构造函数 在原有的默认构造函数上添加一个变量 让它不再是默认的构造函数 此时点击运行 报错信息为:No default constructor found;


      03.Spring的 IOC 和 DI_第21张图片
      验证默认构造函数创建Bean对象

      ,
      这个时候在bean,xml 中 也可以看到配置文件中的报错 它没有找到这个类的默认构造函数


      03.Spring的 IOC 和 DI_第22张图片
      beam,xml中也在报错
    • 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象并存入Spring容器
/**
*     模拟一个工厂类(该类存在于jar包中 我们无法通过修改源码的方式来提供默认构造函数),,
*/
public class InstanceFactory {
   public IAccountService getAccountService(){
     return new AccountServiceImpl();
   }
}

模拟一个工厂类(该类存在于jar包中 我们无法通过修改源码的方式来提供默认构造函数)
在实际开发中 有可能遇上别人写好的类 这中类存在jar包中 属于字节码文件 我们无法修改 同时里面可能提供一个方法,比如说 getAccountService 这个方法可以得到一个 accountService ,如果这个类存在与Jar包,如何获得这个service对象?
在bean.xml中,我们通过instanceFactory这个id 通过反射创建这个类的对象 ,但我们是需要用这个工厂对象吗?不是要用这个类中方法的返回值的对象吗?

       

我们要用的是accoutService,只不过accountService不再是通过我们accountServiceImpl来得到的 ,我们要用工厂类的方法 于是,完整版bean,xml的配置是

    
       
       
       

  • 使用静态工厂中的静态方法创建对象(或者某个类中的静态方法创建对象并存入spring容器)
    Copy一份InstanceFactory的代码,只需要把方法 加一个static的修饰即可.
public class StaticFactory {
    public static IAccountService getAccountService(){
      return new AccountServiceImpl();
    }
}

此时在beam.xml中,通过bean 的id反射得到实例对象是staticFactory,而不是service的实例,所以需要配置factory-method 这个属性来获得这个方法返回的service实例

    
  • Bean对象的作用范围
    • bean的作用范围:
      bean标签的scope属性
      作用:指定bean的作用范围

取值:

  • singleton:单例的(默认的)(常用)
  • prototype:多例的(常用)
  • request:作用于web的请求请求范围
  • session:作用于wdb的会话的请求范围
  • globe-session:作用于集群环境的范围
    单例的
    

多例的

    

在Client中再实例化一个对象,然后看看单例与多例的区别

    public static void main(String[] args) {
        Resource resource = new ClassPathResource("beans.xml");
        BeanFactory factory = new XmlBeanFactory(resource);
        //通过id获取Bean对象
        IAccountService as = (IAccountService) factory.getBean("accountService");
        IAccountService as1 = (IAccountService) factory.getBean("accountService");
        System.out.println(as == as1);
        //as.saveAccount();
    }
}

单例的结果:

AccountServiceImpl创建了
true

多例的结果:

AccountServiceImpl创建了
AccountServiceImpl创建了
false
  • Bean对象的生命周期
  • 单例的
    1.创建:容器创建时 对象出生
    2.活着:只要容器还在 对象一直活着
    3.销毁:容器销毁,对象消亡.
    在 AccountServiceImpl 中 新增 init() 和destory()方法

public class AccountServiceImpl implements IAccountService {
    /**
     * 模拟保存账户
     */

    public AccountServiceImpl() {
        System.out.println("AccountServiceImpl创建了");
    }
    @Override
    public void saveAccount() {
        System.out.println("service中的saveAccount执行了");
    }
    public void init(){
        System.out.println("对象初始化了");
    }
    public  void destory(){
        System.out.println("对象销毁了");
    }
}

在Client中执行

  public static void main(String[] args) {
        Resource resource = new ClassPathResource("beans.xml");
        BeanFactory factory = new XmlBeanFactory(resource);
        //通过id获取Bean对象
        IAccountService as = (IAccountService) factory.getBean("accountService");
        //IAccountService as1 = (IAccountService) factory.getBean("accountService");
        //System.out.println(as == as1);
        as.saveAccount();
    }
}

得到结果:

AccountServiceImpl 创建了
AccountServiceImpl 对象初始化了
service中的saveAccount执行了

为什么desrory方法没有执行 main方法是一切应用程序的入口 当main方法结束后 当前应用中线程占用的内存全部释放 也包括我们的容器 但是此时还没有调用销毁方法 就已经释放了内存
如果想让销毁方法出现 需要手动释放
(然后我发现我的Client中使用的是BeanFactory这个接口来获取核心容器的,下面改成为用ApplicationContext的方式)

 public static void main(String[] args) {
        //获取核心容器对象
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
        //获取Bean对象
        IAccountService accountService = (IAccountService) classPathXmlApplicationContext.getBean("accountService");
        accountService.saveAccount();
        //手动关闭容器
        classPathXmlApplicationContext.close();
    }
}

输出结果

AccountServiceImpl 创建了
AccountServiceImpl 对象初始化了
service中的saveAccount执行了
AccountServiceImpl对象销毁了
![image.png](https://upload-images.jianshu.io/upload_images/13543313-4d005be8bc2641b2.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

如果 以这样的方式获取核心容器对象,是得不到close方法的.

        ApplicationContext ApplicationContext = new ClassPathXmlApplicationContext("beans.xml");

因为我们把ClassPathXmlApplicationContext看成了接口类型,如果看成父类对象的时候,只能调用父类方法,(多态性)所以要让这个对象是自己的对象.
所以用了ClassPathXmlApplicationContext自己的对象

 ClassPathXmlApplicationContext classPathXmlApplicationContext = new ClassPathXmlApplicationContext("beans.xml");
  • 多例的
    1.创建:使用的时候创建
    2.活着:在使用过程中就一直活着
    3.销毁:当对象长时间不用且没有别的对象引用时,由Java的垃圾回收期来执行回收.

spring的依赖注入

  • 依赖注入是指 Dpendency Injection

  • IOC的作用 是降低程序间的耦合
    依赖关系的管理 是指依赖关系以后都交给spring来维护,在当前类所需要用到其他类都由spring为我们提供,我们只需要在配置文件中说明依赖关系的维护,这就称之为依赖注入

  • 依赖注入

    • 能注入的数据有三类
      • 基本类型和String
      • 其他bean类型 (在配置文件中或者注解配置过的bean
      • 复杂类型/集合类型
  • 注入的方式有三种

    • 构造函数注入

    • 由set方法提供

    • 由注解提供

      • 构造函数注入
        使用的标签是 constructor-arg
        标签出现的位置:bean标签内部
        标签中的属性
        type :用于要指定所注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型(如果参数1和参数3是同一类型,它不会知道是赋值给哪一个参数,所以不能独立的实现注入的功能)
        index :用于指定要注入的数据给构造函数中指定位置的参数赋值,索引的位置是从0开始(但这个方式要记住参数的位置,所以有点麻烦)
        name : 用于指定给构造函数中指定名称的参数赋值(常用)
        ==================以上三个用于指定给构造函数中哪个参数赋值===================
        value:用于提供基本类型和String类型的数据
        ref:用于指定其他的bean类型数据,它指的就是在spring的Ioc核心容器中出现过的bean对象.
    优势:在获取bean对象时,注入数据是必须的操作,否则对象无法创建成功
    弊端:改变了类或者bean对象的实例化方式,使我们在创建对象时如果用不到这些数据也必须提供
    

老规矩,新建一个工程,spring01_eesy_05DI,copy上一个工程的代码.先在AccountServiceImpl中创建构造函数

public class AccountServiceImpl implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;
    public AccountServiceImpl() {
        System.out.println("AccountServiceImpl 创建了");
    }
    public AccountServiceImpl(String name,Integer age,Date birthday) {
        this.name = name;
        this.age = age;
        this.birthday = birthday;
        System.out.println("AccountServiceImpl 创建了"+name+"=="+age+"==="+birthday);
    }

在bean,xml中,为参数赋值
第一个参数

    

第二个参数赋值的时候,赋值的12,和Interge类型并不匹配,在xml中 value 的值都是字符串,是spring它把这些数据类型帮我们转换了.


03.Spring的 IOC 和 DI_第23张图片
image.png

第三个参数 Date 赋值,如果写成2019-09-22 是无法为Date类型赋值的,因为它是字符串类型
所以构造函数赋值,只能赋值基本类型和字符串

    

那要如何为Date类型赋值呢?可以用引用.
我们可以配置一个日期对象,它会通过class的全限定名反射创建对象并存入spring容器中,通过now id取出


所以 beam.xml中



   
   
       
       
   
    

  • Set方法注入(更常用)

    • 涉及的标签:property
    • 出现的位置:bean标签内部
    • 标签的属性:
      • name:用于指定注入时所调用的set方法名称,
      • value:用于提供基本类型和Sting类型的数据
      • ref:用于指定其他bean类型数据.它指的就是在spring的Ioc核心容器中出现过的bean对象.
        优势:创建对象时没有明确的限制,可以直接使用默认构造函数
        弊端:如果有某个成员必须有值,则获取对象是有可能set方法方法没有执行.

    Copy一个AccountServiceImpl,为成员变量生成set方法.

public class AccountServiceImpl2 implements IAccountService {
    private String name;
    private Integer age;
    private Date birthday;

    public AccountServiceImpl2() {
        System.out.println("AccountServiceImpl2 创建了");
    }
    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);
    }
}

在Beans.xml中,写法如下

  
    
        
        
        
    
  • name:用于指定注入时所调用的set方法名称,如果把setName 改成为setUsername的话,beam.xml中的自动联想也从name变成username,由此可见name属性用于指定注入时所调用set方法名称,

执行结果

AccountServiceImpl2 创建了
service中的saveAccount执行了xiaoming18Mon Sep 23 10:35:44 GMT+08:00 2019
  • 复杂类型/集合类型的注入
    再复制一个accountserviceImpl3,加上数组,list,map,set.properties等数据的成员变量,并生成set方法.
public class AccountServiceImpl3 implements IAccountService {
    private String[] mystrs;
    private List mylist;
    private Map mymap;
    private Set myset;
    private Properties myprops;

    public AccountServiceImpl3() {
        System.out.println("AccountServiceImpl2 创建了");
    }

    public void setMystrs(String[] mystrs) {
        this.mystrs = mystrs;
    }

    public void setMylist(List mylist) {
        this.mylist = mylist;
    }

    public void setMymap(Map mymap) {
        this.mymap = mymap;
    }

    public void setMyset(Set myset) {
        this.myset = myset;
    }

    public void setMyprops(Properties myprops) {
        this.myprops = myprops;
    }

    public void saveAccount() {
        System.out.println("service中的saveAccount执行了");
        //数组类型需要tostring方法转换一下 否则直接打印是内存地址
        System.out.println(Arrays.toString(mystrs));
        System.out.println(mylist);
        System.out.println(mymap);
        System.out.println(myset);
        System.out.println(myprops);
    }
}

在beam.xml中,因为都不是基本类型和String类型,所以标签里的value赋值也不再有意义

 

标签内,有更多的子标签提供使用.


03.Spring的 IOC 和 DI_第24张图片
image.png

array类型,array标签,value赋值

        
            
                AAA
                BBB
                CCC
            
        

list类型,list标签,value赋值

        
            
                AAA
                BBB
                CCC
            
        

set类型,set标签,value赋值

        
            
                AAA
                BBB
                CCC
            
        

其中,如果把list,array,set的标签呼唤,也能正常运行,

map类型,用map标签,里面有entry标签,entry有key和value两个属性.


            
                
                
                
            
        

\之前这样的写法是有问题的,输出的结果格式会不一样,然后发现自己写错了

 
            
                
                    AAA,BBB,CCC
                
            
        

properties类型 ,里面有标签,props里有的子标签,赋值只有一个属性,是

  
            
                aaa
                bbb
                ccc
            
        

如果map标签与prop标签呼唤,执行结果也不会有问题.

      
            
                
                
                
            
        
        
            
                aaa
                bbb
                ccc
            
        

你可能感兴趣的:(03.Spring的 IOC 和 DI)