Spring IoC总结

IoC概念


Ioc(Inverse of Control)的概念是为了解耦复杂的类依赖关系,在普通的Java类中,一个类依赖另外一个类一般靠构造函数、set方法以及接口注入三种方式,当类的依赖层级很多,或者依赖的类型很多的时候,类的实例化就成为了一个很复杂的过程,譬如说,在类的实例化场景中,要想实例化最终的类,就需要先把该类所需要依赖的对象进行实例化并注入,这是一个递归的过程,并且会随着类依赖的深度而越来越复杂化。

用代码举个例子,首先定义A<--B<--C的依赖关系。

class A {
}

class B {
    private A a;
    public B(A a) {
        this.a = a;
    }

    public setA(A a) {
        this.a = a;
    }
}

class C {
    private B b;
    public C(B b) {
        this.b = b;
    }

    public setB(B b) {
        this.b = b;
    }
}

然后在实例化场景中进行实例化,需要先实例化A,再实例化B,最后实例化C。

public static void main(String[] args) {
    A a = new A();
    B b = new B(a);      //构造函数注入依赖
    b.setA(a);           //set方法注入依赖

    C c = new C(b);      //经过了三个步骤才最终完成了C的实例化
}

从上面的例子可以看到,由代码控制类的实例化较为繁琐,在实例化场景中,需要知道类的完整依赖关系,并且按照正确的顺序进行实例化并注入依赖,这样对于编程来说是很不友好的。

IoC概念的提出就是为了解决实例化场景中类的实例化过于复杂的问题,其实质就是将依赖的过程交由第三方Spring来处理,业务代码只需要考虑依赖的对象而不需要考虑注入的过程和方式,也不需要知道完整的依赖关系,这样就解耦了业务逻辑与实例化场景,使得编程人员可以更聚焦于业务代码。

例如在Spring中,C的实例化可以这样实现。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd>

    <!-- 通过定义bean实现实例化 -->
    <bean id="a" class="com.test.spring.A"/>
    <!-- 通过ref实现依赖注入 -->
    <bean id="b" class="com.test.spring.B"
        p:a-ref="a"/>
    <!-- 实现最终对象的实例化 -->
    <bean id="c" class="com.test.spring.C"
        p:b-ref="b"/>
</beans>

之所以叫做IoC控制反转,就是因为普通的实例化过程需要对象自己操作注入具体的依赖,而使用IoC则转移了控制对象,控制权交给了第三方,从而实现了控制反转,后期提出的DI(Dependency Injection,依赖注入)这个名字要更简洁明了一点。

资源抽象


Spring通过xml文件解析并加载类,然后使用反射的方式去实例化类对象,所以在Spring中,第一个问题就是如何访问资源,例如访问xml资源实现配置解析,访问class资源实现类的加载。

Spring定义了Resource接口来实现资源的抽象,并通过Resource的各种具体实现来加载资源,Resource接口的继承关系如下图所示。

Resource接口继承关系

下面通过代码示例说明如何加载Resource:

public static void main() {
    String filePath = "D:/spring/test/src/main/resource/context.xml";
    //通过文件路径加载资源
    Resource fileResource = new PathResource(filePath);
    //通过类路径加载资源
    Resource classResource = new ClassPathResource("context.xml");
}

另外还可以通过标识和通配符的方式加载Resource:

public static void main() {
    //定义获取资源的resolver
    ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
    //获取file形式的资源
    Resource[] fileResources = resolver.getResources("file:./src/main/resources/*.xml");
    //获取class形式的资源
    Resource[] classResources = resolver.getResources("classpath*:com/ghang/**/*.class");
}

资源类型的地址前缀列表如下:

