不指定扫描包 框架第三方Jar包的Bean如何注入Spring (微服务监控实例)

提出需求

要写一个微服务监控组件,来监控所有业务微服务是否健康。但是 不能侵入微服务的代码(实际上业务开发人员也不会在代码层面加你的监控组件),作为架构师的你,如何完成这一功能 ?

实践流程

新建一个monitor.jar(监控组件第三方jar) 。我们使用oshi进行linux机器的cpu,内存监控。引入maven:

<dependency>
	<groupId>com.github.oshi</groupId>
	<artifactId>oshi-core</artifactId>
	<version>3.5.0</version>
</dependency>

编写业务监控Bean:

流程:实现InitializingBean,在初始化afterPropertiesSet方法里面 开启监控线程,线程循环获取当前cpu,内存信息,进行预警。

新建一个Monitor.java类:

import java.text.DecimalFormat;
import oshi.SystemInfo;
import oshi.hardware.CentralProcessor;
import oshi.hardware.GlobalMemory;
import org.springframework.beans.factory.InitializingBean;

public class Monitor implements InitializingBean, Runnable {

    private Thread thread;

    @Override
    public void afterPropertiesSet() throws Exception {
        // 开启监控线程
        thread = new Thread(this);
        thread.start();
    }

    @Override
    public void run() {

        while (thread.isInterrupted()) {
            SystemInfo systemInfo = new SystemInfo();
            CentralProcessor processor = systemInfo.getHardware().getProcessor();
            long[] prevTicks = processor.getSystemCpuLoadTicks();
            long[] ticks = processor.getSystemCpuLoadTicks();
            long nice =
                ticks[CentralProcessor.TickType.NICE.getIndex()] - prevTicks[CentralProcessor.TickType.NICE.getIndex()];
            long irq =
                ticks[CentralProcessor.TickType.IRQ.getIndex()] - prevTicks[CentralProcessor.TickType.IRQ.getIndex()];
            long softirq = ticks[CentralProcessor.TickType.SOFTIRQ.getIndex()]
                - prevTicks[CentralProcessor.TickType.SOFTIRQ.getIndex()];
            long steal = ticks[CentralProcessor.TickType.STEAL.getIndex()]
                - prevTicks[CentralProcessor.TickType.STEAL.getIndex()];
            long cSys = ticks[CentralProcessor.TickType.SYSTEM.getIndex()]
                - prevTicks[CentralProcessor.TickType.SYSTEM.getIndex()];
            long user =
                ticks[CentralProcessor.TickType.USER.getIndex()] - prevTicks[CentralProcessor.TickType.USER.getIndex()];
            long iowait = ticks[CentralProcessor.TickType.IOWAIT.getIndex()]
                - prevTicks[CentralProcessor.TickType.IOWAIT.getIndex()];
            long idle =
                ticks[CentralProcessor.TickType.IDLE.getIndex()] - prevTicks[CentralProcessor.TickType.IDLE.getIndex()];
            long totalCpu = user + nice + cSys + idle + iowait + irq + softirq + steal;

            System.err.println("CPU总数:" + processor.getLogicalProcessorCount());
            System.err.println("CPU利用率:" + new DecimalFormat("#.##%").format(1.0 - (idle * 1.0 / totalCpu)));

            // 内存 监控
            GlobalMemory memory = systemInfo.getHardware().getMemory();
            long totalByte = memory.getTotal();
            long acaliableByte = memory.getAvailable();

            System.err.println("内存大小 : " + formatByte(totalByte));
            System.err
                .println("内存使用率:" + new DecimalFormat("#.##%").format((totalByte - acaliableByte) * 1.0 / totalByte));

            // 不让其 一直跑监控 减少性能消耗
            try {
                Thread.sleep(10000);
            } catch (InterruptedException e) {
            }
        }

    }

    private static String formatByte(long byteNumber) {
        double FORMAT = 1024.0;
        double kbNumber = byteNumber / FORMAT;
        if (kbNumber < FORMAT) {
            return new DecimalFormat("#.##KB").format(kbNumber);
        }
        double mbNumber = kbNumber / FORMAT;
        if (mbNumber < FORMAT) {
            return new DecimalFormat("#.##MB").format(mbNumber);
        }
        double gbNumber = mbNumber / FORMAT;
        if (gbNumber < FORMAT) {
            return new DecimalFormat("#.##GB").format(gbNumber);
        }
        double tbNumber = gbNumber / FORMAT;
        return new DecimalFormat("#.##TB").format(tbNumber);
    }

}

上述监控bean写完了,问题时,如何注入到业务服务里面去呢?

在此之前 ,我们先来来了解一个技术: SpringBoot的自动配置。

SpringBoot的自动配置技术

springboot在加载的时候,会扫描环境变量下的 META-INF/spring.factories 文件,比如有个配置如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.uc.A,com.uc.B

然后就构建一个Map, key为org.springframework.boot.autoconfigure.EnableAutoConfiguration , 值为一个list,A,B 。 然后 将这个map所有的value都自动注入到Spring容器里面。

SpringBoot自动配置原理

我们复习下springboot的知识, 看下这个启动类的注解SpringBootApplication:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
//.....
}
  • @SpringBootConfiguration :
    就是@Configuration,支持JavaConfig的方式来进行配置(使用Configuration配置类等同于XML文件)。
  • @ComponentScan:
    扫描注解,默认是扫描当前类下的package。将@Controller/@Service/@Component/@Repository等注解加载到IOC容器中。
  • @EnableAutoConfiguration: 开启自动配置功能。

