1. 初识IoC
控制反转(Inversion of Control,缩写为IoC),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。其中最常见的方式叫做依赖注入(Dependency Injection,简称DI),还有一种方式叫“依赖查找”(Dependency Lookup)。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体,将其所依赖的对象的引用传递(注入)给它。
通常Class A中用到了Class B的对象b,需要在A的代码中显式的new一个B的对象。采用依赖注入技术之后,A的代码只需要定义一个私有的B对象,不需要直接new来获得这个对象,而是通过相关的容器控制程序来将B对象在外部new出来并注入到A类里的引用中。而具体获取的方法、对象被获取时的状态由配置文件(如XML)来指定。
在面向对象的编程中,有几种基本技术可以实现控制反转。这些是:
- 使用工厂模式
- 使用服务定位器模式
- 使用以下任何给定类型的依赖项注入:
- 构造函数注入
- setter注射
- 接口注入
IoC容器的职责
依赖查找,依赖注入
生命周期管理 1, 容器 2,托管的资源(JavaBean 或者其他资源)
配置 1,容器 2,外部化配置 3, 托管的资源
IoC容器的实现
对于JavaSE而言,IoC依赖于以下3点来实现。Java Beans 这个API帮助我们来进行Bean的管理。 Java ServiceLoader SPI来帮助我们加载一些组件。JNDI(J啊v啊Naming and Directory Interface)帮助我们查找相应的资源。
传统IoC容器的实现
Java Beans 作为IoC容器
特性:依赖查找,生命周期管理,配置元信息,事件,自定义,资源管理,持久化
什么才是Java Bean?简单说是一个POJO
code
public class Person {
private String name;
private Integer age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
public class BeanInfoDemo {
public static void main(String[] args) {
/**
* 内省(Introspector) 是Java 语言对 JavaBean 类属性、事件的一种缺省处理方法。
* JavaBean是一种特殊的类,主要用于传递数据信息,这种类中的方法主要用于访问私有的字段,
* 且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进JavaBean中,
* 这种对象称为“值对象”(Value Object),或“VO”。方法比较少。这些信息储存在类的私有变量中,
* 通过set()、get()获得。
* 将JavaBean中的属性封装起来进行操作。在程序把一个类当做JavaBean来看,就是调用Introspector.getBeanInfo()方法,
* 得到的BeanInfo对象封装了把这个类当做JavaBean看的结果信息,即属性的信息。
* getPropertyDescriptors(),获得属性的描述,可以采用遍历BeanInfo的方法,来查找、设置类的属性。
*/
try {
BeanInfo beanInfo = Introspector.getBeanInfo(Person.class);
Stream.of(beanInfo.getPropertyDescriptors()).forEach(
propertyDescriptor -> {
System.out.println(propertyDescriptor);
}
);
} catch (IntrospectionException e) {
e.printStackTrace();
}
}
}
这里获得的属性会有一个 class 是因为每个类都有一个父类Object,而这个父类中有个属性getClass。为了只关注我们定义的类,我们使用
public static BeanInfo getBeanInfo(Class<?> beanClass, Class<?> stopClass)
在面的代码上的第二个参数填入 Object.class
PropertyDescriptor在spring框架中使用的非常广泛。
输入是一个字符串,输出是相应的类型。例如 输入age 输出的是它的类型Integer。PropertyEditor用于Spring中的类型转换,对类的处理非常有帮助。
轻量级容器IoC
特征:1,管理coder的应用代码 2,能快速启动 3,不需要特殊的配置和操作 4,轻量级内存占用,5,可管控
2. Spring的控制反转
在org.springframework.beans和org.springframework.context软件包提供了对Spring框架的IoC容器的基础。该BeanFactory接口提供了一种高级配置机制,能够管理任何性质的对象。该ApplicationContext接口建立在该接口的基础上BeanFactory(它是一个子接口),并添加了其他功能,例如与Spring的AOP功能的更轻松集成,消息资源处理(用于国际化),事件传播以及特定于应用程序层的上下文,例如在Web应用程序中使用的WebApplicationContext。
这BeanFactory是Spring IoC容器的实际表示,该容器 负责包含和管理上述bean。该BeanFactory接口是Spring中的中央IoC容器接口。
BeanFactory接口有很多实现。最常用的BeanFactory实现是XmlBeanFactory类。其他常用的类是XmlWebApplicationContext。根据bean的定义,工厂将返回所包含对象的独立实例(Prototype设计模式),或者返回单个共享实例(Singleton设计模式的替代方案,其中实例是作用域中的单例)。的工厂)。将返回哪种类型的实例取决于bean工厂的配置:API是相同的。
在深入研究依赖注入类型之前,首先确定在spring框架中创建bean的方法,因为它将有助于理解下一部分的内容。
3. 如何在Spring中创建bean
Bean定义可以视为创建一个或多个实际对象的方法。使用时,容器将按bean的名称匹配,并使用该bean定义封装的配置元数据来创建(或获取)实际对象。
3.1 使用构造函数
当使用构造函数方法创建bean时,所有普通类都可以被Spring使用并与之兼容。也就是说,正在创建的类不需要实现任何特定的接口或以特定的方式进行编码。仅指定bean类就足够了。使用基于XML的配置元数据时,您可以像这样指定bean类:
<bean id="exampleBean"/>
3.2 使用静态工厂方法
当定义要使用静态工厂方法创建的bean时,连同指定包含静态工厂方法的类的class属性一起,需要另一个名为factory-method的属性来指定工厂方法本身的名称。
<bean id="exampleBean" factory-method="createInstance"/>
Spring希望能够调用此方法并返回一个活动对象,从那时起,该对象将被视为正常通过构造函数创建的。
3.3 使用实例工厂方法
以类似于通过静态工厂方法进行实例化的方式,使用实例工厂方法进行实例化是调用容器中现有bean的factory方法来创建新bean。
<bean id="myFactoryBean" class="...">
<bean id="exampleBean" factory-bean="myFactoryBean" factory-method="createInstance"></bean>
4. Spring的依赖注入
依赖注入(DI)的基本原理是,对象只能通过构造函数参数,工厂方法的参数或在对象实例从工厂方法构造或返回后设置的属性来定义其依赖关系。然后,容器的工作是在创建bean时实际注入那些依赖项。从根本上讲,这是相反的概念,因此被称为控制反转(IoC)。
4.1 二传手注射
通过调用无参数构造函数或无参数静态工厂方法以实例化bean之后,在bean上调用setter方法,可以实现基于setter的DI。
public class TestSetterDI {
DemoBean demoBean = null;
public void setDemoBean(DemoBean demoBean) {
this.demoBean = demoBean;
}
}
4.2 构造函数注入
基于构造函数的DI是通过调用具有多个参数(每个参数代表一个协作者)的构造函数来实现的。另外,调用带有特定参数的静态工厂方法来构造Bean几乎是等效的,本文的其余部分将类似地考虑构造函数的参数和静态工厂方法的参数。
public class ConstructorDI {
DemoBean demoBean = null;
public TestSetterDI (DemoBean demoBean) {
this.demoBean = demoBean;
}
}
4.3 接口注入
在这种方法中,我们实现了IOC框架的接口。IOC框架将使用接口方法将对象注入到主类中。当您需要一些不适用于放置在属性中的逻辑时,使用这种方法更为合适。如日志支持。
public void SetLogger(ILogger logger)
{
_notificationService.SetLogger(logger);
_productService.SetLogger(logger);
}
5.面试题
5.1 组件和服务之间有什么区别?
组件是一整套软件,旨在由不受组件编写者控制的应用程序直接使用。“不更改”表示使用中的应用程序不会更改组件的源代码,尽管它们可以通过以组件编写者允许的方式扩展组件的行为来更改组件的行为。
服务类似于组件,因为它被外部应用程序使用。主要区别在于要在本地使用的组件(请考虑jar文件,程序集,dll或源导入)。服务将通过同步或异步的某个远程接口(例如,Web服务,消息系统,RPC或套接字)远程使用。
5.2 DI与服务定位器模式有何不同?
依赖项注入器的主要好处是,它允许根据环境和使用情况插入合适的服务实现。注入不是打破这种依赖性的唯一方法,另一种方法是使用服务定位器。服务定位器的基本思想是拥有一个对象,该对象知道如何掌握应用程序可能需要的所有服务。然后,它将扫描所有此类服务,并将它们存储为单例注册表。当要求提供服务实现时,请求者可以使用令牌查询注册表并获取适当的实现。
通常,这些注册表是通过一些配置文件填充的。关键区别在于,使用服务定位器时,服务的每个用户都对定位器具有依赖性。定位器可以隐藏对其他实现的依赖关系,但是您确实需要查看定位器。
5.3 使用哪个更好,即服务定位器或依赖项注入?
嗯,正如我已经说过的,关键区别在于,使用服务定位器,服务的每个用户都对定位器有依赖性。这意味着您必须在输入和输出方面了解服务定位器的详细信息。因此,实际上成为选择哪种模式的决定因素。
如果维护注册表信息很容易且很必要,则可以使用服务定位器,或者直接使用依赖项注入,因为它不会因任何必要条件打扰服务的用户。
5.4 构造函数注入或setter注入哪个更好?
在setter和构造函数注入之间进行选择很有趣,因为它反映了面向对象编程的一个更普遍的问题–如果您在构造函数或setter中填充字段。
带参数的构造函数使您清楚地说明了在明显的位置创建有效对象的含义。如果有多种方法可以实现,请创建多个构造函数以显示不同的组合。构造函数初始化的另一个优点是,您可以通过不提供设置器来清楚地隐藏任何不可变的字段。我认为这很重要-如果某些事情不应该改变,那么缺少二传手就可以很好地传达这一点。如果使用setter进行初始化,则可能会很麻烦。
但是,如果您有很多构造函数参数,则看起来会很混乱,尤其是在没有关键字参数的语言中。如果您有多种方法来构造有效的对象,则可能很难通过构造函数来显示它,因为构造函数只能在参数的数量和类型上有所不同。如果您具有简单的参数(例如字符串),构造函数也会受到影响。使用setter注入,您可以为每个setter命名,以指示该字符串应该执行的操作。对于构造函数,您只是依靠位置,这很难遵循。
我的偏好是从构造函数注入开始,但是一旦我上面概述的问题开始成为问题,就准备切换到setter注入。
5.5 什么是BeanFactory?
BeanFactory能够在实例化协作对象之间创建关联。这消除了bean本身和bean客户端的配置负担。BeanFactory还参与bean的生命周期,从而调用自定义初始化和销毁方法。
5.6 什么是应用程序上下文?
Bean工厂适合简单的应用程序,但是要利用Spring框架的全部功能,您可能需要升级到Springs更高级的容器,即应用程序上下文。从表面上看,应用程序上下文与Bean工厂相同,两者都加载Bean定义,将Bean绑定在一起并根据请求分配Bean。但它也提供:
解决文本消息的方法,包括对国际化的支持。加载文件资源的通用方法。注册为侦听器的bean的事件。
5.7 应用程序上下文的常见实现是什么?
三个常用实现ApplicationContext是:
ClassPathXmlApplicationContext:它从位于类路径中的XML文件中加载上下文定义,并将上下文定义视为类路径资源。通过使用code从应用程序的类路径中加载应用程序上下文。
ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
FileSystemXmlApplicationContext:它从文件系统中的XML文件加载上下文定义。使用代码从文件系统中加载应用程序上下文。
ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);
XmlWebApplicationContext :它从Web应用程序中包含的XML文件加载上下文定义。
5.8 最好使用BeanFactory或ApplicationContext?
一个BeanFactory非常简单,只是实例化和配置bean类。ApplicationContext也这样做,它提供了支持基础结构,以启用许多特定于企业的功能,例如事务和AOP。简而言之,建议使用ApplicationContext。