Maison  >  Article  >  Java  >  Comment développer le plug-in Springboot

Comment développer le plug-in Springboot

王林
王林avant
2023-05-16 09:31:051261parcourir

    一Contexte

    Le projet dispose d'un nouveau système de surveillance pour surveiller les appels d'interface de chaque système dans les premiers jours. , les aspects AOP sont ajoutés aux packages de dépendances communément référencés par chaque projet pour surveiller les appels d'interface de chaque système. Cependant, cela présente des inconvénients. Premièrement, les chemins d'interface des différents projets sont différents, ce qui entraîne l'écriture de plusieurs chemins d'aspect. pour les aspects AOP Deuxièmement, certains systèmes qui n'ont pas besoin d'être surveillés sont trop intrusifs car le package public est également surveillé dès son introduction. Afin de résoudre ce problème, vous pouvez utiliser les attributs enfichables de Springboot.

    Deuxième développement du plug-in de journal de surveillance

    1 Créer une nouvelle classe d'exécution d'aspect aop MonitorLogInterceptor

    @Slf4j
    public class MonitorLogInterceptor extends MidExpandSpringMethodInterceptor<MonitorAspectAdviceProperties> {
       @Override
       public Object invoke(MethodInvocation methodInvocation) throws Throwable {
           Object result = null;
           HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
           //拿到请求的url
           String requestURI = request.getRequestURI();
           if (StringUtils.isEmpty(requestURI)) {
               return result;
           }
           try {
               result = methodInvocation.proceed();
           } catch (Exception e) {
               buildRecordData(methodInvocation, result, requestURI, e);
               throw e;
           }
           //参数数组
           buildRecordData(methodInvocation, result, requestURI, null);
           return result;

    Nous pouvons voir qu'il implémente MidExpandSpringMethodInterceptorb5c592e3d0117918a05534a59c59acf1

    @Slf4j
    public abstract class MidExpandSpringMethodInterceptor<T> implements MethodInterceptor {
        @Setter
        @Getter
        protected T properties;
        /**
         * 主动注册,生成AOP工厂类定义对象
         */
        protected String getExpression() {
            return null;
        }
        @SuppressWarnings({"unchecked"})
        public AbstractBeanDefinition doInitiativeRegister(Properties properties) {
            String expression = StringUtils.isNotBlank(this.getExpression()) ? this.getExpression() : properties.getProperty("expression");
            if (StringUtils.isBlank(expression)) {
                log.warn("中台SpringAop插件 " + this.getClass().getSimpleName() + " 缺少对应的配置文件 或者 是配置的拦截路径为空 导致初始化跳过");
                return null;
            }
            BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(AspectJExpressionPointcutAdvisor.class);
            this.setProperties((T) JsonUtil.toBean(JsonUtil.toJson(properties), getProxyClassT()));
            definition.addPropertyValue("advice", this);
            definition.addPropertyValue("expression", expression);
            return definition.getBeanDefinition();
        }
        /**
         * 获取代理类上的泛型T
         * 单泛型 不支持多泛型嵌套
         */
        private Class<?> getProxyClassT() {
            Type genericSuperclass = this.getClass().getGenericSuperclass();
            ParameterizedType parameterizedType = (ParameterizedType) genericSuperclass;
            return (Class<?>) parameterizedType.getActualTypeArguments()[0];
        }
    }

    而最终是实现了MethodInterceptor,这个接口是 方法拦截器,用于Spring AOP编程中的动态代理.实现该接口可以对需要增强的方法进行增强.

    我们注意到我的切面执行类并没有增加任何@Compont和@Service等将类注入到spring的bean中的方法,那他是怎么被注入到bean中的呢,因为使用了spi机制

    SPI机制的实现在项目的资源文件目录中,增加spring.factories文件,内容为

    com.dst.mid.common.expand.springaop.MidExpandSpringMethodInterceptor=
      com.dst.mid.monitor.intercept.MonitorLogInterceptor

    这样就可以在启动过程直接被注册,并且被放到spring容器中了。还有一个问题就是,切面执行类有了,切面在哪里呢。

    @Configuration
    @Slf4j
    @Import(MidExpandSpringAopAutoStarter.class)
    public class MidExpandSpringAopAutoStarter implements ImportBeanDefinitionRegistrar {
        private static final String BEAN_NAME_FORMAT = "%s%sAdvisor";
        private static final String OS = "os.name";
        private static final String WINDOWS = "WINDOWS";
        @SneakyThrows
        @SuppressWarnings({"rawtypes"})
        @Override
        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            // 1 获取MidExpandSpringMethodInterceptor类的所有实现集合
            List<MidExpandSpringMethodInterceptor> list = SpringFactoriesLoader.loadFactories(MidExpandSpringMethodInterceptor.class, null);
            if (!CollectionUtils.isEmpty(list)) {
                String expandPath;
                Properties properties;
                BeanDefinition beanDefinition;
                // 2 遍历类的所有实现集合
                for (MidExpandSpringMethodInterceptor item : list) {
                    // 3 获取资源文件名称 资源文件中存储需要加入配置的
                    expandPath = getExpandPath(item.getClass());
                    // 4 加载资源文件
                    properties = PropertiesLoaderUtils.loadAllProperties(expandPath + ".properties");
                    // 5 赋值beanDefinition为AspectJExpressionPointcutAdvisor
    
                    if (Objects.nonNull(beanDefinition = item.doInitiativeRegister(properties))) {
                        // 6 向容器中注册类  注意这个beanname是不存在的,但是他赋值beanDefinition为AspectJExpressionPointcutAdvisor是动态代理动态生成代理类所以不会报错
                        registry.registerBeanDefinition(String.format(BEAN_NAME_FORMAT, expandPath, item.getClass().getSimpleName()), beanDefinition);
                    }
                }
            }
        }
        /**
         * 获取资源文件名称
         */
        private static String getExpandPath(Class<?> clazz) {
            String[] split = clazz.getProtectionDomain().getCodeSource().getLocation().getPath().split("/");
            if (System.getProperty(OS).toUpperCase().contains(WINDOWS)) {
                return split[split.length - 3];
            } else {
                return String.join("-", Arrays.asList(split[split.length - 1].split("-")).subList(0, 4));
            }
        }
    }

    这个就是切面注册类的处理,首先实现了ImportBeanDefinitionRegistrar,实现他的registerBeanDefinitions方法可以将想要注册的类放入spring容器中,看下他的实现

    • 1 获取MidExpandSpringMethodInterceptor类的所有实现集合

    • 2 遍历类的所有实现集合

    • 3 获取资源文件名称 资源文件中存储需要加入配置的

    • 4 加载资源文件

    • 5 赋值beanDefinition为AspectJExpressionPointcutAdvisor

    • 6 向容器中注册类 注意这个beanname是不存在的,但是他赋值beanDefinition为AspectJExpressionPointcutAdvisor是动态代理动态生成代理类所以不会报错

    看到这里,还有一个问题ImportBeanDefinitionRegistrar实际上是将类注册到容器中,但是还需要一个步骤就是他要被容器扫描才行,以往的方式是项目中通过路径扫描,但是我们是插件,不能依赖于项目,而是通过自己的方式处理,这时候就需要用@Import(MidExpandSpringAopAutoStarter.class)rrreee

    En fin de compte, MethodInterceptor est implémenté. Cette interface est un intercepteur de méthode et est utilisée pour le proxy dynamique dans la programmation Spring AOP. doit être amélioré.

    Nous avons remarqué que ma classe d'exécution d'aspect n'a ajouté aucune méthode @Component et @Service pour injecter des classes dans les beans spring, alors comment a-t-elle été injectée dans le bean ? est utilisé #🎜🎜##🎜🎜# Pour implémenter le mécanisme SPI, ajoutez le fichier spring.factories dans le répertoire des fichiers de ressources du projet avec le contenu de #🎜🎜#
    #🎜🎜#com.dst.mid.common .expand.springaop.MidExpandSpringMethodInterceptor=
    com.dst.mid.monitor.intercept.MonitorLogInterceptor#🎜🎜#
    #🎜🎜#De cette façon, il peut être enregistré directement pendant le processus de démarrage et placé au printemps conteneur Hit. Une autre question est : maintenant que la classe d’exécution des aspects est disponible, où sont les aspects ? #🎜🎜#rrreee#🎜🎜#Il s'agit du traitement des classes d'enregistrement d'aspect. Tout d'abord, ImportBeanDefinitionRegistrar est implémenté. En implémentant sa méthode registerBeanDefinitions, vous pouvez mettre la classe que vous avez. souhaitez vous inscrire dans le conteneur Spring, jetez un œil à son implémentation #🎜🎜#
    • #🎜🎜#1 Obtenez la collection de toutes les implémentations de la classe MidExpandSpringMethodInterceptor # 🎜🎜#
    • #🎜🎜#2 Parcourez toutes les collections d'implémentation de la classe#🎜🎜#
    • #🎜🎜#3 Obtenez le nom du fichier de ressources et stockez la configuration qui doit être ajouté au fichier de ressources #🎜🎜#
    • #🎜🎜#4 Charger le fichier de ressources#🎜🎜#
    • #🎜🎜#5 Attribuer beanDefinition à AspectJExpressionPointcutAdvisor#🎜🎜#
    • #🎜🎜#6 Enregistrez la classe dans le conteneur Notez que ce nom de bean n'existe pas, mais il attribue beanDefinition à AspectJExpressionPointcutAdvisor. Il s'agit d'un proxy dynamique et génère dynamiquement des classes proxy, donc aucune erreur ne sera signalée #. 🎜🎜#
    • #🎜🎜##🎜🎜#En voyant cela, il y a une autre questionImportBeanDefinitionRegistrar enregistre en fait la classe dans le conteneur, mais une étape de plus est qu'elle doit être analysée par le conteneur. La méthode précédente consistait à parcourir le chemin dans le projet, mais nous sommes un plug-in et ne pouvons pas dépendre du projet, mais vous devez le gérer à votre manière pour le moment. @Import(MidExpandSpringAopAutoStarter.class) pour le gérer. #🎜🎜##🎜🎜# Grâce au traitement ci-dessus, le traitement du plug-in de surveillance est réalisé. Ensuite, lors de son utilisation, il vous suffit de présenter ce projet aux différents projets qui doivent être surveillés. #🎜🎜#

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer