Spring全家桶Spring

Spring详解2.理解IoC容器

2018-12-18  本文已影响0人  卢卡斯哔哔哔

点击进入我的博客

1 如何理解IoC

1.1 依然是KFC的案例

interface Burger {
    int getPrice();
}
interface Drink {
    int getPrice();
}

class ZingerBurger implements Burger {
    public int getPrice() {
        return 10;
    }
}
class PepsiCola implements Drink {
    public int getPrice() {
        return 5;
    }
}

/**
 * 香辣鸡腿堡套餐
 */
class ZingerBurgerCombo {
    private Burger burger = new ZingerBurger();
    private Drink drink = new PepsiCola();

    public int getPrice() {
        return burger.getPrice() + drink.getPrice();
    }
}

上述案例中我们实现了一个香辣鸡腿堡套餐,在ZingerBurgerCombo中,我们发现套餐与汉堡、套餐与饮品产生了直接的耦合。要知道肯德基中的套餐是非常多的,这样需要建立大量不同套餐的类;而且如果该套餐中的百事可乐如果需要换成其他饮品的话,是不容易改变的。

class KFCCombo {
    private Burger burger;
    private Drink drink;

    public KFCCombo(Burger burger, Drink drink) {
        this.burger = burger;
        this.drink = drink;
    }
}

class KFCWaiter {
    public KFCCombo getZingerBurgerCombo() {
        return new KFCCombo(new ZingerBurger(), new PepsiCola());
    }
    // else combo…
}

为了防止套餐和汉堡、饮品的耦合,我们统一用KFCCombo来表示所有的套餐组合,引入了一个新的类KFCWaiter,让她负责所有套餐的装配。

1.2 控制反转与依赖注入

控制反转IoC

IoC的字面意思是控制反转,它包括两部分的内容:

对于Spring来说,我们通过Spring容器管理来管理和控制Bean的装配。

依赖注入

由于IoC这个重要的概念比较晦涩隐讳,Martin Fowler提出了DI(Dependency Injection,依赖注入)的概念用以代替IoC,即让调用类对某一接口实现类的依赖关系由第三方(容器或协作类)注入,以移除调用类对某一接口实现类的依赖。

Spring容器

Spring就是一个容器,它通过配置文件或注解描述类和类之间的依赖关系,自动完成类的初始化和依赖注入的工作。让开发着可以从这些底层实现类的实例化、依赖关系装配等工作中解脱出来,专注于更有意义的业务逻辑开发。

2 IoC的类型

从注入方法上看,IoC可以分为:构造函数注入、属性注入、接口注入

构造函数注入
class KFCCombo {
    private Burger burger;
    private Drink drink;

    // 在此注入对应的内容
    public KFCCombo(Burger burger, Drink drink) {
        this.burger = burger;
        this.drink = drink;
    }
}
属性注入
class KFCCombo {
    private Burger burger;
    private Drink drink;

    // 在此注入对应的内容
    public void setBurger(Burger burger) {
        this.burger = burger;
    }

    // 在此注入对应的内容
    public void setDrink(Drink drink) {
        this.drink = drink;
    }
}
接口注入
interface InjectFood {
    void injectBurger(Burger burger);
    void injectDrink(Drink drink);
}

class KFCCombo implements InjectFood {
    private Burger burger;
    private Drink drink;
    
    // 在此注入对应的内容
    public void injectBurger(Burger burger) {
        this.burger = burger; 
    }
    // 在此注入对应的内容
    public void injectDrink(Drink drink) {
        this.drink = drink;
    }
}

接口注入和属性注入并无本质的区别,而且还增加了一个额外的接口导致代码增加,因此不推荐该方式。

3 资源访问

JDK提供的访问资源的类(如java.net.URL、File等)并不能很好地满足各种底层资源的访问需求,比如缺少从类路径或者Web容器上下文中获取资源的操作类。因此,Spring提供了Resource接口,并由此装载各种资源,包括配置文件、国际化属性文件等资源。

3.1 Resource类图

Resource类图.jpg

3.2 资源加载

为了访问不同类型的资源,Resource接口下提供了不同的子类,这造成了使用上的不便。Spring提供了一个强大的加载资源的方式,在不显示使用Resource实现类的情况下,仅通过不同资源地址的特殊标示就可以访问对应的资源。

