面试了个30岁的程序员,让我莫名其妙的开始慌了

若注册成功之后需要更多业务,比如还需要给用户增加积分,只需新增一个监听者C,监听到注册成功消息后,负责给用户添加积分,此时根本不用去调整注册的代码,开发者和测试人员只需要确保监听者C中的正确性就可以了。

上面这种模式就是事件模式。

事件模式中的几个概念


事件源:事件的触发者,比如上面的注册器就是事件源。

事件:描述发生了什么事情的对象,比如上面的:xxx注册成功的事件

事件监听器:监听到事件发生的时候,做一些处理,比如上面的:路人A、路人B

下面我们使用事件模式实现用户注册的业务


我们先来定义和事件相关的几个类。

事件对象

表示所有事件的父类,内部有个source字段,表示事件源;我们自定义的事件需要继承这个类。

package com.javacode2018.lesson003.demo1.test0.event;

/**

* 事件对象

*/

public abstract class AbstractEvent {

//事件源

protected Object source;

public AbstractEvent(Object source) {

this.source = source;

}

public Object getSource() {

return source;

}

public void setSource(Object source) {

this.source = source;

}

}

事件监听器

我们使用一个接口来表示事件监听器,是个泛型接口,后面的类型E表示当前监听器需要监听的事件类型,此接口中只有一个方法,用来实现处理事件的业务;其定义的监听器需要实现这个接口。

package com.javacode2018.lesson003.demo1.test0.event;

/**

* 事件监听器

* @param  当前监听器感兴趣的事件类型

*/

public interface EventListener {

/**

* 此方法负责处理事件

* @param event 要响应的事件对象

*/

void onEvent(E event);

}

事件广播器

负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)

负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)

package com.javacode2018.lesson003.demo1.test0.event;

/**

* 事件广播器:

* 1.负责事件监听器的管理(注册监听器&移除监听器,将事件和监听器关联起来)

* 2.负责事件的广播(将事件广播给所有的监听器,对该事件感兴趣的监听器会处理该事件)

*/

public interface EventMulticaster {

/**

* 广播事件给所有的监听器,对该事件感兴趣的监听器会处理该事件

* @param event

*/

void multicastEvent(AbstractEvent event);

/**

* 添加一个事件监听器(监听器中包含了监听器中能够处理的事件)

* @param listener 需要添加监听器

*/

void addEventListener(EventListener<?> listener);

/**

* 将事件监听器移除

* @param listener 需要移除的监听器

*/

void removeEventListener(EventListener<?> listener);

}

事件广播默认实现

package com.javacode2018.lesson003.demo1.test0.event;

import java.lang.reflect.ParameterizedType;

import java.lang.reflect.Type;

import java.util.ArrayList;

import java.util.List;

import java.util.Map;

import java.util.concurrent.ConcurrentHashMap;

/**

* 事件广播器简单实现

*/

public class SimpleEventMulticaster implements EventMulticaster {

private Map<Class<?>, List> eventObjectEventListenerMap = new ConcurrentHashMap<>();

@Override

public void multicastEvent(AbstractEvent event) {

List eventListeners = this.eventObjectEventListenerMap.get(event.getClass());

if (eventListeners != null) {

for (EventListener eventListener : eventListeners) {

eventListener.onEvent(event);

}

}

}

@Override

public void addEventListener(EventListener<?> listener) {

Class<?> eventType = this.getEventType(listener);

List eventListeners = this.eventObjectEventListenerMap.get(eventType);

if (eventListeners == null) {

eventListeners = new ArrayList<>();

this.eventObjectEventListenerMap.put(eventType, eventListeners);

}

eventListeners.add(listener);

}

@Override

public void removeEventListener(EventListener<?> listener) {

Class<?> eventType = this.getEventType(listener);

List eventListeners = this.eventObjectEventListenerMap.get(eventType);

if (eventListeners != null) {

eventListeners.remove(listener);

}

}

/**

* 获取事件监听器需要监听的事件类型

* @param listener

* @return

*/

protected Class<?> getEventType(EventListener listener) {

ParameterizedType parameterizedType = (ParameterizedType) listener.getClass().getGenericInterfaces()[0];

Type eventType = parameterizedType.getActualTypeArguments()[0];

return (Class<?>) eventType;

}

}

