Na última aula, aprendemos o princípio, a implementação e os cenários de aplicação do padrão Observer, focando em vários métodos de implementação diferentes em diferentes cenários de aplicação, incluindo: método de bloqueio síncrono, não bloqueio assíncrono, método em processo e método entre processos constatar.
O bloqueio síncrono é o método de implementação mais clássico, principalmente para desacoplamento de código; o não-bloqueio assíncrono pode não apenas alcançar o desacoplamento de código, mas também melhorar a eficiência da execução do código; o desacoplamento do modo observador entre os processos é mais completo, geralmente baseado na fila de mensagens é usado para realizar a interação entre o observado e o observador entre diferentes processos.
Hoje, nos concentramos no modo observador assíncrono e sem bloqueio e levamos você a implementar uma estrutura geral semelhante ao Google Guava EventBus. Depois de terminar esta lição, você descobrirá que não é difícil implementar um framework.
Sem mais delongas, vamos começar oficialmente o estudo de hoje!
Implementação simples do padrão de observador assíncrono sem bloqueio
Na última lição, mencionamos que, para o modo de observador assíncrono e sem bloqueio, é realmente muito fácil implementar uma versão simples sem considerar qualquer versatilidade e reutilização.
Temos duas implementações. Uma delas é: criar uma nova thread para executar a lógica do código em cada função handleRegSuccess(); a outra é: usar o pool de threads na função register() do UserController para executar a função handleRegSuccess() de cada observador. Os códigos específicos das duas implementações são os seguintes:
// 第一种实现方式,其他类代码不变,就没有再重复罗列
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService; // 依赖注入
@Override
public void handleRegSuccess(long userId) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
promotionService.issueNewUserExperienceCash(userId);
}
});
thread.start();
}
}
// 第二种实现方式,其他类代码不变,就没有再重复罗列
public class UserController {
private UserService userService; // 依赖注入
private List<RegObserver> regObservers = new ArrayList<>();
private Executor executor;
public UserController(Executor executor) {
this.executor = executor;
}
public void setRegObservers(List<RegObserver> observers) {
regObservers.addAll(observers);
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
for (RegObserver observer : regObservers) {
executor.execute(new Runnable() {
@Override
public void run() {
observer.handleRegSuccess(userId);
}
});
}
return userId;
}
}
Para o primeiro método de implementação, é demorado criar e destruir encadeamentos com frequência, e o número de encadeamentos simultâneos não pode ser controlado. Criar muitos encadeamentos causará estouro de pilha. Embora o segundo método de implementação use o pool de threads para resolver o problema do primeiro método de implementação, o pool de threads e a lógica de execução assíncrona são acoplados na função register(), o que aumenta o custo de manutenção dessa parte do código de negócios.
Se nossas necessidades forem mais extremas e precisarmos alternar com flexibilidade entre bloqueio síncrono e não bloqueio assíncrono, devemos modificar constantemente o código de UserController. Além disso, se mais de um módulo de negócios precisar usar o modo de observador assíncrono sem bloqueio no projeto, essa implementação de código não poderá ser reutilizada.
Sabemos que o papel da estrutura é ocultar os detalhes da implementação, reduzir a dificuldade de desenvolvimento, obter a reutilização de código, separar códigos comerciais e não comerciais e permitir que os programadores se concentrem no desenvolvimento de negócios. Para o modo de observador assíncrono sem bloqueio, também podemos abstraí-lo em uma estrutura para obter esse efeito, e essa estrutura é o EventBus sobre o qual falaremos nesta lição.
Introdução aos Requisitos Funcionais do EventBus Framework
EventBus é traduzido como "ônibus de evento", que fornece o código esqueleto para implementar o padrão do observador. Com base nessa estrutura, podemos implementar facilmente o padrão Observer em nossos próprios cenários de negócios sem desenvolver do zero. Entre eles, o Google Guava EventBus é uma estrutura EventBus bem conhecida, que não apenas suporta o modo assíncrono sem bloqueio, mas também suporta o modo de bloqueio síncrono
Agora, vamos dar uma olhada em quais funções o Guava EventBus tem. Ainda o exemplo de cadastro de usuário da última aula, vamos reimplementar com Guava EventBus, o código é o seguinte:
public class UserController {
private UserService userService; // 依赖注入
private EventBus eventBus;
private static final int DEFAULT_EVENTBUS_THREAD_POOL_SIZE = 20;
public UserController() {
//eventBus = new EventBus(); // 同步阻塞模式
eventBus = new AsyncEventBus(Executors.newFixedThreadPool(DEFAULT_EVENTBUS_THREAD_POOL_SIZE)); // 异步非阻塞模式
}
public void setRegObservers(List<Object> observers) {
for (Object observer : observers) {
eventBus.register(observer);
}
}
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
eventBus.post(userId);
return userId;
}
}
public class RegPromotionObserver {
private PromotionService promotionService; // 依赖注入
@Subscribe
public void handleRegSuccess(long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class RegNotificationObserver {
private NotificationService notificationService;
@Subscribe
public void handleRegSuccess(long userId) {
notificationService.sendInboxMessage(userId, "...");
}
}
Comparado com o modo observer escrito do zero, o modo observer implementado usando a estrutura EventBus é aproximadamente o mesmo em termos de processo em larga escala. Ambos precisam definir o Observer e registrar o Observer por meio da função register() e também precisam pass Chame uma função (por exemplo, a função post() em EventBus) para enviar uma mensagem ao Observer (uma mensagem é chamada de evento em EventBus).
No entanto, em termos de detalhes de implementação, eles são um pouco diferentes. Com base no EventBus, não precisamos definir a interface do Observer, podendo ser cadastrado qualquer tipo de objeto no EventBus, sendo utilizada a anotação @Subscribe para indicar qual função da classe pode receber a mensagem enviada pelo observador.
A seguir, vamos falar sobre várias classes principais e funções do Guava EventBus em detalhes.
- EventBus, AsyncEventBus
Todas as interfaces chamáveis expostas pelo Guava EventBus são encapsuladas na classe EventBus. Entre eles, EventBus implementa o modo de observador de bloqueio síncrono e AsyncEventBus herda de EventBus, fornecendo um modo de observador assíncrono sem bloqueio. O uso específico é o seguinte:
EventBus eventBus = new EventBus(); // 同步阻塞模式
EventBus eventBus = new AsyncEventBus(Executors.newFixedThreadPool(8));// 异步阻塞模式
- função registrar()
A classe EventBus fornece a função register() para registrar observadores. A definição da função específica é a seguinte. Pode aceitar observadores de qualquer tipo (objeto). Na implementação do padrão Observer clássico, a função register() deve aceitar um objeto de classe que implemente a mesma interface Observer.
public void register(Object object);
- função cancelar registro ()
Em relação à função register(), a função unregister() é usada para remover um observador do EventBus. Não vou explicar muito, a definição específica da função é a seguinte:
public void unregister(Object object);
- função post()
A classe EventBus fornece a função post() para enviar mensagens aos observadores. A definição da função específica é a seguinte:
public void post(Object event);
A diferença com o modo observador clássico é que quando chamamos a função post() para enviar uma mensagem, a mensagem não é enviada para todos os observadores, mas para os observadores correspondentes. O chamado matchable significa que o tipo de mensagem que pode ser recebido é a classe pai do tipo da mensagem enviada (evento na definição da função post). Deixe-me explicar com um exemplo.
Por exemplo, o tipo de mensagem que o AObserver pode receber é XMsg, o tipo de mensagem que o BObserver pode receber é YMsg e o tipo de mensagem que o CObserver pode receber é ZMsg. Entre eles, XMsg é a classe pai de YMsg. Quando enviamos uma mensagem da seguinte forma, os observadores compatíveis correspondentes que podem receber a mensagem são os seguintes:
XMsg xMsg = new XMsg();
YMsg yMsg = new YMsg();
ZMsg zMsg = new ZMsg();
post(xMsg); => AObserver接收到消息
post(yMsg); => AObserver、BObserver接收到消息
post(zMsg); => CObserver接收到消息
Você pode perguntar, onde está definido o tipo de mensagem que cada Observer pode receber? Vamos dar uma olhada em um dos recursos mais especiais do Guava EventBus, que é a anotação @Subscribe.
- Anotação @Subscribe
EventBus usa a anotação @Subscribe para indicar qual tipo de mensagem uma função pode receber. O código de uso específico é o seguinte. Na classe DObserver, anotamos duas funções f1() e f2() via @Subscribe.
public DObserver {
//...省略其他属性和方法...
@Subscribe
public void f1(PMsg event) { //... }
@Subscribe
public void f2(QMsg event) { //... }
}
Quando o objeto da classe DObserver é registrado no EventBus através da função register(), o EventBus encontrará f1() e f2() de acordo com a anotação @Subscribe e registrará os tipos de mensagem que as duas funções podem receber (PMsg->f1, QMsg -> f2). Quando enviamos uma mensagem (como uma mensagem QMsg) através da função post(), o EventBus irá chamar a função correspondente (f2) através do registro anterior (QMsg->f2).
Implemente uma estrutura EventBus manualmente
Explicamos claramente a função do Guava EventBus e, de modo geral, é relativamente simples. A seguir, repetiremos a criação da roda e "chalé" um EventBus.
Vamos nos concentrar nos princípios de implementação das duas funções principais register() e post() no EventBus. Depois de entendê-los, você basicamente entende toda a estrutura do EventBus. As duas figuras a seguir são os diagramas esquemáticos de implementação dessas duas funções.
Podemos ver na figura que a estrutura de dados mais crítica é o registro do Observer, que registra a correspondência entre os tipos de mensagem e as funções que podem receber mensagens. Ao chamar a função register() para registrar observadores, o EventBus gera o registro do Observador analisando a anotação @Subscribe. Quando a função post() é chamada para enviar uma mensagem, o EventBus encontra a função correspondente que pode receber a mensagem por meio do registro e, em seguida, cria dinamicamente o objeto e executa a função por meio da sintaxe de reflexão Java. Para o modo de bloqueio síncrono, o EventBus executa as funções correspondentes sequencialmente em um thread. Para o modo sem bloqueio assíncrono, o EventBus executa as funções correspondentes por meio de um pool de threads.
Depois de entender o princípio, é muito mais fácil implementá-lo. A implementação de código de toda a pequena estrutura inclui 5 classes: EventBus, AsyncEventBus, Subscribe, ObserverAction, ObserverRegistry. Em seguida, vamos olhar para essas cinco classes por sua vez.
1.Inscrever-se
Subscribe é uma anotação usada para indicar qual função no observador pode receber mensagens.
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Beta
public @interface Subscribe {}
2.Ação do Observador
A classe ObserverAction é usada para representar o método de anotação @Subscribe, onde target representa a classe do observador e method representa o método. É usado principalmente no registro do observador ObserverRegistry.
public class ObserverAction {
private Object target;
private Method method;
public ObserverAction(Object target, Method method) {
this.target = Preconditions.checkNotNull(target);
this.method = method;
this.method.setAccessible(true);
}
public void execute(Object event) { // event是method方法的参数
try {
method.invoke(target, event);
} catch (InvocationTargetException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
3. Registro do Observador
A classe ObserverRegistry é o Observer Registry mencionado acima e é a classe mais complexa. Quase toda a lógica central da estrutura está nesta classe. Essa classe usa extensivamente a sintaxe de reflexão do Java, mas o código como um todo não é difícil de entender.Entre eles, um dos lugares mais habilidosos é o uso de CopyOnWriteArraySet.
CopyOnWriteArraySet, como o nome indica, cria um novo conjunto ao gravar dados e clona os dados originais no novo conjunto. Depois de gravar os dados no novo conjunto, substitui o antigo conjunto pelo novo conjunto. Dessa forma, pode-se garantir que, quando os dados forem gravados, a operação de leitura dos dados não seja afetada, de modo a resolver o problema de leitura e gravação simultâneas. Além disso, CopyOnWriteSet também evita conflitos de gravação simultâneos por bloqueio. Para a função específica, você pode verificar o código-fonte da classe CopyOnWriteSet, que fica claro à primeira vista.
public class ObserverRegistry {
private ConcurrentMap<Class<?>, CopyOnWriteArraySet<ObserverAction>> registry = new ConcurrentHashMap<>();
public void register(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActions = findAllObserverActions(observer);
for (Map.Entry<Class<?>, Collection<ObserverAction>> entry : observerActions.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> eventActions = entry.getValue();
CopyOnWriteArraySet<ObserverAction> registeredEventActions = registry.get(eventType);
if (registeredEventActions == null) {
registry.putIfAbsent(eventType, new CopyOnWriteArraySet<>());
registeredEventActions = registry.get(eventType);
}
registeredEventActions.addAll(eventActions);
}
}
public List<ObserverAction> getMatchedObserverActions(Object event) {
List<ObserverAction> matchedObservers = new ArrayList<>();
Class<?> postedEventType = event.getClass();
for (Map.Entry<Class<?>, CopyOnWriteArraySet<ObserverAction>> entry : registry.entrySet()) {
Class<?> eventType = entry.getKey();
Collection<ObserverAction> eventActions = entry.getValue();
if (postedEventType.isAssignableFrom(eventType)) {
matchedObservers.addAll(eventActions);
}
}
return matchedObservers;
}
private Map<Class<?>, Collection<ObserverAction>> findAllObserverActions(Object observer) {
Map<Class<?>, Collection<ObserverAction>> observerActions = new HashMap<>();
Class<?> clazz = observer.getClass();
for (Method method : getAnnotatedMethods(clazz)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?> eventType = parameterTypes[0];
if (!observerActions.containsKey(eventType)) {
observerActions.put(eventType, new ArrayList<>());
}
observerActions.get(eventType).add(new ObserverAction(observer, method));
}
return observerActions;
}
private List<Method> getAnnotatedMethods(Class<?> clazz) {
List<Method> annotatedMethods = new ArrayList<>();
for (Method method : clazz.getDeclaredMethods()) {
if (method.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
Preconditions.checkArgument(parameterTypes.length == 1,
"Method %s has @Subscribe annotation but has %s parameters."
+ "Subscriber methods must have exactly 1 parameter.",
method, parameterTypes.length);
annotatedMethods.add(method);
}
}
return annotatedMethods;
}
}
4.EventBus
EventBus implementa um padrão de observador síncrono de bloqueio. Olhando o código, você pode ter algumas dúvidas, pois obviamente utiliza o thread pool Executor. Na verdade, MoreExecutors.directExecutor() é uma classe de ferramenta fornecida pelo Google Guava. Parece ser multi-threaded, mas na verdade é single-threaded. O motivo dessa implementação é principalmente unificar a lógica do código com AsyncEventBus e obter a reutilização do código.
public class EventBus {
private Executor executor;
private ObserverRegistry registry = new ObserverRegistry();
public EventBus() {
this(MoreExecutors.directExecutor());
}
protected EventBus(Executor executor) {
this.executor = executor;
}
public void register(Object object) {
registry.register(object);
}
public void post(Object event) {
List<ObserverAction> observerActions = registry.getMatchedObserverActions(event);
for (ObserverAction observerAction : observerActions) {
executor.execute(new Runnable() {
@Override
public void run() {
observerAction.execute(event);
}
});
}
}
}
5.AsyncEventBus
Com EventBus, a implementação de AsyncEventBus é muito simples. Para implementar o modo observador assíncrono sem bloqueio, ele não pode mais continuar a usar MoreExecutors.directExecutor(), mas precisa ser injetado no pool de encadeamentos pelo chamador no construtor.
public class AsyncEventBus extends EventBus {
public AsyncEventBus(Executor executor) {
super(executor);
}
}
Até agora, usamos menos de 200 linhas de código para implementar um EventBus razoavelmente utilizável, funcionalmente falando, é quase o mesmo que o Google Guava EventBus. No entanto, se você observar o código-fonte do Google Guava EventBus, descobrirá que, em termos de detalhes de implementação, em comparação com nossa implementação atual, ele realmente fez muitas otimizações, como otimizar o algoritmo para encontrar funções de correspondência de mensagens no registro. Se você tiver tempo, sugiro que leia o código-fonte.
revisão chave
Bem, isso é tudo para o conteúdo de hoje. Vamos resumir e revisar o conteúdo que você precisa dominar.
As funções da estrutura incluem: ocultar detalhes de implementação, reduzir a dificuldade de desenvolvimento, alcançar a reutilização de código, desacoplar códigos comerciais e não comerciais e permitir que os programadores se concentrem no desenvolvimento de negócios. Para o modo observador sem bloqueio assíncrono, também podemos abstraí-lo em uma estrutura para obter esse efeito, e essa estrutura é o EventBus sobre o qual falamos nesta lição. EventBus é traduzido como "ônibus de evento", que fornece o código esqueleto para implementar o padrão do observador. Com base nessa estrutura, podemos implementar facilmente o padrão Observer em nossos próprios cenários de negócios sem desenvolver do zero.
Muitas pessoas pensam que não há desafio técnico no desenvolvimento de negócios.Na verdade, o desenvolvimento de negócios também envolve o desenvolvimento de muitas funções não comerciais, como o EventBus mencionado hoje. No desenvolvimento de negócios normal, devemos ser bons em abstrair essas funções não comerciais e reutilizáveis e implementá-las ativamente em uma estrutura comum.
discussão em classe
No segundo módulo do conteúdo de hoje "Introdução aos Requisitos Funcionais do Framework EventBus", reimplementamos UserController com Guava EventBus. Na verdade, o código ainda não está desacoplado o suficiente. O UserController ainda acopla muitos códigos não comerciais relacionados ao padrão Observer, como a criação de pools de threads e o registro de Observers. Você tem alguma sugestão de refatoração para tornar o UserController mais focado nos negócios?