EnableAutoConfiguration注解

@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {
}

@Import 注解

@Import 可以引入ImportSelector的实现类,把ImportSelector接口selectImports()方法返回的Class名称都定义为bean。注意selectImports()方法的参数AnnotationMetadata,通过这个参数我们可以获取到@Import标注的Class的各种信息。这一点特别有用,用于做一些参数的传递。在SpringBoot的自动化配置和@EnableXXX(功能性注解)都有它的存在。

public interface ImportSelector {

    /**
     * 用于指定需要注册为bean的Class名称
     * 当在@Configuration标注的Class上使用@Import引入了一个ImportSelector实现类后,会把实现类中返回的Class名称都定义为bean。
     *
     * 通过其参数AnnotationMetadata importingClassMetadata可以获取到@Import标注的Class的各种信息,
     * 包括其Class名称,实现的接口名称、父类名称、添加的其它注解等信息,通过这些额外的信息可以辅助我们选择需要定义为Spring bean的Class名称
     */
    String[] selectImports(AnnotationMetadata importingClassMetadata);
}

AutoConfigurationImportSelector 实现

@Override
public String[] selectImports(AnnotationMetadata metadata) {
	try {
		AnnotationAttributes attributes = getAttributes(metadata);
               // 这里就是加载META-INF/spring.factories 配置下所有的类名称
		List<String> configurations = getCandidateConfigurations(metadata,
				attributes);
		configurations = removeDuplicates(configurations);
		Set<String> exclusions = getExclusions(metadata, attributes);
		configurations.removeAll(exclusions);
		configurations = sort(configurations);
		recordWithConditionEvaluationReport(configurations, exclusions);
                // 返回 就注入到spring了
		return configurations.toArray(new String[configurations.size()]);
	}
	catch (IOException ex) {
		throw new IllegalStateException(ex);
	}
}

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata,
			AnnotationAttributes attributes) {
        // 这个loadFactoryNames相当于从map中取数据,key为EnableAutoConfiguration.class
	return SpringFactoriesLoader.loadFactoryNames(EnableAutoConfiguration.class, getBeanClassLoader());
}

我们只看关键的加载哪些class定义成bean, 于是要关注getCandidateConfigurations 这个加载的方法,最终会追踪到下面的loadSpringFactories方法:

private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
	// 这个就是上面我说的那个Map。 key为EnableAutoConfiguration.class,值就是用户A,B类
        MultiValueMap<String, String> result = cache.get(classLoader);
	if (result != null) {
		return result;
	}
	try {
		Enumeration<URL> urls = (classLoader != null ?
				classLoader.getResources("META-INF/spring.factories") :
				ClassLoader.getSystemResources("META-INF/spring.factories"));
		result = new LinkedMultiValueMap<>();
		while (urls.hasMoreElements()) {
			URL url = urls.nextElement();
			UrlResource resource = new UrlResource(url);
			Properties properties = PropertiesLoaderUtils.loadProperties(resource);
			for (Map.Entry<?, ?> entry : properties.entrySet()) {
				String factoryTypeName = ((String) entry.getKey()).trim();
				for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {
					result.add(factoryTypeName, factoryImplementationName.trim());
				}
			}
		}
		cache.put(classLoader, result);
		return result;
	}
	catch (IOException ex) {
		throw new IllegalArgumentException("Unable to load factories from location [" +
				FACTORIES_RESOURCE_LOCATION + "]", ex);
	}
}

主要就是 加载 环境变量下的 META-INF/spring.factories 路径下的配置 ,取像下面这种:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.uc.A,com.uc.B

配置的A,B类,然后返回,最终是selectImports 的返回,就注入到spring了。到此原理结束。

回到我们的业务监控

编写自动配置类
我们继续在Monitor.jar中编写一个MonitorAutoConfiguration类:

package com.uc.neighbour.trace.client;

import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.uc.neighbour.trace.client.container.Container;

@Configuration
public class MonitorAutoConfiguration {
    /***
     * 
     * title: 初始化容器
     *
     * @return
     * @author HadLuo 2021-3-2 14:34:43
     */
    @Bean // 注入spring
    // 判断当前需要注入Spring容器中的bean的实现类是否已经含有,有的话不注入,没有就注入
    @ConditionalOnMissingBean(Container.class) 
    public Monitor registerMonitor() {
        //  Monitor 就是文章开头 嗯写的业务监控 bean
        return new Monitor();
    }
}

写好了我们的自动配置类,然后将其放到配置路径下,在 Monitor.jar 的 resources目录下新建 META-INF/spring.factories:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.uc.neighbour.trace.client.MonitorAutoConfiguration

这样就完成了我们的监控 Monitor.jar 。

然后业务微服务在maven中引入我们的 Monitor.jar 就可以了。可以在你们的base工程中直接引入,业务微服务就无需改任务代码拉!!!!!

文章最后,有一个问题

业务微服务都是集群的,这样会导致每台机器都启动了监控线程,所以在监控bean中要把当前机器的ip打出来,这样就定位到哪个服务的cpu,内存情况了。

强烈推荐一个 进阶 JAVA架构师 的博客

Java架构师修炼

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值