以前 Spring 源码系列文章中大可能是底层源码的分析,经过源码可让咱们可以清晰的了解 Spring 究竟是什么,而不是停留于表面的认知。好比当咱们要使用 @Autowired 注解时,能够拿到咱们想要的 bean ,可是为何能够是值得思考的。-- 关于阅读源码php
Spring源码的阅读结合平常的使用,能够帮助咱们更好的掌握这个庞大的技术体系,实际的开发工做中有不少地方能够借鉴它的一些思想来帮助咱们更好的实现本身的业务逻辑。本篇将以扩展点为切入点,来了解下在Spring生命周期中扩展Spring中的Bean功能。java
ApplicationListener
实际上是 spring
事件通知机制中核心概念;在java的事件机制中,通常会有三个概念:git
ApplicationListener
继承自 java.util.EventListener
,提供了对于Spring
中事件机制的扩展。github
ApplicationListener
在实际的业务场景中使用的很是多,好比我通常喜欢在容器初始化完成以后来作一些资源载入或者一些组件的初始化。这里的容器指的就是Ioc
容器,对应的事件是ContextRefreshedEvent
。spring
@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
,用户注册事件;这里做为事件对象,继承自 ApplicationEvent
。this
/** * @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;
}
}
复制代码
用户注册服务,这里须要在用户注册时将注册事件发布出去,因此经过实现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
来完成。
当用户触发注册操做时,向积分服务发送消息,为用户初始化积分。
/** * @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");
}
}
复制代码
客户端类中,注册一个name
为glmapper
的用户,执行结果:
用户: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
中只有一个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();
}
}
复制代码
咱们知道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: <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过来。