2

foreword

In our development process, we often encounter such requirements, such as doing some pre-events before the execution of the main process, and doing some finishing work after the execution of the main process. For some novice programmers, he may directly write code like the following

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

For programmers with certain work experience, he may use AOP or some design patterns such as template pattern. Then let's talk today about using spring + spi + aop + chain of responsibility to achieve the above requirements

Code implementation process analysis

Assuming that the main process only needs to do one preprocessing and one postprocessing, the pseudo code is as follows

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

At this point we can use template mode or AOP, here we use AOP. Its pseudo code 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
    }
}

If you are not used to this way of writing, you can use the @Aspect + @Around method, and the effect is the same.

When the main process requires multiple preprocessing and multiple postprocessing, 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
    }
}

At this point, these pre-processing or post-processing looks like a chain, so we can consider adopting some design patterns such as chain of responsibility or pipeline pattern. In this example we use the Chain of Responsibility pattern

Code

1. Create a processor interface
 public interface AbstarctHandler extends Ordered {

    /**
     * 预处理回调,实现服务的预处理
     * @return true表示流程继续,false表示流程中断,不会继续调用其他处理器或者服务
     */
    default boolean preHandler(Invocation invocation){
        return true;
    }

    /**
     * 整个请求处理完毕回调方法。类似try-catch-finally中的finally。多个afterCompletion按倒序输出
     */
    default void afterCompletion(Invocation invocation){}


}
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(){
        if(CollectionUtils.isEmpty(abstarctHandlers)){
            return Collections.emptyList();
        }
        AnnotationAwareOrderComparator.sort(abstarctHandlers);
        return Collections.unmodifiableList(abstarctHandlers);
    }

}
3. Business logic and chain of responsibility integration
 @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;
        int canExecCount = 0;
        for (AbstarctHandler abstarctHandler : abstarctHandlers) {
             if(!abstarctHandler.preHandler(invocation)){
                 isCanExec = false;
                 break;
             }
            canExecCount++;
        }

           try{
               if(isCanExec){
                   return invocation.invoke();
               }
           }catch (Exception e){
               throw new Exception(e);
           }finally {
               for (int i = 0; i < canExecCount; i++) {
                   int j = canExecCount - i - 1;
                   abstarctHandlers.get(j).afterCompletion(invocation);
               }
           }

        return null;
    }



}

4. Create AOP slices
 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();

        return corHandlerInterceptor.invoke(invoker);
    }
}
5. Configure the pointcut
     @Bean
    @ConditionalOnMissingBean
    public AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor(PointcutProperites pointcutProperites, CorHandlerInterceptor corHandlerInterceptor){
        AspectJExpressionPointcutAdvisor aspectJExpressionPointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        aspectJExpressionPointcutAdvisor.setExpression(pointcutProperites.getExpression());
        aspectJExpressionPointcutAdvisor.setAdvice(new CorMethodInterceptor(corHandlerInterceptor));
        return aspectJExpressionPointcutAdvisor;
    }
6. The processor injects 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(){
        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);
            }
        }
    }

demo

1. Write business services
 public interface HelloService {

    String sayHello(String username);
}
 @Service
public class HelloServiceImpl implements HelloService {
    @Override
    public String sayHello(String username) {
        return "hello : " + username;
    }
}
2. Write the processor

one via @Component

 @Component
public class HelloServiceNameInterceptor implements AbstarctHandler {

    @Override
    public boolean preHandler(Invocation invocation) {
        Object[] args = invocation.getArgs();
        System.out.println("名称校验-->preHandler");
        for (Object arg : args) {
            if("张三".equals(arg)){
                return false;
            }
        }
        return true;
    }

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

    @Override
    public int getOrder() {
        return 102;
    }
}

A via SPI

 public class HelloServiceSpiInterceptor implements AbstarctHandler {

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

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

    @Override
    public int getOrder() {
        return -1;
    }
}

Configure SPI

The content is as follows

 com.github.lybgeek.cor.test.interceptor.HelloServiceSpiInterceptor
3. Configure the pointcut expression
 lybgeek:
  pointcut:
    expression: execution(* com.github.lybgeek.cor.test.service..*.*(..))
4. Test

watch console

Found that the processor is working properly

Summarize

The so-called extensibility means that when new functions are added, there is no need or less modification of the original functions. In terms of design principles, it is closed for modification and open for extension. If you are careful friends of the example in this article, you will find that this is very similar to the interceptor implementation of springmvc

demo link

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-cor


linyb极客之路
336 声望193 粉丝