Spring——IoC扩展

这些扩展其实是在初始过程中植入的钩子或回调method,主要看refresh方法

@Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
                // Initialize message source for this context.
                initMessageSource();
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
                // Initialize other special beans in specific context subclasses.
                onRefresh();
                // Check for listener beans and register them.
                registerListeners();
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
                // Last step: publish corresponding event.
                finishRefresh();
            }
            catch (BeansException ex) {
                ...
            }
        }
    }

ApplicationListener

ApplicationListener 其实是 spring 事件通知机制中核心概念。

在java的事件机制中,一般会有三个概念:

  • event object : 事件对象,描述事件本身一些属性
  • event source :事件源,产生事件的地方
  • event listener :监听具体的事件并作出响应

对应的在Spring事件传播机制中:

  • 事件类:ApplicationEvent
  • 监听类:实现ApplicationListener 接口
  • 事件发布类,:实现ApplicationEventPublisherAware接口
  • 将事件类和监听类交给Spring容器

例子

public class UserRegisterEvent extends ApplicationEvent {

    public String name;

    public UserRegisterEvent(Object o) {
        super(o);
    }

    public UserRegisterEvent(Object o, String name) {
        super(o);
        this.name=name;
    }
}

public class UserService implements ApplicationEventPublisherAware {

    private ApplicationEventPublisher applicationEventPublisher;

    public void setApplicationEventPublisher(ApplicationEventPublisher
    applicationEventPublisher) {
        this.applicationEventPublisher = applicationEventPublisher;
    }

    public void register(String name) {
        System.out.println("用户:" + name + " 已注册!");
        applicationEventPublisher.publishEvent(new UserRegisterEvent(name));
    }
}

public class BonusServerListener implements
ApplicationListener<UserRegisterEvent> {
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println("积分服务接到通知,给 " + event.getSource() +
        " 增加积分...");
    }
}

// 注册到容器
<?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"
       xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">
        
    <bean id="userService" class="com.glmapper.extention.UserService"/>
    <bean id="bonusServerListener"
    class="com.glmapper.extention.BonusServerListener"/>
    
</beans>

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context =new 
        ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService)
        context.getBean("userService");
        //注册事件触发
        userService.register("glmapper");
    }
}
// 输出
用户:glmapper 已注册!
积分服务接到通知,给 glmapper 增加积分...

FactroyBean

XML 方式的 AOP 就是通过该接口实现的

public interface FactoryBean<T> {
    T getObject() throws Exception;
    Class<?> getObjectType();
    boolean isSingleton();
}

Spring 在 IOC 初始化的时候,一般的Bean都是直接调用构造方法,而如果该Bean实现了FactoryBean 接口,则会调用该Bean的 getObject 方法获取bean,这也是Spring 使用此接口构造AOP的原因。在 IOC 调用此方法的时候,返回一个代理,完成AOP代理的创建。

BeanNameAware、ApplicationContextAware 和 BeanFactoryAware 针对bean工厂,可以获取上下文,可以获取当前bena的id

ApplicationContextAware

这个比较常用
ApplicationContextAware中只有一个setApplicationContext方法。实现了ApplicationContextAware接口的类,可以在该Bean被加载的过程中获取Spring的应用上下文ApplicationContext,通过ApplicationContext可以获取
Spring容器内的很多信息。

public class GlmapperApplicationContext implements
ApplicationContextAware {

private  ApplicationContext applicationContext;
public void setApplicationContext(ApplicationContext
applicationContext) throws BeansException {
        this.applicationContext=applicationContext;
}

public ApplicationContext getApplicationContext(){
    return applicationContext;
}

}

BeanFactoryAware

我们知道BeanFactory是整个Ioc容器最顶层的接口,它规定了容器的基本行为。实现BeanFactoryAware接口就表明当前类具体BeanFactory的能力。

BeanFactoryAware接口中只有一个setBeanFactory方法。实现了BeanFactoryAware接口的类,可以在该Bean被加载的过程中获取加载该Bean的BeanFactory,同时也可以获取这个BeanFactory中加载的其它Bean。

有一个问题,我们为什么需要通过BeanFactory的getBean来获取Bean呢?Spring已经提供了很多便捷的注入方式,那么通过BeanFactory的getBean来获取Bean有什么好处呢?来看一个场景:写死的逻辑改为由动态配置决定

public interface HelloService {
    void sayHello();
}

