之前 Spring 源码系列文章中大多是底层源码的分析,通过源码可以让我们能够清晰的了解 Spring 到底是什么,而不是停留于表面的认知。比如当我们要使用 @Autowired 注解时,可以拿到我们想要的 bean ,但是为什么可以是值得思考的。-- 关于阅读源码
Spring源码的阅读结合日常的使用,可以帮助我们更好的掌握这个庞大的技术体系,实际的开发工作中有很多地方可以借鉴它的一些思想来帮助我们更好的实现自己的业务逻辑。本篇将以扩展点为切入点,来了解下在Spring生命周期中扩展Spring中的Bean功能。
ApplicationListener 扩展
ApplicationListener
其实是 spring
事件通知机制中核心概念;在java的事件机制中,一般会有三个概念:
- event object : 事件对象
- event source :事件源,产生事件的地方
- event listener :监听事件并处理
ApplicationListener
继承自 java.util.EventListener
,提供了对于Spring
中事件机制的扩展。
ApplicationListener
在实际的业务场景中使用的非常多,比如我一般喜欢在容器初始化完成之后来做一些资源载入或者一些组件的初始化。这里的容器指的就是Ioc
容器,对应的事件是ContextRefreshedEvent
。
@Componentpublic class StartApplicationListener implementsApplicationListener{ @Override public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) { //初始化资源文件 //初始化组件 如:cache }}复制代码
上面这段代码会在容器刷新完成之后来做一些事情。下面通过自定义事件来看看怎么使用,在看具体的demo
之前,先来了解下一些关注点。
日常工作了,如果要使用 Spring
事件传播机制,我们需要关注的点有以下几点:
- 事件类,这个用来描述事件本身一些属性,一般继承
ApplicationEvent
- 监听类,用来监听具体的事件并作出响应。需要实现
ApplicationListener
接口 - 事件发布类,需要通过这个类将时间发布出去,这样才能被监听者监听到,需要实现
ApplicationContextAware
接口。 - 将事件类和监听类交给
Spring
容器。
那么下面就按照这个思路来看下demo
的具体实现。
事件类:UserRegisterEvent
UserRegisterEvent
,用户注册事件;这里作为事件对象,继承自 ApplicationEvent
。
/** * @description: 用户注册事件 * @email: * @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
具有事件发布能力。
ApplicationEventPublisherAware:发布事件,也就是把某个事件告诉的所有与这个事件相关的监听器。
/** * @description: 用户注册服务,实现ApplicationEventPublisherAware接口 ,表明本身具有事件发布能力 * @email: * @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: * @author: guolei.sgl * @date: 18/7/25 */public class BonusServerListener implementsApplicationListener{ public void onApplicationEvent(UserRegisterEvent event) { System.out.println("积分服务接到通知,给 " + event.getSource() + " 增加积分..."); }}复制代码
注册到容器中
复制代码
客户端类
/** * @description: 客户端类 * @email: * @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"); }}复制代码
客户端类中,注册一个name
为glmapper
的用户,执行结果:
用户:glmapper 已注册!积分服务接到通知,给 glmapper 增加积分...复制代码
现在来考虑另外一个问题,增加一个功能,用户注册之后给用户发一个邮件。这个其实就是增加一个监听类就可以,前提是这个监听者是监听当前事件的。
/** * @description: 邮件服务监听器,当监听到用户的注册行为时, 给用户发送邮件通知 * @email: * @author: guolei.sgl * @date: 18/7/25 */public class EmailServerListener implementsApplicationListener{ 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: * @author: guolei.sgl * @date: 18/7/29 */public class GlmapperApplicationContext implementsApplicationContextAware { private ApplicationContext applicationContext; public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { this.applicationContext=applicationContext; } public ApplicationContext getApplicationContext(){ return applicationContext; }}复制代码
需要手动获取的bean
:
/** * @description: HelloService * @email: * @author: guolei.sgl * @date: 18/7/29 */public class HelloService { public void sayHello(){ System.out.println("Hello Glmapper"); }}复制代码
在配置文件中进行配置:
复制代码
客户端类调用:
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
被加载的过程中获取加载该Bean
的BeanFactory
,同时也可以获取这个BeanFactory
中加载的其它Bean
。
来想一个问题,我们为什么需要通过BeanFactory
的getBean
来获取Bean
呢?Spring已经提供了很多便捷的注入方式,那么通过BeanFactory
的getBean
来获取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: * @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过来。