Spring4系列
第二章 IOC容器和Bean的配置
2.1 IOC和DI
2.1.1 IOC(Inversion of Control):反转控制
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率。
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。
2.1.2 DI(Dependency Injection):依赖注入
IOC的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。
IOC 描述的是一种思想,而DI 是对IOC思想的具体实现。
2.1.3 IOC容器在Spring中的实现
1)在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化。
2)Spring提供了IOC容器的两种实现方式
① BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向Spring本身的,不是提供给开发人员使用的。
② ApplicationContext:的子接口,提供了更多高级特性。面向的使用者,几乎所有场合都使用而不是底层的。
2.1.4 ApplicationContext的主要实现类
-
ClassPathXmlApplicationContext:对应类路径下的XML格式的配置文件
-
FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
-
在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多实例的。
2.1.5 ConfigurableApplicationContext
-
是ApplicationContext的子接口,包含一些扩展方法
-
refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力。
2.1.6 WebApplicationContext
- 专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
2.2 Bean
2.2.1 FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。
工厂bean跟普通bean不同,其返回的对象不是指定类的一个实例,其返回的是该工 厂bean的getObject方法所返回的对象。
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口。
public class CarFactoryBean implements FactoryBean<Car> {
/**
* 工厂bean具体创建的bean对象是由getObject方法来返回的.
*/
@Override
public Car getObject() {
return new Car("五菱宏光", "五菱", 50000);
}
/**
* 返回具体的bean对象的类型
*/
@Override
public Class<?> getObjectType() {
return Car.class;
}
/**
* bean 可以是单例的 也可以是原型的(非单例)
*/
@Override
public boolean isSingleton() {
return true;
}
}
引入面试题:BeanFactory与FactoryBean的相同点与不同点?
2.2.2 bean的作用域
在Spring中,可以在元素的scope属性里设置bean的作用域,以决定这个bean是单实例的还是多实例的。
默认情况下,Spring只为每个在IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例:所有后续的getBean()调用和bean引用都将返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域。
当bean的作用域为单例时,Spring会在IOC容器对象创建时就创建bean的对象实例。而当bean的作用域为prototype时,IOC容器在获取bean的实例时创建bean的实例对象。
2.2.3 bean的生命周期
-
Spring IOC容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务。
-
Spring IOC容器对bean的生命周期进行管理的过程:
① 通过构造器或工厂方法创建bean实例
② 为bean的属性设置值和对其他bean的引用
③ 调用bean的初始化方法
④ bean可以使用了
⑤ 当容器关闭时,调用bean的销毁方法
-
在配置bean时,通过init-method和destroy-method 属性为bean指定初始化和销毁方法
-
bean的后置处理器
① bean后置处理器允许在调用初始化方法前后对bean进行额外的处理
② bean后置处理器对IOC容器里的所有bean实例逐一处理,而非单一实例。其典型 应用是:检查bean属性的正确性或根据特定的标准更改bean的属性。
③ bean后置处理器时需要实现接口:
org.springframework.beans.factory.config.BeanPostProcessor。在初始化方法被调用前后,Spring将把每个bean实例分别传递给上述接口的以下两个方法:
●postProcessBeforeInitialization(Object, String)
●postProcessAfterInitialization(Object, String)
- 添加bean后置处理器后bean的生命周期
①通过构造器或工厂方法创建bean实例
②为bean的属性设置值和对其他bean的引用
③将bean实例传递给bean后置处理器的postProcessBeforeInitialization()方法
④调用bean的初始化方法
⑤将bean实例传递给bean后置处理器的postProcessAfterInitialization()方法
⑥bean可以使用了
⑦当容器关闭时调用bean的销毁方法
2.2.4 通过注解配置Bean
- 普通组件:@Component
标识一个受Spring IOC容器管理的组件
- 持久化层组件:@Repository
标识一个受Spring IOC容器管理的持久化层组件
-
业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件 -
表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制器组件
- 组件命名规则
①默认情况:使用组件的简单类名首字母小写后得到的字符串作为bean的id
②使用组件注解的value属性指定bean的id
注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何错误,所以 @Respository、@Service、@Controller这几个注解仅仅是为了让开发人员自己明确当前的组件扮演的角色。
2.3 动态代理
2.3.1 动态代理的原理
代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对象取代原始对象。任何对原始对象的调用都要通过代理。代理对象决定是否以及何时将方法调用转到原始对象上。
2.3.2 动态代理的方式
- 基于接口实现动态代理: JDK动态代理
/**
* 生成代理对象。
* <p>
* JDK的动态代理:
* 1. Proxy : 是所有动态代理类的父类, 专门用户生成代理类或者是代理对象
* public static Class<?> getProxyClass(ClassLoader loader,
* Class<?>... interfaces)
* 用于生成代理类的Class对象.
* <p>
* public static Object newProxyInstance(ClassLoader loader,
* Class<?>[] interfaces,InvocationHandler h)
* 用于生成代理对象
* <p>
* 2. InvocationHandler :完成动态代理的整个过程.
* public Object invoke(Object proxy, Method method, Object[] args)
* throws Throwable;
*/
public class ArithmeticCalculatorProxy {
//动态代理: 目标对象 如何获取代理对象 代理要做什么
//目标对象
private ArithmeticCalculator target;
public ArithmeticCalculatorProxy(ArithmeticCalculator target) {
this.target = target;
}
//获取代理对象的方法
public Object getProxy() {
//代理对象
Object proxy;
/**
* loader: ClassLoader对象。 类加载器对象. 帮我们加载动态生成的代理类。
*
* interfaces: 接口们. 提供目标对象的所有的接口. 目的是让代理对象保证与目标对象都有接口中想同的方法.
*
* h: InvocationHandler类型的对象.
*/
ClassLoader loader = target.getClass().getClassLoader();
Class[] interfaces = target.getClass().getInterfaces();
proxy = Proxy.newProxyInstance(loader, interfaces, new InvocationHandler() {
/**
* invoke: 代理对象调用代理方法, 会回来调用invoke方法。
*
* proxy: 代理对象 , 在invoke方法中一般不会使用.
*
* method: 正在被调用的方法对象.
*
* args: 正在被调用的方法的参数.
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//将方法的调用转回到目标对象上.
//获取方法的名字
String methodName = method.getName();
//记录日志
System.out.println("LoggingProxy==> The method " + methodName + " begin with " + Arrays.asList(args));
Object result = method.invoke(target, args); // 目标对象执行目标方法. 相当于执行ArithmeticCalculatorImpl中的方法
//记录日志
System.out.println("LoggingProxy==> The method " + methodName + " ends with :" + result);
return result;
}
});
return proxy;
}
}
- 基于继承实现动态代理: Cglib、Javassist动态代理