//英文打招呼实现
public class GlmapperHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("Hello Glmapper");
    }
}

//中文打招呼实现
public class LeishuHelloServiceImpl implements HelloService {
    public void sayHello() {
        System.out.println("你好,磊叔");
    }
}

// 之前做法
if (condition=="英文"){
    glmapperHelloService.sayHello();
}
if (condition=="中文"){
    leishuHelloService.sayHello();
}
// 有没有什么方式可以动态的去决定客户端类到底去调用哪一种语言实现,而不是用过if-else方式来罗列呢?
// 是的,对于这些需要动态的去获取对象的场景,BeanFactoryAware就可以很好的搞定。

public class GlmapperBeanFactory implements BeanFactoryAware {

    private BeanFactory beanFactory;

    public void setBeanFactory(BeanFactory beanFactory) throws
    BeansException {
        this.beanFactory=beanFactory;
    }
    /**
     * 提供一个execute 方法来实现不同业务实现类的调度器方案。
     * @param beanName
     */
    public void execute(String beanName){
        HelloService helloService=(HelloService)
        beanFactory.getBean(beanName);
        helloService.sayHello();
    }
}

// 
public class HelloFacade {
    private GlmapperBeanFactory glmapperBeanFactory;
    //调用glmapperBeanFactory的execute方法
    public void sayHello(String beanName){
        glmapperBeanFactory.execute(beanName);
    }
    public void setGlmapperBeanFactory(GlmapperBeanFactory beanFactory){
        this.glmapperBeanFactory = beanFactory;
    }
}
public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new
        ClassPathXmlApplicationContext("beans.xml");
        
        HelloFacade helloFacade = (HelloFacade)
        context.getBean("helloFacade");

        GlmapperBeanFactory glmapperBeanFactory = (GlmapperBeanFactory)
        context.getBean("glmapperBeanFactory");
        
        //这里其实可以不通过set方法注入到helloFacade中,
        //可以在helloFacade中通过autowired
        //注入;这里在使用main方法来执行验证,所以就手动set进入了
        helloFacade.setGlmapperBeanFactory(glmapperBeanFactory);

        //这个只需要传入不同HelloService的实现类的beanName,
        //就可以执行不同的业务逻辑
        helloFacade.sayHello("glmapperHelloService");
        helloFacade.sayHello("leishuHelloService");

    }
}

优化后只需要传入不同HelloService的实现类的beanName,就可以执行不同的业务逻辑

Bean 初始化顺序相关接口

验证Spring-Bean初始化顺序,先看几个关键接口


  • InitializingBean
    Spirng的InitializingBean为bean提供了定义初始化方法的方式。InitializingBean是一个接口,它仅仅包含一个方法:afterPropertiesSet()。
    在spring 初始化后,执行完所有属性设置方法(即setXxx)将自动调用 afterPropertiesSet(), 在配置文件中无须特别的配置, 但此方式增加了bean对spring 的依赖,应该尽量避免使用

  • init-method(非接口)
    Spring虽然可以通过InitializingBean完成一个bean初始化后对这个bean的回调,但是这种方式要求bean实现 InitializingBean接口。一但bean实现了InitializingBean接口,那么这个bean的代码就和Spring耦合到一起了。通常情况下我不鼓励bean直接实现InitializingBean,可以使用Spring提供的init-method的功能来执行一个bean 子定义的初始化方法。

  • BeanFactoryPostProcessor接口
    可以在spring的bean创建之前,修改bean的定义属性。也就是说,Spring允许BeanFactoryPostProcessor在容器实例化任何其它bean之前读取配置元数据,并可以根据需要进行修改,例如可以把bean的scope从singleton改为prototype,也可以把property的值给修改掉。可以同时配置多个BeanFactoryPostProcessor,并通过设置'order'属性来控制各个BeanFactoryPostProcessor的执行次序。
    注意:BeanFactoryPostProcessor是在spring容器加载了bean的定义文件之后,在bean实例化之前执行的。接口方法的入参是ConfigurrableListableBeanFactory,使用该参数,可以获取到相关bean的定义信息

  • BeanPostProcessor接口
    BeanPostProcessor,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些自己的处理逻辑。这里说的初始化方法,指的是下面两种:
    1)bean实现了InitializingBean接口,对应的方法为afterPropertiesSet
    2)在bean定义的时候,通过init-method设置的方法

  • InstantiationAwareBeanPostProcessor
    个接口实际上我们也是非常的熟悉,该接口在我们剖析注解配置AOP的时候是我们的老朋友,实际上,注解配置的AOP是间接实现 BeanPostProcess 接口的,而 InstantiationAwareBeanPostProcessor 就是继承该接口的。