上面3个类支撑了整个时间模型,下面我们使用上面三个类来实现注册的功能,目标是:高内聚低耦合,让注册逻辑方便扩展。

自定义用户注册成功事件类

继承了AbstractEvent类

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.AbstractEvent;

/**

* 用户注册成功事件

*/

public class UserRegisterSuccessEvent extends AbstractEvent {

//用户名

private String userName;

/**

* 创建用户注册成功事件对象

* @param source   事件源

* @param userName 当前注册的用户名

*/

public UserRegisterSuccessEvent(Object source, String userName) {

super(source);

this.userName = userName;

}

public String getUserName() {

return userName;

}

public void setUserName(String userName) {

this.userName = userName;

}

}

用户注册服务

负责实现用户注册逻辑

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;

/**

* 用户注册服务

*/

public class UserRegisterService {

//事件发布者

private EventMulticaster eventMulticaster; //@0

/**

* 注册用户

* @param userName 用户名

*/

public void registerUser(String userName) { //@1

//用户注册(将用户信息入库等操作)

System.out.println(String.format(“用户【%s】注册成功”, userName)); //@2

//广播事件

this.eventMulticaster.multicastEvent(new UserRegisterSuccessEvent(this, userName)); //@3

}

public EventMulticaster getEventMulticaster() {

return eventMulticaster;

}

public void setEventMulticaster(EventMulticaster eventMulticaster) {

this.eventMulticaster = eventMulticaster;

}

}

@0:事件发布者

@1:registerUser这个方法负责用户注册,内部主要做了2个事情

@2:模拟将用户信息落库

@3:使用事件发布者eventPublisher发布用户注册成功的消息:

下面我们使用spring来将上面的对象组装起来

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.EventListener;

import com.javacode2018.lesson003.demo1.test0.event.EventMulticaster;

import com.javacode2018.lesson003.demo1.test0.event.SimpleEventMulticaster;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.context.annotation.Bean;

import org.springframework.context.annotation.ComponentScan;

import org.springframework.context.annotation.Configuration;

import java.util.List;

@Configuration

@ComponentScan

public class MainConfig0 {

/**

* 注册一个bean:事件发布者

* @param eventListeners

* @return

*/

@Bean

@Autowired(required = false)

public EventMulticaster eventMulticaster(List eventListeners) { //@1

EventMulticaster eventPublisher = new SimpleEventMulticaster();

if (eventListeners != null) {

eventListeners.forEach(eventPublisher::addEventListener);

}

return eventPublisher;

}

/**

* 注册一个bean:用户注册服务

* @param eventMulticaster

* @return

*/

@Bean

public UserRegisterService userRegisterService(EventMulticaster eventMulticaster) { //@2

UserRegisterService userRegisterService = new UserRegisterService();

userRegisterService.setEventMulticaster(eventMulticaster);

return userRegisterService;

}

}

上面有2个方法,负责向spring容器中注册2个bean。

@1:向spring容器中注册了一个bean:事件发布者,方法传入了EventListener类型的List,这个地方会将容器中所有的事件监听器注入进来,丢到EventMulticaster中。

@2:向spring容器中注册了一个bean:用户注册服务

来个测试用例模拟用户注册

package com.javacode2018.lesson003.demo1;

import com.javacode2018.lesson003.demo1.test0.userregister.MainConfig0;

import org.junit.Test;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class EventTest {

@Test

public void test0() {

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MainConfig0.class);

//获取用户注册服务

com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService userRegisterService =

context.getBean(com.javacode2018.lesson003.demo1.test0.userregister.UserRegisterService.class);

//模拟用户注册

userRegisterService.registerUser(“路人甲Java”);

}

}

运行输出

用户【路人甲Java】注册成功

添加注册成功发送邮件功能

下面添加一个注册成功发送邮件的功能,只需要自定义一个监听用户注册成功事件的监听器就可以了,其他代码不需要任何改动,如下

package com.javacode2018.lesson003.demo1.test0.userregister;

