关于spring:一起来读官方文档SpringIOC07

2次阅读

共计 11135 个字符,预计需要花费 28 分钟才能阅读完成。

1.8。容器扩大点

通常,应用程序开发人员不须要对 ApplicationContext 实现类进行子类化。相同,能够通过插入非凡集成接口的实现来扩大 Spring IoC 容器。接下来的几节形容了这些集成接口。

1.8.1。自定义 bean 实现 BeanBeanPostProcessor 接口

BeanPostProcessor 接口定义了回调办法,您能够实现这些回调办法来批改默认的 bean 实例化的逻辑,依赖关系解析逻辑等。
如果您想在 Spring 容器实现实例化,配置和初始化 bean 之后实现一些自定义逻辑,则能够插入一个或多个自定义 BeanPostProcessor。

您能够配置多个 BeanPostProcessor 实例,并且能够 BeanPostProcessor 通过实现 Ordered 接口设置 order 属性来管制这些实例的运行程序。

@Component
public class MyBeanPostProcessor implements BeanPostProcessor, Ordered {
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {return bean;}

    @Override
    public int getOrder() {return 0;}
}
BeanPostProcessor 实例操作的是 bean 的实例。也就是说,Spring IoC 容器实例化一个 bean 实例,而后应用 BeanPostProcessor 对这些实例进行解决加工。BeanPostProcessor 实例是按容器划分作用域的。仅在应用容器层次结构时,这才有意义。如果 BeanPostProcessor 在一个容器中定义一个,它将仅对该容器中的 bean 进行后处理。换句话说,一个容器中定义的 bean 不会被 BeanPostProcessor 另一个容器中的定义进行后处理,即便这两个容器是同一层次结构的一部分也是如此。BeanPostProcessor 批改的是 bean 实例化之后的内容,如果要更改理论的 bean 定义(即 bean definition)您须要应用 BeanFactoryPostProcessor 接口.

org.springframework.beans.factory.config.BeanPostProcessor 接口恰好由两个回调办法组成。
当此类被注册为容器的 post-processor 时,对于容器创立的每个 bean 实例,post-processor 都会在任何 bean 实例化之后并且在容器初始化办法(例如 InitializingBean.afterPropertiesSet()或任何申明的 init 办法)被应用之前调用。
post-processor 能够对 bean 实例执行任何操作,也能够齐全疏忽回调。
post-processor 通常查看回调接口,或者能够用代理包装 Bean。
一些 Spring AOP 根底构造类被实现为 post-processor,以提供代理包装逻辑。

ApplicationContext 自动检测实现 BeanPostProcessor 接口所有 bean,留神是要注册成 bean,仅仅实现接口是不能够的。

请留神,通过应用 @Bean 工厂办法申明 BeanPostProcessor 时,工厂办法的返回类型应该是实现类自身或至多是 org.springframework.beans.factory.config.BeanPostProcessor 接口,以分明地表明该 bean 的 post-processor 性质。
否则,ApplicationContext 无奈在齐全创立之前按类型自动检测它。
因为 BeanPostProcessor 须要提前实例化以便利用于上下文中其余 bean 的初始化,因而这种晚期类型检测至关重要。

    @Bean
    public BeanPostProcessor myBeanPostProcessor(){return new MyBeanPostProcessor();
    } 
