The project added a monitoring system to monitor the interface calls of each system. In the early days, it was based on the dependency packages commonly referenced by each project. The AOP aspect is added to monitor the interface calls of each system. However, this has disadvantages. First, the interface paths of different projects are different, resulting in multiple aspect paths to be written for the AOP aspect. Second, some systems do not need to be monitored. Because the public package is introduced and monitored, it is too intrusive. In order to solve this problem, you can use springboot's pluggable attributes.
@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;
We can see that it implementsMidExpandSpringMethodInterceptor<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]; } }
In the end, MethodInterceptor was implemented. This interface is a method interceptor, used for dynamic proxy in Spring AOP programming. Implementing this interface can enhance the methods that need to be enhanced.
We noticed My aspect execution class does not add any methods such as @Component and @Service to inject classes into spring beans, so how is it injected into the bean, because the spi mechanism is used
SPI To implement the mechanism, add the spring.factories file in the project's resource file directory with the content
com.dst.mid.common.expand.springaop.MidExpandSpringMethodInterceptor=\
com.dst. mid.monitor.intercept.MonitorLogInterceptor
In this way, it can be registered directly during the startup process and placed in the spring container. Another question is, now that the aspect execution class is available, where are the aspects?
@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)); } } }
This is the processing of aspect registration classes. First, ImportBeanDefinitionRegistrar
is implemented. By implementing its registerBeanDefinitions
method, you can put the classes you want to register into the spring container. Take a look at his implementation
1 Get all implementation collections of the MidExpandSpringMethodInterceptor class
2 Traverse all implementation collections of the class
3 Get the name of the resource file. The resource file stores the configuration that needs to be added.
4 Load the resource file
5 Assign beanDefinition Register the class with the container for AspectJExpressionPointcutAdvisor
6. Note that this beanname does not exist, but it assigns beanDefinition to AspectJExpressionPointcutAdvisor. It is a dynamic proxy and dynamically generates proxy classes, so no error will be reported
Seeing this, there is another problemImportBeanDefinitionRegistrar
It actually registers the class into the container, but one more step is required, that is, it needs to be scanned by the container. The previous method is The project scans through paths, but we are plug-ins and cannot rely on the project. Instead, we handle it in our own way. At this time, we need to use @Import(MidExpandSpringAopAutoStarter.class)
to handle it.
Through the above processing, the processing of the monitoring plug-in is realized. Then when using it, you only need to introduce this project to different projects that need to be monitored.
The above is the detailed content of How to develop Springboot plug-in. For more information, please follow other related articles on the PHP Chinese website!