spring中的IOC2020-02-22

什么是IOC

今天开始学习Spring了,先接触一下IOC和AOP,OK,又是不懂的词,先看看维基百科的解释:

控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫「依赖查找」(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。技术描述:Class A中用到了Class B的对象b,一般情况下,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。

well,如果在我没学习耦合和解耦之前,这个解释我肯定会给个白眼,不过在今天的我看来,似乎有点能理解了(还是想打个黑人问号,依赖注入什么鬼???),继续深入学习一下

把我上篇博文的案例代码扒过来,这个片段:

public class AccountServiceImpl implements IAccountService {

//  private IAccountDao accountDao = new AccountDaoImpl();

  private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");

  public void saveAccount() {
    accountDao.saveAccount();
  }
}

生成accountDao可以分为以下两种截然不同的创建方式,一个通过new方式来加载类,一个通过工厂对象来加载类

// new AccountDaoImpl();
// BeanFactory.getBean("accountDao");

通过new来找对象时,每个当前有new关键字的程序都是主动的,需要自己去找类是否存在,整个程序的对应关系是这样:

o8tbs5

而当我们换成工厂模式时,则对象之间的关系变成这样:

RatvAi

此时的应用已经和资源断开联系,变成找工厂要一个资源,由工厂找到资源后在返回给应用,这时就断开了应用对资源的依赖,这种思想,就是我们所说的IOC

上面这个代码片段所在的类,它自己有方法可以加载类,但它却吧这个过程放弃了,让BeanFactory去执行,完完全全交给BeanFactory,自己所有的控制权转交给BeanFactory,这就叫控制反转,这就是IOC的思想。

这带来的好处,可以减低程序间的依赖,也叫削减计算机的耦合

Spring中的IOC

回到上篇文章最初的例子

ryogPo

还是可以运行,不过有很强的依赖性就是了,开始使用Spring

maven配置文件导入依赖:

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.0.2.RELEASE</version>
    </dependency>

待下好jar包,我们就可以写代码了

先在resource下面创建一个XMl文件,就叫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 http://www.springframework.org/schema/beans/spring-beans.xsd">

  <bean id="accountService" class="com.karasawa.service.AccountServiceImpl"></bean>

  <bean id="accountDao" class="com.karasawa.dao.AccountDaoImpl"></bean>
</beans>

bean标签的内容是不是很熟悉??是的,跟上次讲过的用propertise文件配置是一样,只不过key变成id,value变成class,但对应的值都一样的,

在servlet中,修改代码:

public class TestSaveAccount extends HttpServlet {

  public static void main(String[] args) {
    ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");

    IAccountService ias = ac.getBean("accountService", IAccountService.class);
    IAccountDao iac = (IAccountDao) ac.getBean("accountDao");

    System.out.println(ias);
    System.out.println(iac);
  }
}

运行后可以看到控制台打印了两个首地址,成功拿到对应的类,感觉很熟悉啊,跟上篇博文中的方法一样嘛,是的,Spring把解析配置文件、获取对应的类、放进Map里等操作全做了,我们就不用去做了

这里拿了两个类,是为了提及一嘴获取类的时候有两种方式,getBean接收两个参数,第一个参数是配置的id,而第二个参数可以指定对应文件的字节流来让生成对应的类,这样就不用去强转了,推荐这种

这里用了AppLicationcontext这个类,没听过更别说见过,来看一下这个这个类,这类的三个常用实现类是:

  1. CLassPathxmLAppLicationcontext

    它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。

  2. FileSystemXmlAppLicationContext

    它可以加载磁盘任意路径下的配置文件(必须有访问权限)

  3. AnnotationConfigAppLicationContext

    它是用于读取注解创建容器的

我们来用用第二个,把bean.xml放到桌面去,修改servlet中的代码

public class TestSaveAccount extends HttpServlet {

  public static void main(String[] args) {
    ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\karasawa\\Desktop\\bean.xml")

    IAccountDao iac = (IAccountDao) ac.getBean("accountDao");

    System.out.println(iac);
  }
}

运行,成功运行,不过这个路径问题...想想就知道这个FileSystemXmlApplicationContext很少用

在IDEA下把光标移到AppLicationcontext这个关键词上,右键进入show Diagrams

LCvko3

这家伙既是BeanFactory的孙子,用BeanFactory写一个相同效果的

  public static void main(String[] args) {
    Resource resource = new ClassPathResource("bean.xml");
    BeanFactory factory = new XmlBeanFactory(resource);
    IAccountDao iac = (IAccountDao) factory.getBean("accountDao");
    System.out.println(iac);
  }

成功运行,那爷孙俩有啥不同呢?

ApplicationContext:它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。

BeanFactory:则构建核心容器时,创建对象采取的策略是采用延迟加载的方式。即什么时候要用再去创建

可以自己实验一下,实验方式为:在beans标签下的任意类中的代码里添加一个无参构造函数,随便打印点啥,在servlet除了获取核心容器对象,其他注释上,运行的时候就会打印出来了,没有打印是BeanFactory,因为没有用到对应的类

.是不是觉得很熟悉 ? ?

ApplicationContext不就是我上篇博文最后的模样嘛,也就是单例模式,增加执行效率,但有线程问题,怪不得都说要理解耦合和解耦再去学spring,那什么时候用BeanFactory什么时候用ApplicationContext,就看你是单例模式还是多例模式了,另外,BeanFactory是顶层接口,功能啥的肯定没有ApplicationContext多,实际开发还是用ApplicationContext较多。Spring可以通过配置文件,让ApplicationContext在创建的时候采用单例还有多例。嗯???不对劲,那还要BeanFactory干啥,像极了孙子拆了老爷子的台hhhh....

Spring对Bean的管理细节

让我们把Dao去掉,留下Service和Servlet

iOj9wS

通过三个方面来说Spring对Bean的管理细节

创建bean的三种方式

第一种:上面举例的就是,且没有其他属性和标签时。必须要有默认构造函数,没有的话会报错,像Service类改一下像这样,没有了默认构造函数,运行Servlet后会报错:

package com.karasawa.service;
public class AccountServiceImpl implements IAccountService {

  public AccountServiceImpl(String name){
    System.out.println(name);
  }
  public void saveAccount() {
    System.out.println("AccountServiceImpl被调用了");
  }
}

第二种方式:当我们使用别人的工厂类且该类没有默认构造函数(jar包形式),无法修改的情况下,用这种方式(例如我们要调用一个工厂类中某个方法来创建对象,此时该工厂类没有默认构造函数):

<Bean id="instanceFactory" class="该工厂类的全限定类名"></Bean>
<Bean id="accountService" factory-bean="instanceFactory" factory-method="工厂类中要调用的方法"></Bean>

第三种的情况和第二种类似,不同的是要调用的方法是一个静态方法,对此应该这样用:

<Bean id="accountService" class="该工厂类的全限定类名" factory-method="工厂类中要调用的方法"></Bean>

bean对象的作用范围

ApplicationContext默认创建的Beans对象是一个单例的,但可以通过scope属性来控制

scope属性:用于指定bean的作用范围

取值:

  1. singleton:单例的(默认值)
  2. prototype:多例的e
  3. request:作用于web应用的请求范围session:作用于web应用的会话范围
  4. g1obal-session:作用于集群环境的会话范围(全局e会话范围),当不是集群环境时,它就是session

常用的是1和2,但开发中具体还是得看情况

bean对象的生命周期

当scope值为singleton是,生命周期和容器一样,当容器创建时创建,随容器销毁时销毁,而当值为prototype时,bean的生命周期等到用到时才创建,销毁时需要自行close,或者长时间没用到(包括没被引用)时,由java的垃圾回收机制进行回收。

依赖注入

依赖注入,英文名Dependency Injection,前面说过Spring的IOC是减低耦合,耦合不能消除,依赖关系还是会存在,用了Spring之后,依赖关系就交给Spring来维护了

通俗点讲,以后我们在一个类中要到其他类、变量、集合等,直接由Spring来提供,嘛,在这之前还需先配置好文件的说明,这种方式呢,就叫做依赖注入!

依赖注入能注入的数据有三类(将在注入方式体现出来):

  1. 基本类型和String
  2. 其他bean类型(在配置文件中或者注解配置过的bean)
  3. 复杂类型/集合类型

注入的方式:有三种

  1. 使用构造函数提供

    使用的标签:constructor-arg

    标签出现的位置:bean标签的内部

    五个属性:点击查看

     public class AccountServiceImpl implements IAccountService {
    
         private String name;
         private Integer age;
         private Date brithDay;
    
         public AccountServiceImpl(String name, Integer age, Date brithDay) {
             this.name = name;
             this.age = age;
             this.brithDay = brithDay;
         }
    
         public void saveAccount() {
             System.out.println("我是"+name+",出生于"+brithDay+"今年"+age+"岁");
         }
     }
         <bean id="accountService" class="com.karasawa.service.AccountServiceImpl">
             <constructor-arg name="age" value="21"></constructor-arg>
             <constructor-arg name="name" value="karasawa"></constructor-arg>
             <constructor-arg name="brithDay" ref="Date"></constructor-arg>
         </bean>
    
         <bean id="Date" class="java.util.Date"></bean>
    运行Servlet,查看控制台:

    QaxQEs

    注意!!这里只是拿name,age,birthDay来测试,实际中这种经常改变的变量是不适用于注入的,再者,使用这种方式会必须得提供参数(因为改变的默认构造函数),参数缺一不可,不然会报错,这也导致了我们有些时候只是要调用改类中的一个方法而已,就多了传参这个固定步骤,开发的时候除了必须采用这种方式,否则不会用到,更多是用以下方法

  2. 使用set方法提供

    代码依旧是上一步的代码,我们改一下,去掉service中的含参构造函数,生成每个成员的setter,然后修改一下bean.xml文件,把constructor-arg标签都换成propertise标签,属性照样用,运行,发现效果一样,把方法setName改成setUserName,发现propertise的name值需要改成userName才行,所以这里的name值是对应的setter后面带的名字

    set方法解决了第一种的弊端,但也产生了新的弊端,如果某个成员必须要有值,那么获取对象时有可能不会执行set方法(因为调用了默认构造函数),两种方式各有利弊,但开发中的绝大多数场景的都是使用set方式

  3. 使用注解提供:看下面的Spring IOC注解模块

上面无论是构造函数提供,还是set方法提供,都只用到了基本类型和String、其他Bean类型,这里记录一下复杂类型的注入:

修改一下Service中的代码,删掉name、age、brithDay和对应的setter,写上Map、List、String[]、Set、Propertise等类型的参数和对应的setter

public class AccountServiceImpl implements IAccountService {

  private Map<String,String> myMap;
  private Properties myProps;
  private List<String> myList;
  private Set<String> mySet;
  private String[] myStr;

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

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

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

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

  public void setMyStr(String[] myStr) {
    this.myStr = myStr;
  }

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

然后再修改一下bean.xml配置文件

  <bean id="accountService" class="com.karasawa.service.AccountServiceImpl">
    <property name="myList">
      <list>
        <value>AAA</value>
        <value>BBB</value>
        <value>CCC</value>
      </list>
    </property>
    <property name="myStr">
      <array>
        <value>AAA</value>
        <value>BBB</value>
        <value>CCC</value>
      </array>
    </property>
    <property name="mySet">
      <set>
        <value>AAA</value>
        <value>BBB</value>
        <value>CCC</value>
      </set>
    </property>
    <property name="myMap">
      <map>
        <entry key="karasawa" value="he is fucking handsome"/>
      </map>
    </property>
    <property name="myProps">
      <props>
        <prop key="karasawa">she is fucking beautiful</prop>
      </props>
    </property>
  </bean>

运行后成功打印出注入的数据

偷偷告诉记一个知识点,集合类型的注入,其实就两种,一种是只记录值的list、array、set,另一种是记录key=value的propertise和map,也就是说他们配置的时候,只要结构一样,写乱标签也是可以!!!举个例子:

配置里的props标签和map标签互换一下(name不用改),运行后发现效果还是一样

`constructor-arg的五个属性点击返回 | 属性 | 作用 | | ----- | --------------------------------------------------------------------------------------------- | | type | 用于指定要注入的数据的数据类型 | | index | 要注入的数据在构造函数中指定索引位置的参数赋值,从0开始 | | name | 用于指定构造函数的参数名字赋值 | | value | 用于提供基本类型和String类型的数据 | | ref | 用于指定其他的bean类型数据(非基本类型时需要先用Bean标签把其类型创建放入核心容器,再用ref引用) |

.type、index、name都是给构造函数的参数赋值,三个选一个就行了,通常我们命名不会重复,所以使用name会比较准确,开发中比较常用

Spring IOC注解

注解是IOC是另一种方式,学起来很容易懂,就像servlet可以用xml配置访问路径,也可以在servlet类中用WebServlet直接写,来起到和xml配置一样的效果

当初在学servlet的时候我就喜欢用WebServlet,学Spring IOC注解的时候,不用问也还是会更喜欢注解方式,不过据说每个公司的方式都不一样,所以两种方式都掌握起来比较好,上面用到的都是XML,下面开始IOC注解的学习记录

基本的注解分四类(非纯注解),对应着XML配置中的< Bean >标签、< propertise >标签、Scope属性、Bean标签的生命周期,比较简单就不过多的讲述了

注解作用
@Component和Bean标签一样,有value属性,形同Bean中的id,默认值为类名(首字母小写)
@Controller同Component,用于表现层,Spring提供名称让三层对象更加清晰
@Service同Component,用于业务层
@Repository同Component,用于持久层
@Autowrited依赖注入,自动按照类型注入,需要匹配上核心容器里已存在的类型,否则报错,当容器存在多个一样的类型Bean时,则按变量名来匹配
@Qualifier依赖注入,在按照类注入的基础上加上变量名注入,即在Autowrited的基础上使用,在给类成员注入时不能单独使用
@Resource依赖注入,直接按照所给的name属性值(即变量名)去核心容器找匹配的 ,以上三个依赖注入,只能注入其他Bean类型的数据
@Value依赖注入,用于基本类型和String的依赖注入,value属性用spring的EL表达式(${}),另外,集合的注入的必须用XML方式
@Scope用于指定Bean的作用范围,属性value所取的值有singleton和prototype,不用此注解,默认是单例
@PreDestroy指定销毁方法,类似Bean标签中的destroy-method
@PostContruct指定初始化方法,类似Bean标签中的init-method

以上就是Spring的IOC内容了。。。其实注解还有好多,但是看一下就懂得怎么使用了,没写例子是因为功能和XML类似,自己上网搜一搜注解的使用方法就知道怎么用了,懒得记录....