地址前缀 实例 释义
classpath: classpath:com/ankeetc/spring/Main.class 从类不经中加载资源,classpath: 和 classpath:/ 是等价的,都是相对于类的根路径,资源文件可以在标准的文件系统中,也可以在jar或者zip的类包中
file: file:/Users/zhengzhaoxi/.gitconfig 使用UrlResource从文件系统目录中装载资源,可以采用绝对路径或者相对路径
http:// http://spiderlucas.github.io/atom.xml 使用UrlResource从web服务器中加载资源
ftp:// ftp://spiderlucas.github.io/atom.xml 使用UrlResource从FTP服务器中装载资源
没有前缀 com/ankeetc/spring/Main.class 根据ApplicationContext的具体实现类采用对应类型的Resource
classpath:与classpath*:

假设有多个Jar包或者文件系统类路径下拥有一个相同包名(如com.ankeetc):

这对于分模块打包的应用非常有用,假设一个应用分为2个模块,每一个模块对应一个配置文件,分别为module1.yaml 、module2.yaml,都放在了com.ankeetc的目录下,每个模块单独打成JAR包。可以使用 classpath*:com/ankeetc/module*.xml加载所有模块的配置文件。

Ant风格的资源地址通配符
资源加载器
ResourceLoader类图.png

4 BeanFactory

4.1 BeanFactory体系结构

BeanFactory体系结构.png

4.2 初始化BeanFactory

BeanFactory有多种实现,最常用的是 XmlBeanFactory,但在Spring 3.2时已被废弃。目前建议使用XmlBeanDefinitionReader与DefaultListableBeanFactory。

<?xml version="1.0" encoding="utf-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
           http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
    <bean id="car" class="com.ankeetc.spring.Car"></bean>
</beans>
    public static void main(String[] args) throws Exception {
        ResourceLoader resourceLoader = new DefaultResourceLoader();
        Resource resource = resourceLoader.getResource("beans.xml");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(resource);

        beanFactory.getBean("");
    }

4.3 BeanFactory中Bean的生命周期

