【SpringBoot笔记13】Spring容器提供的扩展点

参考:官方文档

1 容器扩展点

通常,开发者不需要去实现ApplicationContext接口,Spring提供了很多接口用于扩展容器的功能。

1.1 BeanPostProcessor接口

BeanPostProcessor接口定义了回调方法,您可以实现这些方法来提供自己的(或覆盖容器的默认)实例化逻辑、依赖项解析逻辑,等等。如果您想在Spring容器完成bean的实例化、配置和初始化之后实现一些自定义逻辑,您可以插入一个或多个自定义的BeanPostProcessor实现。

您可以配置多个BeanPostProcessor实例,并且可以通过实现Ordered接口来设置order属性来控制这些BeanPostProcessor实例执行的顺序。有关详细信息,请参见BeanPostProcessorOrdered 接口的javadoc。还可以请参见programmatic registration of BeanPostProcessor instances

BeanPostProcessor接口的定义如下:

public interface BeanPostProcessor {

	@Nullable
	default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}

	@Nullable
	default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
		return bean;
	}
}

org.springframework.beans.factory.config.BeanPostProcessor接口定义了两个回调方法:

  • postProcessBeforeInitialization(Object bean, String beanName)方法:对于该Spring IOC容器所创建的每个bean实例在初始化方法(如afterPropertiesSet()和任意已声明的init方法)调用前,将会调用BeanPostProcessor中的postProcessBeforeInitialization()方法 。
  • postProcessAfterInitialization(Object bean, String beanName)方法:在bean实例初始化方法调用完成后,会调用BeanPostProcessor中的postProcessAfterInitialization()方法 。

1.2 例子

下面的例子展示了如何使用BeanPostProcessor接口。

这个例子中,通过实现BeanPostProcessor接口,在容器创建每个bean的时候打印beantoString()的内容到控制台。

package scripting;

import org.springframework.beans.factory.config.BeanPostProcessor;

/*
* 自定义类InstantiationTracingBeanPostProcessor实现BeanPostProcessor接口
*/
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {

    // 直接返回实例化的bean
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean; // 在这里其实可以返回任何对象
    }

    public Object postProcessAfterInitialization(Object bean, String beanName) {
        // 在bean初始化之后,打印toString()的内容到控制台
        System.out.println("Bean '" + beanName + "' created : " + bean.toString());
        return bean;
    }
}

下面是在xml配置中注册这个bean

<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/lang
        http://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 class="scripting.InstantiationTracingBeanPostProcessor"/>

</beans>

下面是测试代码:

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 = (Messenger) ctx.getBean("messenger");
        System.out.println(messenger);
    }

}

控制台的输出为:

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

可以看到,后处理器在容器实例化messenger这个bean的时候,将toString()的内容打赢到了控制台。

1.3 BeanFactoryPostProcessor接口

org.springframework.beans.factory.config.BeanFactoryPostProcessor接口是Spring提供的一个扩展点。Spring IoC容器允许BeanFactoryPostProcessor读取bean的配置元数据,并在容器实例化除BeanFactoryPostProcessor实例之外的任何bean之前更改它的配置元数据信息。

同样的,你也可以配置多个BeanFactoryPostProcessor实例,并且可以实现Ordered接口来控制执行的顺序。

Spring包含许多预定义的BeanFactoryPostProcessor的实现,例如PropertyOverrideConfigurerPropertyPlaceholderConfigurer。您还可以使用自定义BeanFactoryPostProcessor ,例如,注册自定义属性编辑器。

1.4 例子

PropertyPlaceholderConfigurer

下面的例子说明了如何使用PropertyPlaceholderConfigurer

你可以通过配置PropertyPlaceholderConfigurer来从外部的.properties文件中读取配置,来设置属性例如数据库urlpassword等。

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <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>

对应的配置文件:

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

Spring 2.5之后,可以使用<context:property-placeholder />来配置PropertyPlaceholderConfigurer

<context:property-placeholder location="classpath:com/something/jdbc.properties"/>

PropertyOverrideConfigurer

下面的例子展示了如何使用PropertyOverrideConfigurer

PropertyPlaceholderConfigurer不同的是:PropertyOverrideConfigurer利用属性文件的相关信息,覆盖XML 配置文件中定义。即PropertyOverrideConfigurer 允许XML 配置文件中有默认的配置信息。如果PropertyOverrideConfigurer 的属性文件有对应配置信息,则XML 文件中的配 置信息被覆盖;否则,直接使用XML 文件中的配置信息。

使用PropertyOverrideConfigurer 属性文件的格式如下:

beanName.property=value

beanName 是属性占位符企图覆盖的bean 名, property 是企图覆盖的属性名。

下面是配置:

