Spring IOC 学习总结

1 什么是IOC、DI

  IoC—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。

传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对象的创建。

  IoC 不是一种技术,只是一种思想,一个重要的面向对象编程的法则,它能指导我们如何设计出松耦合、更优良的程序。传统应用程序都是由我们在类内部主动创建依赖对象,从而导致类与类之间高耦合,难于测试;有了IoC容器后,把创建和查找依赖对象的控制权交给了容器,由容器进行注入组合对象,所以对象与对象之间是 松散耦合,这样也方便测试,利于功能复用,更重要的是使得程序的整个体系结构变得非常灵活。

  其实IoC对编程带来的最大改变不是从代码上,而是从思想上,发生了“主从换位”的变化。应用程序原本是老大,要获取什么资源都是主动出击,但是在IoC/DI思想中,应用程序就变成被动的了,被动的等待IoC容器来创建并注入它所需要的资源了。

  DI—Dependency Injection,即“依赖注入”:组件之间依赖关系由容器在运行期决定,形象的说,即由容器动态的将某个依赖关系注入到组件之中。依赖注入的目的并非为软件系统带来更多功能,而是为了提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。通过依赖注入机制,我们只需要通过简单的配置,而无需任何代码就可指定目标需要的资源,完成自身的业务逻辑,而不需要关心具体的资源来自何处,由谁实现。

  理解DI的关键是:“谁依赖谁,为什么需要依赖,谁注入谁,注入了什么”,那我们来深入分析一下:

  ●谁依赖于谁:当然是应用程序依赖于IoC容器;

  ●为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;

  ●谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;

  ●注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

  IoC和DI有什么关系呢?其实它们是同一个概念的不同角度描述,由于控制反转概念比较含糊(可能只是理解为容器控制对象这一个层面,很难让人想到谁来维护对象关系),所以2004年大师级人物Martin Fowler又给出了一个新的名字:“依赖注入”,相对IoC 而言,“依赖注入”明确描述了“被注入对象依赖IoC容器配置依赖对象”。

  IoC技术是Spring的核心,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。所有的类都会在spring容器中登记,告诉spring你是个什么东西,你需要什么东西,然后spring会在系统运行到适当的时候,把你要的东西主动给你,同时也把你交给其他需要你的东西。所有的类的创建、销毁都由 spring来控制,也就是说控制对象生存周期的不再是引用它的对象,而是spring。对于某个具体的对象而言,以前是它控制其他对象,现在是所有对象都被spring控制,所以这叫控制反转。IoC的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。

  所以控制反转IoC(Inversion of Control)是说创建对象的控制权进行转移,以前创建对象的主动权和创建时机是由自己把控的,而现在这种权力转移到第三方,比如转移交给了IoC容器,它就是一个专门用来创建对象的工厂,你要什么对象,它就给你什么对象,有了 IoC容器,依赖关系就变了,原先的依赖关系就没了,它们都依赖IoC容器了,通过IoC容器来建立它们之间的关系。

  注意,一般将BeanFactory称为IoC容器,而称ApplicationContext和WebApplicationContext为应用上下文或Web应用上下文。在本篇博客中,把WebApplicationContext和ApplicationContext统称为Spring容器。

 

2 IoC相关的Java基础知识