地址前缀 示例 资源类型
classPath: classpath:com/ghang/domain/User.class 从类路径加载资源
file: file:./src/main/resources/*.xml 从文件路径加载资源
http: http://www.ghang.com/resource/beans.xml 从Url路径加载资源,也可以是ftp等形式

Bean生命周期


Spring中生成Bean主要是通过BeanFactory接口的实现类来完成的,BeanFacotry是spring中比较原始的Factory,原始的BeanFactory无法支持spring的许多插件,如AOP功能、Web应用等。

BeanFactory接口继承关系

ApplicationContext接口由BeanFactory接口派生而来,因而提供BeanFactory所有的功能。ApplicationContext以一种更向面向框架的方式工作以及对上下文进行分层和实现继承。因此现在的Spring主要通过ApplicationContext生成实际的Bean,这个过程可以由代码实现,也可以由Spring通过配置文件和注解的方式实现。

ApplicationContext接口继承关系

在实际的开发中很少会用到BeanFactory,一般都是用ApplicationContext,在这里仅仅列举一下ApplicationContext的生命周期。

ApplicationContext中Bean的生命周期

我们如果需要对Bean的生命周期进行控制的话,就需要实现Bean生命周期流程中的接口,并自定义各种操作,同时还需要将我们定义的实现类在beans.xml中配置,Spring会自动识别并注册到容器中。

Bean装配


Spring启动时读取程序提供的Bean配置信息,并在Spring容器中生成一张对应的Bean定义注册表,然后根据这张表实例化Bean,组装好Bean之间的依赖关系,为上层应用提供准备好的运行环境。

Spring容器内部流程

Bean注入

Bean的注入方式主要有两种,基于xml进行配置和基于注解进行配置,另外还有基于Java语言和Groovy DSL进行配置的方式,后两者用的比较少,这里只描述一下xml和注解方式的配置。

基于xml文件的方式进行注入时,可以有属性注入、构造函数注入以及工厂方法注入等多种形式,但是这些形式其实都已经过时了,现在主要使用的方式主要是使用p命令空间来进行注入。

采用p命令空间进行注入,需要在xml文件中声明p命名空间,然后使用p:的方式进行注入,例如注入一个User和一个Boss:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:p="http://www.springframework.org/schema/p"    -----------引入p命名空间
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd>

    <!-- 定义user -->
    <bean id="user" class="com.test.spring.User"
        p:name="张三"/>
    <!-- 定义boss -->
    <bean id="boss" class="com.test.spring.Boss"
        p:name="李四"
        p:user-ref="user"/>    
</beans>

基于注解的方式进行注入在形式上要比xml文件的方式更为简洁,Spring主要提供了四个注解来标注Bean:

  • @Component 通用的标注形式
  • @Repository 对DAO实现类进行标注
  • @Service 对Service实现类进行标注
  • @Controller 对Controller实现类进行标注

用注解实现Bean的注入主要是@Autowired来实现,@Autowired注解可以作用在属性和方法上,通常的建议是将注解放在set方法上,这样可以对注入的过程进行自定义的控制。

@Component
public class User {
    private String name;
    private int age;

    @Autowired
    public void setName(String name) {
        this.name = name;
    }
    @Autowired
    public void setAge(int age) {
        this.age = age;
    }
}

在对Java类进行注解后,需要使用Spring提供的context命名空间来扫描并获取Bean的定义信息:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:context="http://www.springframework.org/schema/context"    ------引入context命名空间
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context-4.0.xsd>

    <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 -->
    <context:component-scan base-package="com.ghang.dao"/>
    <context:component-scan base-package="com.ghang.service"/>
</beans>

Bean作用域

Bean主要有两个作用域,即singleton作用域和prototype作用域。在默认情况下,ApplicationContext在启动的时候自动实例化所有的singleton的Bean并缓存到容器中。

prototype作用域是指定Bean只在本次获取的时候起效,比如指定对一个prototype类型的Bean的ref,则每个ref都会返回该Bean的一个新的实例。

属性编辑器

使用p命名空间为Bean设置属性值很便捷,但当项目扩大时,Spring的Bean配置文件会变得较为庞大,此时再为了修改Bean的属性而修改Bean配置文件就显得小题大作了,Spring提供了属性编辑器来解决这个问题。

对于Bean的某些默认属性来说,最好的设置方法是为其单独创建一个properties文件,并在文件中设置需要设置的属性值,例如连接mysql数据库的属性值可以这样设置:

driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test
username=root
password=root

创建了properties之后,需要在Bean的配置文件中引用它,可以通过默认的PropertyPlaceholderConfigure类来引入配置文件。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigure"
    p:location="classpath:=com/ghang/placeholder/jdbc.properties"
    p:fileEncoding="utf-8"/>

引入配置文件后,可以通过Bean配置文件或者注解的方式赋值,Bean配置文件的方式如下:

<bean id="datasource" class="org.apache.commons.dbcp.BaseDataSource"
    destory-method="close"
    p:driverClassName="${driverClassName}" 
    p:url="${url}"
    p:username="${userName}"
    p:password="${password}" />

使用注解的方式如下:

@Component
public class MyDataSource {
    @Value("${driverClassName}")
    private String driverClassName;
    
    @Value("${url}")
    private String url;
    
    @Value("${userName}")
    private String userName;
    
    @Value("${password}")
    private String password;
    
    public String toString(){
         return ToStringBuilder.reflectionToString(this);
    }   
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容