> Java > java지도 시간 > 본문

Springboot 플러그인을 개발하는 방법

王林
풀어 주다: 2023-05-16 09:31:05
앞으로
1310명이 탐색했습니다.

    Background

    프로젝트에는 각 시스템의 인터페이스 호출을 모니터링하기 위한 모니터링 시스템이 추가되었습니다. 초기 단계에서는 각 프로젝트에서 공통적으로 참조하는 종속성 패키지에 aop 측면을 추가하여 각 시스템의 인터페이스 호출을 완료했습니다. 모니터링은 단점이 있습니다. 첫째, 서로 다른 프로젝트의 인터페이스 경로가 다르기 때문에 AOP 측면에 대해 여러 측면 경로를 작성해야 합니다. 둘째, 모니터링할 필요가 없는 일부 시스템도 모니터링됩니다. 공개 패키지이므로 침입이 너무 강력합니다. 이 문제를 해결하기 위해 springboot의 플러그 가능 속성을 사용할 수 있습니다.

    2차 모니터링 로그 플러그인 개발

    1 새로운 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;
    로그인 후 복사

    MidExpandSpringMethodInterceptor<T>MidExpandSpringMethodInterceptor<T>

    @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

    를 구현하고 마지막으로 MethodInterceptor를 구현하는 것을 볼 수 있습니다. Spring AOP 프로그래밍에서 동적 프록시에 사용되는 메서드 인터셉터입니다. 이 인터페이스를 구현하면 향상이 필요한 메서드가 향상될 수 있습니다.

    내 측면 실행 클래스에 @Component 및 @Service 등이 추가되지 않은 것을 확인했습니다. spring bean의 메서드에 spi 메커니즘이 사용되기 때문에 어떻게 bean에 주입됩니까?🎜🎜SPI 메커니즘의 구현은 프로젝트의 리소스 파일 디렉터리에 있으며 spring.factories 파일을 추가하면 내용은 🎜
    🎜com.dst.mid.common.expand.springaop.MidExpandSpringMethodInterceptor=
    com.dst.mid.monitor.intercept.MonitorLogInterceptor🎜
    🎜이렇게 하면 이벤트 진행 중에 직접 등록이 가능합니다. 시동 프로세스를 거쳐 스프링 컨테이너에 배치됩니다. 또 다른 질문은 이제 측면 실행 클래스를 사용할 수 있는데 측면은 어디에 있습니까? 🎜rrreee🎜Aspect 등록 클래스 처리입니다. 먼저 ImportBeanDefinitionRegistrar를 구현하여 registerBeanDefinitions 메서드를 구현하면 등록하려는 클래스를 스프링에 넣을 수 있습니다. 그의 구현을 살펴보세요🎜
    • 🎜1 MidExpandSpringMethodInterceptor 클래스의 모든 구현 컬렉션을 가져옵니다🎜
    • 🎜2 클래스의 모든 구현 컬렉션을 탐색합니다. 🎜
    • 🎜3 리소스 파일 이름을 가져옵니다. 리소스 파일에는 추가해야 하는 구성이 저장됩니다.🎜
    • 🎜4 리소스 파일 로드🎜
    • 🎜 5 AspectJExpressionPointcutAdvisor에 beanDefinition 할당🎜
    • 🎜6 이 beanname은 존재하지 않지만 AspectJExpressionPointcutAdvisor에 beanDefinition을 할당한다는 점에 유의하세요. 오류는 보고되지 않습니다🎜
    • 🎜🎜이를 보면 ImportBeanDefinitionRegistrar가 실제로 클래스를 컨테이너에 등록하지만 한 단계 더 나아가 컨테이너에서 클래스를 스캔해야 한다는 또 다른 문제가 있습니다. 이전 방법은 프로젝트 내 경로를 통해 스캔하는 것이었지만 우리는 플러그인이므로 프로젝트에 의존할 수 없으므로 이때는 @를 사용하여 처리해야 합니다. Import(MidExpandSpringAopAutoStarter.class)를 사용하여 처리합니다. 🎜🎜위 처리를 통해 모니터링 플러그인 처리가 실현됩니다. 그러면 이를 사용할 때 모니터링해야 하는 다양한 프로젝트에 이 프로젝트를 소개하기만 하면 됩니다. 🎜

    위 내용은 Springboot 플러그인을 개발하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    원천:yisu.com
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