Spring系列—— IOC容器

1 概念

IoC(Inverse of Control)控制反转是Spring容器的内核,AOP、声明式事务都是在此基础上开花结果。
因为IoC不够开门见山,Martin Fowler 提出了DI(Dependency Injection 依赖注入)的概念来代替IoC,即调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖

2 IOC类型

注入方法上看,主要划分为三种类型:

  • 构造函数注入
  • 属性注入
  • 接口注入

Spring支持构造函数注入和属性注入!

2.1 构造函数注入

通过调用类的构造函数,将接口实现类通过构造函数变量传入。
借助《精通Spring4.x企业应用实战》中liudehua饰演革离攻城理解构造函数注入如下:
MoAttack不用care具体谁来饰演GeLi这个角色,MoAttack更像剧本操作类!Director通过实现direct()方法将选定的演员注入到MoAttack中,发起MoAttack需要执行的操作(比如攻城,cityGateAsk())。

public class MoAttack {
    private GeLi geli;

    // 构造函数注入
    public MoAttack(GeLi geli) {
        this.geli = geli;
    }

    public void cityGateAsk() {
        this.geli.responseAsk("墨者革离");
    }
}

public class Director {
    public void direct() {
        // 指定接口实现类
        GeLi geli = new LiuDeHua();
        // 注入对象
        MoAttack moAttack = new MoAttack(geli);
        // 执行操作
        moAttack.cityGateAsk();
    }
}

public interface GeLi {
    void responseAsk(String str);
}

public class LiuDeHua implements GeLi {
    @Override
    public void responseAsk(String str) {
        System.out.println(str);
    }
}

2.2 属性注入

通过Setter方法完成调用类所需依赖的注入,更加灵活方便。

public class MoAttack {
    private GeLi geli;

    public void cityGateAsk() {
        this.geli.responseAsk("墨者革离");
    }

    public void setter(GeLi geli) {
        this.geli = geli;
    }
}

public class Director {
    public void direct() {
        GeLi geli = new LiuDeHua();
        MoAttack moAttack = new MoAttack();
        moAttack.setter(geli);
        moAttack.cityGateAsk();
    }
}

2.3 接口注入

同使用Setter方法对属性注入无本质区别,并且会增加接口,不推荐使用!

3 BeanFactory 和 ApplicationContext

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

BeanFactory(Bean 工厂)是Spring框架最核心的接口,提供了高级IOC的配置机制,管理不同类型的Java对象。
ApplicationContext(应用上下文)建立在BeanFactory基础之上,提供了更多面向应用的功能。

我们一般称BeanFactory为IOC容器;ApplicationContext为应用上下文或者Spring容器。

4 装配Bean

Spring容器成功启动的三个必要条件

  • Spring 框架的类包都已经放在应用程序的类路径下。
  • 应用程序为Spring提供了完整的Bean配置信息
  • Bean 的类都已经放在应用程序的类路径下。

Bean配置信息包括以下四部分:

  • Bean 实现类
  • Bean 属性信息
  • Bean 依赖关系 Bean之间的依赖
  • Bean 行为配置 生命周期范围、回调函数等

Spring 支持多种形式的Bean配置方式。基于XML的配置、基于注解配置、基于Java类配置、基于Groovy动态语言配置。

Bean 配置信息定义了Bean的实现和依赖关系,Spring容器根据各种形式的配置信息在容器内部建立Bean定义注册表;然后根据注册表加载实例化Bean,并建立Bean之间的依赖关系;最后将这些准备好的Bean放到Bean缓存池中,供外层应用程序调用。

4.1 基于XML的配置

4.1.1 XML文件概念

Spring2.0 以后采用Schema格式,让不同类型的配置拥有自己的命名空间,配置文件更具扩展性。

<?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标准命名空间,用于指定自定义命名空间的Schema文件
	xmlns:aop="http://www.springframework.org/schema/aop" 自定义命名空间
	xsi:schemaLocation=" 为每个命名空间指定具体的Schema文件
		http://www.springframework.org/schema/beans 
		http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
	    http://www.springframework.org/schema/aop 
	    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">

Schema在文档跟节点通过xmlns对文档所引用的命名空间进行声明。
默认命名空间:没有空间名,用于SpringBean的定义。
xsi标准命名空间:用于为每个文档中的命名空间指定相应的Schema样式文件,W3C定义的标准命名空间。
自定义命名空间:用于Spring配置的自己定义命名空间。如果自己定义命名空间别名为空,表示该命名空间为文档默认命名空间

指定命名空间的用途:

  1. XML解析器可以获取Schema文件并对文档进行格式合法性炎症;
  2. 开发环境下,IDE引用Schema文件对文档编辑提供自动补全功能。
4.1.2 装配Bean
<bean id = "foo" class = "com.smart.Foo" />