例子

public class SpringIoCBean implements InitializingBean, BeanPostProcessor, BeanFactoryPostProcessor, BeanFactoryAware,
        BeanNameAware, DisposableBean {

    static {
        System.out.println(" static block ");
    }

    private String hello;

    {
        System.out.println("{} block hello = " + hello);
    }

    public SpringIoCBean(){
        System.out.println("调用HelloWorld构造器...");
    }

    public String getHello() {
        return hello;
    }

    public void setHello(String hello) {
        this.hello = hello;
        System.out.println("调用setHello()...");
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("调用BeanPostProcessor的postProcessBeforeInitialization() -- " + beanName);
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("调用BeanPostProcessor的postProcessAfterInitialization() -- "+ beanName);
        return bean;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("调用InitializingBean的afterPropertiesSet()...");
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory paramConfigurableListableBeanFactory)
            throws BeansException {
        System.out.println("调用BeanFactoryPostProcessor的postProcessBeanFactory()...");

    }

    @Override
    public String toString() {
        return "HelloWorld [hello=" + hello + "]";
    }

    @Override
    public void setBeanName(String paramString) {
        System.out.println("调用BeanNameAware.setBeanName paramString:"+paramString);

    }

    @Override
    public void setBeanFactory(BeanFactory paramBeanFactory) throws BeansException {
        System.out.println("调用BeanFactoryAware.setBeanFactory");

    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean 接口 destroy方法");

    }

    public void init() throws Exception {
        System.out.println("HelloWorld类 init-method 方法");

    }

}
// 配置
<?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 id="springIoCBean" class="com.alicp.jetcache.test.spring.SpringIoCBean" init-method="init" >
        <property name="hello" value="bigtest" />
    </bean>

</beans>

// 测试类
@RunWith(SpringJUnit4ClassRunner.class)  //使用junit4进行测试
@ContextConfiguration({"classpath:spring-bean.xml"}) //加载配置文件
public class SpringBeanIoCTest {

    @Autowired
    private SpringIoCBean bean;

    @Test
    public void testMain(){
        System.out.println("testMain");
    }

}
// ======输出
static block 
{} block hello = null
调用HelloWorld构造器...
调用setHello()...
调用BeanNameAware.setBeanName paramString:springIoCBean
调用BeanFactoryAware.setBeanFactory
调用InitializingBean的afterPropertiesSet()...
HelloWorld类 init-method 方法
调用BeanFactoryPostProcessor的postProcessBeanFactory()...
调用BeanPostProcessor的postProcessBeforeInitialization() -- org.springframework.context.event.internalEventListenerProcessor
调用BeanPostProcessor的postProcessAfterInitialization() -- org.springframework.context.event.internalEventListenerProcessor
调用BeanPostProcessor的postProcessBeforeInitialization() -- org.springframework.context.event.internalEventListenerFactory
调用BeanPostProcessor的postProcessAfterInitialization() -- org.springframework.context.event.internalEventListenerFactory
调用BeanPostProcessor的postProcessBeforeInitialization() -- com.alicp.jetcache.spring.SpringBeanIoCTest
调用BeanPostProcessor的postProcessAfterInitialization() -- com.alicp.jetcache.spring.SpringBeanIoCTest
testMain
DisposableBean 接口 destroy方法

由结果得出初始化bean顺序

  • 构造函数 construct

  • 初始化属性 property

  • BeanNameAware : setBeanName

  • BeanFactoryAware : 接口执行setBeanFactory方法

  • InitializingBean 接口执行afterPropertiesSet方法

  • 如果在配置文件中指定了init-method,那么执行该方法

  • 如果实现了BeanFactoryPostProcessor 接口在 “new”其他类之前执行 postProcessBeanFactory 方法(通过这个方法可以改变配置文件里面的属性值的配置)// 该bean只会调用一次

  • 如果实现了BeanPostProcessor 接口,那么会在其他bean初始化方法之前执行postProcessBeforeInitialization 方法,之后执行postProcessAfterInitialization方法 // 该bean会在每个其他bean初始化前后得到调用,所以可以用来过滤有关联的bean相应的处理

Ref:
https://juejin.im/post/5b7964d6f265da43412866c7

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容