import com.javacode2018.lesson003.demo1.test0.event.EventListener;

import org.springframework.stereotype.Component;

/**

* 用户注册成功事件监听器->负责给用户发送邮件

*/

@Component

public class SendEmailOnUserRegisterSuccessListener implements EventListener {

@Override

public void onEvent(UserRegisterSuccessEvent event) {

System.out.println(

String.format(“给用户【%s】发送注册成功邮件!”, event.getUserName()));

}

}

上面这个类使用了@Component,会被自动扫描注册到spring容器。

再次运行测试用例输出

用户【路人甲Java】注册成功

给用户【路人甲Java】发送注册成功邮件!

小结

上面将注册的主要逻辑(用户信息落库)和次要的业务逻辑(发送邮件)通过事件的方式解耦了。次要的业务做成了可插拔的方式,比如不想发送邮件了,只需要将邮件监听器上面的@Component注释就可以了,非常方便扩展。

上面用到的和事件相关的几个类,都是我们自己实现的,其实这些功能在spring中已经帮我们实现好了,用起来更容易一些,下面带大家来体验一下。

Spring中实现事件模式


事件相关的几个类

Spring中事件相关的几个类需要先了解一下,下面来个表格,将spring中事件相关的类和我们上面自定义的类做个对比,方便大家理解

面试了个30岁的程序员,让我莫名其妙的开始慌了

这些类和我们自定义的类中代码有点类似,有兴趣的可以去看一下源码,这里就不列出来了。

硬编码的方式使用spring事件3步骤

步骤1:定义事件

自定义事件,需要继承ApplicationEvent类,

步骤2:定义监听器

自定义事件监听器,需要实现ApplicationListener接口,这个接口有个方法onApplicationEvent需要实现,用来处理感兴趣的事件。

@FunctionalInterface

public interface ApplicationListener extends EventListener {

/**

* Handle an application event.

* @param event the event to respond to

*/

void onApplicationEvent(E event);

}

步骤3:创建事件广播器

创建事件广播器

ApplicationEventMulticaster,这是个接口,你可以自己实现这个接口,也可以直接使用系统给我们提供的

SimpleApplicationEventMulticaster,如下:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

步骤4:向广播器中注册事件监听器

将事件监听器注册到广播器

ApplicationEventMulticaster中,如:

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

步骤5:通过广播器发布事件

广播事件,调用

ApplicationEventMulticaster#multicastEvent方法广播事件,此时广播器中对这个事件感兴趣的监听器会处理这个事件。

applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

下面我们来个案例将这5个步骤串起来感受一下。

案例

实现功能:电商中订单创建成功之后,给下单人发送一封邮件,发送邮件的功能放在监听器中实现。

下面上代码

来个事件类:订单创建成功事件

package com.javacode2018.lesson003.demo1.test1;

import org.springframework.context.ApplicationEvent;

/**

* 订单创建事件

*/

public class OrderCreateEvent extends ApplicationEvent {

//订单id

private Long orderId;

/**

* @param source  事件源

* @param orderId 订单id

*/

public OrderCreateEvent(Object source, Long orderId) {

super(source);

this.orderId = orderId;

}

public Long getOrderId() {

return orderId;

}

public void setOrderId(Long orderId) {

this.orderId = orderId;

}

}

来个监听器:负责监听订单成功事件,发送邮件

package com.javacode2018.lesson003.demo1.test1;

import org.springframework.context.ApplicationListener;

import org.springframework.stereotype.Component;

/**

* 订单创建成功给用户发送邮件

*/

@Component

public class SendEmailOnOrderCreateListener implements ApplicationListener {

@Override

public void onApplicationEvent(OrderCreateEvent event) {

System.out.println(String.format(“订单【%d】创建成功,给下单人发送邮件通知!”, event.getOrderId()));

}

}

测试用例

@Test

public void test2() throws InterruptedException {

//创建事件广播器

ApplicationEventMulticaster applicationEventMulticaster = new SimpleApplicationEventMulticaster();

//注册事件监听器

applicationEventMulticaster.addApplicationListener(new SendEmailOnOrderCreateListener());

//广播事件订单创建事件

applicationEventMulticaster.multicastEvent(new OrderCreateEvent(applicationEventMulticaster, 1L));

}

