1 IOC 理解

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

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

  • 谁控制谁,控制什么:传统Java SE程序设计,我们直接在对象内部通过new进行创建对象,是程序主动去创建依赖对象;而IoC是有专门一个容器来创建这些对象,即由Ioc容器来控制对 象的创建;谁控制谁?当然是IoC 容器控制了对象;控制什么?那就是主要控制了外部资源获取(不只是对象包括比如文件等)。
  • 为何是反转,哪些方面反转了:有反转就有正转,传统应用程序是由我们自己在对象中主动控制去直接获取依赖对象,也就是正转;而反转则是由容器来帮忙创建及注入依赖对象;为何是反转?因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。

用一句话来讲解什么是IOC:IOC中文名称是控制反转。主要目的是为了实现程序的解耦,由程序员主动实例化对象的过程转交给Spring容器。IOC将本来是由程序员创建对象以及对象的管理和销毁的控制权交给了Spring IOC容器中,Spring IOC将这一块操作从应用程序中解耦出来,降低程序的耦合程度,简化程序设计。

2. DI 理解

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

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

  • 谁依赖于谁:当然是应用程序依赖于IoC容器;
  • 为什么需要依赖:应用程序需要IoC容器来提供对象需要的外部资源;
  • 谁注入谁:很明显是IoC容器注入应用程序某个对象,应用程序依赖的对象;
  • 注入了什么:就是注入某个对象所需要的外部资源(包括对象、资源、常量数据)。

用一句话来讲解什么是依赖注入:当我们一个类需要依赖于另一个对象,我们把另一个对象实例化后注入到这个对象的过程,我们就称为DI。由于Spring IOC掌控对象的创建、管理以及销毁工作,如果一个类需要依赖另外一个对象,Spring IOC容器需要将另外一个对象注入到应用程序某个对象中来,应用程序需要依赖某个外部对象。所有依赖注入提升组件重用的频率,并为系统搭建一个灵活、可扩展的平台。

3. Spring Bean装配

在Spring中,对象无需自己查找或者创建与其所关联的其他对象。相反容器负责把需要相互协作的对象引用赋予各个对象。创建应用对象之间协作关系的行为通常被称为装配(wiring),这也是依赖注入(DI)的本质。

3.1 Spring Bean装配的可选方案

当描述bean如何装配时,Spring具有非常大的灵活性,它提供了三种主要的装配机制:

  • 在XML中进行显示配置
  • 在Java中进行显示配置
  • 隐式的Bean发现机制和自动装配

我们需要尽可能使用在Java中进行显示配置和Bean自动装配机制,极少推荐使用XML中进行显示配置,因为XML中配置需要大量的XML配置工作。

3.2 隐式的Bean发现机制和自动装配

Spring从两个角度来实现自动化装配:

  • 组件扫描(component scanning):Spring会发现应用上下文中所创建的Bean
  • 组件装配(autowiring):Spring自动满足Bean之间的依赖

实现自动化装配有两种方式:(组件扫描默认是不启动的)

1)通过XML启用组件扫描

首先在mvc-config.xml中启动组件扫描功能,并用base-package属性指定扫描范围

<context:component-scan base-package="com.hust.edu"/>

再通过在需要注解的class上添加@Controller、@Service、@Repository、@Component等注解,比如:

@Controller
public class UserController {// ......}

2)通过@ComponentScan注解启用组件扫描

首先在class上使用@ComponentScan启用组件扫描,例如:

