聊一聊 Spring 中的扩展机制(一)

以前 Spring 源码系列文章中大可能是底层源码的分析,经过源码可让咱们可以清晰的了解 Spring 究竟是什么,而不是停留于表面的认知。好比当咱们要使用 @Autowired 注解时,能够拿到咱们想要的 bean ,可是为何能够是值得思考的。-- 关于阅读源码php

Spring源码的阅读结合平常的使用,能够帮助咱们更好的掌握这个庞大的技术体系,实际的开发工做中有不少地方能够借鉴它的一些思想来帮助咱们更好的实现本身的业务逻辑。本篇将以扩展点为切入点,来了解下在Spring生命周期中扩展Spring中的Bean功能。java

ApplicationListener 扩展

ApplicationListener 实际上是 spring 事件通知机制中核心概念;在java的事件机制中,通常会有三个概念:git

  • event object : 事件对象
  • event source :事件源,产生事件的地方
  • event listener :监听事件并处理

ApplicationListener 继承自 java.util.EventListener ,提供了对于Spring中事件机制的扩展。github

ApplicationListener 在实际的业务场景中使用的很是多,好比我通常喜欢在容器初始化完成以后来作一些资源载入或者一些组件的初始化。这里的容器指的就是Ioc容器,对应的事件是ContextRefreshedEventspring

@Component
public class StartApplicationListener implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
       //初始化资源文件
       //初始化组件 如:cache
    }
}
复制代码

上面这段代码会在容器刷新完成以后来作一些事情。下面经过自定义事件来看看怎么使用,在看具体的demo以前,先来了解下一些关注点。bash

平常工做了,若是要使用 Spring 事件传播机制,咱们须要关注的点有如下几点:app

  • 事件类,这个用来描述事件自己一些属性,通常继承ApplicationEvent
  • 监听类,用来监听具体的事件并做出响应。须要实现 ApplicationListener 接口
  • 事件发布类,须要经过这个类将时间发布出去,这样才能被监听者监听到,须要实现ApplicationContextAware接口。
  • 将事件类和监听类交给Spring容器。

那么下面就按照这个思路来看下demo的具体实现。ide

事件类:UserRegisterEvent

UserRegisterEvent ,用户注册事件;这里做为事件对象,继承自 ApplicationEventthis