2.1 反射

  IOC中最基本的技术就是“反射(Reflection)”。反射机制: 反射是java语言的一个特性,它允程序在运行时(注意不是编译的时候)来进行自我检查并且对内部的成员进行操作。例如它允许一个java的类获取它所有的成员变量和方法并且显示出来。通过这种能力可以彻底的了解自身的情况为下一步的动作做准备。Java的反射机制的实现要借助于4个类:class,Constructor,Field,Method;其中class代表的时类对象,Constructor-类的构造器对象,Field-类的属性对象,Method-类的方法对象。通过这四个对象我们可以粗略的看到一个类的各个组 成部分。

  Java反射的作用:在Java运行时环境中,对于任意一个类,可以知道这个类有哪些属性和方法。对于任意一个对象,可以调用它的任意一个方法。这种动态获取类的信息以及动态调用对象的方法的功能来自于Java 语言的反射(Reflection)机制。

  Java 反射机制主要提供了以下功能,在运行时判断任意一个对象所属的类。在运行时构造任意一个类的对象。在运行时判断任意一个类所具有的成员变量和方法。在运行时调用任意一个对象的方法。

  从Java内存模型的角度理解反射,首先上一张Java内存模型的神图:

  

  

  首先我们了解一下JVM,什么是JVM,Java的虚拟机,java之所以能跨平台就是因为这个东西,你可以理解成一个进程,程序,只不过他的作用是用来跑你的代码的。

  假如你写了一段代码:Object o=new Object(); 运行了起来!

  首先JVM会启动,你的代码会编译成一个.class文件,然后被类加载器加载进jvm的内存中,你的类Object加载到方法区中,创建了Object类的class对象到堆中,注意这个不是new出来的对象,而是类的类型对象,每个类只有一个class对象,作为方法区类的数据结构的接口。jvm创建对象前,会先检查类是否加载,寻找类对应的class对象,若加载好,则为你的对象分配内存,初始化也就是代码:new Object()。

  上面的流程就是你自己写好的代码扔给jvm去跑,跑完就over了,jvm关闭,你的程序也停止了。

  为什么要讲这个呢?因为要理解反射必须知道它在什么场景下使用。大家想想上面的程序对象是自己new的,程序相当于写死了给jvm去跑。假如一个服务器上突然遇到某个请求哦要用到某个类,哎呀但没加载进jvm,是不是要停下来自己写段代码,new一下,哦启动一下服务器,(脑残)!

  反射是什么呢?当我们的程序在运行时,需要动态的加载一些类这些类可能之前用不到所以不用加载到jvm,而是在运行时根据需要才加载,这样的好处对于服务器来说不言而喻,举个例子我们的项目底层有时是用mysql,有时用oracle,需要动态地根据实际情况加载驱动类,这个时候反射就有用了,假设 com.java.dbtest.myqlConnection,com.java.dbtest.oracleConnection这两个类我们要用,这时候我们的程序就写得比较动态化,通过Class tc = Class.forName("com.java.dbtest.TestConnection");通过类的全类名让jvm在服务器中找到并加载这个类,而如果是oracle则传入的参数就变成另一个了。这时候就可以看到反射的好处了,这个动态性就体现出java的特性了。

  反射可以实现动态创建对象和编译,体现出很大的灵活性(特别是在J2EE的开发中它的灵活性就表现的十分明显)。通过反射机制我们可以获得类的各种内容,进行了反编译。对于JAVA这种先编译再运行的语言来说,反射机制可以使代码更加灵活,更加容易实现面向对象。反射机制的缺点:对性能有影响。使用反射基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它 满足我们的要求。这类操作总是慢于只直接执行相同的操作。

  Spring的IOC正是反射技术最好的舞台,IOC中大量利用反射技术,通过配置XML或者@Annotation的方式配置装配bean到Spring容器,程序运行起来后,根据需要,Spring容器在动态的注入bean到需要的对象中。

  那么,Spring是如何装载XML文件以及资源文件的呢?在2.2中介绍。

2.2 资源访问利器:Spring Resource接口

  JDK所提供的访问资源的类(如java.net.URL、File等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器的上下文中获取资源的操作类。鉴于此,Spring设计了一个Resource接口,它为应用提供了更强的底层资源访问能力。Spring的各种资源加载,都是通过这个接口来实现的

  Resource接口的主要方法有

    boolean exists():资源是否存在。

    boolean isOpen():资源是否打开。

    URL getURL():如果底层资源可以表示为URL,该方法返回对应的URL对象。

    File getFile():如果底层资源对应一个文件,该方法返回对应的File对象。

    InputStream getInputStream() throws IOException:返回资源对应的输入流。

  为了可以在不显示使用Resource实现类的情况下,仅通过资源地址的特殊标识就可以加载相应的资源,Spring提供了一个强大的加载资源的机制,可以通过"classpath:","file:"等资源地址前缀识别不同的资源类型,还支持ant风格的带通配符的资源地址。

  

 

3 BeanFactory和ApplicationContext

  什么是POJO,什么是Java Bean,什么是Bean?

  1)POJO = "Plain Old Java Object",是MartinFowler等发明的一个术语,用来表示普通的Java对象,个人理解就是含有一些私有属性,以及getter/setter/constructer方法的普通java类。

  2)Java Bean比POJO要复杂一些,需要满足一些规范,例如:需要实现 Serializable 接口用于实现 Bean 的持久性。

  3)Bean比Java Bean更宽泛一些,所有可以被Spring容器实例化并管理的Java类都可以称之为Bean。

  Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。Spring的IOC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、生命周期管理、Bean实例代理、事件发布、资源装载等高级服务。

  BeanFactory是Spring框架最核心的接口,它提供了高级Ioc的配置机制。ApplicationContext建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。两者都可称为Spring容器。

  二者的用途,一般可以进行简单的划分:BeanFactory是Spring框架的基础设施,面向Spring本身;ApplicationContext面向使用Spring框架的开发者,几乎所有的应用场合都可以直接使用ApplicationContext而非底层的BeanFactory。

