preface

During development, we often encounter the need to do some pre-events before the main process is executed, and some finishing touches after the main process is executed. For some novice programmers, they might write code like this

  public void execute(a){
        doBefore();
        doBiz();
        doAfter();
    }

Copy the code

For programmers with some experience, they might use AOP or some design pattern such as the template pattern. Today we will talk about using Spring + SPI + AOP + responsibility chain to implement the above requirements

Code implementation process analysis

Assuming that the main process requires only one pre-processing and one post-processing, the pseudocode is as follows

  public void execute(a){
        doBefore();
        doBiz();
        doAfter();
    }

Copy the code

At this point we can use template pattern or AOP, here we use AOP. The pseudocode is as follows

public class CorMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
       
        try {
            doBefore();
            Object result = invocation.proceed();
            return result;
        } catch (Throwable throwable) {
           log.error("{}",e);
        } finally {
            doAfter();
        }

        return null}}Copy the code

If you’re not comfortable with this, you can use @aspect + @around for the same effect.

When the main process requires multiple pre-processing and multiple post-processing, our code may become

public class CorMethodInterceptor implements MethodInterceptor {

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
       
        try{ doBefore(); doBefore(); doBefore(); . Object result = invocation.proceed();return result;
        } catch (Throwable throwable) {
           log.error("{}",e);
        } finally{ doAfter(); doAfter(); doAfter(); . }return null}}Copy the code

At this point the pre – or post-processing looks like a chain, so we can consider some design pattern such as chain of responsibility or pipeline pattern. For this example, we use the chain of responsibility pattern

Code implementation

1. Create the processor interface

public interface AbstarctHandler extends Ordered {

    /** * preprocessing callback to implement service preprocessing *@returnTrue means that the process continues, false means that the process breaks and does not continue to call other processors or services */
    default boolean preHandler(Invocation invocation){
        return true;
    }

    /** * The entire request completes the callback method. Similar to finally in try-catch-finally. Multiple afterCompletion outputs in reverse order */
    default void afterCompletion(Invocation invocation){}}Copy the code

2. Create a processor chain

public class MethodInterceptorChain {

    private final List<AbstarctHandler> abstarctHandlers = new ArrayList<>();


    public void addHandler(AbstarctHandler handler){
        abstarctHandlers.add(handler);
    }

    public List<AbstarctHandler> getHanlders(a){
        if(CollectionUtils.isEmpty(abstarctHandlers)){
            return Collections.emptyList();
        }
        AnnotationAwareOrderComparator.sort(abstarctHandlers);
        returnCollections.unmodifiableList(abstarctHandlers); }}Copy the code

3. Integration of business logic and responsibility chain

@Data
@AllArgsConstructor
@NoArgsConstructor
public class CorHandlerInterceptor {

    private MethodInterceptorChain chain;


    public Object invoke(Invocation invocation) throws Exception {
        List<AbstarctHandler> abstarctHandlers = chain.getHanlders();
        if(CollectionUtils.isEmpty(abstarctHandlers)){
            invocation.invoke();
        }

        boolean isCanExec = true;
        for (AbstarctHandler abstarctHandler : abstarctHandlers) {
             if(! abstarctHandler.preHandler(invocation)){ isCanExec =false;
                 break; }}try{
               if(isCanExec){
                   returninvocation.invoke(); }}catch (Exception e){
               throw new Exception(e);
           }finally {
//               

               for (int i = 0; i < abstarctHandlers.size(); i++) {
                   int j = abstarctHandlers.size() - 1- i; abstarctHandlers.get(j).afterCompletion(invocation); }}return null; }}Copy the code

4. Create AOP facets

public class CorMethodInterceptor implements MethodInterceptor {

    private CorHandlerInterceptor corHandlerInterceptor;

    public CorMethodInterceptor(CorHandlerInterceptor corHandlerInterceptor) {
        this.corHandlerInterceptor = corHandlerInterceptor;
    }

    @Override
    public Object invoke(MethodInvocation invocation) throws Throwable {
        Invocation invoker = Invocation.builder()
                .args(invocation.getArguments())
                .method(invocation.getMethod())
                .target(invocation.getThis()).build();

        returncorHandlerInterceptor.invoke(invoker); }}Copy the code

