프로젝트에는 각 시스템의 인터페이스 호출을 모니터링하기 위한 모니터링 시스템이 추가되었습니다. 초기 단계에서는 각 프로젝트에서 공통적으로 참조하는 종속성 패키지에 aop 측면을 추가하여 각 시스템의 인터페이스 호출을 완료했습니다. 모니터링은 단점이 있습니다. 첫째, 서로 다른 프로젝트의 인터페이스 경로가 다르기 때문에 AOP 측면에 대해 여러 측면 경로를 작성해야 합니다. 둘째, 모니터링할 필요가 없는 일부 시스템도 모니터링됩니다. 공개 패키지이므로 침입이 너무 강력합니다. 이 문제를 해결하기 위해 springboot의 플러그 가능 속성을 사용할 수 있습니다.
@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;
MidExpandSpringMethodInterceptor8742468051c85b06f0a0af9e3e506b5c
MidExpandSpringMethodInterceptor8742468051c85b06f0a0af9e3e506b5c
@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
🎜com.dst.mid.common.expand.springaop.MidExpandSpringMethodInterceptor=🎜이렇게 하면 이벤트 진행 중에 직접 등록이 가능합니다. 시동 프로세스를 거쳐 스프링 컨테이너에 배치됩니다. 또 다른 질문은 이제 측면 실행 클래스를 사용할 수 있는데 측면은 어디에 있습니까? 🎜rrreee🎜Aspect 등록 클래스 처리입니다. 먼저
com.dst.mid.monitor.intercept.MonitorLogInterceptor🎜
ImportBeanDefinitionRegistrar
를 구현하여 registerBeanDefinitions
메서드를 구현하면 등록하려는 클래스를 스프링에 넣을 수 있습니다. 그의 구현을 살펴보세요🎜ImportBeanDefinitionRegistrar
가 실제로 클래스를 컨테이너에 등록하지만 한 단계 더 나아가 컨테이너에서 클래스를 스캔해야 한다는 또 다른 문제가 있습니다. 이전 방법은 프로젝트 내 경로를 통해 스캔하는 것이었지만 우리는 플러그인이므로 프로젝트에 의존할 수 없으므로 이때는 @를 사용하여 처리해야 합니다. Import(MidExpandSpringAopAutoStarter.class)
를 사용하여 처리합니다. 🎜🎜위 처리를 통해 모니터링 플러그인 처리가 실현됩니다. 그러면 이를 사용할 때 모니터링해야 하는 다양한 프로젝트에 이 프로젝트를 소개하기만 하면 됩니다. 🎜위 내용은 Springboot 플러그인을 개발하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!