3.1 WebApplicationContext

  WebApplicationContext专门为Web应用准备的,它允许从相对于Web根目录的路径中装在配置文件(web.xml)完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。

  在非Web应用的环境下,Bean只有singleton和prototype两种作用域,在WebApplicationContext中又添加了三个新的作用域:request、session和global session。

  WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实力,也就是说,它必须在拥有Web容器(Tomcat、Jetty等)的前提下才能完成启动工作。可以在web.xml中配置自启动的Servlet或定义Web容器监听器(ServletContextListner),借助两者中的任何一个,就可以完成启动Spring Web应用上下文的工作。

  自启动的Servlet启动WebApplicationContext:

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                      http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0"
         metadata-complete="true">
  <!--用maven创建的web-app需要修改servlet的版本为3.1-->
  <!--配置DispatcherServlet-->
  <servlet>
    <servlet-name>seckill-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!--
        配置SpringMVC 需要配置的文件
        spring-dao.xml,spring-service.xml,spring-web.xml
        Mybites -> spring -> springMVC
    -->
    <init-param>
      <param-name>contextConfigLocation</param-name>
      <param-value>classpath:spring/spring-*.xml</param-value>
    </init-param>
  </servlet>
  <servlet-mapping>
    <servlet-name>seckill-dispatcher</servlet-name>
    <!--默认匹配所有请求-->
    <url-pattern>/</url-pattern>
  </servlet-mapping>
</web-app>

  通过Web容器监听器启动WebApplicationContext:

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5" 
    xmlns="http://java.sun.com/xml/ns/javaee" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee 
    http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:smart-context.xml</param-value>
    </context-param>
    <listener>
        <listener-class>
            org.springframework.web.context.ContextLoaderListener
        </listener-class>
    </listener>

    <servlet>
        <servlet-name>smart</servlet-name>
        <servlet-class>
            org.springframework.web.servlet.DispatcherServlet
        </servlet-class>
        <load-on-startup>3</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>smart</servlet-name>
        <url-pattern>*.html</url-pattern>
    </servlet-mapping>
</web-app>

4 Bean作用域

  Scope,也称作用域,在 Spring IoC 容器是指其创建的 Bean 对象相对于其他 Bean 对象的请求可见范围。在 Spring IoC 容器中具有以下几种作用域:基本作用域(singleton、prototype),Web 作用域(reqeust、session、globalsession),自定义作用域。

    • singleton:单例模式,在整个Spring IoC容器中,使用singleton定义的Bean将只有一个实例

    • prototype:原型模式,每次通过容器的getBean方法获取prototype定义的Bean时,都将产生一个新的Bean实例

    • request:对于每次HTTP请求,使用request定义的Bean都将产生一个新实例,即每次HTTP请求将会产生不同的Bean实例。只有在Web应用中使用Spring时,该作用域才有效

    • session:对于每次HTTP Session,使用session定义的Bean豆浆产生一个新实例。同样只有在Web应用中使用Spring时,该作用域才有效

    • globalsession:每个全局的HTTP Session,使用session定义的Bean都将产生一个新实例。典型情况下,仅在使用portlet context的时候有效。同样只有在Web应用中使用Spring时,该作用域才有效

  其中比较常用的是singleton和prototype两种作用域。对于singleton作用域的Bean,每次请求该Bean都将获得相同的实例。容器负责跟踪Bean实例的状态,负责维护Bean实例的生命周期行为;如果一个Bean被设置成prototype作用域,程序每次请求该id的Bean,Spring都会新建一个Bean实例,然后返回给程序。在这种情况下,Spring容器仅仅使用new 关键字创建Bean实例,一旦创建成功,容器不在跟踪实例,也不会维护Bean实例的状态。

  如果不指定Bean的作用域,Spring默认使用singleton作用域。Java在创建Java实例时,需要进行内存申请;销毁实例时,需要完成垃圾回收,这些工作都会导致系统开销的增加。因此,prototype作用域Bean的创建、销毁代价比较大。而singleton作用域的Bean实例一旦创建成功,可以重复使用。因此,除非必要,否则尽量避免将Bean被设置成prototype作用域。

 

