Spring3.X学习笔记-IoC容器概述

2017即将接近尾声,每当年末的时候总要检查下今年还有什么事情没做,结果却发现今年好像也没做多少事情。于是就想着把之前一直想梳理的Spring知识,趁着年末好好整理下。本系列笔记基于”Spring3.x企业应用开发实战“一书,说来惭愧,书买了好多年了也没认真的看一遍,于是就有了这个想法,算是2017年的最后给自己的一份交代。

在开始之前,先简要介绍下Spring吧!

Spring是分层的Java SE/EE应用一站式的轻量级开源框架,由Rod Johnson创建,以IoC和AOP为内核,提供了展现层Spring MVC和持久层Spring JDBC以及业务层事务管理等众多企业级应用技术,并以海纳百川的胸怀整合了开源世界里众多的企业级应用技术,逐渐成为使用最多的Java EE企业应用开发框架。

1、IoC概述

1.1 IoC的概念

IoC(控制反转:Inverse of Control)是一个重要的面向对象编程理论,Spring核心模块实现了IoC的功能。Spring中的其他模块,像AOP、声明式事务等功能都是建立在IoC的基础之上,它将类和类之间的依赖从代码中脱离出来,用配置的方式进行依赖关系描述,由IoC容器负责依赖类之间的创建、拼接、管理、获取等工作。一般来说IoC的概念有两种表示方式,一个叫控制反转,一个叫依赖注入。由于控制反转并不好理解,业界也曾进行广泛的讨论,最终软件界的泰斗级人物Martin Folwer提出了DI(依赖注入:Dependency Injection)的概念用以代替IoC。

控制反转:对于软件来说,即某一接口具体实现类的选择控制权从调用类中移除,转交给第三方决定。

依赖注入:调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

上面两个概念第一次接触的时候,你会发现都不太好理解,那是因为还不清楚IoC的实现机制,随着对IoC了解的深入,你会发现依赖注入的概念直接明了。

1.2 IoC的类型

从注入方法上看,主要可以划分为三种类型:构造函数注入、属性注入和接口注入。Spring支持构造函数注入和属性注入。

  • 构造函数注入: 在构造函数注入中,我们通过调用类的构造函数,将接口实现类通过构造函数变量传入。
  • 属性注入: 属性注入是指通过Setter方法完成调用类所需依赖的注入。
  • 接口注入: 将调用类所有依赖注入的方法抽取到一个接口中,调用类通过实现该接口提供相应的注入方法。

由于通过接口注入需要额外声明一个接口,增加了类的数目,而且它的效果和属性注入并无本质区别,所以不提倡采用这种方式。

2、IoC的底层实现原理

Spring的核心模块实现了IoC的功能,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。让开发者们从底层实现类的实例化、依赖关系装配等工作中脱离出来,专注于更有意义的业务逻辑开发工作。这种“神奇”的力量归功于Java语言本身的类反射功能。

2.1 Java反射知识

Java语言允许通过程序化的方式间接对Class的对象实例操作,Class文件由类装载器装载后,在JVM中将形成一份描述CLass结构的元信息对象,通过该元信息对象可以获知Class的结构信息:如构造函数、属性和方法等。Java允许用户借由这个Class相关的元信息对象间接调用Class对象的功能,这就为使用程序化方式操作Class对象开辟了途径。下面先看一个例子:

public class Car {
    private String brand;
    private String color;
    private int maxSpeed;

    public Car() {}

    public Car(String brand, String color, int maxSpeed) {
        this.brand = brand;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public void introduce() {
        System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
    }

    // 省略参数的getter/Setter方法
}

public class ReflectTest {

    public static Car initByDefaultConst() throws Throwable {
        // 通过类装载器获取Car类对象
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        Class<?> clazz = loader.loadClass("com.hhxs.bbt.web.Car");
        // 获取类的默认构造器对象并通过它实例化Car
        Constructor<?> cons = clazz.getDeclaredConstructor((Class[]) null);
        Car car = (Car) cons.newInstance();
        // 通过反射方法设置属性
        Method setBrand = clazz.getMethod("setBrand", String.class);
        setBrand.invoke(car, "红旗CA72");
        Method setColor = clazz.getMethod("setColor", String.class);
        setColor.invoke(car, "黑色");
        Method setMaxSpeed = clazz.getMethod("setMaxSpeed", int.class);
        setMaxSpeed.invoke(car, 200);
        return car;
    }