/** * @description: 用户注册事件 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
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;
    }
}
复制代码

事件发布类:UserService

用户注册服务,这里须要在用户注册时将注册事件发布出去,因此经过实现ApplicationEventPublisherAware接口,使UserService具备事件发布能力。spa

ApplicationEventPublisherAware:发布事件,也就是把某个事件告诉的全部与这个事件相关的监听器。

/** * @description: 用户注册服务,实现ApplicationEventPublisherAware接口 ,代表自己具备事件发布能力 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
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));
    }
}
复制代码

这里的UserService其实是做为事件源存在的,经过register将用户注册事件传播出去。那么下面就是须要定义如何来监听这个事件,而且将事件进行消费处理掉,这里就是经过ApplicationListener来完成。

监听类:BonusServerListener

当用户触发注册操做时,向积分服务发送消息,为用户初始化积分。

/** * @description: BonusServerListener 积分处理,当用户注册时,给当前用户增长初始化积分 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
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>
复制代码

客户端类

/** * @description: 客户端类 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context =new 
        ClassPathXmlApplicationContext("beans.xml");
        UserService userService = (UserService)
        context.getBean("userService");
        //注册事件触发
        userService.register("glmapper");
    }
}
复制代码

客户端类中,注册一个nameglmapper的用户,执行结果:

用户:glmapper 已注册!
积分服务接到通知,给 glmapper 增长积分...
复制代码

如今来考虑另一个问题,增长一个功能,用户注册以后给用户发一个邮件。这个其实就是增长一个监听类就能够,前提是这个监听者是监听当前事件的。

/** * @description: 邮件服务监听器,当监听到用户的注册行为时, 给用户发送邮件通知 * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/25 */
public class EmailServerListener implements ApplicationListener<UserRegisterEvent> {
    public void onApplicationEvent(UserRegisterEvent event) {
        System.out.println("邮件服务接到通知,给 " + event.getSource() +
        " 发送邮件...");
   
复制代码

这里若是将UserRegisterEvent换成UserLoginEvent,那么邮件服务将不会有任何行为。

增长发送邮件监听类以后的执行结果:

用户:glmapper 已注册!
邮件服务接到通知,给 glmapper 发送邮件...
积分服务接到通知,给 glmapper 增长积分...
复制代码

Spring 的事件传播机制是基于观察者模式(Observer)实现的,它能够将 Spring Bean的改变定义为事件 ApplicationEvent,经过 ApplicationListener 监听 ApplicationEvent 事件,一旦Spring Bean 使用 ApplicationContext.publishEvent( ApplicationEvent event )发布事件后,Spring 容器会通知注册在 容器中全部 ApplicationListener 接口的实现类,最后 ApplicationListener 接口实现类判断是否处理刚发布出来的 ApplicationEvent 事件。

ApplicationContextAware 扩展

ApplicationContextAware中只有一个setApplicationContext方法。实现了ApplicationContextAware接口的类,能够在该Bean被加载的过程当中获取Spring的应用上下文ApplicationContext,经过ApplicationContext能够获取 Spring容器内的不少信息。

这种通常在须要手动获取Bean的注入实例对象时会使用到。下面经过一个简单的demo来了解下。

GlmapperApplicationContext 持有ApplicationContext对象,经过实现 ApplicationContextAware接口来给ApplicationContext作赋值。

/** * @description: GlmapperApplicationContext * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
public class GlmapperApplicationContext implements ApplicationContextAware {

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

    public ApplicationContext getApplicationContext(){
        return applicationContext;
    }
}
复制代码

须要手动获取的bean:

/** * @description: HelloService * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
public class HelloService {
    public void sayHello(){
        System.out.println("Hello Glmapper");
    }
}
复制代码

在配置文件中进行配置:

<bean id="helloService" class="com.glmapper.extention.applicationcontextaware.HelloService"/>

<bean id="glmapperApplicationContext" class="com.glmapper.extention.applicationcontextaware.GlmapperApplicationContext"/>
复制代码

客户端类调用:

public class MainTest {
    public static void main(String[] args) {
        ApplicationContext context = new
        ClassPathXmlApplicationContext("beans.xml");
        
        HelloService helloService = (HelloService)
        context.getBean("helloService");
        helloService.sayHello();

        //这里经过实现ApplicationContextAware接口的类来完成bean的获取
        GlmapperApplicationContext glmapperApplicationContext =
        (GlmapperApplicationContext) context.getBean("glmapperApplicationContext");
        
        ApplicationContext applicationContext =
        glmapperApplicationContext.getApplicationContext();
        
        HelloService glmapperHelloService = (HelloService)
        applicationContext.getBean("helloService");
        
        glmapperHelloService.sayHello();
    }
}
复制代码

BeanFactoryAware 扩展

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

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

来想一个问题,咱们为何须要经过BeanFactorygetBean来获取Bean呢?Spring已经提供了不少便捷的注入方式,那么经过BeanFactorygetBean来获取Bean有什么好处呢?来看一个场景。

如今有一个HelloService,这个HelloService就是打招呼,咱们须要经过不一样的语言来实现打招呼,好比用中文,用英文。通常的作法是:

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就能够很好的搞定。OK,来看代码改造:

引入BeanFactoryAware

/** * @description: 实现BeanFactoryAware ,让当前bean自己具备 BeanFactory 的能力 * * 实现 BeanFactoηAware 接口的 bean 能够直接访问 Spring 容器,被容器建立之后, * 它会拥有一个指向 Spring 容器的引用,能够利用该bean根据传入参数动态获取被spring工厂加载的bean * * @email: <a href="glmapper_2018@163.com"></a> * @author: guolei.sgl * @date: 18/7/29 */
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();
    }

}
复制代码

这里为了逻辑方便理解,再加入一个HelloFacade 类,这个类的做用就是持有一个BeanFactoryAware的实例对象,而后经过HelloFacade实例对象的方法来屏蔽底层BeanFactoryAware实例的实现细节。

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

    }
}
复制代码

能够看到在调用者(客户端)类中,只须要经过一个beanName就能够实现不一样实现类的切换,而不是经过一堆if-else来判断。另外有的小伙伴可能会说,程序怎么知道用哪一个beanName呢?其实这个也很简单,这个参数咱们能够经过一些途径来拼接获得,好比使用一个prefix用来指定语言,prefix+HelloService就能够肯定惟一的beanName

小结

原本想着在一篇文章里面把扩展点都写一下的,可是实在太长了。后面差很少还有两篇。本系列中全部的demo能够在github获取,也欢迎小伙伴把可以想到的扩展点pr过来。

相关文章
相关标签/搜索