资讯专栏INFORMATION COLUMN

Spring详解2.理解IoC容器

Ververica / 2863人阅读

摘要:目前建议使用与。入參是当前正在处理的,是当前的配置名,返回的对象为处理后的。如果,则将放入容器的缓存池中,并返回。和这两个接口,一般称它们的实现类为后处理器。体系结构让容器拥有了发布应用上下文事件的功能,包括容器启动事件关闭事件等。

点击进入我的博客 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类图

WritableResource:可写资源接口,Spring3.1新增的接口,有2个实现类:FileSystemResourcePathResource

ByteArrayResource:二进制数组表示的资源,二进制数组资源可以在内存中通过程序构造。

ClassPathResource:类路径下的资源,资源以相对于类路径的方式表示

FileSystemResouce:文件系统资源,资源以文件系统路径的方式表示。

InputStreamResource:以输入流返回标识的资源

ServletContextResource:为访问Web容器上下文中的资源而设计的类,负责以相对于Web应用根目录的路径来加载资源。支持以流和URL的形式访问,在war包解包的情况下,也可以通过File方式访问。 该类还可以直接从JAR包中访问资源。

UrlResource:封装了java.net.URL,它使用户能够访问任何可以通过URL表示的资源,如文件系统的资源、HTTP资源、FTP资源

PathResource:Spring 4.0提供的读取资源文件的新类。PathResource封装了java.net.URLjava.nio.file.Path、文件系统资源,它使用户能够访问任何可以通过URL、Path、系统文件路径标识的资源,如文件系统的资源、HTTP资源、FTP资源

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/... 使用UrlResource从web服务器中加载资源
ftp:// ftp://spiderlucas.github.io/atom.xml 使用UrlResource从FTP服务器中装载资源
没有前缀 com/ankeetc/spring/Main.class 根据ApplicationContext的具体实现类采用对应类型的Resource
classpath:与classpath*:

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

classpath:只会在第一个加载的com.ankeetc包的类路径下查找

classpath*:会扫描到所有的这些JAR包及类路径下出现的com.ankeetc类路径。

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

Ant风格的资源地址通配符

? 匹配文件名中的一个字符

* 匹配文件名中的任意字符

** 匹配多层路径

资源加载器

ResourceLoader接口仅有一个getResource(String loacation)方法,可以根据一个资源地址加载文件资源。不过,资源地址仅支持带资源类型前缀的表达式,不支持Ant风格的资源路径表达式。

ResourcePatternResolver扩展ResourceLoader接口,定义了一个新的接口方法getResource(String locationPattern),改方法支持带资源类型前缀及Ant风格的资源路径表达式。

PathMatchingResourcePatternResolver是Spring提供的标准实现类。

4 BeanFactory

BeanFactory是Spring框架最核心的接口,它提供了高级IoC的配置机制。我们一般称BeanFactory为IoC容器。

BeanFactory是Spring框架等基础设施,面向Spring本身。

BeanFactory是一个类工厂,可以创建并管理各种类的对象。

所有可以被Spring容器实例化并管理的Java类都可以成为Bean。

BeanFactory主要方法就是getBean(String beanName);

BeanFactory启动IoC容器时,并不会初始化配置文件中定义的Bean,初始化动作在第一个调用时产生。

对于单实例的Bean来说,BeanFactory会缓存Bean实例,在第二次使用geBean()获取Bean时,将直接从IoC容器的缓存中获取Bean实例。

4.1 BeanFactory体系结构

BeanFactory:BeanFactory接口位于类结构树的顶端,它最主要的方法就是getBean(String beanName) ,该方法从容器中返回特定名称的Bean,BeanFactory的功能通过其他接口而得到不断扩展。

ListableBeanFactory:该接口定义了访问容器中Bean基本信息的若干方法,如:查看 Bean 的个数、获取某一类型Bean的配置名、或查看容器中是否包含某一个Bean。

HierarhicalBeanFactory:父子级联 IoC 容器的接口,子容器可以通过接口方法访问父容器。