<?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.xsd">
 
	<!-- 配置一个属性占位符bean ApplictionContext能自动识别PropertyPlaceholderConfigurer bean --> 
	<bean id="propertyOverrider"  class= "org.springframework.beans.factory.config.PropertyOverrideConfigurer"> 
	    <property name="locations"> 
	        <list> 
	           <value>dbconn.properties</value> 
	           <!--如果有多个属性文件,依次在下面列出来 -->
	       </list> 
	   </property> 
	</bean> 
	
	<!-- 配置本地的DBCP 数据源 -->
	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource" destroy-method="close">
	    <property name="driverClassName" value="dd"/> 
	    <property name="url" value="xx"/> 
	    <property name="username" value="dd"/> 
	    <property name="password" value="dd"/> 
	</bean> 
 
</beans>

容器自动注册propertyOverrider 这个bean,读取dbconn.properties文件中的属性,并用于覆盖目标bean的属性。其配置文件中dataSource这个bean 的属性完全是随意输入的,最终被属性文件的配置覆盖,其属性文件如下:

dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql://localhost:3306/demo
dataSource.username=root
dataSource.password=1234567890

1.5 FactoryBean接口(非常重要)

FactoryBean接口提供了功能让我们可以自定义一个bean的实例化逻辑。

org.springframework.beans.factory.FactoryBeanSpring容器实例化bean逻辑的扩展点。如果你有复杂的初始化代码,而不是冗长的XML,那么你可以创建自己的FactoryBean,在该类中编写复杂的初始化,然后将定制的FactoryBean插入容器中。

FactoryBean接口的定义如下:

public interface FactoryBean<T> {

	@Nullable
	T getObject() throws Exception;

	@Nullable
	Class<?> getObjectType();

	default boolean isSingleton() {
		return true;
	}
}

它定义了3个方法:

  • Object getObject():返回此工厂创建的对象的实例。可以共享实例,这取决于该工厂返回的是单例还是原型。
  • Class getObjectType():返回getObject()方法返回的对象类型,如果类型事先不知道,则返回null
  • boolean isSingleton():如果FactoryBean返回单例,则返回true,否则返回alse

当你在容器中想要获取对应beanFactoryBean的实例的时候,在名称前面先加上&。例如,getBean("myBean")返回的是一个叫做myBeanbean,而getBean("&myBean")返回的是对应的FactoryBean

注意:

FactoryBeanBeanFactory很像,但是它们的作用是不一样的。

1.6 FactoryBean和BeanFactory的区别,以及FactoryBean的使用

转载:http://www.cnblogs.com/aspirant/p/9082858.html

1.6.1 FactoryBean和BeanFactory的区别

(一)BeanFacotry

BeanFactory,以Factory结尾,表示它是一个工厂类(接口), 它是负责生产和管理bean的一个工厂。在Spring中,BeanFactoryIOC容器的核心接口,它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。BeanFactory只是个接口,并不是IOC容器的具体实现,但是Spring容器给出了很多种实现,如 DefaultListableBeanFactoryXmlBeanFactoryApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系

BeanFactoryApplicationContext就是Spring框架的两个IOC容器,现在一般使用ApplicationnContext,其不但包含了BeanFactory的作用,同时还进行更多的扩展。

BeanFacotrySpring中比较原始的Factory。如XMLBeanFactory就是一种典型的BeanFactory
原始的BeanFactory无法支持Spring的许多插件,如AOP功能、Web应用等ApplicationContex接口,它由BeanFactory接口派生而来, ApplicationContext包含BeanFactory的所有功能,还扩展了很多功能,通常建议比BeanFactory优先。

(二)FactoryBean

一般情况下,Spring通过反射机制利用<bean>class属性指定实现类实例化bean。在某些情况下,实例化bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化bean的逻辑。

FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式。

Bean结尾,表示它是一个Bean

不同于普通Bean的是:它是实现了FactoryBean<T>接口的Bean,根据该BeanIDBeanFactory中获取的实际上是FactoryBeangetObject()返回的对象,而不是FactoryBean本身,如果要获取FactoryBean对象,请在id前面加一个&符号来获取。

1.6.2 FactoryBean的使用

转载:https://www.jianshu.com/p/6f0a59623090

应用场景

FactoryBean 通常是用来创建比较复杂的bean 。一般的bean 直接用xml配置即可,但如果一个bean的创建过程中涉及到很多其他的bean 和复杂的逻辑,用xml配置比较困难,这时可以考虑用FactoryBean

应用案例

很多开源项目在集成Spring 时都使用到FactoryBean,比如 MyBatis3 提供 mybatis-spring项目中的 org.mybatis.spring.SqlSessionFactoryBean

<bean id="tradeSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="trade" />
        <property name="mapperLocations" value="classpath*:mapper/trade/*Mapper.xml" />
        <property name="configLocation" value="classpath:mybatis-config.xml" />
        <property name="typeAliasesPackage" value="com.bytebeats.mybatis3.domain.trade" />
</bean>

org.mybatis.spring.SqlSessionFactoryBean 如下:

package org.mybatis.spring;

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
        // ......
}

另外,阿里开源的分布式服务框架 Dubbo 中的Consumer 也使用到了FactoryBean

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd
    ">
     
    <!-- 当前应用信息配置 -->
    <dubbo:application name="demo-consumer" />
   
   <!-- 暴露服务协议配置 -->
    <dubbo:protocol name="dubbo" port="20813" />    

    <!-- 暴露服务配置 -->
    <dubbo:reference id="demoService" interface="com.alibaba.dubbo.config.spring.api.DemoService"  />
     