id是Bean的名称,通过容器的getBean(“foo”)即可获取对应的Bean,在容器中起到定位查找的作用;class属性指定了Bean对应的实现类。
配置规则:

  • 指定id,id在IOC容器中必须是唯一的。
  • 指定name,允许出现重复name,如果有多个相同name的Bean,那么通过getBean(beanName)获取Bean时,将返回后面生命的那个Bean,原因是后面的Bean覆盖了前面同名的Bean。因为bean仅配置name,默认ID=name。
  • id、name均未指定,Spring自动将全限定类名作为Bean名称,用户可以通过getBean(“com.smart.Foo”)获取Bean。如果存在多个实现类相同的匿名Bean,全限定类名"com.smart.Foo"、“com.smart.Foo#1”、“com.smart.Foo#2”、······依次类推
4.1.3 基于XML配置的依赖注入

属性注入:
要求Bean提供一个默认的构造函数,并为需要注入的属性提供对应的Setter方法。

<bean id = "foo" class = "com.smart.Foo" >
	<property name = "name"><value>"fooName"</value> </property>
</bean>

Public class Foo {
   private String name;
   public void setName(String name) {
   	this.name = name;
   }
}

构造函数注入:
参照属性注入,在构造方法中传入参数,通过<constructor-arg>标签配置。

工厂方法注入:
省略。。。

4.2 Bean作用域

类型说明
singletonSpring IOC容器中只存在一个Bean实例,Bean以单例的方式存在,默认作用域
prototype每次从容器中调用Bean时,都会返回一个新的实例。每次调用getBean()时,等效new XXXBean()操作
request每次Http请求都会返回一个新的Bean,该作用域仅适用于WebApplicationContext环境
session同一Http Session 共享一个Bean,不同Http Session 使用不同Bean,该作用域仅适用于WebApplicationContext环境
globalSession同一全局Session 共享同一Bean,一般用于Portlet应用环境,该作用域仅适用于WebApplicationContext环境
singleton

默认情况下,Spring的ApplicationContext容器在启动时,自动实例化所有singleton的Bean并缓存在容器中。单例模式优点:

  1. 对Bean提前进行实例化操作会及早发现一些潜在的配置问题
  2. Bean以缓存的方式保存,在运行时无需实例化,提高了运行效率

singleton Lazy 初始化
使用@Lazy在类中注解或者在XML配置Bean标签中添加lazy-init=“true”。

prototype

默认情况下,Spring容器启动时不实例化prototype的Bean,Spring容器将Bean交给调用者后不再管理它的生命周期。

4.3 基于注解的配置

不管是XML还是注解,都是表达Bean定义的载体,实质都是为Spring容器提供Bean定义的信息,在表现形式上都是将XML定义的内容通过类注解进行描述

Spring容器成功启动的是三个必要条件:Bean定义信息、Bean实现类和Spring本身。

如果基于XML的配置,则Bean定义信息和Bean实现类本身是分离的;如果采用基于注解的配置文件,则Bean定义信息通过在Bean实现类上的标注注解实现。

package com.smart.anno;
import org.springframework.stereotype.Component;
@Component("userDao")
public class UserDao {
    // ....
}

@Component注解在UserDao类声明处对类进行标注,它可以被Spring容器识别,Spring容器自动将POJO转换为容器管理的Bean。
等效XML配置如下:

<bean id = "userDao" class = "com.smart.anno.UserDao"/>

除@Component外,Spring还提供了3个基本功能和@Component等效的注解(@Repository、@Service、@Controller),分别用于对Dao、Service和Web层的Controller进行注解。

提供这3个注解是为了让标注类本身的用途清晰化,推荐使用特定的注解标注特定Bean

1.@Autowired属性注入
Spring通过@Autowired注解实现Bean的依赖注入。

package com.smart.anno;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class LoginService {
    @Autowired
    private UserDao userDao;
}

@Service将LoginService标注为一个Bean,@Autowired注入UserDao的Bean,@Autowired默认按类型匹配的方式在容器中查找匹配的Bean,当且仅有一个匹配的Bean时,Spring将其注入@Autowired标注的变量中

2.使用@Autowired的required属性
如果容器中没有一个和标注变量类型匹配的Bean,Spring容器启动时报NoSuchBeanDefinitionException异常,如果希望Spring即使找不到匹配的Bean完成注入也不要抛异常,使用@Autowired(required = false)进行标注。
默认情况下required属性值为true。

3.使用@Qualifier指定注入Bean的名称
如果容器中有一个以上匹配的Bean时,通过@Qualifier限定Bean的名称。
假设有两个类型为UserDao的Bean,一个名为userDao,一个名为otherUserDao,@Qualifier限定名称解决类型匹配问题!

@Service
public class LoginService {
    @Autowired
    @Qualifier("userDao")
    private UserDao userDao;
}

4.对类方法进行标注
@Autowired可以对类成员变量及方法的入参进行标注