ConfigurableBeanFactory:该接口增强了IoC容器的可定制性,它定义了设置类装载器、属性 编辑器、容器初始化后置处理器等方法。

AutowireCapableBeanFactory:定义了将容器中的Bean按某种规则(如:按名称匹配、按类型匹配)进行自动装配的方法。

SingletonBeanFactory:定义了允许在运行期间向容器注册单实例Bean的方法。

BeanDefinitionRegistry:Spring配置文件中每一个Bean节点元素在Spring容器里都通过一个 BeanDefinition对象表示,它描述了Bean的配置信息。而BeanDefinitionRegistry接口提供了向容器手工注册BeanDefinition对象的方法。

4.2 初始化BeanFactory

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



    
    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的生命周期

当调用者通过getBean()向容器请求一个Bean时,如果容器注册了InstantiationAwareBeanPostProcessor接口,则在实例化Bean之前,调用postProcessBeforeInstantiation()方法。

根据配置调用构造方法或者工厂方法实例化Bean。

调用InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation()

调用InstantiationAwareBeanPostProcessor#postProcessPropertyValues()方法。

设置属性值。

如果Bean实现了BeanNameAware接口,则将调用BeanNameAware#setBeanName()接口方法,将配置文件中该Bean对应的名称设置到Bean中。

如果Bean实现了BeanFactoryAware接口,将调用BeanFactoryAware#setBeanFactory()接口方法。

如果容器注册了BeanPostProcessor接口,将调用BeanPostProcessor#postProcessBeforeInitialization()方法。入參Bean是当前正在处理的Bean,BeanName是当前Bean的配置名,返回的对象为处理后的Bean。BeanPostProcessor在Spring框架中占有重要的地位,为容器提供对Bean进行后续加工处理的切入点,AOP、动态代理都通过BeanPostProcessor来实现。

如果Bean实现了InitializingBean接口,则将调用InitializingBean#afterPropertiesSet()方法。

如果中定义了init-method初始化方法,则执行这个方法。

调用BeanPostProcessor#postProcessAfterInitialization()方法再次加工Bean。

如果中指定了Bean的作用范围为scope="prototype",则将Bean返回给调用者,Spring不再管理这个Bean的生命周期。如果scope="singleton",则将Bean放入Spring IoC容器的缓存池中,并返回Bean。

对于scope="singleton"的Bean,当容器关闭时,将触发Spring对Bean的后续生命周期的管理工作。如果Bean实现了DisposableBean接口,将调用DisposableBean#destroy()方法。