5 IoC Bean的4种配置方式比较

类别 基于XML配置 基于注解配置 基于Java类配置 基于Groovy DSL配置
Bean定义 在XML文件中通过<bean>元素定义Bean,如:<bean class="com.xgj.UserDao"/> 在Bean实现类处通过标注@Component或衍型类@Repository、@Service及@Controller定义Bean 在标注了@Configuration的Java类中,通过在类方法上标注@Bean定义一个Bean。方法必须提供Bean的实例化逻辑 在Groovy 文件中通过DSL定义Bean的名称 ,如 userDao(UserDao)
Bean名称 通过<bean>的id或name属性定义,如:<bean id="userDao" class="com.xgj.UserDao"/> 默认名称为:com.xgj.userDao#0 通过注解的value属性定义,如@Component(“userDao”)。默认名称为小写字母打头的类名(不带包名):userDao 通过@Bean的name属性定义,如@Bean(“userDao”),默认名称为方法名 通过GroovyDSL定义Bean的名称
Bean注入 通过<property>子元素或通过p命名空间的动态属性,如p:userDao-ref=”userDao”进行注入 通过在成员变量或方法入参处标注@Autowired,按类型匹配自动注入。还可以配合使用@Qualifier按名称匹配方式注入 比较灵活,可以通过在方法处通过@Autowired方法入参绑定Bean,然后在方法中通过代码进行注入,还可以通过调用配置类的@Bean方法进行注入 比较灵活,可以在方法出通过ref()方法进行注入,如ref(“logDao”)
Bean生命过程方法 通过<bean>的init-method和destory-method属性指定Bean实现类的方法名。最多只能指定一个初始化方法和一个销毁方法。 通过在目标方法上标注@PostConstruct和@PreDestroy注解指定初始化或销毁方法,可以定义任意多个方法 通过@Bean的initMethod或destoryMethod指定一个初始化或销毁方法.对于初始化方法来说,你可以直接在方法内部通过代码的方式灵活定义初始化逻辑 通过bean->bean,initMehtod或者bean.destoryMethod指定一个初始化或者销毁方法
Bean作用范围 通过<bean>的scope属性指定,如:<bean class="com.xgj.UserDao" scope="prototype"/> 通过在类定义处标注@Scope指定,如@Scope(“prototype”) 通过在Bean方法定义处标注@Scope指定 通过bean->bean,scope=”prototype”指定
Bean延迟初始化 通过<bean>的lazy-init属性指定,默认为default,继承于的default-lazy-init设置,该值默认为false 通过在类定义处标注@Lazy指定,如@Lazy(true) 通过在Bean方法定义处标注@Lazy指定 通过bean->bean.lazyInit-true指定

   我在项目中一般采用“XML+注解”的配置方式,DataSource、JdbcTemplate由于无法在类中标注注解,所以通过XML配置方式比较好,命名空间:如aop、context等,只能采用基于XML的配置。Bean的实现类是当前项目开发的,可以直接在Java类中使用基于注解的配置,例如说Service层实现类可以标注@Service,Controller实现类标注@Controller,配合@Autowired就可以很好地使用基于注解的配置进行Bean的定义和注入。

6 IoC 在Spring Web中的应用

  个人理解,一个Spring Web应用中,是从web.xml开始加载的,通过WebApplicationContext中加载web.xml可以获得ServletContext的引用,读取到web.xml中其他Spring配置文件的路径,再通过Spring Resource接口加载配置文件,根据注解扫描或者XML配置实例化、注入Bean到WebApplicationContext(Spring 容器),整个Spring 容器(WebApplicationContext对象)将作为属性放置到Web容器(ServletContext)中,以便Web容器可以访问Spring容器中的内容。

参考资料:

https://blog.csdn.net/qq_22654611/article/details/52606960/

https://www.zhihu.com/question/24304289/answer/76541818

https://www.zhihu.com/question/24304289/answer/147529485

https://blog.csdn.net/u011468990/article/details/49995865

《精通Spring 4.x 企业应用开发实战》 陈雄华 林开雄 文建国 编著

猜你喜欢

转载自www.cnblogs.com/kukri/p/9034912.html