以编程形式注册 BeanPostProcessor 实例
尽管举荐的 BeanPostProcessor 注册办法是通过 ApplicationContext 自动检测,然而您能够 ConfigurableBeanFactory 应用 addBeanPostProcessor 办法通过编程形式对它们进行注册。当您须要在注册之前评估条件逻辑(比方利用场景是 xxx 条件才注册,xxx 条件不注册时),甚至须要跨层次结构的上下文复制 Bean post-processor 时,这将十分有用。然而请留神,以 BeanPostProcessor 编程形式增加的实例不恪守该 Ordered 接口。在这里,注册的程序决定了执行的程序。还要留神,以 BeanPostProcessor 编程形式注册的实例总是在通过自动检测注册的实例之前进行解决,而不思考任何明确的程序。
BeanPostProcessor 实例和 AOP 主动代理
实现 BeanPostProcessor 接口的类是非凡的,并且容器对它们的解决形式有所不同。BeanPostProcessor 它们间接援用的所有实例和 bean 在启动时都会实例化,作为 ApplicationContext 的非凡启动阶段的一部分。接下来,BeanPostProcessor 以排序形式注册所有实例,并将其利用于容器中的所有其余 bean。然而因为 AOP 主动代理的实现是通过 BeanPostProcessor 接口,所以在 AOP 的 BeanPostProcessor 接口实例化之前的
BeanPostProcessor 实例或 BeanPostProcessor 实例间接援用的 bean 都没有资格进行主动代理。并且对于任何此类 bean 都没有任何解决切面的 BeanPostProcessor 指向他们。您应该看到一条参考性日志音讯:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)。这条音讯的意思大略就是说这个 bean 没有失去所有 BeanPostProcessor 的解决

上面剖析一下这条日志的逻辑:咱们不必 AOP 的 BeanPostProcessor 用 AutowiredAnnotationBeanPostProcessor 来看这个状况

首先这条日志是在 BeanPostProcessorChecker 类中打印的,这个类自身就实现了 BeanPostProcessor,Spring 容器减少这个 processor 的代码如下:// 获取所有的 BeanPostProcessor 类型的 bean 
        // 第一个 true 示意包含非单例的 bean
        // 第二个 false 示意仅查找曾经实例化实现的 bean,如果是 factory-bean 则不算入内
        String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
    
        // 以后 beanFactory 内的所有 post-processor 数 +  1 + postBeanNames 的数量
        // 这个数量在后续有个判断
        //beanFactory.getBeanPostProcessorCount() 零碎内置 processor
        //1 就是 BeanPostProcessorChecker
        //postProcessorNames.length 就是能扫描到的 processor
        // 这个数量之和就是目前零碎能看到的所有 processor
        // 还有的就可能是解析完了某些 bean 又新增了 processor 那个不算在内 
        int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
        
        //add BeanPostProcessorChecker 进入 beanPostProcessor 链
        beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));

BeanPostProcessorChecker 中判断并打印上边那条日志的办法如下:@Override
        public Object postProcessAfterInitialization(Object bean, String beanName) {
            // 如果以后 bean 不是 postProcessor 的实例
            // 并且不是外部应用的 bean
            // 并且 this.beanFactory.getBeanPostProcessorCount()小于方才相加的值
            // 三个都满足才会打印那行日志
            if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName) &&
                    this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {if (logger.isInfoEnabled()) {logger.info("Bean'" + beanName + "'of type [" + bean.getClass().getName() +
                            "] is not eligible for getting processed by all BeanPostProcessors" +
                            "(for example: not eligible for auto-proxying)");
                }
            }
            return bean;
        }

        // 以后 beanName 不为空,并且对应的 bean 是容器外部应用的 bean 则返回 true    
        private boolean isInfrastructureBean(@Nullable String beanName) {if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
                return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
            }
            return false;
        }
        
在看 Spring createBean 时遍历 postProcessor 的代码
    @Override
    public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
            throws BeansException {

        Object result = existingBean;
        for (BeanPostProcessor processor : getBeanPostProcessors()) {Object current = processor.postProcessAfterInitialization(result, beanName);
            if (current == null) {return result;}
            result = current;
        }
        return result;
    }
就是通过这么一个循环来执行后置办法 applyBeanPostProcessorsAfterInitialization,前置办法也是这样的

当初假如咱们有一个自定义的 beanPostProcessor 外面须要注入一个咱们自定义的 beanA,那么在 beanPostProcessor 被实例化的时候必定会要求注入咱们自定义的 beanA,那么当初就有多种状况了:1. 咱们用的 set 或者结构器注入那 beanA 会被实例化并注入
    2. 如果咱们用的 @Autowired,当咱们自定义的 beanPostProcessor 实例化
    在 AutowiredAnnotationBeanPostProcessor 实例化之前,那么 beanA 都无奈被注入值
    如果在之后,则还是能够被注入值
    然而这两种状况都会打印这行日志