Bean的生命周期
  1. 当调用者通过getBean()向容器请求一个Bean时,如果容器注册了InstantiationAwareBeanPostProcessor接口,则在实例化Bean之前,调用postProcessBeforeInstantiation()方法。
  2. 根据配置调用构造方法或者工厂方法实例化Bean。
  3. 调用InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()
  4. 调用InstantiationAwareBeanPostProcessor#postProcessPropertyValues()方法。
  5. 设置属性值。
  6. 如果Bean实现了BeanNameAware接口,则将调用BeanNameAware#setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中。
  7. 如果Bean实现了BeanFactoryAware接口,将调用BeanFactoryAware#setBeanFactory()接口方法。
  8. 如果容器注册了BeanPostProcessor接口,将调用BeanPostProcessor#postProcessBeforeInitialization()方法。入參Bean是当前正在处理的Bean,BeanName是当前Bean的配置名,返回的对象为处理后的Bean。BeanPostProcessor在Spring框架中占有重要的地位,为容器提供对Bean进行后续加工处理的切入点,AOP、动态代理都通过BeanPostProcessor来实现。
  9. 如果Bean实现了InitializingBean接口,则将调用InitializingBean#afterPropertiesSet()方法。
  10. 如果<bean>中定义了init-method初始化方法,则执行这个方法。
  11. 调用BeanPostProcessor#postProcessAfterInitialization()方法再次加工Bean。
  12. 如果<bean>中指定了Bean的作用范围为scope='prototype',则将Bean返回给调用者,Spring不再管理这个Bean的生命周期。如果scope='singleton',则将Bean放入Spring IoC容器的缓存池中,并返回Bean。
  13. 对于scope='singleton'的Bean,当容器关闭时,将触发Spring对Bean的后续生命周期的管理工作。如果Bean实现了DisposableBean接口,将调用DisposableBean#destroy()方法。
  14. 对于`scope='singleton'的Bean,如果通过<bean>的destroy-method属性指定了Bean的销毁方法,那么Spring将执行这个方法。
Bean方法的分类

4.4 BeanFactory生命周期案例

public class Main {
    public static void main(String[] args) {
        Resource resource = new PathMatchingResourcePatternResolver().getResource("classpath:beans.xml");

        DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
        XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
        reader.loadBeanDefinitions(resource);

        beanFactory.addBeanPostProcessor(new InstantiationAwareBeanPostProcessor() {
            public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation()");
                return null;
            }

            public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()");
                return true;
            }

            public PropertyValues postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
                System.out.println("InstantiationAwareBeanPostProcessor.postProcessPropertyValues()");
                return pvs;
            }

            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("BeanPostProcessor.postProcessBeforeInitialization()");
                return bean;
            }

            public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
                System.out.println("BeanPostProcessor.postProcessAfterInitialization()");
                return bean;
            }
        });

        MyBean myBean = beanFactory.getBean("myBean", MyBean.class);
        beanFactory.destroySingletons();
    }
}

class MyBean implements BeanNameAware, BeanFactoryAware, InitializingBean, DisposableBean {
    private String prop;

    public MyBean() {
        System.out.println("MyBean:构造方法");
    }

    public String getProp() {
        System.out.println("MyBean:get方法");
        return prop;
    }

    public void setProp(String prop) {
        System.out.println("MyBean:set方法");
        this.prop = prop;
    }

    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("MyBean:BeanFactoryAware.setBeanFactory()");
    }

    public void setBeanName(String name) {
        System.out.println("MyBean:BeanNameAware.setBeanName()");
    }

    public void destroy() throws Exception {
        System.out.println("MyBean:DisposableBean.destroy()");
    }

    public void afterPropertiesSet() throws Exception {
        System.out.println("MyBean:InitializingBean.afterPropertiesSet()");
    }

    // 配置文件中init-method
    public void myInit() {
        System.out.println("MyBean:myInit()");
    }

    // 配置文件中destroy-method
    public void myDestroy() {
        System.out.println("MyBean:myDestroy()");
    }
}
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean class="com.ankeetc.spring.MyBean" name="myBean" init-method="myInit" destroy-method="myDestroy">
        <property name="prop" value="prop"/>
    </bean>
</beans>

4.5 关于Bean生命周期接口的探讨

5 ApplicationContext

5.1 Application体系结构

ApplicationContext体系结构

5.2 初始化ApplicationContext

    public static void main(String[] args) throws Exception {
        ClassPathXmlApplicationContext classPathXmlApplicationContext =
                new ClassPathXmlApplicationContext("beans.xml");
        FileSystemXmlApplicationContext fileSystemXmlApplicationContext =
                new FileSystemXmlApplicationContext("file:/Users/zhengzhaoxi/Git/spring/src/main/resources/beans.xml");
        AnnotationConfigApplicationContext annotationConfigApplicationContext =
                new AnnotationConfigApplicationContext(Config.class);
    }

5.3 WebApplicationContext体系结构

见后续章节

5.4 ApplicationContext中Bean的生命周期

Bean的生命周期
不同点

Bean在ApplicationContext和BeanFactory中生命周期类似,但有以下不同点

  1. 如果Bean实现了ApplicationContextAware接口,则会增加一个调用该接口方法的ApplicationContextAware.setApplicationContext的方法。
  2. 如果在配置文件中生命了工厂后处理器接口BeanFactoryPostProcessor的实现类,则应用上下文在装在配置文件之后、初始化Bean实例之前会调用这些BeanFactoryPostProcessor对配置信息进行加工处理。Spring提供了多个工厂容器,如CustomEditorConfigurePropertyPlaceholderConfigurer等。工厂后处理器是容器级的,只在应用上下文初始化时调用一次,其目的是完成一些配置文件加工处理工作。
  3. ApplicationContext可以利用Java反射机制自动识别出配置文件中定义的BeanPostProcessorInstantiationAwareBeanPostProcessorBeanFactoryPostProcessor,并自动将它们注册到应用上下文中(如下所示);而BeanFactory需要通过手工调用addBeanPostProcessor()方法注册。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">

    <bean class="com.ankeetc.spring.MyBean" name="myBean" init-method="myInit" destroy-method="myDestroy">
        <property name="prop" value="prop"/>
    </bean>

    <!-- 工厂后处理器 -->
    <bean id="myBeanPostProcessor" class="com.ankeetc.spring.MyBeanPostProcessor"/>

    <!-- 注册Bean后处理器 -->
    <bean id="myBeanFactoryPostProcessor" class="com.ankeetc.spring.MyBeanFactoryPostProcessor"/>
</beans>

6 BeanFactory与ApplicationContext区别

7 父子容器

上一篇 下一篇

猜你喜欢

热点阅读