@ComponentScan
public class AppConfig {// ......}

此外:@ComponentScan(basePackages=“conf”)等同于@ComponentScan(“conf”),然后通过在需要注解的class上添加@Controller、@Service、@Repository、@Component等注解,例如:

对于@ComponentScan可以通过basePackages或者basePackageClasses指定扫描范围,等同于XML注解中的base-package属性;如果不指定扫描范围,则默认扫描当前类所在包以及子包的所有类。当然Craig Walls建议使用basePackageClasses,因为如果代码重构的话这种方式会立马发现错误,下面是basePackageClasses的使用方式(指定基础扫描类):

@ComponentScan(basePackageClasses={UserController.class// ......
})
public class AppConfig {}

使用@Autowired可以为bean实现自动装配,@Autowired可以使用在构造函数、Setter方法、普通方法和成员变量上。比如下面的用法:


// 用法一:
@Autowired
MessageSend messageSend;
// 用法二:(构造函数也一样,主要是函数参数的依赖)
@Autowired
public void setMessageSend(MessageSend messageSend) {this.messageSend = messageSend;
}

设置@Autowired(required=false)时,Spring尝试执行自动装配,但是如果没有匹配的bean则忽略,但是这种情况故意出现空指针异常NullPointerException。@Autowired注解可以使用@Inject替换,@component可以使用@Named注解替换,后者是源于Java依赖注入规范。

3.3. 通过Java代码装配

大部分的场景自动化装配Bean是满足要求的,但是在一些特殊场景下自动化装配Bean是无法满足要求的,比如说要将第三方的组件装配到自己的应用中,因为没有方法将@component或者@Autowired注解放置在它们的类上。但是你仍然可以采用显示装配方式:Java代码装配和XML配置。

首先需要创建配置类,创建配置类的关键是使用@Configuration注解来表明这个类是一个配置类,该类包涵Spring上下文中如何创建Bean的细节。

声明一个简单Bean:在Java的配置类中编写一个带有@Bean注解的方法,比如下面:

@Bean
public UserServiceImpl userService(){return new UserServiceImpl();
}

默认情况下Bean的ID是方法名,也可以指定Bean的ID:@Bean(name=“userService”),如果有依赖可以使用下面这些的方式来实现:

@Bean
public UserServiceImpl userService(){return new UserServiceImpl(userDao());
}
@Bean
public UserDaoImpl userDao(){return new UserDaoImpl();
}

上面看起来是调用UserDaoImpl(),其实在配置类中Spring会拦截对这个方法的引用,并返回该方法所创建的bean,而不是每次都对其进行实际调用。当然下面这种方式也是可以的,userService()方法需要userDao作为参数,Spring创建Bean的时候会自动装配一个UserDaoImpl到方法中(我猜测应该和@Autowired意思差不多,当Spring Context下只有一个UserDaoImpl就可以通过匹配原则进行装配),这种方式是被推荐的,如果UserDaoImpl不是在本配置类下配置,任然可以正常使用(比如XML默认的组件扫描等)。

@Bean
public UserServiceImpl userService(UserDaoImpl userDao){return new UserServiceImpl(userDao);
}

上面我们通过构造函数的方式实现依赖注入(DI),当然我们也可以用一种更好的方式来实现依赖注入,就是用Setter方法注入,如下所示:


@Bean
public UserServiceImpl userService(UserDaoImpl userDao){UserServiceImpl userService = new UserServiceImpl();userService.setDao(userDao);return userService;
}

3.4 通过XML装配Bean

XML装配Bean的方式,虽然已经不推荐了,但是还是我们最早使用的一种Bean装配的方式,还是需要学习的(很多古老的项目还在用,我目前的公司也是)。下面是一个简单Bean的声明:

<!--index表示获取到对象的标识 clsss创建哪个对象--><bean id="peo" class="com.hust.edu.pojo.People"><!--ref表示引用另一个bean vaule表示基本数据类型或者String--><constructor-arg index="1" type="int" value="1"></constructor-arg><constructor-arg index="0" type="java.lang.String" value="AAA"></constructor-arg></bean>

通过构造器创建Bean

  • 通过无参数构造创建:默认情况
  • 有参数构造创建:需要明确配置

在applicationContext.xml中设置调用哪个构造方法创建对象

  1. 如果设定的条件匹配多个构造方法执行最后的构造方法
  2. index:参数的索引,从0开始
  3. name:参数名字
  4. type:类型(区分关键字和非封装类 int 和 Integer)
public class People {private int id;private String name;public People(int id, String name) {this.id = id;this.name = name;System.out.println("有参数构造方法1 name:"+name+" id:"+id);}public People(String name, int id) {this.id = id;this.name = name;System.out.println("有参数构造方法2 name:"+name+" id:"+id);}public People() {System.out.println("执行默认的构造方法");}public int getId() {return id;}public void setId(int id) {System.out.println("setter id: "+ id);this.id = id;}public String getName() {return name;}public void setName(String name) {System.out.println("setter name: "+ name);this.name = name;}@Overridepublic String toString() {return "People{" +"id=" + id +", name='" + name + '\'' +'}';}
}<!--index表示获取到对象的标识 clsss创建哪个对象--><bean id="peo" class="com.hust.edu.pojo.People"><!--ref表示引用另一个bean vaule表示基本数据类型或者String--><constructor-arg index="1" type="int" value="1"></constructor-arg><constructor-arg index="0" type="java.lang.String" value="AAA"></constructor-arg></bean>

通过实例工厂方法创建Bean

工厂设计模式:帮助创建对象,一个工厂可以参数多个对象。

实例工厂:需要创建工厂,才能创建对象。

实现步骤:

  1. 必须要有一个实例工厂
  2. 在applicationContext.xml 中配置工厂对象和需要创建的对象
public class PeopleFactory {public People createPeople(String type){switch (type){case "A":return new PeopleA();case "B":return new PeopleB();default:return null;}}
}public class PeopleA extends People{@Overridepublic String toString() {System.out.println("A");return super.toString();}
}public class PeopleB extends People{@Overridepublic String toString() {System.out.println("B");return super.toString();}
}<bean id="factory" class="com.hust.edu.constructor.factory.PeopleFactory"></bean><bean id="peo1" factory-bean="factory" factory-method="createPeople" ><constructor-arg name="type" value="A"/></bean><bean id="peo2" factory-bean="factory" factory-method="createPeople" ><constructor-arg name="type" value="B"/></bean>

通过静态工厂创建Bean

静态工厂:不需要创建工厂, 可以快速创建对象

实现步骤:

  1. 必须需要有一个静态工厂,在方法上添加static
  2. 在applicationContext.xml 中创建对象
public class PeopleStaticFactory {public static People getPeopleAInstance(){return new PeopleA();}public static People getPeopleBInstance(){return new PeopleB();}
}<bean id="peo3" class="com.hust.edu.constructor.factory.PeopleStaticFactory" factory-method="getPeopleAInstance"></bean><bean id="peo4" class="com.hust.edu.constructor.factory.PeopleStaticFactory" factory-method="getPeopleBInstance"></bean>

设置属性

  1. 如果属性是基本数据类型或者String类型,注入就比较简单
  2. 如果属性是Set、List、Map、Array, 注入不同的数据类型
 <bean id="peo" class="com.hust.edu.pojo.People"><property name="id" value="1"></property><property name="name" value="zhangsan"></property></bean><bean id="peoSet" class="com.hust.edu.pojo.PeopleSet"><property name="id" value="1"></property><property name="name" value="zhangsan"></property><property name="set" ><set><value>1</value><value>2</value><value>3</value><value>4</value></set></property></bean><bean id="peoList" class="com.hust.edu.pojo.PeopleList"><property name="id" value="1"></property><property name="name" value="zhangsan"></property><property name="list" ><set><value>1</value><value>2</value><value>3</value><value>4</value></set></property></bean><bean id="peoArray" class="com.hust.edu.pojo.PeopleArray"><property name="id" value="1"></property><property name="name" value="zhangsan"></property><property name="strings" ><array><value>1</value><value>2</value><value>3</value><value>4</value></array></property></bean><bean id="peoMap" class="com.hust.edu.pojo.PeopleMap"><property name="id" value="1"></property><property name="name" value="zhangsan"></property><property name="map" ><map><entry key="a" value="123"></entry><entry key="b" value="456"></entry><entry key="c" value="789"></entry></map></property></bean>

4 处理自动装配的歧义性

虽然在实际编写代码中,很少有情况会遇到Bean装配的歧义性,更多的情况是给定的类只有一个实现,这样自动装配就会很好的实现。但是当发生歧义性的时候,Spring提供了多种的可选解决方案。比如People父类,有三个实现类分别是PeopleA、PeopleB和PeopleC。那么在自动装配的时候具体是装配哪个类呢?所以Spring必须提供表选自动装配的Bean。

public class PeopleAutowired {@Autowiredprivate People people;@Overridepublic String toString() {return "PeopleAutowired{" +"people=" + people +'}';}
}@Component
public class PeopleA extends People {@Overridepublic String toString() {System.out.println("A");return super.toString();}
}@Component
public class PeopleB extends People {@Overridepublic String toString() {System.out.println("B");return super.toString();}
}@Component
public class PeopleC extends People {@Overridepublic String toString() {System.out.println("C");return super.toString();}
}

4.1 表示首选的Bean

注意,对于多可选择项,只能有一个可以加上@Primary。通过@Primary注解来标识首选Bean。

@Component
@Primary
public class PeopleA extends People {@Overridepublic String toString() {System.out.println("A");return super.toString();}
}

4.2 限定自动装配的Bean

@Qualifier(“peopleA”)指向的是扫描组件时创建的Bean,并且这个Bean是IceCream类的实例。事实上如果所有的Bean都没有自己指定一个限定符(Qualifier),则会有一个默认的限定符(与Bean ID相同),我们可以在Bean的类上添加@Qualifier注解来自定义限定符,如下所示:

@Component
@Primary
@Qualifier("peopleA")
public class PeopleA extends People {@Overridepublic String toString() {System.out.println("A");return super.toString();}
}@Component
public class PeopleAutowired {@Autowired@Qualifier("peopleB")private People people;@Overridepublic String toString() {return "PeopleAutowired{" +"people=" + people +'}';}
}

5. Bean作用域

默认情况下,Spring应用上下文中所有的Bean都是单例模式。在大多数情况下单例模式都是非常理想的方案。但是如果,你要注入或者装配的Bean是易变的,他们会有一些特有的状态。这种情况下单例模式就会容易被污染。Spring为此定义了很多作用域,可以基于这些作用域创建Bean,包括:

  1. 单例(Singleton):在整个应用中,只创建Bean的一个实例
  2. 原型(Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的Bean实例。这个相当于new的操作
  3. 会话(Session):在Web应用中,为每个会话创建一个Bean实例。对于同一个接口的请求,如果使用不同的浏览器,将会得到不同的实例(Session不同)
  4. 请求(Request):在Web应用中,为每个请求创建一个Bean实例

@Component
@Scope("prototype")
public class Car {// 。。。。。。
}
// ——————或者——————
@Bean
@Scope("prototype")
public LinuxConfig getLinux() {LinuxConfig config = new LinuxConfig();return config;
}

5.1 使用会话和请求作用域

我们常用@Scope来定义Bean的作用域。如用户的购物车信息,如果将购物车类声明为单例(Singleton),那么每个用户都向同一个购物车中添加商品,这样势必会造成混乱;你也许会想到使用原型模式声明购物车,但这样同一用户在不同请求时,所获得的购物车信息是不同的,这也是一种混乱。如下所示:

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION,proxyMode = ScopedProxyMode.INTERFACES)
public class ShoppingCart {}

在这里我们要注意一下,属性proxyMode。这个属性解决了将会话或者请求作用域的Bean注入到单例Bean中所遇到的问题。假设我们要将Cart bean注入到单例StoreService bean的Setter方法中:

StoreService是一个单例bean,会在Spring应用上下文加载的时候创建。 当它创建的时候, Spring会试图将Cart bean注入到setCart()方法中。 但是Cart bean是会话作用域的, 此时并不存在。 直到某个用户进入系统,创建了会话之后,才会出现Cart实例。系统中将会有多个Cart实例: 每个用户一个。 我们并不想让Spring注入某个固定的Cart实例到StoreService中。 我们希望的是当StoreService处理购物车功能时, 它所使用的Cart实例恰好是当前会话所对应的那一个。Spring并不会将实际的Cart bean注入到StoreService中,Spring会注入一个到Cart bean的代理。这个代理会暴露与Cart相同的方法,所以StoreService会认为它就是一个购物车。但是,当StoreService调用Cart的方法时, 代理会对其进行懒解析并将调用委托给会话作用域内真正的Cart bean。

proxyMode属性被设置成了ScopedProxyMode.INTERFACES, 这表明这个代理要实现Cart接口,并将调用委托给实现bean。如果Cart是接口而不是类的话,这是可以的(也是最为理想的代理模式)。但如果Cart是一个具体的类的话,Spring就没有办法创建基于接口的代理了。此时,它必须使用CGLib来生成基于类的代理。所以,如果bean类型是具体类的话,我们必须要将proxyMode属性设置为ScopedProxyMode.TARGET_CLASS,以此来表明要以生成目标类扩展的方式创建代理。

6.运行时值注入

Bean的属性注入的时候有时候硬编码是可以的,但是有时候我们希望避免硬编码值,而是想让这些在运行时再确定。为了实现这些功能,Spring提供了两种在运行时的求值方式:

  • 属性占位符 ${}
  • Spring表示式语言(SpEL)#{}

6.1 属性占位符

   <!-- 加载配置文件,支持注解的方法 --><bean id="prop" class="org.springframework.beans.factory.config.PropertiesFactoryBean"><property name="locations"><array></array></property></bean><!-- 加载配置文件,支持xml的方式--><bean class="org.springframework.beans.factory.config.PreferencesPlaceholderConfigurer"><property name="locations"><array></array></property></bean>

通过配置PreferencesPlaceholderConfigurer和PropertiesFactoryBean用来在Java代码中可以使用属性占位符来方位外部资源属性。

6.2 Spring表示式语言(SpEL)

Spring表达式语言(Spring Expression Language),它能够以一种强大的和简洁的方式将值装配到Bean属性和构造参数中,这个过程中所使用到的表达式会在运行时计算得到。

SPEL有很多特性:

  • 使用Bean ID来引用Bean
  • 调用方法和访问对象的属性
  • 对值进行算式、关系和逻辑运算
  • 正在表达式匹配
  • 集合操作

SPEL的使用方法

  1. 表达字面值
  2. 引用Bean、属性和方法
  3. 在表达式中使用类型
  4. SPEL运算符
  5. 计算正则表达式
  6. 计算集合

Spring 详解(二):IOC 和DI相关推荐

  1. Spring详解二号AOP冰霜篇

    依旧是常用的学习讨论进行的论述 Spring的另一个核心就是AOP:Aspect Oriented Programming 面向切面编程 面向切面编程:基于OOP基础之上新的编程思想,OOP面向的主要 ...

  2. 安卓 linux init.rc,[原创]Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  3. [转]文件IO详解(二)---文件描述符(fd)和inode号的关系

    原文:https://www.cnblogs.com/frank-yxs/p/5925563.html 文件IO详解(二)---文件描述符(fd)和inode号的关系 ---------------- ...

  4. PopUpWindow使用详解(二)——进阶及答疑

    相关文章: 1.<PopUpWindow使用详解(一)--基本使用> 2.<PopUpWindow使用详解(二)--进阶及答疑> 上篇为大家基本讲述了有关PopupWindow ...

  5. Android init.rc文件解析过程详解(二)

    Android init.rc文件解析过程详解(二) 3.parse_new_section代码如下: void parse_new_section(struct parse_state *state ...

  6. linux 进程间通信 dbus-glib【实例】详解二(下) 消息和消息总线(ListActivatableNames和服务器的自动启动)(附代码)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  7. linux 进程间通信 dbus-glib【实例】详解二(上) 消息和消息总线(附代码)

    linux 进程间通信 dbus-glib[实例]详解一(附代码)(d-feet工具使用) linux 进程间通信 dbus-glib[实例]详解二(上) 消息和消息总线(附代码) linux 进程间 ...

  8. Android Gradle 自定义Task详解二:进阶

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/78523958 本文出自[赵彦军的博客] 系列目录 Android Gradle使用 ...

  9. Android Loader 异步加载详解二:探寻Loader内部机制

    Android Loader 异步加载详解二:探寻Loader内部机制 转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/7025991 ...

  10. EXT核心API详解(二)-Array/Date/Function/Number/String

    EXT核心API详解(二)-Array/Date/Function/Number/String Array类 indexOf( Object o )  Number object是否在数组中,找不到返 ...

最新文章

  1. IOS开发 Block的学习
  2. SQL编程---存储过程和存储函数
  3. 小皮面板有php环境吗,体验phpStudy小皮面板创建LAMP/LNMP系统和建站图文
  4. gitlab版本控制系统源码部署
  5. play for scala 通过网易smtp发送邮件
  6. php 控制器方法,ThinkPhp3.2跨控制器调用方法
  7. 新高考计算机学业水平考试,新高考对学业水平测试的要求
  8. 魔方Newlife.Cube权限系统的使用及模版覆盖详解
  9. java好听的名字_(张天爱)女孩好听的名字又有内涵 - Java3y - 宝宝起名网
  10. 【独行秀才】macOS Monterey 12.1Beta2(21C5031d)原版镜像
  11. 对于rh v5系列服务器,华为rh2288v5服务器重定向问题引起pxe报错
  12. touch触摸事件以及常用触摸功能
  13. Java循环控制语句
  14. 深度优先搜索与宽度优先搜索
  15. yii2框架 电商系统在线直播开发
  16. 2020年度十大流行语
  17. PBX、FXO、FXS等通信相关知识
  18. 2.8 将一个整型变量的值赋给一个布尔型变量,再将这个布尔型变量的值赋给一个整型变量,得到的值是多少?
  19. 为祖国庆生, 我们都经历了些什么?
  20. AutoCAD2012从入门到精通中文视频教程 第21课 偏移与镜像(个人收藏)

热门文章

  1. [T-ARA][Goodbye, OK]
  2. javascript的null 和undifined
  3. Nginx多域名多Server反向代理配置
  4. oracle获取时间毫秒数
  5. 2、掌握C++基本语法
  6. OpenCV库使用sift函数,出现“The function/feature is not implemented”问题解决方法
  7. 转载:malloc()与new()的区别详解
  8. 语言 提取列名_学习健明老师发布的R语言练习题的学习笔记(二)
  9. 485有时候从机接收指令没反应_原创案例丨秒杀一辆12年爱唯欧启动无反应
  10. 说说数据库连接池工作原理和实现方案?