运行输出

订单【1】创建成功,给下单人发送邮件通知!

ApplicationContext容器中事件的支持

上面演示了spring中事件的使用,那么平时我们使用spring的时候就这么使用?

非也非也,上面只是我给大家演示了一下原理。

通常情况下,我们会使用以ApplicationContext结尾的类作为spring的容器来启动应用,下面2个是比较常见的

AnnotationConfigApplicationContext

ClassPathXmlApplicationContext

来看一个类图

面试了个30岁的程序员,让我莫名其妙的开始慌了

对这个图我们来解释一下:

1.AnnotationConfigApplicationContext和ClassPathXmlApplicationContext都继承了AbstractApplicationContext

2.AbstractApplicationContext实现了ApplicationEventPublisher接口

3.AbstractApplicationContext内部有个ApplicationEventMulticaster类型的字段

上面第三条,说明了

AbstractApplicationContext内部已经集成了事件广播器

ApplicationEventMulticaster,说明

AbstractApplicationContext内部是具体事件相关功能的,这些功能是通过其内部的

ApplicationEventMulticaster来实现的,也就是说将事件的功能委托给了内部的

ApplicationEventMulticaster来实现。

ApplicationEventPublisher接口

上面类图中多了一个新的接口ApplicationEventPublisher,来看一下源码

@FunctionalInterface

public interface ApplicationEventPublisher {

default void publishEvent(ApplicationEvent event) {

publishEvent((Object) event);

}

void publishEvent(Object event);

}

这个接口用来发布事件的,内部定义2个方法都是用来发布事件的。

spring中不是有个

ApplicationEventMulticaster接口么,此处怎么又来了一个发布事件的接口?

这个接口的实现类中,比如

AnnotationConfigApplicationContext内部将这2个方法委托给

ApplicationEventMulticaster#multicastEvent进行处理了。

所以调用

AbstractApplicationContext中的publishEvent方法,也实现广播事件的效果,不过使用

AbstractApplicationContext也只能通过调用publishEvent方法来广播事件。

获取ApplicationEventPublisher对象

如果我们想在普通的bean中获取ApplicationEventPublisher对象,需要实现

ApplicationEventPublisherAware接口

public interface ApplicationEventPublisherAware extends Aware {

void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher);

}

spring容器会自动通过上面的

setApplicationEventPublisher方法将ApplicationEventPublisher注入进来,此时我们就可以使用这个来发布事件了。

Spring为了简化事件的使用,提供了2种使用方式

  1. 面相接口的方式

  2. 面相@EventListener注解的方式

面相接口的方式


案例

实现用户注册成功后发布事件,然后在监听器中发送邮件的功能。

用户注册事件

需要继承ApplicationEvent

package com.javacode2018.lesson003.demo1.test2;

import org.springframework.context.ApplicationEvent;

/**

* 用户注册事件

*/

public class UserRegisterEvent extends ApplicationEvent {

//用户名

private String userName;

public UserRegisterEvent(Object source, String userName) {

super(source);

this.userName = userName;

}

public String getUserName() {

return userName;

}

}

发送邮件监听器

需实现ApplicationListener接口

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分享一套我整理的面试干货,这份文档结合了我多年的面试官经验,站在面试官的角度来告诉你,面试官提的那些问题他最想听到你给他的回答是什么,分享出来帮助那些对前途感到迷茫的朋友。

面试经验技巧篇
  • 经验技巧1 如何巧妙地回答面试官的问题
  • 经验技巧2 如何回答技术性的问题
  • 经验技巧3 如何回答非技术性问题
  • 经验技巧4 如何回答快速估算类问题
  • 经验技巧5 如何回答算法设计问题
  • 经验技巧6 如何回答系统设计题
  • 经验技巧7 如何解决求职中的时间冲突问题
  • 经验技巧8 如果面试问题曾经遇见过,是否要告知面试官
  • 经验技巧9 在被企业拒绝后是否可以再申请
  • 经验技巧10 如何应对自己不会回答的问题
  • 经验技巧11 如何应对面试官的“激将法”语言
  • 经验技巧12 如何处理与面试官持不同观点这个问题
  • 经验技巧13 什么是职场暗语

