You are probably familiar with the five SOLID principles (Single Responsibility Principle, Open Closed Principle, Liskov Substitution Principle, Interface Segregation Principle, and Dependency Inversion Principle) and the 23 design patterns (such as singleton pattern, builder pattern, decorator pattern, adapter pattern, proxy pattern, composite pattern, template pattern, etc.). These principles and design patterns can assist us in making design choices to achieve high cohesion and low coupling.
When it comes to design, it is inevitable to mention the term architecture. Some common architecture terms include layered architecture, hexagonal architecture, SOA architecture, CQRS architecture, and EDA architecture. I believe that architecture is about defining boundaries and combining the internal elements within those boundaries. In fact, every programmer is an architect, but there are good architects and ordinary architects. I don't think there is any problem with saying that everyone is an architect. The difference lies in their understanding which determines the boundaries they can define and how efficiently they can combine internal elements. Technical architects focus on technology, business architects focus on the business domain, and commodity architects may be limited to the commodity domain. This is my personal understanding of architecture.
Today, I will not discuss a specific architecture. Instead, I will talk about some routines. In daily development, I have summarized some low-cost routines that I usually use to maintain extensibility, and I will share them in this article. You are welcome to share your opinions.
• Pipeline - used for cascading valves.
• PipelineValue - used by each node to process actual business demands.
• PipelineContext - used for twisting data in pipeline context.
• When your data stream needs to be processed by a lot of equivalent logic, you can consider using this routine to facilitate subsequent extension.
• Pipeline/StandardPipeline
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:46
*/
public interface Pipeline {
/**
* Execute
*
* @return
*/
boolean invoke(PipelineContext pipelineContext);
/**
* Add a value
*
* @param pipelineValue
* @return
*/
boolean addValue(PipelineValue pipelineValue);
/**
* Remove value
*
* @param pipelineValue
* @return
*/
boolean removeValue(PipelineValue pipelineValue);
}
package com.example.ownertest.dm.pipelline;
import java.util.List;
import com.google.common.collect.Lists;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:46
*/
@Data
@Slf4j
public class StandardPipeline implements Pipeline {
private List<PipelineValue> pipelineValueList = Lists.newArrayList();
@Override
public boolean invoke(PipelineContext pipelineContext) {
boolean isResult = true;
for (PipelineValue pipelineValue :
pipelineValueList) {
try {
isResult = pipelineValue.execute(pipelineContext);
if (!isResult) {
log.error("{},exec is wrong", pipelineValue.getClass().getSimpleName());
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
return isResult;
}
@Override
public boolean addValue(PipelineValue pipelineValue) {
if (pipelineValueList.contains(pipelineValue)) {
return true;
}
return pipelineValueList.add(pipelineValue);
}
@Override
public boolean removeValue(PipelineValue pipelineValue) {
return pipelineValueList.remove(pipelineValue);
}
}
• PipelineContext/StandardPipelineContext
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:47
*/
public interface PipelineContext {
String FOR_TEST = "forTest";
/**
* Settings
*
* @param contextKey
* @param contextValue
*/
void set(String contextKey, Object contextValue);
/**
* Obtain value
*
* @param contextKey
* @return
*/
Object get(String contextKey);
}
package com.example.ownertest.dm.pipelline;
import java.util.Map;
import com.google.common.collect.Maps;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:47
*/
public class StandardPipelineContext implements PipelineContext {
private Map<String, Object> contentMap = Maps.newConcurrentMap();
@Override
public void set(String contextKey, Object contextValue) {
contentMap.put(contextKey, contextValue);
}
@Override
public Object get(String contextKey) {
return contentMap.get(contextKey);
}
}
• PipelineValue/AbstractPipelineValue/GraySwitchValue/ForTestValue
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:47
*/
public interface PipelineValue {
/**
* Node execution
*
* @param pipelineContext
* @return
*/
boolean execute(PipelineContext pipelineContext);
}
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:48
*/
public abstract class AbstractPipelineValue implements PipelineValue {
@Override
public boolean execute(PipelineContext pipelineContext) {
System.out.println(this.getClass().getSimpleName() + " start ");
boolean result = doExec(pipelineContext);
System.out.println(this.getClass().getSimpleName() + " end ");
return result;
}
protected abstract boolean doExec(PipelineContext pipelineContext);
}
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:48
*/
public class GraySwitchValue extends AbstractPipelineValue {
@Override
public boolean doExec(PipelineContext pipelineContext) {
pipelineContext.set(PipelineContext.FOR_TEST, true);
return true;
}
}
package com.example.ownertest.dm.pipelline;
/**
* @Author: linear.zw
* @Date: 2023/10/25 19:48
*/
public class ForTestValue extends AbstractPipelineValue {
@Override
public boolean doExec(PipelineContext pipelineContext) {
return true;
}
}
• PipelineClient
package com.example.ownertest.dm.pipelline;
/**
* Entry class
*
* @Author: linear.zw
* @Date: 2023/10/25 19:48
*/
public class PipelineClient {
public static void main(String[] args) {
// Initialize the pipeline
Pipeline pipeline = new StandardPipeline();
// Value extension
PipelineValue pipelineValue = new GraySwitchValue();
PipelineValue pipelineValue2 = new ForTestValue();
// Context
PipelineContext pipelineContext = new StandardPipelineContext();
pipeline.addValue(pipelineValue);
pipeline.addValue(pipelineValue2);
// Call the pipeline
pipeline.invoke(pipelineContext);
}
}
• The netty frameworks of the network layer, such as ChannelPipeline, ChannelHandler, and ChannelHandlerContext, are used for TCP unpacking, and encoding and decoding, etc.
Source - https://www.oracle.com/java/technologies/intercepting-filter.html
• Filter - the node that actually processes the business.
• FilterChain - a chain used for cascading filters.
• For example, common web request scenarios.
• Filter/ForTest1Filter/ForTest2Filter
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:22
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:22
*/
public interface Filter {
void doFilter(HttpRequest httpRequest,FilterChain filterChain);
}
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:22
*/
public class ForTest1Filter implements Filter {
@Override
public void doFilter(HttpRequest httpRequest, FilterChain filterChain) {
// do
System.out.println(this.getClass().getSimpleName() + " before " + System.currentTimeMillis());
filterChain.doFilter(httpRequest);
// after
System.out.println(this.getClass().getSimpleName() + " end " + System.currentTimeMillis());
}
}
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:22
*/
public class ForTest2Filter implements Filter {
@Override
public void doFilter(HttpRequest httpRequest, FilterChain filterChain) {
// do
System.out.println(this.getClass().getSimpleName() + " before " + System.currentTimeMillis());
filterChain.doFilter(httpRequest);
// after
System.out.println(this.getClass().getSimpleName() + " end " + System.currentTimeMillis());
}
}
• FilterChain/StandardFilterChain
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:23
*/
public interface FilterChain {
void doFilter(HttpRequest httpRequest);
void addFilter(Filter filter);
}
package com.example.ownertest.dm.filter;
import java.util.List;
import com.google.common.collect.Lists;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:24
*/
public class StandardFilterChain implements FilterChain {
private List<Filter> filterList = Lists.newArrayList();
private int currentIndex = 0;
@Override
public void doFilter(HttpRequest httpRequest) {
if (currentIndex == filterList.size()) { return; }
Filter filter = filterList.get(currentIndex);
currentIndex = currentIndex + 1;
filter.doFilter(httpRequest, this);
}
@Override
public void addFilter(Filter filter) {
if (filterList.contains(filter)) {
return;
}
filterList.add(filter);
}
}
• HttpRequest/StandardHttpRequest
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:24
*/
public interface HttpRequest {
}
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:24
*/
public class StandardHttpRequest implements HttpRequest {
}
• FilterClient - Entry test
package com.example.ownertest.dm.filter;
/**
* @Author: linear.zw
* @Date: 2023/10/26 19:25
*/
public class FilterClient {
public static void main(String[] args) {
FilterChain filterChain = new StandardFilterChain();
filterChain.addFilter(new ForTest1Filter());
filterChain.addFilter(new ForTest2Filter());
filterChain.doFilter(new StandardHttpRequest());
}
}
• The filter mechanism of hsf includes ServerFilter extended by the server and ClientFilter extended by the client.
• Some of you who have developed Java web applications must know about servlets. The entries of servlets are FilterChain and Filter.
• Handler Register - used to store a collection of handlers.
• Handler Factory - used to create handlers.
• Handler - used to implement the actual handlers and extension.
• Handler Context - used for parameter passing.
• It is suitable for scenarios with commonality and subsequent continuous extension.
• PiiHandlerRegistry - handler register
package com.example.ownertest.dm.comp;
import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:45
*/
@Slf4j
public class PiiHandlerRegistry {
private static Map<String, PiiDomainFieldHandler> piiDomainFieldHandlerMap = Maps.newHashMap();
public static void putHandler(String piiDomainFieldName, PiiDomainFieldHandler piiDomainFieldHandler) {
if (StringUtils.isEmpty(piiDomainFieldName)) {
log.warn(" piiDomainFieldName is null,continue");
return;
}
if (piiDomainFieldHandler == null) {
log.warn(piiDomainFieldName + " piiDomainFieldHandler is null,continue");
return;
}
if (!piiDomainFieldHandlerMap.containsKey(piiDomainFieldName)) {
piiDomainFieldHandlerMap.put(piiDomainFieldName, piiDomainFieldHandler);
}
}
public static <T extends Object> int handlerRead(T domain, Field domainField, PiiContent piiContent) {
int num = 0;
for (Map.Entry<String, PiiDomainFieldHandler> piiDomainFieldHandlerEntry :
piiDomainFieldHandlerMap.entrySet()) {
if (piiDomainFieldHandlerEntry.getValue().isSupport(domain, domainField)) {
piiDomainFieldHandlerEntry.getValue().handlerRead(domain, domainField, piiContent);
}
}
return num;
}
public static <T extends Object> int handlerWrite(T domain, Field domainField, PiiContent piiContent) {
int num = 0;
for (Map.Entry<String, PiiDomainFieldHandler> piiDomainFieldHandlerEntry :
piiDomainFieldHandlerMap.entrySet()) {
if (piiDomainFieldHandlerEntry.getValue().isSupport(domain, domainField)) {
piiDomainFieldHandlerEntry.getValue().handlerWrite(domain, domainField, piiContent);
}
}
return num;
}
public static Map<String, PiiDomainFieldHandler> getPiiDomainFieldHandlerMap() {
return piiDomainFieldHandlerMap;
}
public static void init() {
List<PiiDomainFieldHandler> piiDomainFieldHandlerList = PiiDomainFieldHandlerFactory
.createPiiDomainFieldHandler();
if (CollectionUtils.isNotEmpty(piiDomainFieldHandlerList)) {
for (PiiDomainFieldHandler piiDomainFieldHandler :
piiDomainFieldHandlerList) {
putHandler(piiDomainFieldHandler.getPiiDomainMeta(), piiDomainFieldHandler);
}
}
}
}
• PiiDomainFieldHandlerFactory - handler factory
package com.example.ownertest.dm.comp;
import java.util.List;
import com.google.common.collect.Lists;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:46
*/
public class PiiDomainFieldHandlerFactory {
/**
* Creating a domain handler
*
* @return
*/
public static List<PiiDomainFieldHandler> createPiiDomainFieldHandler() {
List<PiiDomainFieldHandler> piiDomainFieldHandlerList = Lists.newArrayList();
//
piiDomainFieldHandlerList.add(new ForTestSupportFieldHandler());
piiDomainFieldHandlerList.add(new ForTestNotSupportFieldHandler());
return piiDomainFieldHandlerList;
}
}
• PiiDomainFieldHandler/PiiDomainFieldHandlerBase/ForTestNotSupportFieldHandler/ForTestSupportFieldHandler - handler
package com.example.ownertest.dm.comp;
import java.lang.reflect.Field;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:46
*/
public interface PiiDomainFieldHandler {
/**
* Handle actual operation
* Read—obtain data from PiiContent and backfill the domain.
* @param domain
* @param domainField
* @param piiContent
* @param <T>
* @return
*/
<T extends Object> boolean handlerRead(T domain, Field domainField, PiiContent piiContent);
/**
* Handle actual operation
* Write—write the field data in the domain that needs to be written to pii to PiiContent
*
* @param domain
* @param domainField
* @param piiContent
* @param <T>
* @return
*/
<T extends Object> boolean handlerWrite(T domain, Field domainField, PiiContent piiContent);
/**
* Whether the current handler supports this domain object
*
*
* @param domain
* @param domainField
* @param <T>
* @return
*/
<T extends Object> boolean isSupport(T domain, Field domainField);
/**
* Obtain the metadata of the handler.
*
* @return
*/
String getPiiDomainMeta();
}
package com.example.ownertest.dm.comp;
import java.lang.reflect.Field;
import lombok.extern.slf4j.Slf4j;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:47
*/
@Slf4j
public abstract class PiiDomainFieldHandlerBase implements PiiDomainFieldHandler {
@Override
public <T extends Object> boolean handlerRead(T domain, Field domainField, PiiContent piiContent) {
// to do business read
return true;
}
@Override
public <T extends Object> boolean handlerWrite(T domain, Field domainField, PiiContent piiContent) {
// to do business write
return true;
}
}
package com.example.ownertest.dm.comp;
import java.lang.reflect.Field;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:47
*/
public class ForTestSupportFieldHandler extends PiiDomainFieldHandlerBase {
@Override
public <T> boolean isSupport(T domain, Field domainField) {
if (this.getClass().getSimpleName().equalsIgnoreCase(domain.getClass().getSimpleName())) {
// to do business
System.out.println(this.getClass().getSimpleName() + " is support, to do some business");
return true;
}
return false;
}
@Override
public String getPiiDomainMeta() {
return this.getClass().getSimpleName();
}
}
package com.example.ownertest.dm.comp;
import java.lang.reflect.Field;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:48
*/
public class ForTestNotSupportFieldHandler extends PiiDomainFieldHandlerBase {
@Override
public <T> boolean isSupport(T domain, Field domainField) {
if (this.getClass().getSimpleName().equalsIgnoreCase(domain.getClass().getSimpleName())) {
// to do business
System.out.println(this.getClass().getSimpleName() + " is support, to do some business");
return true;
}
return false;
}
@Override
public String getPiiDomainMeta() {
return this.getClass().getSimpleName();
}
}
• PiiContent - context
package com.example.ownertest.dm.comp;
import java.util.Map;
import com.google.common.collect.Maps;
import lombok.Data;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:48
*/
@Data
public class PiiContent {
public static String FORTEST="fortest";
private Map<String, Object> piiDataMap = Maps.newHashMap();
private Map<String, Object> piiContextMap = Maps.newHashMap();
public void putPiiData(String domainFieldName, Object domainFieldValue) {
piiDataMap.put(domainFieldName, domainFieldValue);
}
public Object getPiiData(String domainFieldName) {
return piiDataMap.get(domainFieldName);
}
public void putPiiContext(String contextName, Object contextNameValue) {
piiContextMap.put(contextName, contextNameValue);
}
public Object getPiiContext(String contextName) {
return piiContextMap.get(contextName);
}
}
• PiiClient - test class for entry
package com.example.ownertest.dm.comp;
import java.util.Map;
/**
* @Author: linear.zw
* @Date: 2023/10/31 20:48
*/
public class PiiClient {
public static void main(String[] args) {
PiiHandlerRegistry.init();
// Traversal handler
for (Map.Entry<String, PiiDomainFieldHandler> entryHandler :
PiiHandlerRegistry.getPiiDomainFieldHandlerMap().entrySet()) {
System.out.println(entryHandler.getKey() + "\t" + entryHandler.getValue().getPiiDomainMeta());
}
//
PiiContent piiContent = new PiiContent();
piiContent.putPiiContext(PiiContent.FORTEST, PiiContent.FORTEST);
// Process the request
System.out.println("ForTestSupportFieldHandler start");
PiiHandlerRegistry.handlerRead(new ForTestSupportFieldHandler(), null, piiContent);
System.out.println("ForTestSupportFieldHandler end");
// Process the request
System.out.println("ForTestNotSupportFieldHandler start");
PiiHandlerRegistry.handlerRead(new ForTestNotSupportFieldHandler(), null, piiContent);
System.out.println("ForTestNotSupportFieldHandler end");
}
}
• There are numerous applications. For example, spring's core BeanPostProcessor mechanism manages the beanPostProcessors of some columns through org.springframework.beans.factory.support.AbstractBeanFactory#beanPostProcessors. When the spring context is org.springframework.context.support.AbstractApplicationContext#refresh, perform init (InitDestroyAnnotationBeanPostProcessor) on bean, parse annotations (ScheduledAnnotationBeanPostProcessor, AutowiredAnnotationBeanPostProcessor), parse aop (AnnotationAwareAspectJAutoProxyCreator), etc.
• Annotation Meta Definition - used to define common meta information.
• Annotation Parser - parse whether there is a specified annotation on the class, and then carry out the corresponding extension operation.
• Spring BeanPostProcessor - use the Spring BeanPostProcessor mechanism to call back when the spring container is initialized to complete the expected extension behavior.
• Simplified internal use
• ForTestAnnotation - annotation meta definition
package com.example.ownertest.dm.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.stereotype.Component;
/**
* Identification annotations for testing
*
* @Author: linear.zw
* @Date: 2023/11/1 10:21
*/
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface ForTestAnnotation {
}
• ForTestAnnotationProcessor - annotation parser
package com.example.ownertest.dm.annotation;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.stereotype.Component;
/**
* Annotation parser
* @Author: linear.zw
* @Date: 2023/11/1 10:25
*/
@Component
public class ForTestAnnotationProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
// Find whether the target class has ForTestAnnotation annotations
ForTestAnnotation annotation = AnnotationUtils.findAnnotation(AopUtils.getTargetClass(bean),
ForTestAnnotation.class);
if (annotation == null) {
return bean;
}
// Process the desired extension
System.out.println(beanName + " has ForTestAnnotation");
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
• ForTestBean - test bean
package com.example.ownertest.dm.annotation;
/**
* @Author: linear.zw
* @Date: 2023/11/1 10:26
*/
@ForTestAnnotation
public class ForTestBean {
public ForTestBean() {
System.out.println(ForTestBean.class.getSimpleName() + " init");
}
}
• ForTestClient - test entry
package com.example.ownertest.dm.annotation;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
/**
* @Author: linear.zw
* @Date: 2023/11/1 10:26
*/
public class ForTestClient {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(
"com.example.ownertest.dm.annotation");
System.out.println(ForTestClient.class.getSimpleName());
}
}
• For example, spring-boot-alibaba-diamond-autoconfigure within a group.
• Event Source - event trigger.
• Event - the source of the identifications.
• Event Listener - the follower of the event, that is, the handler.
• Event Distributor - used to forward events from the event source to the event listener.
• EventSource/EventSourceForTest/EventSourceForTest2
package com.example.ownertest.dm.event;
/**
* Emit events
* @Author: linear.zw
* @Date: 2023/11/1 14:12
*/
public interface EventSource {
/**
* Emit events
*
* @return
*/
Event fireEvent();
}
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:14
*/
public class EventSourceForTest implements EventSource {
@Override
public Event fireEvent() {
Event event = new EventForTest();
System.out.println(getClass().getSimpleName() + " \t fireEvent " + event.getName());
return event;
}
}
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:15
*/
public class EventSourceForTest2 implements EventSource {
@Override
public Event fireEvent() {
Event event = new EventForTest2();
System.out.println(getClass().getSimpleName() + " \t fireEvent " + event.getName());
return event;
}
}
• Event/EventForTest/EventForTest2
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:15
*/
public interface Event {
/**
* Event name
*
* @return
*/
String getName();
}
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:17
*/
public class EventForTest implements Event {
@Override
public String getName() {
return getClass().getSimpleName();
}
}
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:17
*/
public class EventForTest2 implements Event {
@Override
public String getName() {
return getClass().getSimpleName();
}
}
• EventListener/EventListenerForTest
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:17
*/
public interface EventListener {
/**
* Whether this event is supported
*
* @param event
* @return
*/
boolean supportEvent(Event event);
/**
* Handle events
*
* @return
*/
boolean handlerEvent(Event event);
}
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:18
*/
public class EventListenerForTest implements EventListener {
@Override
public boolean supportEvent(Event event) {
return event.getName().contains("Test");
}
@Override
public boolean handlerEvent(Event event) {
System.out.println(this.getClass().getSimpleName() + "\t handler " + event.getName());
return true;
}
}
• EventDispatcher/EventListenerManager
package com.example.ownertest.dm.event;
import org.apache.commons.collections4.CollectionUtils;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:18
*/
public class EventDispatcher {
/**
* Singleton pattern
*/
private static EventDispatcher eventDispatcher = new EventDispatcher();
private EventDispatcher() {
}
/**
* Distribute events
*
* @param event
* @return
*/
public static boolean dispatchEvent(Event event) {
if (CollectionUtils.isNotEmpty(EventListenerManager.getEventListenerList())) {
for (EventListener eventListener :
EventListenerManager.getEventListenerList()) {
if (eventListener.supportEvent(event)) {
eventListener.handlerEvent(event);
}
}
}
return true;
}
}
package com.example.ownertest.dm.event;
import java.util.List;
import com.google.common.collect.Lists;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:18
*/
public class EventListenerManager {
private static List<EventListener> eventListenerList = Lists.newArrayList();
/**
* Add an event listener
*
* @param eventListener
* @return
*/
public static boolean addEventListener(EventListener eventListener) {
if (!eventListenerList.contains(eventListener)) {
return eventListenerList.add(eventListener);
}
return true;
}
/**
* Remove an event listener
*
* @param eventListener
* @return
*/
public static boolean removeEventListener(EventListener eventListener) {
if (eventListenerList.contains(eventListener)) {
return eventListenerList.remove(eventListener);
}
return true;
}
public static List<EventListener> getEventListenerList() {
return eventListenerList;
}
}
• EventClient
package com.example.ownertest.dm.event;
/**
* @Author: linear.zw
* @Date: 2023/11/1 14:19
*/
public class EventClient {
public static void main(String[] args) {
// Create an event source
EventSource eventSourceForTest = new EventSourceForTest();
EventSource eventSourceForTest2 = new EventSourceForTest2();
// Create an event listener
EventListener eventListener = new EventListenerForTest();
EventListenerManager.addEventListener(eventListener);
// Publish events
EventDispatcher.dispatchEvent(eventSourceForTest.fireEvent());
EventDispatcher.dispatchEvent(eventSourceForTest2.fireEvent());
}
}
• Service Caller
• Service Implementer - takes the interface name as the file name, places it in the META-INF/services, and the value is the implementation of the interface.
• Standard Service Interface
• SpiServiceLoaderHelper
package com.example.ownertest.dm.spi;
import java.util.Iterator;
import java.util.Objects;
import java.util.ServiceLoader;
/**
* @Author: linear.zw
* @Date: 2023/11/1 15:32
*/
public class SpiServiceLoaderHelper {
public static ProductPackageRemoteServiceInterface getProductPackageRemoteServiceInterface() {
// Load from the cache first
Object serviceCache = DependServiceRegistryHelper.getDependObject(ProductPackageRemoteServiceInterface.class);
if (serviceCache != null) {
return (ProductPackageRemoteServiceInterface) serviceCache;
}
// Load in SPI mode
ProductPackageRemoteServiceInterface serviceInterface = loadSpiImpl(ProductPackageRemoteServiceInterface.class);
// Prevent the injected bean from being null and judge in advance to avoid service execution problems
boolean isExist = true;
if (Objects.isNull(serviceInterface)) {
isExist = false;
} else if (Objects.isNull(serviceInterface.getProductPackageRemoteService())) {
isExist = false;
}
if (!isExist) {
throw new RuntimeException("getProductPackageRemoteService load impl failed,please check spi service");
}
// Add unified dependency management
DependServiceRegistryHelper.registry(ProductPackageRemoteServiceInterface.class, serviceInterface);
return serviceInterface;
}
/**
* Load the implementation class in SPI mode
*
* @param cls
* @param <P>
* @return
*/
private static <P> P loadSpiImpl(Class<P> cls) {
ServiceLoader<P> spiLoader = ServiceLoader.load(cls);
Iterator<P> iaIterator = spiLoader.iterator();
if (iaIterator.hasNext()) {
return iaIterator.next();
}
return null;
}
}
• DependServiceRegistryHelper
package com.example.ownertest.dm.spi;
import java.util.Map;
import com.google.common.collect.Maps;
/**
* @Author: linear.zw
* @Date: 2023/11/1 15:35
*/
public class DependServiceRegistryHelper {
/**
* Storage policy-dependent services and unified management
*/
*/
private static Map<String, Object> dependManagerMap = Maps.newHashMap();
public static boolean registryMap(Map<Class, Object> dependManagerMap) {
for (Map.Entry<Class, Object> dependEntry :
dependManagerMap.entrySet()) {
registry(dependEntry.getKey(), dependEntry.getValue());
}
return true;
}
public static boolean registry(Class cls, Object dependObject) {
dependManagerMap.put(cls.getCanonicalName(), dependObject);
return true;
}
public static Object getDependObject(Class cls) {
return dependManagerMap.get(cls.getCanonicalName());
}
}
• SpiServiceLoaderClientTest
package com.example.ownertest.dm.spi;
/**
* @Author: linear.zw
* @Date: 2023/11/1 15:37
*/
public class SpiServiceLoaderClientTest {
public static void main(String[] args) {
ProductPackageRemoteServiceInterface productPackageRemoteServiceInterface
= SpiServiceLoaderHelper.getProductPackageRemoteServiceInterface();
}
}
• At present, most middle platform policy packages are based on SPI mode, which dynamically load the implementation of business and then achieve the purpose of extension.
• For example, the open source auto-service of Google automatically generates the implementation directory of SPI through annotations.
Since most programmers are doers, you are welcome to share your routines.
Alibaba Cloud Native Community - May 23, 2023
Alibaba Cloud Community - April 20, 2022
Alibaba Cloud Community - April 17, 2024
Alibaba Cloud Native Community - February 9, 2023
Alibaba Cloud Community - March 9, 2022
Alibaba Cloud_Academy - October 7, 2023
Customized infrastructure to ensure high availability, scalability and high-performance
Learn MoreAccelerate software development and delivery by integrating DevOps with the cloud
Learn MoreA one-stop, cloud-native platform that allows financial enterprises to develop and maintain highly available applications that use a distributed architecture.
Learn MoreAn intelligent tool that can be used to perform quick inspections on your cloud resources and application architecture to detect underlying risks and provide solutions.
Learn More