×
Community Blog Several Routines and Implementations of Maintaining Extensibility in Java

Several Routines and Implementations of Maintaining Extensibility in Java

In this article, the author summarizes some low-cost routines for maintaining extensibility.

1

Overview

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-based Routine

Key Points

2

Pipeline - used for cascading valves.

PipelineValue - used by each node to process actual business demands.

PipelineContext - used for twisting data in pipeline context.

Common Scenarios

• When your data stream needs to be processed by a lot of equivalent logic, you can consider using this routine to facilitate subsequent extension.

Implementation Code

• 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);

    }
}

Applications in Common Frameworks

• The netty frameworks of the network layer, such as ChannelPipeline, ChannelHandler, and ChannelHandlerContext, are used for TCP unpacking, and encoding and decoding, etc.

3

FilterChain-based Routine

Key Points

4


5
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.

Common Scenarios

• For example, common web request scenarios.

Implementation Code

• 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());
    }
}

Applications in Common Frameworks

• The filter mechanism of hsf includes ServerFilter extended by the server and ClientFilter extended by the client.

6

• Some of you who have developed Java web applications must know about servlets. The entries of servlets are FilterChain and Filter.

7

Routine Based on Composite and Template Patterns

Key Points

8

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.

Common Scenarios

• It is suitable for scenarios with commonality and subsequent continuous extension.

Implementation Code

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");

    }
}

Applications in Common Frameworks

• 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-based Routine

Key Points

9

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.

Common Scenarios

• Simplified internal use

Implementation Code

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());
    }
}

Applications in Common Frameworks

• For example, spring-boot-alibaba-diamond-autoconfigure within a group.

10

Routine Based on Event Distribution

Key points

11

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.

Implementation Code

• 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());

    }
}

Routine Based on SPI Mechanism

Key Points

12

13

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.

14

Standard Service Interface

Applicable Scenarios

15

Implementation Code

• 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();

    }
}

Applications in Common Frameworks

• 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.

16

Conclusion

Since most programmers are doers, you are welcome to share your routines.

0 1 0
Share on

linear.zw

1 posts | 0 followers

You may also like

Comments

linear.zw

1 posts | 0 followers

Related Products