面试真题篇
  • 真题详解1 某知名互联网下载服务提供商软件工程师笔试题
  • 真题详解2 某知名社交平台软件工程师笔试题
  • 真题详解3 某知名安全软件服务提供商软件工程师笔试题
  • 真题详解4 某知名互联网金融企业软件工程师笔试题
  • 真题详解5 某知名搜索引擎提供商软件工程师笔试题
  • 真题详解6 某初创公司软件工程师笔试题
  • 真题详解7 某知名游戏软件开发公司软件工程师笔试题
  • 真题详解8 某知名电子商务公司软件工程师笔试题
  • 真题详解9 某顶级生活消费类网站软件工程师笔试题
  • 真题详解10 某知名门户网站软件工程师笔试题
  • 真题详解11 某知名互联网金融企业软件工程师笔试题
  • 真题详解12 国内某知名网络设备提供商软件工程师笔试题
  • 真题详解13 国内某顶级手机制造商软件工程师笔试题
  • 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题
  • 真题详解15 某著名社交类上市公司软件工程师笔试题
  • 真题详解16 某知名互联网公司软件工程师笔试题
  • 真题详解17 某知名网络安全公司校园招聘技术类笔试题
  • 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题

资料整理不易,点个关注再走吧
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

最后

分享一套我整理的面试干货,这份文档结合了我多年的面试官经验,站在面试官的角度来告诉你,面试官提的那些问题他最想听到你给他的回答是什么,分享出来帮助那些对前途感到迷茫的朋友。

面试经验技巧篇
  • 经验技巧1 如何巧妙地回答面试官的问题
  • 经验技巧2 如何回答技术性的问题
  • 经验技巧3 如何回答非技术性问题
  • 经验技巧4 如何回答快速估算类问题
  • 经验技巧5 如何回答算法设计问题
  • 经验技巧6 如何回答系统设计题
  • 经验技巧7 如何解决求职中的时间冲突问题
  • 经验技巧8 如果面试问题曾经遇见过,是否要告知面试官
  • 经验技巧9 在被企业拒绝后是否可以再申请
  • 经验技巧10 如何应对自己不会回答的问题
  • 经验技巧11 如何应对面试官的“激将法”语言
  • 经验技巧12 如何处理与面试官持不同观点这个问题
  • 经验技巧13 什么是职场暗语

[外链图片转存中…(img-FI1WnJa9-1712529427120)]

面试真题篇
  • 真题详解1 某知名互联网下载服务提供商软件工程师笔试题
  • 真题详解2 某知名社交平台软件工程师笔试题
  • 真题详解3 某知名安全软件服务提供商软件工程师笔试题
  • 真题详解4 某知名互联网金融企业软件工程师笔试题
  • 真题详解5 某知名搜索引擎提供商软件工程师笔试题
  • 真题详解6 某初创公司软件工程师笔试题
  • 真题详解7 某知名游戏软件开发公司软件工程师笔试题
  • 真题详解8 某知名电子商务公司软件工程师笔试题
  • 真题详解9 某顶级生活消费类网站软件工程师笔试题
  • 真题详解10 某知名门户网站软件工程师笔试题
  • 真题详解11 某知名互联网金融企业软件工程师笔试题
  • 真题详解12 国内某知名网络设备提供商软件工程师笔试题
  • 真题详解13 国内某顶级手机制造商软件工程师笔试题
  • 真题详解14 某顶级大数据综合服务提供商软件工程师笔试题
  • 真题详解15 某著名社交类上市公司软件工程师笔试题
  • 真题详解16 某知名互联网公司软件工程师笔试题
  • 真题详解17 某知名网络安全公司校园招聘技术类笔试题
  • 真题详解18 某知名互联网游戏公司校园招聘运维开发岗笔试题

[外链图片转存中…(img-w5wmpvIH-1712529427120)]

资料整理不易,点个关注再走吧
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值