对于`scope="singleton"的Bean,如果通过的destroy-method属性指定了Bean的销毁方法,那么Spring将执行这个方法。

Bean方法的分类

Bean自身的方法:构造方法、Setter设置属性值、init-method、destroy-method。

Bean级生命周期接口方法:如上图中蓝色的部分。BeanNameAware、BeanFactoryAware、InitializingBean、DisposableBean,这些由Bean本身直接实现

容器级生命周期接口方法:如上图中的红色的部分。InstantiationAwareBeanPostProcessor和BeanPostProcessor这两个接口,一般称它们的实现类为后处理器。后处理器接口不由Bean本身实现,实现类以容器附加装置的形式注册到Spring容器中。当Spring容器创建任何Bean的时候,这些后处理器都会起作用,所以这些后处理器的影响是全局的。如果需要多个后处理器,可以同时实现Ordered接口,容器将按照特定的顺序依此调用这些后处理器。

工厂后处理器接口方法:AspectJWeavingEnabler、CustomAutowireConfigurer、ConfigurationClassPostProcessor等方法,工厂后处理器也是容器级的,在应用上下文装配配置文件后立即使用。

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()");
    }
}


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

Bean生命周期带来的耦合:通过实现Spring的Bean生命周期接口对Bean进行额外控制,虽然让Bean具有了更细致的生命周期阶段,但也带来了一个问题,Bean和Spring框架紧密绑定在一起了,这和Spring一直推崇的“不对应用程序类作任何限制”的理念是相悖的。业务类本应该完全POJO化,只实现自己的业务接口,不需要和某个特定框架(包括Spring框架)的接口关联。

init-method和destroy-method:可以通过init-methoddestroy-method属性配置方式为Bean指定初始化和销毁的方法,采用这种方式对Bean生命周期的控制效果和通过实现InitializingBeanDisposableBean接口所达到的效果是完全相同的,而且达到了框架解耦的目的。

BeanPostProcessor:BeanPostProcessor接口不需要Bean去继承它,可以像插件一样注册到Spring容器中,为容器提供额外功能。

5 ApplicationContext 5.1 Application体系结构

ApplicationEventPublisher:让容器拥有了发布应用上下文事件的功能,包括容器启动事件 、关闭事件等。实现了ApplicationListener事件监听接口的Bean可以接收到容器事件,并对容器事件进行响应处理。在ApplicationContext抽象实现类AbstractApplicationContext中存在一个 ApplicationEventMulticaster,它负责保存所有的监听器,以便在容器产生上下文事件时通知这些事件监听者。

MessageSource:为容器提供了i18n国际化信息访问的功能。

ResourcePatternResolver:所有ApplicationContext实现类都实现了类似于PathMatchingResourcePatternResolver的功能,可以通过带前缀 Ant 风格的资源类文件路径来装载Spring的配置文件。

LifeCycle:它提供了start()和stop()两个方法,主要用于控制异步处理过程。在具体使用时,该接口同时被ApplicationContext实现及具体Bean实现,ApplicationContext会将start/stop的信息传递给容器中所有实现了该接口的Bean,以达到管理和控制JMX、任务调度等目的。

ConfigurableApplicationContext:它扩展了ApplicationContext,让ApplicationContext具有启动、刷新和关闭应用上下文的能力。上下文关闭时,调用 refresh()即可启动上下文;如果已经启动,则调用refresh()可清除缓存并重新加载配置信息;调用close()可关闭应用上下文。

5.2 初始化ApplicationContext

ClassPathXmlApplicationContext:从类路径中加载XML配置文件,也可以显示的使用带资源类型前缀的路径。

FileSystemXmlApplicationContext:从文件系统路径下加载XML配置文件,也可以显示的使用带资源类型前缀的路径。

AnnotationConfigApplicationContext:一个标注@Configuration注解的POJO即可提供Spring所需的Bean配置信息。

GenericGroovyApplicationContext:从Groovy配置中提取Bean。

    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在ApplicationContext和BeanFactory中生命周期类似,但有以下不同点

如果Bean实现了ApplicationContextAware接口,则会增加一个调用该接口方法的ApplicationContextAware.setApplicationContext的方法。

如果在配置文件中生命了工厂后处理器接口BeanFactoryPostProcessor的实现类,则应用上下文在装在配置文件之后、初始化Bean实例之前会调用这些BeanFactoryPostProcessor对配置信息进行加工处理。Spring提供了多个工厂容器,如CustomEditorConfigurePropertyPlaceholderConfigurer等。工厂后处理器是容器级的,只在应用上下文初始化时调用一次,其目的是完成一些配置文件加工处理工作。

ApplicationContext可以利用Java反射机制自动识别出配置文件中定义的BeanPostProcessorInstantiationAwareBeanPostProcessorBeanFactoryPostProcessor,并自动将它们注册到应用上下文中(如下所示);而BeanFactory需要通过手工调用addBeanPostProcessor()方法注册。



    
        
    

    
    

    
    
6 BeanFactory与ApplicationContext区别

一般称BeanFactory为IoC容器:Bean工厂(com.springframework.beans.factory.BeanFactory)是Spring框架中最核心的接口,它提供了高级 IoC 的配置机制 。BeanFactory使管理不同类型的Java对象成为可能。BeanFactory是Spring框架的基础设施,面向Spring本身。

一般称ApplicationContext为应用上下文或Spring容器:应用上下文(com.springframework.context.ApplicationContext)建立在BeanFactory基础之上,提供了更多面向应用的功能,它提供了国际化支持和框架事件体系,更易于创建实际应用 。 ApplicationContext 面向使用 Spring 框架的开发者,几乎所有的应用场合都可以直接使用 ApplicationContext。

BeanFactory在初始化容器的时候,并未实例化Bean,直到第一次访问某个Bean时才实例化Bean;ApplicationContext在初始化应用上下文的时候就实例化全部单实例Bean。

ApplicationContext可以利用Java反射机制自动识别出配置文件中定义的BeanPostProcessorInstantiationAwareBeanPostProcessorBeanFactoryPostProcessor,并自动将它们注册到应用上下文中;而BeanFactory需要通过手工调用addBeanPostProcessor()方法注册。

7 父子容器

通过HierarchicalBeanFactory接口,Spring的IoC容器可以建立父子层级关联的容器体系,子容器可以访问父容器中的 Bean,但父容器不能访问子容器的Bean。

在容器内,Bean的id必须是唯一的,但子容器可以拥有一个和父容器id相同的 Bean。

父子容器层级体系增强了Spring容器架构的扩展性和灵活性,因此第三方可以通过编程的方式,为一个已经存在的容器添加一个或多个特殊用途的子容器,以提供一些额外的功能。

Spring使用父子容器的特性实现了很多能力,比如在Spring MVC中,展现层Bean位于一个子容器中,而业务层和持久层的Bean位于父容器中。 这样展现层Bean就可以引用业务层和持久层的Bean,而业务层和持久层的Bean则看不到展现层的Bean。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/72862.html

相关文章

  • Java深入-框架技巧

    摘要:从使用到原理学习线程池关于线程池的使用,及原理分析分析角度新颖面向切面编程的基本用法基于注解的实现在软件开发中,分散于应用中多出的功能被称为横切关注点如事务安全缓存等。 Java 程序媛手把手教你设计模式中的撩妹神技 -- 上篇 遇一人白首,择一城终老,是多么美好的人生境界,她和他历经风雨慢慢变老,回首走过的点点滴滴,依然清楚的记得当初爱情萌芽的模样…… Java 进阶面试问题列表 -...

    chengtao1633 评论0 收藏0
  • SpringBoot自动配置原理

    摘要:开启自动配置功能后文详解这个注解,学过的同学应该对它不会陌生,就是扫描注解,默认是扫描当前类下的。简单来说,这个注解可以帮助我们自动载入应用程序所需要的所有默认配置。简单理解这二者扫描的对象是不一样的。 前言 只有光头才能变强。 文本已收录至我的GitHub仓库,欢迎Star:https://github.com/ZhongFuCheng3y/3y 回顾前面Spring的文章(以学习...

    Rainie 评论0 收藏0
  • Spring IoC学习总结

    摘要:学习总结学习整理的一些笔记,很简单。大部分认为和只是不同的叫法而已。依赖注入的两种方式和注解使用注释驱动的功能源码剖析 Spring IoC学习总结 学习spring Ioc整理的一些笔记,很简单。分享给大家。 IoC 基本概念 在这之前,我们先记住一句话。好莱坞原则:Dont call us, we will call you.其实这句话很恰当地形容了反转的意味;Ioc, Inve...

    silencezwm 评论0 收藏0
  • Spring框架学习笔记(一):官方文档介绍,IoC与AOP概念学习

    摘要:构造函数注入通过调用类的构造函数,将接口实现类通过构造函数变量传入。而在中,其使用横切技术,将这类代码从原属的封装对象中提取出来,封装到一个可重用模块中,称为。 最近实习用到Spring的开发框架,但是之前没有接触过,因此希望利用网上的资源来学习以下。 Spring官方给出了非常全面的介绍,非常适合我这种完全的小白……在这一系列学习中,我阅读的主要资源是5.1.2 Reference ...

    mindwind 评论0 收藏0

发表评论

0条评论

Ververica

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<