Bean 'beanA' of type [org.springframework.beanA] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)

以下示例显示了如何在 ApplicationContext 中编写,注册和应用 BeanPostProcessor 实例。

示例:Hello World,BeanPostProcessor-style

第一个示例演示了根本用法。示例展现了一个自定义 BeanPostProcessor 实现,它在容器创立每个 bean 时调用该 bean 的 toString()办法,并将后果字符串打印到零碎控制台。

上面的清单显示了自定义的 BeanPostProcessor 实现类定义:

package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // 只需按原样返回实例化的 bean
    public Object postProcessBeforeInitialization(Object bean, String beanName) {return bean; // 咱们能够返回任何对象援用}

    public Object postProcessAfterInitialization(Object bean, String beanName) {System.out.println("Bean'" + beanName + "'created :" + bean.toString());
        return bean;
    }
}

以下 beans 元素应用 InstantiationTracingBeanPostProcessor:

<?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:lang="http://www.springframework.org/schema/lang"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        https://www.springframework.org/schema/lang/spring-lang.xsd">

    <lang:groovy id="messenger"
            script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
        <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
    </lang:groovy>

    <!--
   当上述 bean (messenger)被实例化时,这个自定义的 BeanPostProcessor 实现将事实输入到零碎控制台   -->
    <bean class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

请留神实例化 tracingbeanpostprocessor 是如何定义的。它甚至没有名称,而且,因为它是一个 bean,所以能够像其余 bean 一样进行依赖注入。

上面的 Java 利用程序运行后面的代码和配置:

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;

public final class Boot {public static void main(final String[] args) throws Exception {ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
        Messenger messenger = ctx.getBean("messenger", Messenger.class);
        System.out.println(messenger);
    }

}

后面的应用程序的输入相似于以下内容:

Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:RequiredAnnotationBeanPostProcessor

将回调接口或注解与自定义 BeanPostProcessor 实现联合应用是扩大 Spring IoC 容器的一种常见办法。

一个例子是 Spring 的 AutowiredAnnotationBeanPostProcessor——一个随 Spring 发行版附带的 BeanPostProcessor 实现,它确保被注解 (@Autowired,@Value,@Inject 等注解) 正文的属性会被注入一个 bean 实例。

1.8.2。自定义配置元数据 BeanFactoryPostProcessor

咱们要看的下一个扩大点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor。

该接口与 BeanPostProcessor 次要区别在于:BeanFactoryPostProcessor 对 Bean 配置元数据进行操作。
也就是说,Spring IoC 容器容许 BeanFactoryPostProcessor 读取配置元数据,并有可能在容器实例化实例任何 bean 之前更改元数据。

您能够配置多个 BeanFactoryPostProcessor 实例,并且能够 BeanFactoryPostProcessor 通过设置 order 属性来管制这些实例的运行程序。然而,仅当 BeanFactoryPostProcessor 实现 Ordered 接口时能力设置此属性。

如果心愿更改理论 bean 实例(从配置元数据创立的对象),则须要应用 BeanPostProcessor。只管在 BeanFactoryPostProcessor 中应用 bean 实例在技术上是可行的(例如,通过应用 BeanFactory.getBean()),然而这样做会导致过早的 bean 实例化,违反规范的容器生命周期。这可能会导致负面的副作用,比方绕过 bean 的后处理。另外,BeanFactoryPostProcessor 实例的作用域为每个容器。这只有在应用容器层次结构时才有用。如果您在一个容器中定义了 BeanFactoryPostProcessor,那么它只利用于该容器中的 bean 定义。一个容器中的 Bean 定义不会被另一个容器中的 BeanFactoryPostProcessor 实例进行后处理,即便这两个容器属于同一层次结构。

当 BeanFactoryPostProcessor 在 ApplicationContext 中申明时,它将主动运行,以便对定义容器的配置元数据利用更改。
Spring 包含许多预约义的 bean 工厂后处理器,如 PropertyOverrideConfigurer 和 PropertySourcesPlaceholderConfigurer。
您还能够应用自定义 BeanFactoryPostProcessor 例如,用于注册自定义属性编辑器。