5. Configure pointcuts

    @Bean
    @ConditionalOnMissingBean
    public AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor(PointcutProperites pointcutProperites, CorHandlerInterceptor corHandlerInterceptor){
        AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        aspectJExpressionPointcutAdvisor.setExpression(pointcutProperites.getExpression());
        aspectJExpressionPointcutAdvisor.setAdvice(new CorMethodInterceptor(corHandlerInterceptor));
        return aspectJExpressionPointcutAdvisor;
    }
Copy the code

6. Processor injection into Spring

  @Bean
    @ConditionalOnMissingBean
    public CorHandlerInterceptor corHandlerInterceptor(ObjectProvider<List<AbstarctHandler>> provider){
        MethodInterceptorChain methodInterceptorChain = new MethodInterceptorChain();
        loadedHandlerBySpring(provider, methodInterceptorChain);
        loadedHanlderBySpi(methodInterceptorChain);
        CorHandlerInterceptor corHandlerInterceptor = new CorHandlerInterceptor();
        corHandlerInterceptor.setChain(methodInterceptorChain);
        return corHandlerInterceptor;
    }

    @Bean
    @ConditionalOnMissingBean
    public DefaultHandler defaultHandler(a){
        return new DefaultHandler();
    }

    private void loadedHanlderBySpi(MethodInterceptorChain methodInterceptorChain) {
        ServiceLoader<AbstarctHandler> serviceLoader = ServiceLoader.load(AbstarctHandler.class);
        Iterator<AbstarctHandler> iterator = serviceLoader.iterator();
        while(iterator.hasNext()){
            AbstarctHandler abstarctHandler = iterator.next();
            log.info("load hander by spi -> 【{}】",abstarctHandler.getClass().getName()); methodInterceptorChain.addHandler(abstarctHandler); }}private void loadedHandlerBySpring(ObjectProvider<List<AbstarctHandler>> provider, MethodInterceptorChain methodInterceptorChain) {
        List<AbstarctHandler> getListBySpring = provider.getIfAvailable();
        if(! CollectionUtils.isEmpty(getListBySpring)){for (AbstarctHandler abstarctHandler : getListBySpring) {
                log.info("load hander by spring -> 【{}】",abstarctHandler.getClass().getName()); methodInterceptorChain.addHandler(abstarctHandler); }}}Copy the code

Example demonstrates

1. Write business services

public interface HelloService {

    String sayHello(String username);
}

Copy the code
@Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String username) {
        return "hello : "+ username; }}Copy the code

2. Write the processor

One way is through @Component

@Component
public class HelloServiceNameInterceptor implements AbstarctHandler {

    @Override
    public boolean preHandler(Invocation invocation) {
        Object[] args = invocation.getArgs();
        System.out.println("Name verification -->preHandler");
        for (Object arg : args) {
            if("Zhang".equals(arg)){
                return false; }}return true;
    }

    @Override
    public void afterCompletion(Invocation invocation) {
        System.out.println("Name check -->afterCompletion:" + Arrays.toString(invocation.getArgs()));
    }

    @Override
    public int getOrder(a) {
        return 102; }}Copy the code

One goes through SPI

public class HelloServiceSpiInterceptor implements AbstarctHandler {

    @Override
    public boolean preHandler(Invocation invocation) {
        Object[] args = invocation.getArgs();
        System.out.println("Parameter Conversion -->preHandler");
        for (int i = 0; i < args.length; i++) {
            if("lisi".equals(args[i])){
                args[i] = "Bill"; }}return true;
    }

    @Override
    public void afterCompletion(Invocation invocation) {
        System.out.println(">afterCompletion:" + Arrays.toString(invocation.getArgs()));
    }

    @Override
    public int getOrder(a) {
        return -1; }}Copy the code

Configuration of SPIThe following

com.github.lybgeek.cor.test.interceptor.HelloServiceSpiInterceptor
Copy the code

3. Configure the pointcut expression

lybgeek:
  pointcut:
    expression: execution(* com.github.lybgeek.cor.test.service.. *. * (..) )
Copy the code

4, test,

Observation consoleThe processor is found to be working properly

conclusion

The so-called extensibility refers to the addition of new functions with little or no modification of the original functions. As a design principle, this is closed to modification and open to extension. The example in this article is very similar to the interceptor implementation of SpringMVC, as careful friends will notice

The demo link

Github.com/lyb-geek/sp…