@Service
public class LoginService {
    private UserDao userDao;

    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

Spring容器中大部分Bean都是单例的,所以一般无须通过@Repository@Service等注解的value属性为Bean指定名称,也无需使用@Qualifier注解按名称进行注入。

Tips:
项目开发中建议在方法上标注@Autowired注解。

5.对集合类进行标注
如果对类中集合类的变量或者和方法的入参进行@Autowired标注,那么Spring会将容器中类型匹配的Bean都自动注入进来。

@Component
public class MyComponent {
    @Autowired
    private List<Plugin> plugins;

    @Autowired
    private Map<String, Plugin> map;

    public List<Plugin> getPlugins() {
        return plugins;
    }

    public Map<String, Plugin> getMap() {
        return map;
    }
}

Plugin是一个接口,OnePlugin和TwoPlugin是Plugin接口的实现类。
注入时,plugins会将所有类型匹配的Bean都注入到list中;所有实现Plugin接口的Bean注入到map集合,key是Bean的名字,value是所有实现了Plugin的Bean。
OnePlugin和TwoPlugin的加载顺序是不确定的,可以通过@Order注解或实现Order接口来决定Bean加载顺序,值越小,优先被加载。

import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

@Component
@Order(value = 1)
public class OnePlugin implements Plugin {
    //...
}

注意:
Autowired是类型匹配的,除对集合类进行标注外,注入类型为接口时!一个接口只能有一个实现,否则会报错。

@Component
public class MyComponent {
    @Autowired
    private Plugin plugin;
}

6.对延迟依赖注入支持
Spring4.0支持延迟依赖注入,即在Spring容器启动时,对于在Bean上标注@Lazy和@Autowired注解的属性,不会立即注入属性值,而是延迟到调用此属性的时候才会注入属性值。
注意:@Lazy的使用,需要在属性和目标Bean上同时标注,缺一不可
比如:

@Lazy
@Component("userDao")
public class UserDao {
}

@Service
public class LoginService {
    private UserDao userDao;

    @Lazy
    @Autowired
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
}

7.对标准注解的支持
Spring支持JSR-250中的@Resource和JSR-330中的@Inject注解,这两个注解和@Autowired注解的功能类似,都是对类变更及刚发入参提供自动注入功能。
@Resource需要提供一个Bean名称属性,按名称匹配注入Bean;@Inject和@Autowired同样是按类型匹配注入Bean,但没有required属性。
无论@Recourse还是@Inject都没有@Autowired功能强大!!!可以忽略这两个注解

Bean作用范围

通过注解配置的Bean和通过配置的Bean一样,默认作用范围是singleton。Spring为注解配置提供了一个@Scope注解,可以通过它显示指定Bean的作用范围。

4.4 使用Java类提供Bean定义信息

普通POJO只要标注@Configuration注解,就可以为Spring容器提供Bean定义的信息,每个标注了@Bean的类方法都相当于提供了一个Bean的定义信息。

package com.smart.conf;

import com.smart.dao.LogDao;
import com.smart.dao.UserDao;
import com.smart.service.LogonService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class AppConf {

    @Bean
    public UserDao userDao() {
        return new UserDao();
    }

    @Bean
    public LogDao logDao() {
        return new LogDao();
    }

    @Bean
    public LogonService logonService() {
        LogonService logonService = new LogonService();
        logonService.setUserDao(userDao());
        logonService.setLogonDao(LogDao());
        return logonService;
    }
}

Bean类型由方法返回值的类型决定,名称默认和方法名相同,也可以通过入参显示指定Bean名称,如Bean(name = “userDao”)。@Bean所标注的方法提供了Bean的实例化逻辑。

以上配置和如下XML配置等效

<bean id="userDao" class="com.smart.dao.UserDao" />
<bean id="logDao" class="com.smart.dao.LogDao" />
<bean id="logonService" class="com.smart.service.logonService"
    p:logDao-ref="userDao" p:userDao-ref="logDao" />

基于Java类的配置方式和基于XML或者基于注解的配置方式相比,前者通过代码编程的方式可以更加灵活的实现Bean的实例化和Bean之间的装配;后者都是通过配置声明的方式,在灵活性上要稍逊一些,但是配置上更简单一些。

如果Bean在多个@Configuration配置类中定义,@Configuration注解类本身已经标注了@Component注解,所以任何标注了@Configuration的类,本身也相当于标注了@Component,他们可以像普通Bean一样被注入到其他Bean中。可以通过@Autowired注入到其他配置类中。

4.5 基于Groovy DSL 配置

省略。。。

4.6 对比总结

基于XML配置适用场景:
1)Bean实现类来源于第三方类库,如DataSource、JdbcTemplate等,因为无法再类中标注注解,所以使用XML配置。
2)命名空间的配置,如aop、context等,只能使用XML配置

基于注解配置适用场景:
Bean的实现类是当前项目开发的,可以直接在Java类中适用基于注解的配置。

基于Java类配置和基于Groovy DSL配置适用场景:
实例化Bean的逻辑比较复杂。

项目开发一般采用:XML+基于注解的配置方式

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值