一、IoC容器概述
IoC容器是一种面向接口编程,将接口的具体实现和对象的组装延后至编译后,并将这些配置从代码提取到配置文件中的一种编程方式。IoC容器通过JAVA提供的反射机制根据配置文件提供的信息选择实现类并装配实例化对象。
IoC容器允许这样一种编程模式:首先定义一个接口,并且通过接口实现两个不同实现方式的实现类(比如对于存储数据这个接口,实现一个用操作系统文件方式存储的类和一个用数据库存储的类),然后某个业务类使用这个接口的实现来完成某项任务。在通常使用的编程模式中此时需要在业务类中创建接口的某个实现(即使用哪个实现类在代码中,并且在编译前必须决定),而IoC容器则将此过程延后,并将其提取到一个xml配置文件中(也可以使用注解的模式),如果在应用发布后需要修改某个接口的实现,只需要修改配置文件而无需对代码进行修改、重新编译和重新发布。
IoC容器提供了这样的一种理念:按照清晰的功能模块来构造应用,用接口来描述模块功能,同个模块功能提供针对不同应用环境的不同实现,在部署时按照实际应用环境通过配置文件重新组装、优化应用。
二、IoC容器的基本底层实现
IoC容器的底层实现主要依赖于JAVA的反射机制。通过反射机制和配置文件,容器载入相应的.class文件,实例化对象。使用java.reflect.Method类调用相应的set方法来设置对象属性。如下的配置文件:
1.容器通过某个XML解析器,解析配置文件;
2.在节点处,解析出class=com.sample.bean.Car属性,使用ClassLoader载入对应的.class文件,获得一个Class类型的对象;
3.通过Class.getDeclaredContructor成员函数获取Constructor>类型的对象,这里没有配置子节点,所以获取使用空参数的构造函数对象;
4.使用Constructor>.newInstance成员函数实例化com.sample.bean.Car对象;
5.解析p命名空间下的所有属性,并通过Class.getDeclaredMethod方法获取相应setXXX方法对应的Method对象,并且通过调用Method.invoke方法来实际执行Car.setXxx方法设置属性;
6.解析init-method属性,通过Class.getDeclaredMethod和属性的值来获得对应Car.init的Method对象,执行Method.invoke,实际效果为执行Car.init;
7.将生成完成的对象放入到IoC的Bean缓冲池中;
8.当IoC容器被释放时,通过解析destory-method属性,通过Class.getDeclaredMethod和属性的值来获得对应Car.destory的Method对象,执行Method.invoke,实际效果为执行Car.destory,完成相关资源的释放;
通过以下代码即可获得配置文件中id为car的Car类实例对象,通过打印相关信息我们可以看到相应的属性已经被设置为配置文件中我们配置好的值。
public class MainEntry
{
public static void main(String[] args)
{
ClassPathXmlApplicationContext actx = new ClassPathXmlApplicationContext("applicationContext.xml");
Car car = (Car)actx.getBean("car");
System.out.println(car);
actx.close();
}
}
三、相关XML文件概念
在前面的配置文件中
在beans节点中,有xmlns和xsi:schemaLocation两个属性,这两个属性的语法意义如下文
1.xmlns 命名空间定义
定义一个命名空间的语法为xmlns:namespace-prefix="namespaceURI",例如以上xml文件中的xmlns:p="http://www.springframework.org/schema/p"。namespace-prefix是命名空间的缩写,而后面的namespaceURI是命名空间的全称。在后面使用命名空间时只需要在节点或者属性的名字前加上“namesapce-prefix:”即可。作为命名空间全称的namespaceURI在形式上虽然是一个URI,但是这只是命名空间名字的一种约定俗成的命名方法,并不作为查找schema的路径或其他任何用处,这样做的好处只是为了有效避免命名空间的名字冲突。xml当中命名空间的用处和C++的名字空间以及java的包的用处是相同的,即避免名字冲突的作用。
在这里我们看到有一个命名空间的定义省略了namespace-prefix部分,即xmlns="http://www.springframework.org/schema/beans",这定义了一个默认命名空间的名字,也就是说,下面不用省略namespace-prefix:nodeName标识的节点,如上面xml中的bean节点使用的是以上这个默认命名空间。
2.xsi:schemaLocation命名空间对应schema文件的URI
这个属性定义了一个命名空间使用的schema文件路径,此属性告诉xml解析器当遇到命名空间时去哪里查找对应的schema文件。这里定义的格式为namespaceURI schemaFileURI。namespaceURI和schemaFileURI之间用空格、换行符隔开。
四、IoC容器的配置(XML和注解模式)
Spring通过一个配置文件或者注解的方式来描述Bean(生命周期和属性值)和Bean之间的依赖关系,利用JAVA的反射机制实现Bean和Bean之间的依赖关系,并且通过BeanFactory或者ApplicationContext的实例将Bean提供给应用使用。
1.基本属性的配置
基本属性是指JAVA中的基本数据类型,例如整型、浮点、字符等等。
a.XML配置
在XML中我们使用p命名空间的属性来配置基本属性,配置的格式为p:<属性名>=值。实例如下
在这个XML配置文件的实例中p:companyName会由容器使用JAVA的反射机制自动调用Car.setCompanyName("Chevrolet")这个访问器函数来实现属性值的设置。注意在xmlns和xsi:schemaLocation当中对于p命名空间的设置。
2.引用属性的注入
引用属性是指,一个类当中的一个字段需要应用类一个类的一个实例,例如下面的代码
public class Car
{
private String companyName;
private String carName;
private String color;
private int maxSpeed;
//set/geter
}
public class Person
{
private Car car;
//set/geter
}
在类Person当中有一个引用了Car实例的引用属性,在对这种类型的配置时有两种配置方法:
a.xml配置方法
使用xml配置时,我们依旧使用p命名空间,只是格式与基本属性配置略有不同,我们使用p:<属性名>-ref,例如:
b.使用annotation配置
使用annotation配置Bean时,首先要通过一个注解告知容器某个类是一个Bean,用于告知容器某个类是一个Bean的注解一共有四个个:@Compoent、@Service、@Controller和@Repository。这四个注解从功能上讲完全一样,只是用来区分所标识的类在应用中所属的角色
@Compoent:表示类在应用中属于领域角色,是组件类
@Repository:表示类在应用中属于DAO层
@Service:Service业务层的类
@Controller:控制层的类
在使用相应的注解标识完类为Bean以后就可以开始注入Bean属性的值了,默认情况下Spring利用类名来选择注入的引用实例,此时只需要使用@Autowired来标识属性即可。当对应的类在容器中配置有多个实例时需要配合使用@Qualifier和Bean的id属性来做限制。
@Autowired中有一个可选的参数required。这个参数的作用是:当标注为Autowired的属性如果在容器中找不到相关的Bean时,如果设置为true时则抛出异常,否则设置为null。
@Autowired可以设置在属性声明上,也可以设置在set访问器函数上。具体实例如下
@Component
@Scope(value="singleton")
public class Person
{
@Autowired(required=false)
@Qualifier("car")
private Car car;
}
在使用annotation进行属性注入时,我们需要在主配置文件中通过来告知Spring容器去哪里扫描那些被注解配置为Bean的类
3.容器属性的配置
a.xml配置
List容器:
shopping
music
Set容器
shopping
music
Map容器
Property容器,该容器与Map容器相同,区别只是在该容器当中key必须为字符串,配置时也与Map容器完全相同,只需要把节点名字