当前位置: 首页 > 后端技术 > Java

说说如何实现一个带有拦截器功能的SPI

时间:2023-04-01 17:29:57 Java

前言上一篇我们讲了如何实现一个支持键值对的SPI。本期我们就来说说如何实现带有拦截器功能的SPI。什么是拦截器,是指在方法或字段被访问之前拦截它,然后在其之前或之后添加一些操作?什么是拦截器链,是指拦截器按照一定的顺序连接起来形成一个链。当访问被拦截的方法或字段时,会按照之前定义的顺序调用拦截器链中的拦截器,实现拦截器逻辑。本文实现思路核心:使用责任链+动态代理1.定义拦截器接口publicinterfaceInterceptor{intHIGHEST_PRECEDENCE=Integer.MIN_VALUE;intLOWEST_PRECEDENCE=Integer.MAX_VALUE;对象拦截(Invocationinvocation)throwsThrowable;默认对象插件(对象目标){returnPlugin.wrap(target,this);}intgetOrder();}2.自定义需要拦截器拦截的类接口注解@Documented@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.TYPE)public@interfaceIntercepts{Signature[]value();}@Documented@Retention(RetentionPolicy.RUNTIME)@Target({})public@interfaceSignature{Classtype();字符串方法();Class[]args();}3.通过jdk动态代理实现拦截器调用逻辑publicclassPluginimplementsInvocationHandler{privatefinalObjecttarget;privatefinalInterceptor拦截器;privatefinalMap,Set>signatureMap;privatePlugin(Objecttarget,Interceptor拦截器,Map,Set>>signatureMap){this.target=target;this.interceptor=拦截器;this.signatureMap=signatureMap;}publicstaticObjectwrap(Objecttarget,Interceptorinterceptor){Map,Set>signatureMap=getSignatureMap(interceptor);类类型=target.getClass();类[]interfaces=getAllInterfaces(type,signatureMap);if(interfaces.length>0){returnProxy.newProxyInstance(type.getClassLoader(),interfaces,newPlugin(target,interceptor,signatureMap));}返回目标;}@OverridepublicObjectinvoke(Objectproxy,Methodmethod,Object[]args)throwsThrowable{try{Setmethods=signatureMap.get(method.getDeclaringClass());if(methods!=null&&methods.contains(method)){调用invocation=newInvocation(target,method,args);返回拦截器。拦截(调用化);}returnmethod.invoke(target,args);}catch(Exceptione){throwExceptionUtils.unwrapThrowable(e);}}privatestaticMap,Set>getSignatureMap(Interceptorinterceptor){InterceptsinterceptsAnnotation=interceptor.getClass().getAnnotation(Intercepts.class);if(interceptsAnnotation==null){thrownewPluginException("在拦截器中没有找到@Intercepts注解"+interceptor.getClass().getName());签名[]sigs=interceptsAnnotation.value();Map,Set>signatureMap=newHashMap<>();for(Signaturesig:sigs){Setmethods=signatureMap.computeIfAbsent(sig.type(),k->newHashSet<>());尝试{Methodmethod=sig.type().getMethod(sig.method(),sig.args());方法。添加(方法);}catch(NoSuchMethodExceptione){thrownewPluginException(“无法在“+sig.type()+”上找到名为“+sig.method()+”的方法。原因:“+e,e);}}返回signatureMap;}privatestaticClass[]getAllInterfaces(Classtype,Map,Set>signatureMap){Set>interfaces=newHashSet<>();while(type!=null){for(Classc:type.getInterfaces()){if(signatureMap.containsKey(c)){interfaces.add(c);}}type=type.getSuperclass();}returninterfaces.toArray(newClass[interfaces.size()]);}}4、构造出拦截器链publicclassInterceptorChain{privatefinalListinterceptors=newArrayList<>();publicObjectpluginAll(Objecttarget){if(CollectionUtil.isNotEmpty(interceptors)){for(Interceptorinterceptor:getInterceptors()){target=interceptor.plugin(target);}}返回目标;}公共接口rceptorChainaddInterceptor(Interceptor拦截器){interceptors.add(拦截器);归还这个;}publicListgetInterceptors(){ListinterceptorsByOrder=interceptors.stream().sorted(Comparator.comparing(Interceptor::getOrder).reversed()).collect(Collectors.toList());返回Collections.unmodifiableList(interceptorsByOrder);}}5、通过拦截器链接与之前实现的SPI绑定@Activate@Slf4jpublicclassInterceptorExtensionFactoryimplementsExtensionFactory{privateInterceptorChainchain;@OverridepublicTgetExtension(finalStringkey,finalClassclazz){if(Objects.isNull(chain)){log.warn("NoInterceptorChainIsConfig");返回空值;}if(clazz.isInterface()&&clazz.isAnnotationPresent(SPI.class)){ExtensionLoaderextensionLoader=ExtensionLoader.getExtensionLoader(clazz);如果(!extensionLoader.getSupportedExtensions().isEmpty()){if(StringUtils.isBlank(key)){return(T)chain.pluginAll(extensionLoader.getDefaultActivate());}返回(T)chain.pluginAll(extensionLoader.getActivate(key));}}返回空值;}publicInterceptorChaingetChain(){返回链;}publicInterceptorExtensionFactorysetChain(InterceptorChainchain){this.chain=chain;归还这个;}示例演示1、确定正义拦截器并说明要拦截的类接口方法@Intercepts(@Signature(type=SqlDialect.class,method="dialect",args={}))publicclassMysqlDialectInterceptorimplementsInterceptor{@OverridepublicObject拦截(调用调用)抛出Throwable{System.out.println(“MysqlDialectInterceptor”);返回调用.proceed();}@OverridepublicintgetOrder(){返回HIGHEST_PRECEDENCE;}@Overridepublic对象插件(对象目标){if(target.toString().startsWith(MysqlDialect.class.getName())){returnPlugin.wrap(target,this);}返回目标;}}2.构造拦截器链并设置到spi工厂@Beforepublicvoidbefore(){InterceptorChainchain=newInterceptorChain();chain.addInterceptor(newDialectInterceptor()).addInterceptor(newMysqlDialectInterceptor()).addInterceptor(newOracleDialectInterceptor());factory=(InterceptorExtensionFactory)(ExtensionLoadergetExtensionLoader(ExtensionFactory.class).getActivate("拦截器"));factory.setChain(链);}3.测试@TestpublicvoidtestMysqlDialectInterceptor(){SqlDialectdialect=factory.getExtension("mysql",SqlDialect.class);Assert.assertEquals("mysql",dialect.dialect());从控制台输出可以看出拦截器调用逻辑已经实现。看完这篇文章的拦截器实现,眼尖的朋友会发现,你这不是照搬mybatis拦截器的实现,确实如此,但我更愿意厚颜无耻地把我所学的应用称为本应用。mybatis的拦截器实现真的很巧妙,因为我们通常使用递归的方式来实现拦截器链式调用,而mybatis使用的是动态代理。当然,本文的拦截器也加入了一些彩蛋,比如加入了原生mybatis拦截器没有提供的自定义执行顺序功能。原生的mybatis拦截器只能拦截Executor、ParameterHandler、StatementHandler、ResultSetHandler。本文没有这个限制,但是需要注意的是拦截器的实现是基于jdk动态代理的,所以自定义注解的类只能指定为接口,不能具体实现demo链接https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-enhance/springboot-spi-framework

最新推荐
猜你喜欢