</beans>

<dubbo:reference 对应的Bean是com.alibaba.dubbo.config.spring.ReferenceBean 类,如下:

public class ReferenceBean<T> extends ReferenceConfig<T> implements FactoryBean, ApplicationContextAware, InitializingBean, DisposableBean {

}

实践

当前微服务日趋流行,项目开发中不可避免的需要去调用一些其他系统接口,这个时候选择一个合适的HTTP client library 就很关键了,本人项目开发中使用 Square 开源的OkHttp ,关于OkHttp 可以参考 OkHttp Recipes
OkHttp本身的API 已经非常好用了,结合Spring 以及在其他项目中复用的目的,对OkHttp 创建过程做了一些封装,例如超时时间、连接池大小、http代理等。

maven依赖:

<dependency>
   <groupId>com.squareup.okhttp3</groupId>
   <artifactId>okhttp</artifactId>
   <version>3.6.0</version>
</dependency>

首先,自定义一个OkHttpClientFactoryBean ,代码如下:

package com.bytebeats.codelab.http.okhttp;

import com.bytebeats.codelab.http.util.StringUtils;
import okhttp3.*;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2017-03-04 22:18
 */
package com.tao.springboot.demo;

import okhttp3.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;

import java.io.IOException;
import java.net.Authenticator;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.util.concurrent.TimeUnit;

/**
 * ${DESCRIPTION}
 *
 * @author Ricky Fung
 * @create 2017-03-04 22:18
 */
public class OkHttpClientFactoryBean implements FactoryBean<OkHttpClient>, DisposableBean {
    private int connectTimeout;
    private int readTimeout;
    private int writeTimeout;

    /**
     * http proxy config
     **/
    private String host;
    private int port;
    private String username;
    private String password;


    /**
     * OkHttpClient instance
     **/
    private OkHttpClient client;

    @Override
    public OkHttpClient getObject() throws Exception {

        ConnectionPool pool = new ConnectionPool(5, 10, TimeUnit.SECONDS);

        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(connectTimeout, TimeUnit.MILLISECONDS)
                .readTimeout(readTimeout, TimeUnit.MILLISECONDS)
                .writeTimeout(writeTimeout, TimeUnit.MILLISECONDS)
                .connectionPool(pool);

        if (StringUtils.isNotBlank(host) && port > 0) {
            Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(host, port));
            builder.proxy(proxy);
        }

        if (StringUtils.isNotBlank(username) && StringUtils.isNotBlank(password)) {
            Authenticator proxyAuthenticator = new Authenticator() {
                @Override
                public Request authenticate(Route route, Response response) throws IOException {
                    String credential = Credentials.basic(username, password);
                    return response.request().newBuilder()
                            .header("Proxy-Authorization", credential)
                            .build();
                }
            };
            builder.proxyAuthenticator(proxyAuthenticator);
        }

        client = builder.build();
        return client;
    }

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

    @Override
    public boolean isSingleton() {
        return true;
    }

    @Override
    public void destroy() throws Exception {
        if (client != null) {
            client.connectionPool().evictAll();
            client.dispatcher().executorService().shutdown();
            client.cache().close();
            client = null;
        }
    }

    public void setConnectTimeout(int connectTimeout) {
        this.connectTimeout = connectTimeout;
    }

    public void setReadTimeout(int readTimeout) {
        this.readTimeout = readTimeout;
    }

    public void setWriteTimeout(int writeTimeout) {
        this.writeTimeout = writeTimeout;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

项目中引用 applicationContext.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:context="http://www.springframework.org/schema/context"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="
       http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">


    <bean id="okHttpClient" class="com.bytebeats.codelab.http.okhttp.OkHttpClientFactoryBean">
        <property name="connectTimeout" value="2000" />
        <property name="readTimeout" value="2000" />
        <property name="writeTimeout" value="2000" />
        <property name="host" value="" />
        <property name="port" value="0" />
        <property name="username" value="" />
        <property name="password" value="" />
    </bean>

</beans>

写个测试类测试一下:

@Service
public class DemoService {
    @Resource
    private OkHttpClient client;

    public void run() throws Exception {
        Request request = new Request.Builder()
                .url("https://www.baidu.com/")
                .build();

        Response response = client.newCall(request).execute();
        if (!response.isSuccessful()) throw new IOException("Unexpected code " + response);

        System.out.println(response.body().string());
    }

作者在上边的自定义OkHttpClientFactoryBean中,对okhttp进行了封装。

该类实现了FactoryBean接口。

这样,在代码中注入OkHttpClient依赖的时候,得到的是OkHttpClientFactoryBeangetObject()方法返回的对象。注意返回的对象不是OkHttpClientFactoryBean 的实例。

如果想得到OkHttpClientFactoryBean的实例,在制定name的时候,加上&前缀,如下图:
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值