    public static void main(String[] args) throws Throwable {
        Car car1 = initByDefaultConst();
        car1.introduce();
    }
}


通过查看运行结果,可以看到这和直接通过构造函数和方法调用类功能的效果是一致的,只不过前者是间接调用,后者是直接调用罢了。这说明我们完全可以通过编程方式调用Class的各项功能。如果我们将这些信息以一个配置文件的方式提供,就可以使用Java语言的反射功能编写一段通用代码对类似于Car的类进行实例化及功能调用操作了。有没有感觉这和上面提到的Spring框架的实现机制很相似,Spring正是基于Java语言自带的反射机制实现了IoC的功能。

下面简要介绍下上述例子用到得三个主要反射类,这些反射对象类在java.reflect包中定义:

  • Constructor: 类的构造函数反射类,通过Class#getConstructors()方法可以获得类的所有构造函数反射对象数组。Constructor的一个主要方法是newInstance(Object[]… initargs),通过该方法可以创建一个对象类的实例,相当new关键字。
  • Method: 类方法的反射类,通过Class#getDeclaredMethods()方法可以获取类的所有方法发射类对象数组Method[]。Method最主要的方法是invoke(Object obj, Objcet… args),obj表示操作的目标对象,args为方法入参。
  • Field: 类的成员变量反射类,通过Class#getDeclaredFields()方法可以获取类的成员变量反射对象数组。Filed类最主要的方法是set(Object obj, Object value),obj表示操作的目标对象,通过value为目标类对象的成员变量设置值。

此外,Java还为包提供了Package反射类,在JDK5.0中还未注解提供了AnnotatedElement反射类。总之,Java的反射体系保证了可以通过程序化的方式访问目标类中的所有元素,对于private或protected的成员变量和方法,只要JVM的安全机制允许,也可以通过反射进行调用setAccessible(boolean access)

3、三个核心接口

Spring通过一个配置文件描述Bean及Bean之间的依赖关系,利用Java语言的反射功能实例化Bean并建立Bean之间的依赖关系。Spring的IoC容器在完成这些底层工作的基础上,还提供了Bean实例缓存、声明周期管理、Bean实例代理、事件发布、资源装载等高级服务。

3.1 BeanFactory

Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架最核心的接口,它提供了高级IoC的配置机制。BeanFactory使管理不同不同类型的Java对象成为可能,一般称BeanFactory为IoC容器。BeanFactory是类的通用工厂,它可以创建并管理各种类的对象,Spring称这些被创建和管理的Java对象为Bean。Bean最主要的方法就是getBean(String beanName),该方法从容器中返回特定名称的Bean。下面看一个小例子:

// spring文件
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="car" class="com.hhxs.bbt.web.Car"
        p:brand="红旗CA72"
        p:color="黑色"
        p:maxSpeed="200" />
</beans>

// JAVA代码
public class BeanFactoryTest {
    public static void main(String[] args) throws Throwable{
       ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
       Resource res = resolver.getResource("classpath:spring/beans.xml");
       System.out.println(res.getURL());
       BeanFactory bf = new XmlBeanFactory(res);
       System.out.println("init BeanFactory.");

       Car car = bf.getBean("car",Car.class);
       System.out.println("car bean is ready for use!");
       car.introduce();
    }
}


注意: 通过BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作发生在第一次调用时。对于单实例的Bean来说,BeanFactory会缓存Bean实例,所以第二次使用getBean()获取Bean时将直接从IoC容器的缓存中获取Bean实例。Spring在DefaultSingletonBeanRegistry类中提供了一个用于缓存单实例Bean的缓存器,它是一个用HashMap实现的缓存器,单实例的Bean以beanName为键保存在这个HashMap中。

3.2 ApplicationContext

应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用。一般称ApplicationContext为应用上下文或者Spring容器。

ApplicationContext的主要实现类是ClassPathXmlApplicationContext和FileSystemXmlApplicationContext,前者默认从类路径中加载配置文件,后者默认从文件系统中装载配置文件。

ApplicationContext ctx = new ClassPathXmlApplicationContext("spring/beans.xml");    
//或者
ApplicationContext ctx = new FileSystemXmlApplicationContext("spring/beans.xml");


在获取ApplicationContext实例后,就可以像BeanFactory一样调用getBean(beanName)返回Bean了。需要注意的是ApplicationContext在初始化应用上下文时就实例化所有单实例的Bean。因此,相比BeanFactory,初始化时间也会相对较长些,不过之后的调用就不在有“第一次惩罚”的问题。

3.3 WebApplicationContext

WebApplicationContext是专门为Web应用准备的,它允许应用从相对于Web根目录的路径中装载配置文件完成初始化工作。从WebApplicationContext中可以获得ServletContext的引用,整个Web应用上下文对象将作为属性放置到ServletContext中,以便Web应用环境可以访问Spring应用上下文。

Spring与Wen应用的上下文融合

WebApplicationContext的初始化方式和BeanFactory、ApplicationContext有所区别,因为WebApplicationContext需要ServletContext实例,也就是说它必须在拥有Web容器的前提下才能完成启动的工作。有过Web开发经验的读者都知道可以在web.xml中配置自启动的Servlet(spring3.0及以后版本中已经删除)或定义Web容器监听器(ServletContextListener),借助这两者中的任何一个都可以完成启动Spring Web应用上下文的工作。

通过web容器监听器启动:web.xml

<context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>
    classpath:/spring/spring-context.xml
  </param-value>
</context-param>

<!-- spring容器启动监听器 -->
<listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>


4、Bean的生命周期

4.1 BeanFactory中Bean的生命周期

我们知道Web容器中的Servlet拥有明确的生命周期,Spring容器中的Bean也拥有相似的生命周期。我们可以从两个层面定义Bean的生命周期:第一个层面是Bean的作用范围;第二个层面是实例化Bean时所经历的一系列阶段。

BeanFactory中Bean的生命周期

1. 当调用者通过getBean(beanName)向容器请求某一个Bean时,如果容器注册了org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor接口,在实例化Bean之前,将调用接口的postProcessBeforeInstantiation()方法;
2. 根据配置情况调用Bean构造函数或工厂方法实例化Bean;
3. 如果容器注册了InstantiationAwareBeanPostProcessor接口,在实例化Bean之后,调用该接口的postProcessAfterInstantiationn()方法,可以在这里对已经实例化的对象进行处理。
4. 如果Bean配置了属性信息,容器在这一步着手将配置值设置到Bean对应的属性中,不过在设置每个属性值之前先调用InstantiationAwareBeanPostProcessor接口的postProcessPropertyValues()方法;
5. 调用Bean的属性设置方法设置属性值;
6. 如果Bean实现了org.springframework.beans.factory.BeanNameAware接口,将调用setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中;
7. 如果Bean实现了org.springframework.bean.factory.BeanFactoryAware接口,将调用setBeanFactory()接口方法,将BeanFactory容器实例设置到Bean中;
8. 如果BeanFactory装配了org.springframework.beans.factory.config.BeanPostProcessor后处理器,将调用BeanPostProcessor的Object postProcessBeforeInstantiation(Object bean, Stringn beanName)接口方法对Bean进行加工操作。其中入参bean是当前正在处理的Bean,而beanName是当前Bean的配置名,返回的对象为加工处理后的Bean。BeanPostProcessor在Spring框架中占有重要的地位,为容器提供duiBean进行后续架构处理的切入点,Spring容器所提供的各种“神奇功能”(如AOP,动态代理等)都通过BeanPostProcessor实施
9. 如果Bean实现了InitializingBean的接口,将调用该接口的afterPropertiesSet()方法;
10. 如果在通过init-method属性定义了初始化方法,将执行这个方法;
11. BeanPostProcessor后处理定义了两个方法:其一时postProcessBeforeInstantiation()在第8步调用;其二是Object postProcessAfterInstantiationn(Object bean, String beanName)方法,这个方法在此时调用,容器中再次获得对Bean进行加工处理的机会。
12. 如果在中指定Bean的作用范围是scope=”prototype”,将Bean返回给调用者,调用者负责Bean后续生命的管理,Spring不再管理这个Bean的生命周期。如果作用范围设置为scope=“singleton”,则将Bean放入到Spring IoC容器的缓存池中,并将Bean引用返回给调用者,Spring继续对这些Bean进行后续的生命管理。
13. 对于scope=“singleton”的Bean,当容器关闭时,将触发Spring对Bean的后续生命周期的管理工作,首先如果Bean实现了DisposableBean接口,则将调用接口的afterPropertiesSet()方法,可以在此编写释放资源、记录日志等操作。
14. 对于scope=“singleton”的Bean,如果通过的destroy-method属性指定了Bean的销毁方法,Spring将执行Bean的这个方法,完成Bean资源的释放等操作。

Bean生命周期实例:

public class Car implements BeanFactoryAware, BeanNameAware, InitializingBean, DisposableBean {
    private String brand;
    private String color;
    private int maxSpeed;

