Spring之IoC容器
关于Spring的理解文章已经很多了,本文仅是个人总结性记录,不足之处尽请拍砖。。
Spring是一个轻量级的IoC容器和AOP框架。Spring是非侵入式的容器框架,它通过分离应用的业务逻辑和系统级服务来进行内聚性开发。
IoC也称DI(Depency Injection依赖注入), 即被调用者所依赖的对象的创建是由调用者完成的,并将对象的引用传递给被调用者。这个过程的核心是利用Java反射机制。反射是在运行时动态创建、使用对象的一种机制。
其实在我们以往的开发过程中,我们都会用到Java反射机制,而Spring更进一步利用反射机制形成IoC容器来管理JavaBean的生命周期。我们在代码编写时,常常会将动态生成的类配置在配置文件中,然后在程序运行时动态生成该类,
我们来进一步考察Java反射:
使用反射总是先找到一个java.lang.Class 实例,在Java安全限制内,利用反射提供的关于加载类的Field、Method和构造方法的编程访问手段来动态运用类的对象实例,如下:
Constructor - 提供关于类的单个构造方法的信息以及对它的访问权限。
Field - 提供有关类或接口的单个字段的信息,以及对它的动态访问权限。
Method - 提供关于类或接口上单独某个方法(以及如何访问该方法)的信息
Modifier - 提供了 static
方法和常量,对类和成员访问修饰符进行解码。
Proxy - 提供用于创建动态代理类和实例的静态方法
ReflectPermission - 反射操作的 Permission 类。
这里还有一些类具有反射能力,如Array类(提供动态创建和访问 Java 数组的方法)。
使用这些类的时候一般都遵循三个步骤:
第一步,获得被操作类的 java.lang.Class 对象。在运行环境中,使用 java.lang.Class 类来描述。
第二步,调用 getDeclaredMethods 等方法,取得被操作类中定义的所有方法列表,或查找所需要方法。
第三步,使用 相关的反射API 进行访问操作
下面几个例子展示了如何在代码中运用Constructor 、 Field 和 Method 类来扩展了一个普通的Java类。
下面引用了The Reflection API中的一个例子, 展示了如何利用类所定义的构造函数来创建一个新的对象实例。(原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/ctorInstance.html)
import java.io.Console;
import java.nio.charset.Charset;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import static java.lang.System.out;
public class ConsoleCharset {
public static void main(String... args) {
Constructor[] ctors = Console.class.getDeclaredConstructors();
Constructor ctor = null;
for (int i = 0; i < ctors.length; i++) {
ctor = ctors[i];
if (ctor.getGenericParameterTypes().length == 0)
break;
}
try {
ctor.setAccessible(true);
Console c = (Console)ctor.newInstance(); //创建一个新的对象实例
Field f = c.getClass().getDeclaredField("cs");
f.setAccessible(true);
out.format("Console charset : %s%n", f.get(c));
out.format("Charset.defaultCharset(): %s%n", Charset.defaultCharset());
// production code should handle these exceptions more gracefully
} catch (InstantiationException x) {
x.printStackTrace();
} catch (InvocationTargetException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
} catch (NoSuchFieldException x) {
x.printStackTrace();
}
}
}
下面引用了The Reflection API中的一个例子, 该例展示了如何为类的变量设值。(原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/fieldValues.html)
import java.lang.reflect.Field;
import java.util.Arrays;
import static java.lang.System.out;
enum Tweedle { DEE, DUM }
public class Book {
public long chapters = 0;
public String[] characters = { "Alice", "White Rabbit" };
public Tweedle twin = Tweedle.DEE;
public static void main(String... args) {
Book book = new Book();
String fmt = "%6S: %-12s = %s%n";
try {
Class<?> c = book.getClass();
Field chap = c.getDeclaredField("chapters");
out.format(fmt, "before", "chapters", book.chapters);
chap.setLong(book, 12);
out.format(fmt, "after", "chapters", chap.getLong(book));
Field chars = c.getDeclaredField("characters");
out.format(fmt, "before", "characters", Arrays.asList(book.characters));
String[] newChars = { "Queen", "King" };
chars.set(book, newChars);//对数组进行设值
out.format(fmt, "after", "characters", Arrays.asList(book.characters));
Field t = c.getDeclaredField("twin");
out.format(fmt, "before", "twin", book.twin);
t.set(book, Tweedle.DUM);
out.format(fmt, "after", "twin", t.get(book));
// production code should handle these exceptions more gracefully
} catch (NoSuchFieldException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
下面引用了The Reflection API中的一个例子,该例完整地说明了如何在一个类中找到匹配的方法,然后调用该方法。(原文地址:http://docs.oracle.com/javase/tutorial/reflect/member/methodInvocation.html)
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Locale;
import static java.lang.System.out;
import static java.lang.System.err;
public class Deet<T> {
private boolean testDeet(Locale l) {
// getISO3Language() may throw a MissingResourceException
out.format("Locale = %s, ISO Language Code = %s%n", l.getDisplayName(), l.getISO3Language());
return true;
}
private int testFoo(Locale l) { return 0; }
private boolean testBar() { return true; }
public static void main(String... args) {
if (args.length != 4) {
err.format("Usage: java Deet <classname> <langauge> <country> <variant>%n");
return;
}
try {
Class<?> c = Class.forName(args[0]);
Object t = c.newInstance();
Method[] allMethods = c.getDeclaredMethods(); //得到方法列表
for (Method m : allMethods) {
String mname = m.getName();
if (!mname.startsWith("test")
|| (m.getGenericReturnType() != boolean.class)) {
continue;
}
Type[] pType = m.getGenericParameterTypes();
if ((pType.length != 1)
|| Locale.class.isAssignableFrom(pType[0].getClass())) {
continue;
}
out.format("invoking %s()%n", mname);
try {
m.setAccessible(true);
Object o = m.invoke(t, new Locale(args[1], args[2], args[3]));//调用类方法
out.format("%s() returned %b%n", mname, (Boolean) o);
// Handle any exceptions thrown by method to be invoked.
} catch (InvocationTargetException x) {
Throwable cause = x.getCause();
err.format("invocation of %s failed: %s%n",
mname, cause.getMessage());
}
}
// production code should handle these exceptions more gracefully
} catch (ClassNotFoundException x) {
x.printStackTrace();
} catch (InstantiationException x) {
x.printStackTrace();
} catch (IllegalAccessException x) {
x.printStackTrace();
}
}
}
我们在代码中更常用的一种得到class对象的方式如下:
Class cls = Class.forName("类名");
[注],关于使用反射的高级教程,请阅读 http://docs.oracle.com/javase/tutorial/reflect/index.html
上面我们已经讨论了Java的反射机制与使用过程,它构成了DI的基础。在Spring中实现DI主要是下面两种方式:
> 基于构造器的DI —— 通过调用带参数的构造器来实现,每个参数代表着一个依赖。
> 基于setter的DI —— 通过调用无参构造器或无参static工厂方法实例化bean之后,调用该bean的setter方法,即可实现基于setter的DI。
Ioc 容器
Spring框架的核心组件主要有三个:核心容器、Context和Beans。
BeanFactory是Spring核心容器的主要组件,它通过控制反转将应用程序的配置和依赖性规范与实际的应用程序代码分开,构成了整个Spring的基础;Context通过配置文件为Spring框架提供上下文信息。Bean是由Spring容器初始化、装配及管理的对象,Spring框架规范了Bean的定义、创建和解析过程。
1. BeanFactory 工厂
Bean Factory负责根据配置文件创建Bean实例,可以配置的项目有:
1) Bean属性值及依赖关系(对其他Bean的引用)
2) Bean创建模式和行为定义,这些定义将决定bean在容器中的行为(作用域、生命周期回调等等)
3) Bean初始化和销毁方法
4) Bean的依赖关系
BeanFactory 的使用方式如下:
InputStream is=new FileInputStream(”config.xml”);
XmlBeanFactory factory=new XmlBeanFactory(is);
HelloWorld hw=(HelloWorld) factory.getBean(”HelloWorld”);
system.out.println(hw.getHelloWorldMsg());
2. ApplicationContext类
ApplicationContext提供了一个更为框架化的实现,覆盖了BeanFactory的所有功能,并提供了更多的特性。
相对BeanFactory而言,ApplicationContext提供了以下扩展功能:
1) 国际化支持;
2) 资源访问;
3) 事件传播;
4) 多实例加载
3. Bean的生命周期和作用域
通常我们不需要关注容器对 Bean 的初始化和销毁操作,这些操作由 Spring 经过构造函数或者工厂方法完成。但同时在某些情况下,仍需要我们做一些额外的初始化或者销毁操作,通常是针对一些资源的获取和释放操作。主要存在两种方式来扩展初始化或销毁行为:
第一种方式:实现 Spring 提供的两个接口:InitializingBean 和 DisposableBean。InitializingBean接口包含一个 afterPropertiesSet() 方法,容器在为 Bean 设置属性之后,将自动调用该方法;DisposableBean 接口包含一个destroy()方法,容器在销毁该 Bean 之前,将调用该方法。
第二种方式是:在 XML 文件中使用 <bean> 的 init-method 和 destroy-method 属性指定初始化之后和销毁之前的回调方法,这两个属性的取值是相应 Bean 类中的初始化和销毁方法,方法名任意,但是方法不能有参数。
Spring Framework支持五种作用域(其中有三种只能用在基于web的Spring ApplicationContext)。
作用域 |
描述 |
singleton |
在每个Spring IoC容器中一个bean定义对应一个对象实例。 |
prototype |
一个bean定义对应多个对象实例。 |
request |
在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext情形下有效。 |
session |
在一个HTTP Session中,一个bean定义对应一个实例。该作用域仅在基于web的Spring ApplicationContext情形下有效。 |
global session |
在一个全局的HTTP Session中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext情形下有效。 |
4. Ioc 容器的扩展点
Ioc容器的扩展点可以为 Bean 对象增加用户自定义的业务逻辑。主要的扩展点有以下三个:
BeanPostProcessor接口:在Spring容器完成bean的实例化、配置和其它的初始化后执行BeanPostProcessor中自定义的逻辑。
BeanFactoryPostProcessor接口:在构建BeanFactory对象时调用。
FactoryBean接口:可以增加更复杂的初始化代码,给用户更多控制权。
5. 使用Ioc 容器
Bean是通过配置文件来被Spring加载的,Ioc 容 器的默认配置文件是ApplicatonContext.xml,Spring 的所有特性功能都是基于 Ioc 容器工作的。在Spring框架中,最基本的运用Ioc容器的例子就是AOP。如果我们仅仅是使用Ioc容器的反射能力,我们可以如同前面描述BeanFactory的使用方式那样来使用Ioc容器,这种方式与我们过去直接使用Java反射机制的用法相类似。