ApplicationContext 自动检测部署其中实现 BeanFactoryPostProcessor 接口的任何 bean。在适当的时候,这些 bean 会被 bean factory post-processors 来应用。

你也能够像部署任何其余 bean 一样部署这些自定义的 bean factory post-processors。

示例:PropertySourcesPlaceholderConfigurer

您能够应用 PropertySourcesPlaceholderConfigurer 应用规范的 Java 属性格局将 bean 定义中的属性值内部化到独自的文件中。这样,部署应用程序的人员就能够自定义特定于环境的属性,比方数据库 url 和明码,而无需批改主 XML 定义文件或容器文件的复杂性或危险。

思考以下基于 xml 的配置元数据片段,其中定义了具备占位符值的数据源:

<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
    <property name="locations" value="classpath:com/something/jdbc.properties"/>
</bean>

<bean id="dataSource" destroy-method="close"
        class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driverClassName}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

该示例显示了从内部 Properties 文件配置的属性。
在运行时,将 PropertySourcesPlaceholderConfigurer 利用于替换数据源的某些属性的元数据。将要替换的值指定为模式的占位符,该模式 ${property-name}遵循 Ant 和 log4j 和 JSP EL 款式。

理论值来自规范 Java Properties 格局的另一个文件:

jdbc.driverClassName = org.hsqldb.jdbcDriver
jdbc.url = jdbc:hsqldb:hsql://production:9002
jdbc.username = sa
jdbc.password = root

因而,${jdbc.username}在运行时将字符串替换为值“sa”,并且其余与属性文件中的键匹配的占位符值也实用。
在 PropertySourcesPlaceholderConfigurer 为大多数属性和 bean 定义的属性占位符查看。此外,您能够自定义占位符前缀和后缀。

    <bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
        <property name="locations" value="classpath:jdbc.properties"/>
        // 自定义前缀后缀
        <property name="placeholderPrefix" value="${"/>
        <property name="placeholderSuffix" value="}"/>
    </bean>
1.8.3。自定义实例化逻辑 FactoryBean

您能够 org.springframework.beans.factory.FactoryBean 为自身就是工厂的对象实现接口。

该 FactoryBean 接口是可插入 Spring IoC 容器的实例化逻辑的一点。
如果您有简单的初始化代码,而不是(可能)简短的 XML,能够用 Java 更好地表白,则以创立本人的代码 FactoryBean,
在该类中编写简单的初始化,而后将自定义 FactoryBean 插入容器。

该 FactoryBean 界面提供了三种办法:

  • Object getObject():返回此工厂创立的对象的实例。实例能够共享,具体取决于该工厂是否返回单例或原型。
  • boolean isSingleton():true 如果 FactoryBean 返回单例或 false 其余则返回。
  • Class getObjectType():返回 getObject()办法返回的对象类型,或者 null 如果类型未知,则返回该对象类型。

FactoryBeanSpring 框架中的许多中央都应用了该概念和接口。Spring 附带了 50 多种 FactoryBean 接口实现。Spring 中的理解的少,然而 Mybatis 的 MybatisSqlSessionFactoryBean 很闻名。

当您须要向容器询问 FactoryBean 自身而不是由它产生的 bean 的理论实例时,请在调用的办法时在该 bean 的 id 后面加上“&”符号(&)。
因而,对于给定 id 为 myBean 的一个 FactoryBean,调用 getBean(“myBean”)返回的是 FactoryBean 生成的实例,getBean(“&myBean”)返回的是 FactoryBean 自身。

public class MyFactoryBean implements FactoryBean<MyBean> {

    @Override
    public MyBean getObject() throws Exception {return new MyBean();
    }

    @Override
    public Class<?> getObjectType() {return MyBean.class;}
}

<bean id="myFactoryBean" class="org.springframework.example.factoryBean.MyFactoryBean"/>

getBean("myFactoryBean")  返回的是 MyBean 实例
getBean("&myFactoryBean")  返回的是 MyFactoryBean 实例
正文完
 0