    private BeanFactory beanFactory;
    private String beanName;

    public Car() {
        System.out.println("调用Car()构造函数。");
    }

    public Car(String brand, String color, int maxSpeed) {
        this.brand = brand;
        this.color = color;
        this.maxSpeed = maxSpeed;
    }

    public void introduce() {
        System.out.println("brand:" + brand + ";color:" + color + ";maxSpeed:" + maxSpeed);
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        System.out.println("调用setBrand()设置属性。");
        this.brand = brand;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    public int getMaxSpeed() {
        return maxSpeed;
    }

    public void setMaxSpeed(int maxSpeed) {
        this.maxSpeed = maxSpeed;
    }
    // 5 DisposableBean方法
    @Override
    public void destroy() throws Exception {
        System.out.println("调用DisposaleBean.destroy()。");
    }
    // 4 IntializingBean接口方法
    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用IntializingBean.afterPropertiesSet()。");
    }
    // 3 BeanNameAware接口方法
    @Override
    public void setBeanName(String name) {
        System.out.println("调用BeanNameAware.setBeanName()。");
        this.beanName = beanName;
    }
    // 2 BeanFactoryAware接口方法
    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("调用BeanFactoryAware.setBeanFactory()。");
        this.beanFactory = beanFactory;
    }
    // 6 通过<bean>的init-method属性指定的初始化方法
    public void myInit() {
        System.out.println("调用init-method所指定的myInit(),将maxSpeed设置为240。");
        this.maxSpeed = 240;
    }
    // 7 通过<bean>的destroy-method属性指定的销毁方法
    public void myDestroy() {
        System.out.println("调用destroy-method所指定的myDestroy()。");
    }
}

public class MyInstantiationAwareBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    // 1 在实例化Bean前进行调用
    public Object postProcessBeforeInitialization(Class beanClass, String beanName) throws BeansException {
        // 1-1 仅对容器中Car Bean进行处理
        if ("car".equals(beanName)) {
            System.out.println("InstantiationAware BeanPostProcessor.postProcessBeforeInstantiation");
        }
        return null;
    }

    // 2 在实例化Bean后调用
    public boolean postProcessAfterInstantiation(Object bean, String beanName) {
        // 2-1 仅对容器中Car Bean进行处理
        if ("car".equals(beanName)) {
            System.out.println("InstantiationAware BeanPostProcessor.postProcessAfterInstantiation");
        }
        return true;
    }

    // 3 在设置某个属性时调用
    public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean,
            String beanName) throws BeansException {
        // 3-1 仅对容器中Car Bean进行处理,还可以通过pdst入参进行过滤,仅对car的某个特定属性时进行处理
        if("car".equals(beanName)) {
            System.out.println("InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues");
        }
        return pvs;
    }
}

public class MyBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        if(beanName.equals("car")) {
            Car car = (Car)bean;
            if(car.getColor() == null) {
                System.out.println("调用BeanPostProcessor.postProcessBeforeInitialization(),color为空,设置为默认黑色。");
                car.setColor("黑色");
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(beanName.equals("car")) {
            Car car = (Car)bean;
            if(car.getMaxSpeed() >= 200) {
                System.out.println("调用BeanPostProcessor.postProcess AfterInitialization(), 将maxSpeed调整为200。");
                car.setMaxSpeed(200);
            }
        }
        return bean;
    }

}

**beans.xml**
<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
    <bean id="car" class="com.hhxs.bbt.web.Car"
        init-method="myInit"
        destroy-method="myDestroy"
        p:brand="红旗CA72"
        p:maxSpeed="200"
        scope="singleton" />
</beans>

public class BeanLifeCycle {

    private static void lifeCycleInBeanFactory() {

        Resource res = new ClassPathResource("spring/beans.xml");
        BeanFactory bf = new XmlBeanFactory(res);

        // 向容器中注册后处理器
        ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyBeanPostProcessor());
        ((ConfigurableBeanFactory)bf).addBeanPostProcessor(new MyInstantiationAwareBeanPostProcessor());

        // 第一次从容器中获取Car,将触发容器实例化该Bean,这将引发Bean生命周期方法的调用。
        Car car1 = (Car)bf.getBean("car");
        car1.introduce();
        car1.setColor("红色");
        car1.introduce();

        // 第二次从容器中获取Car,直接从缓存池中获取
        Car car2 = (Car)bf.getBean("car");

        // 查看car1和car2是否指向同一引用
        System.out.println("car1==car2:" + (car1==car2));

        // 关闭容器
        ((XmlBeanFactory)bf).destroySingletons();
    }

    public static void main(String[] args) {
        lifeCycleInBeanFactory();
    }
}


运行上述代码,我们在控制台上得到以下输出信息,仔细观察,将发现它验证了我们前面所介绍的Bean生命周期过程。

调用Car()构造函数。
InstantiationAware BeanPostProcessor.postProcessAfterInstantiation
InstantiationAware AwareBeanPostProcessor.postProcessPropertyValues
调用setBrand()设置属性。
调用BeanNameAware.setBeanName()。
调用BeanFactoryAware.setBeanFactory()。
调用BeanPostProcessor.postProcessBeforeInitialization(),color为空,设置为默认黑色。
调用IntializingBean.afterPropertiesSet()。
调用init-method所指定的myInit(),将maxSpeed设置为240。
调用BeanPostProcessor.postProcess AfterInitialization(), 将maxSpeed调整为200。
brand:红旗CA72;color:黑色;maxSpeed:200
brand:红旗CA72;color:红色;maxSpeed:200
car1==car2:true
调用DisposaleBean.destroy()。
调用destroy-method所指定的myDestroy()。


4.2 ApplicationContext中Bean的生命周期

Bean在应用上下文中的生命周期和BeanFactory中生命周期类似,不同的是,如果Bean实现org.springframework.context.ApplicationContextAware接口,会增加一个调用该接口方法setApplicationContext()的步骤。

ApplicationContext中Bean的生命周期

此外,如果配置文件中声明了工作后处理器接口BeanFactoryPostProcessor的实现类,则应用上下文在装载配置文件之后初始化Bean实例之前将调用这些BeanFactoryPostProcessor对配置信息进行加工处理。工厂后处理器是容器级的,仅在应用上下文初始化时调用一次,其目的是完成一些配置文件的加工处理工作。

ApplicationContext和BeanFactory另一最大的不同之处在于:前者会利用Java反射机制自动识别出配置文件中定义的BeanPostProcessor、InstantiationAwareBeanPostProcessor和BeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而后者需要在代码中拿你给通过手工调用addBeanPostProcessor()方法进行注册。这也是为什么在应用开发时,我们普遍使用ApplicationContext而很少使用BeanFactory的原因之一。


————本文结束感谢您的阅读————

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值