@RabbitListener起作用的原理

2023-11-09

一、前言

在spring中,定义rabbitMq的消费者可以相当方便,只需要在消息处理类或者类方法加上@RabbitListener注解,指定队列名称即可。如下代码:

@Component
public class RabbitMqListener1 {
    @RabbitListener(queues = "queue1")
    public void consumer1(Message message) {

    }

    @RabbitListener(queues = "queue2")
    public void consumer2(String messsageBody) {

    }
}


@Component
@RabbitListener(queues = "queue3")
public class RabbitMqListener2 {
    @RabbitHandler(isDefault=true)
    public void consumer3() {

    }
}

注意!!!如果@RabbitListener加在类上面,需要有一个默认的处理方法@RabbitHandler(isDefault=true),默认是false。不设置一个true,消费mq消息的时候会出现“Listener method ‘no match’ threw exception”异常。
原因在RabbitListenerAnnotationBeanPostProcessor.processMultiMethodListeners方法,有兴趣的可以看下。

可以看到代码相当的简单。但是!!!为什么加上这个注解,就能作为一个consumer接受mq的消息呢?为啥处理mq消息的方法,入参可以那么随意?
有经验的程序员,可能会有这样的设想:
1、单纯看这些listener的代码,只是定义了由spring管理的bean,要能监听rabbitMq的消息,肯定需要有另外一个类,这个类会扫描所有加了@RabbitListener的bean,进行加工。

2、看这些listener的代码,可以发现处理mq消息的,都是具体的某个方法。那加工的过程,应该就是利用反射拿到对象、方法和@RabbitListener中的queue属性,然后建立一个绑定关系(对象+方法)——>(queue的consumer)。queue的consumer在接收到mq消息后,找到绑定的“对象+方法”,再通过反射的方式,调用真正的处理方法。

3、mq消息的处理方法,可以那么随意,应该是queue的consumer在调用真正处理方法之前,需要根据处理方法的参数类型,做一次数据转换。

接下来,就去看看源码,看一下设想是不是正确的~~

二、源码分析

1、谁来扫描@RabbitListener注解的bean

在springBoot使用rabbit,一般是在@Configuration类上加上@EnableRabbit注解来开启rabbit功能。那我们就去看看@EnableRabbit注解的源码,看这个注解的作用

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import(RabbitBootstrapConfiguration.class)
public @interface EnableRabbit {
}

可以看到,这个注解的作用,是导入RabbitBootstrapConfiguration配置类

@Configuration
public class RabbitBootstrapConfiguration {

	@Bean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ANNOTATION_PROCESSOR_BEAN_NAME)
	@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
	public RabbitListenerAnnotationBeanPostProcessor rabbitListenerAnnotationProcessor() {
		return new RabbitListenerAnnotationBeanPostProcessor();
	}

	@Bean(name = RabbitListenerConfigUtils.RABBIT_LISTENER_ENDPOINT_REGISTRY_BEAN_NAME)
	public RabbitListenerEndpointRegistry defaultRabbitListenerEndpointRegistry() {
		return new RabbitListenerEndpointRegistry();
	}
}

RabbitBootstrapConfiguration 配置类的作用,就是定义了RabbitListenerAnnotationBeanPostProcessor 和RabbitListenerEndpointRegistry 两个bean。
看到RabbitListenerAnnotationBeanPostProcessor 这个类名,就可以猜到,该类的实例bean就是用来扫描加了@RabbitListener 的类,并做一些加工。(“RabbitListenerAnnotationBean”——针对添加了@RabbitListener注解的bean; “PostProcessor”——后置加工)

2、怎么建立(对象+方法)——>(queue的consumer)的映射关系

分析一下RabbitListenerAnnotationBeanPostProcessor类的源码

// 实现了BeanPostProcessor、Ordered、BeanFactoryAware、BeanClassLoaderAware、EnvironmentAware和SmartInitializingSingleton 6个接口
public class RabbitListenerAnnotationBeanPostProcessor
		implements BeanPostProcessor, Ordered, BeanFactoryAware, BeanClassLoaderAware, EnvironmentAware,
		SmartInitializingSingleton {
		
	.......
	
	// 完成初始化bean之后,调用该方法
	@Override
	public Object postProcessAfterInitialization(final Object bean, final String beanName) throws BeansException {
		Class<?> targetClass = AopUtils.getTargetClass(bean);

		TypeMetadata metadata = this.typeCache.get(targetClass);
		if (metadata == null) {
			metadata = buildMetadata(targetClass);
			this.typeCache.putIfAbsent(targetClass, metadata);
		}

		for (ListenerMethod lm : metadata.listenerMethods) {
			for (RabbitListener rabbitListener : lm.annotations) {
				processAmqpListener(rabbitListener, lm.method, bean, beanName);
			}
		}
		if (metadata.handlerMethods.length > 0) {
			processMultiMethodListeners(metadata.classAnnotations, metadata.handlerMethods, bean, beanName);
		}
		return bean;
	}

	// 根据Class,获取元数据
	private TypeMetadata buildMetadata(Class<?> targetClass) {
		Collection<RabbitListener> classLevelListeners = findListenerAnnotations(targetClass);
		final boolean hasClassLevelListeners = classLevelListeners.size() > 0;
		final List<ListenerMethod> methods = new ArrayList<ListenerMethod>();
		final List<Method> multiMethods = new ArrayList<Method>();
		ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {

			@Override
			public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
				Collection<RabbitListener> listenerAnnotations = findListenerAnnotations(method);
				if (listenerAnnotations.size() > 0) {
					methods.add(new ListenerMethod(method,
							listenerAnnotations.toArray(new RabbitListener[listenerAnnotations.size()])));
				}
				if (hasClassLevelListeners) {
					RabbitHandler rabbitHandler = AnnotationUtils.findAnnotation(method, RabbitHandler.class);
					if (rabbitHandler != null) {
						multiMethods.add(method);
					}
				}

			}
		}, ReflectionUtils.USER_DECLARED_METHODS);
		if (methods.isEmpty() && multiMethods.isEmpty()) {
			return TypeMetadata.EMPTY;
		}
		return new TypeMetadata(
				methods.toArray(new ListenerMethod[methods.size()]),
				multiMethods.toArray(new Method[multiMethods.size()]),
				classLevelListeners.toArray(new RabbitListener[classLevelListeners.size()]));
	}

	// 检查一下是否使用jdk代理,使用jdk代理方式必须实现了接口
	// new一个MethodRabbitListenerEndpoint对象,交由processListener方法进行处理
	protected void processAmqpListener(RabbitListener rabbitListener, Method method, Object bean, String beanName) {
		Method methodToUse = checkProxy(method, bean);
		MethodRabbitListenerEndpoint endpoint = new MethodRabbitListenerEndpoint();
		endpoint.setMethod(methodToUse);
		endpoint.setBeanFactory(this.beanFactory);
		processListener(endpoint, rabbitListener, bean, methodToUse, beanName);
	}

// 前面大半代码都是对MethodRabbitListenerEndpoint对象的属性设置:处理消息的bean、消息处理方法的工厂类、监听的队列名。。。。
// 通过beanFactory获取RabbitListenerContainerFactory类的bean
// 调用RabbitListenerEndpointRegistar的registerEndpoint方法注册mq消息消费端点
protected void processListener(MethodRabbitListenerEndpoint endpoint, RabbitListener rabbitListener, Object bean,
			Object adminTarget, String beanName) {
		endpoint.setBean(bean);
		endpoint.setMessageHandlerMethodFactory(this.messageHandlerMethodFactory);
		endpoint.setId(getEndpointId(rabbitListener));
		endpoint.setQueueNames(resolveQueues(rabbitListener));
		String group = rabbitListener.group();
		if (StringUtils.hasText(group)) {
			Object resolvedGroup = resolveExpression(group);
			if (resolvedGroup instanceof String) {
				endpoint.setGroup((String) resolvedGroup);
			}
		}

		endpoint.setExclusive(rabbitListener.exclusive());
		String priority = resolve(rabbitListener.priority());
		if (StringUtils.hasText(priority)) {
			try {
				endpoint.setPriority(Integer.valueOf(priority));
			}
			catch (NumberFormatException ex) {
				throw new BeanInitializationException("Invalid priority value for " +
						rabbitListener + " (must be an integer)", ex);
			}
		}

		String rabbitAdmin = resolve(rabbitListener.admin());
		if (StringUtils.hasText(rabbitAdmin)) {
			Assert.state(this.beanFactory != null, "BeanFactory must be set to resolve RabbitAdmin by bean name");
			try {
				endpoint.setAdmin(this.beanFactory.getBean(rabbitAdmin, RabbitAdmin.class));
			}
			catch (NoSuchBeanDefinitionException ex) {
				throw new BeanInitializationException("Could not register rabbit listener endpoint on [" +
						adminTarget + "], no " + RabbitAdmin.class.getSimpleName() + " with id '" +
						rabbitAdmin + "' was found in the application context", ex);
			}
		}


		RabbitListenerContainerFactory<?> factory = null;
		String containerFactoryBeanName = resolve(rabbitListener.containerFactory());
		if (StringUtils.hasText(containerFactoryBeanName)) {
			Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
			try {
				factory = this.beanFactory.getBean(containerFactoryBeanName, RabbitListenerContainerFactory.class);
			}
			catch (NoSuchBeanDefinitionException ex) {
				throw new BeanInitializationException("Could not register rabbit listener endpoint on [" +
						adminTarget + "] for bean " + beanName + ", no " +
						RabbitListenerContainerFactory.class.getSimpleName() + " with id '" +
						containerFactoryBeanName + "' was found in the application context", ex);
			}
		}

		this.registrar.registerEndpoint(endpoint, factory);
	}
	........

}

这个类的代码比较长,只贴部分比较主要的部分,其他的,可以自己查看源码
进行了解。
RabbitListenerAnnotationBeanPostProcessor实现了BeanPostProcessor(bean初始化后的后置处理)、Ordered(后置处理的排序)、BeanFactoryAware(注入BeanFactory)、BeanClassLoaderAware(注入BeanClassLoader)、EnvironmentAware(注入spring环境)和SmartInitializingSingleton(单例bean初始化后的回调) 6个接口。
我们需要关注的是BeanPostProcessor接口定义的方法,看postProcessAfterInitialization方法的代码,大致流程为:
1、通过AopUtils得到bean代理的对象的class
2、判断缓存中是否有该class的类型元数据,如果没有则调用buildMetadata方法生成类型元数据并放入缓存
3、遍历加了@RabbitListener注解的方法,调用processAmqpListener方法进行处理
4、调用processMultiMethodListeners方法对加了@RabbitHandler的方法进行处理

关于buildMetadata方法:
代码不复杂,就是利用反射,拿到class中,添加了@RabbitListener和@RabbitHandler注解的方法。另外,从代码中也可以看出,@RabbitHandler注解要生效,必须在class上增加@RabbitListener注解

关于processAmqpListener方法:
没有什么实际内容,就干两个事情:
1、检查一下是否使用jdk代理,使用jdk代理方式必须实现了接口
2、new一个MethodRabbitListenerEndpoint对象,交由processListener方法进行处理

关于processListener方法:
1、前面大半代码都是对MethodRabbitListenerEndpoint对象的属性设置:处理消息的bean、消息处理方法的工厂类、监听的队列名。。。。
其中要关注一下setMessageHandlerMethodFactory方法,查看MessageHandlerMethodFactory接口的源码

public interface MessageHandlerMethodFactory {
	InvocableHandlerMethod createInvocableHandlerMethod(Object bean, Method method);

从入参和返回值可以看出来,这个工厂的作用就是将spring的bean对象和方法包装成一个InvocableHandlerMethod对象,也就是我们上面提到的(对象+方法)。

2、通过beanFactory获取RabbitListenerContainerFactory类的bean。

3、调用RabbitListenerEndpointRegistar的registerEndpoint方法注册mq消息消费端点。

继续往下追,看一下RabbitListenerEndpointRegistar的代码:

public class RabbitListenerEndpointRegistrar implements BeanFactoryAware, InitializingBean {
	// 将整个endpointDescriptors数组进行注册
	protected void registerAllEndpoints() {
		synchronized (this.endpointDescriptors) {
			for (AmqpListenerEndpointDescriptor descriptor : this.endpointDescriptors) {
				this.endpointRegistry.registerListenerContainer(
						descriptor.endpoint, resolveContainerFactory(descriptor));
			}
			this.startImmediately = true;  // trigger immediate startup
		}
	}
	
	// 解析得到RabbitListenerContainerFactory
	// 如果AmqpListenerEndpointDescriptor 的containerFactory属性不为空,直接返回containerFactory
	// 如果为空,尝试从beanFactory获取
	private RabbitListenerContainerFactory<?> resolveContainerFactory(AmqpListenerEndpointDescriptor descriptor) {
		if (descriptor.containerFactory != null) {
			return descriptor.containerFactory;
		}
		else if (this.containerFactory != null) {
			return this.containerFactory;
		}
		else if (this.containerFactoryBeanName != null) {
			Assert.state(this.beanFactory != null, "BeanFactory must be set to obtain container factory by bean name");
			this.containerFactory = this.beanFactory.getBean(
					this.containerFactoryBeanName, RabbitListenerContainerFactory.class);
			return this.containerFactory;  // Consider changing this if live change of the factory is required
		}
		else {
			throw new IllegalStateException("Could not resolve the " +
					RabbitListenerContainerFactory.class.getSimpleName() + " to use for [" +
					descriptor.endpoint + "] no factory was given and no default is set.");
		}
	}
	
	// new一个AmqpListenerEndpointDescriptor对象
	// 如果立即启动,则调用RabbitListenerEndpointRegistry注册器来注册消息监听
	// 如果不是立即启动,则添加到endpointDescriptors列表中,后面通过registerAllEndpoints方法统一启动
	public void registerEndpoint(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory) {
		Assert.notNull(endpoint, "Endpoint must be set");
		Assert.hasText(endpoint.getId(), "Endpoint id must be set");
		// Factory may be null, we defer the resolution right before actually creating the container
		AmqpListenerEndpointDescriptor descriptor = new AmqpListenerEndpointDescriptor(endpoint, factory);
		synchronized (this.endpointDescriptors) {
			if (this.startImmediately) { // Register and start immediately
				this.endpointRegistry.registerListenerContainer(descriptor.endpoint,
						resolveContainerFactory(descriptor), true);
			}
			else {
				this.endpointDescriptors.add(descriptor);
			}
		}
	}
}

从上面的代码可以看出,我们关心的内容,应该是在RabbitListenerEndpointRegistry类的registerListenerContainer方法!!

public class RabbitListenerEndpointRegistry implements DisposableBean, SmartLifecycle, ApplicationContextAware,
		ApplicationListener<ContextRefreshedEvent> {
		// 检查是否被注册过,注册过就不能注册第二次
		// 调用createListenerContainer创建消息监听
		// 关于分组消费的,我们不关心
		// 是否立即启动,是的话,同步调用startIfNecessary方法
		public void registerListenerContainer(RabbitListenerEndpoint endpoint, RabbitListenerContainerFactory<?> factory,
	                                      boolean startImmediately) {
		Assert.notNull(endpoint, "Endpoint must not be null");
		Assert.notNull(factory, "Factory must not be null");

		String id = endpoint.getId();
		Assert.hasText(id, "Endpoint id must not be empty");
		synchronized (this.listenerContainers) {
			Assert.state(!this.listenerContainers.containsKey(id),
					"Another endpoint is already registered with id '" + id + "'");
			MessageListenerContainer container = createListenerContainer(endpoint, factory);
			this.listenerContainers.put(id, container);
			if (StringUtils.hasText(endpoint.getGroup()) && this.applicationContext != null) {
				List<MessageListenerContainer> containerGroup;
				if (this.applicationContext.containsBean(endpoint.getGroup())) {
					containerGroup = this.applicationContext.getBean(endpoint.getGroup(), List.class);
				}
				else {
					containerGroup = new ArrayList<MessageListenerContainer>();
					this.applicationContext.getBeanFactory().registerSingleton(endpoint.getGroup(), containerGroup);
				}
				containerGroup.add(container);
			}
			if (startImmediately) {
				startIfNecessary(container);
			}
		}

	// 其实就是调用了RabbitListenerContainerFactory的createListenerContainer生成了一个MessageListenerContainer对象
	protected MessageListenerContainer createListenerContainer(RabbitListenerEndpoint endpoint,
			RabbitListenerContainerFactory<?> factory) {

		MessageListenerContainer listenerContainer = factory.createListenerContainer(endpoint);

		if (listenerContainer instanceof InitializingBean) {
			try {
				((InitializingBean) listenerContainer).afterPropertiesSet();
			}
			catch (Exception ex) {
				throw new BeanInitializationException("Failed to initialize message listener container", ex);
			}
		}

		int containerPhase = listenerContainer.getPhase();
		if (containerPhase < Integer.MAX_VALUE) {  // a custom phase value
			if (this.phase < Integer.MAX_VALUE && this.phase != containerPhase) {
				throw new IllegalStateException("Encountered phase mismatch between container factory definitions: " +
						this.phase + " vs " + containerPhase);
			}
			this.phase = listenerContainer.getPhase();
		}

		return listenerContainer;
	}
}

createListenerContainer方法调用了RabbitListenerContainerFactory接口的createListenerContainer方法创建一个MessageListenerContainer对象。

在这里,如果是通过RabbitAutoConfiguration自动配置的,那么RabbitListenerContainerFactory接口的具体实现类是SimpleRabbitListenerContainerFactory,MessageListenerContainer接口的具体实现类是SimpleMessageListenerContainer。有兴趣的话,可以去看下rabbitMq自动配置的几个类。

RabbitListenerContainerFactory接口的createListenerContainer方法是由AbstractRabbitListenerContainerFactory抽象类实现,代码如下:

	@Override
	public C createListenerContainer(RabbitListenerEndpoint endpoint) {
		C instance = createContainerInstance();

		if (this.connectionFactory != null) {
			instance.setConnectionFactory(this.connectionFactory);
		}
		if (this.errorHandler != null) {
			instance.setErrorHandler(this.errorHandler);
		}
		if (this.messageConverter != null) {
			instance.setMessageConverter(this.messageConverter);
		}
		if (this.acknowledgeMode != null) {
			instance.setAcknowledgeMode(this.acknowledgeMode);
		}
		if (this.channelTransacted != null) {
			instance.setChannelTransacted(this.channelTransacted);
		}
		if (this.autoStartup != null) {
			instance.setAutoStartup(this.autoStartup);
		}
		if (this.phase != null) {
			instance.setPhase(this.phase);
		}
		instance.setListenerId(endpoint.getId());
		// 最重要的一行!!!
		endpoint.setupListenerContainer(instance);
		initializeContainer(instance);

		return instance;
	}

乍一看,都是对MessageListenerContainer实例的初始化,实际上有一行,相当重要
endpoint.setupListenerContainer(instance); ”,这一行最终是走到AbstractRabbitListenerEndpoint.setupListenerContainer

public abstract class AbstractRabbitListenerEndpoint implements RabbitListenerEndpoint, BeanFactoryAware {
	......
	
	// 设置MessageListenerContainer,最重要的就是设置监听的队列名称!!!
	@Override
	public void setupListenerContainer(MessageListenerContainer listenerContainer) {
		SimpleMessageListenerContainer container = (SimpleMessageListenerContainer) listenerContainer;

		boolean queuesEmpty = getQueues().isEmpty();
		boolean queueNamesEmpty = getQueueNames().isEmpty();
		if (!queuesEmpty && !queueNamesEmpty) {
			throw new IllegalStateException("Queues or queue names must be provided but not both for " + this);
		}
		if (queuesEmpty) {
			Collection<String> names = getQueueNames();
			container.setQueueNames(names.toArray(new String[names.size()]));
		}
		else {
			Collection<Queue> instances = getQueues();
			container.setQueues(instances.toArray(new Queue[instances.size()]));
		}

		container.setExclusive(isExclusive());
		if (getPriority() != null) {
			Map<String, Object> args = new HashMap<String, Object>();
			args.put("x-priority", getPriority());
			container.setConsumerArguments(args);
		}

		if (getAdmin() != null) {
			container.setRabbitAdmin(getAdmin());
		}
		setupMessageListener(listenerContainer);
	}
	
	// 创建MessageListener
	protected abstract MessageListener createMessageListener(MessageListenerContainer container);

	// 创建MessageListener,设置到MessageListenerContainer 里
	private void setupMessageListener(MessageListenerContainer container) {
		MessageListener messageListener = createMessageListener(container);
		Assert.state(messageListener != null, "Endpoint [" + this + "] must provide a non null message listener");
		container.setupMessageListener(messageListener);
	}
	......
}

用@RabbitLinstener注解的方法,使用的endpoint是MethodRabbitListenerEndpoint继承自AbstractRabbitListenerEndpoint,所以看看AbstractRabbitListenerEndpointcreateMessageListener方法

public class MethodRabbitListenerEndpoint extends AbstractRabbitListenerEndpoint {
	......
	@Override
	protected MessagingMessageListenerAdapter createMessageListener(MessageListenerContainer container) {
		Assert.state(this.messageHandlerMethodFactory != null,
				"Could not create message listener - MessageHandlerMethodFactory not set");
		MessagingMessageListenerAdapter messageListener = createMessageListenerInstance();
		messageListener.setHandlerMethod(configureListenerAdapter(messageListener));
		String replyToAddress = getDefaultReplyToAddress();
		if (replyToAddress != null) {
			messageListener.setResponseAddress(replyToAddress);
		}
		MessageConverter messageConverter = container.getMessageConverter();
		if (messageConverter != null) {
			messageListener.setMessageConverter(messageConverter);
		}
		if (getBeanResolver() != null) {
			messageListener.setBeanResolver(getBeanResolver());
		}
		return messageListener;
	}

	protected MessagingMessageListenerAdapter createMessageListenerInstance() {
		return new MessagingMessageListenerAdapter(this.bean, this.method);
	}
	
	......
}

从上面代码可以看出,createMessageListener方法返回了一个MessagingMessageListenerAdapter实例,MessagingMessageListenerAdapter实现了MessageListener接口

到这里,我们就能得出一些结论:
1、有@RabbitListener注解的方法,会生成MethodRabbitListenerEndpoint对象
2、通过MethodRabbitListenerEndpoint对象和SimpleRabbitListenerContainerFactory工厂bean,生成SimpleMessageListenerContainer对象
3、SimpleMessageListenerContainer对象保存了要监听的队列名,创建了用于处理消息的MessagingMessageListenerAdapter实例
4、MessagingMessageListenerAdapter持有@RabbitListener注解的对象和方法,起到一个适配器的作用

SimpleMessageListenerContainer是相当重要的一个类,,包装了整个mq消息消费需要的信息:
1、保存了要监听的队列名,启动的时候,根据队列名创建从服务器拉取消息的consumer——BlockingQueueConsumer
2、创建了一个MessagingMessageListenerAdapter对象,当consumer从服务器拿到消息后,由MessagingMessageListenerAdapter进行处理

3、谁来做数据转换?

是MessagingMessageListenerAdapter,有兴趣的,可以看看MessagingMessageListenerAdapter转换参数的源码~~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

@RabbitListener起作用的原理 的相关文章

  • div contenteditable 限制文本框输入的文字个数

  • Flask 打印动态url内容

    Flask 文档 英文 http flask pocoo org docs 0 10 quickstart variable rules 中文 http www pythondoc com flask quickstart html id4
  • Git 第一次提交代码到远程分支

    1 创建远程仓库名称 创建远程仓库名称 2 复制远程仓库链接 这个链接到最后把本地库与远程库关联的时候要使用 复制远程仓库链接 3 到你要上传远程仓库的项目文件中点击右键 选择Git Bash Here 右键上传项目 右键选择Git Bas
  • Android学习之路-常用布局方式(4)

    设置视图间距有两种方式 layout margin 它指定了当前视图与周围平级视图之间的距离 包括 layout marginLeft layout marginTop layout marginRight layout marginBot
  • 微软 WSL 重装操作系统

    如果你对 WSL 还不怎么了解的话 请自行脑补下 简单理解就是 WSL 主要为了让你能够在 Windows 系统中运行 Linux 为什么需要这样 这是因为很多时候一些环境需要在 Linux 下运行 操作系统重装 如果希望对操作系统进行重装
  • 有关音频编码的知识与技术参数

    自然界中的声音非常复杂 波形极其复杂 通常我们采用的是脉冲代码调制编码 即PCM编码 PCM通过抽样 量化 编码三个步骤将连续变化的模拟信号转换为数字编码 1 什么是采样率和采样大小 位 bit 声音其实是一种能量波 因此也有频率和振幅的特
  • mongodb的优缺点

    对比mysql mongo的优缺点有 缺点 l 不支持事务操作 l 占用空间过大 l MongoDB没有如MySQL那样成熟的维护工具 l 无法进行关联表查询 不适用于关系多的数据 l 复杂聚合操作通过mapreduce创建 速度慢 模式自
  • 关于我在(PTA)程序设计类实验辅助教学平台的重修经历

    这里还是有必要说一下 为什么要搞这个该死的PTA的垃圾题目 这是关于我重修发博客的灰暗时刻 简单说一下为什么笔者要重修吧 大一上学期 在学校陆陆续续学C语言 其实也还算好 期末考试配上个平时分也还算能及格 但是经历了疫情之后 一整个学期 大
  • go语言基础学习

    Go 语言基础 学习笔记 一 go语言的特点 既有静态编译语言的安全和性能 又拥有动态语言开发维护的高效性 即 go c python 从c语言中继承了很多的理念 表达式语法 控制结构 基础的数据类型 调用参数传值 指针等等 保留了和c语言
  • 人工智能应用加速落地,推动券商业务+IT双升级|爱分析报告

    券商数字化转型已驶入快车道 多部政策文件相继发布 要求提升金融科技治理水平 加大科技资金投入 深化数字化转型 与此同时 受宏观经济环境下行影响 券商同质化竞争加剧 传统经纪业务增长承压 券商亟待寻求业务新增长点 在政策驱动和业绩承压的双重影
  • 第二十三课:Data setup

    PR Flow内容总览 在整个PR阶段 我们要做的主要可以分为三大部分 data setup部分 就类似于我们一般的Creat new project创建新项目的阶段 此阶段我们主要就是导入各种文件 Technology file TF文件
  • STM32F103常用的HAL库函数

    1 GPIO PA0输出高低电平 HAL GPIO WritePin GPIOA GPIO PIN 0 GPIO PIN RESET 低电平 HAL GPIO WritePin GPIOA GPIO PIN 0 GPIO PIN SET 高
  • valgrind简介与使用

    一 valgrind简介 Valgrind是一款用于内存调试 内存泄漏检测以及性能分析 检测线程错误的软件开发工具 Valgrind 是运行在Linux 上的多用途代码剖析和内存调试软件 主要包括Memcheck Callgrind Cac
  • Mysql API实践

    Mysql API实践 MySql 5 6的API官方见 http dev mysql com doc refman 5 6 en c api function overview html 下面给出一个简单的例子 include
  • Mybatis快速入门

    基本步骤 1 2 导入坐标 使用maven来构建项目只需将所需依赖注入
  • 【Java】基础知识练习题(编程题为主!)

    答案后续发布 欢迎来到小明的练习空间 一 异常 编程题 1 1 题目一 1 2 题目二 1 3 题目三 1 4题目四 二 集合 问答 编程题目 2 1 集合 编程题 Collection接口 2 2 集合 编程题 Collection接口

随机推荐

  • 调用用于获取服务器信息,一种获取测试用例的方法以及服务器

    1 一种获取测试用例的方法 其特征在于 包括 获取被测对象的函数调用关系信息 获取目标函数 所述目标函数为根据所述被测对象的被测版本源码和历史版本源码的差异信息确定的相关函数 所述被测版本源码是所述历史版本源码经过处理得到的 根据所述函数调
  • openEuler怎么查询端口是否被占用-ChatGPT回答

    openEuler怎么查询端口是否被占用 ChatGPT回答 在 openEuler 系统中 你可以使用以下命令来查询端口是否被占用 查看所有使用中的端口 lsof i 查看指定端口是否被占用 lsof i 端口号 例如 查看 80 端口是
  • 高次谐波电压、电流主要有哪些危害?

    高次谐波电压 电流主要有哪些危害 答 高次谐波电流超过一定限度会引起发电机 变压器 电动机损失增大 产生过热 高次谐波电压可能引起设备异常振动 继电保护误动 计量误差增大 晶闸管装置失控和影响通信质量等 高次谐波电流 电压更容易使电力电容器
  • JS获取随机或指定数据

    1 不能全是相同的数字或者字母 如 000000 111111 222222 333333等等 2 不能是连续数字 如 123456 12345678 87654321等等 顺序表 static String orderStr static
  • 模板类的继承问题

    首先大家来看这段代码 plain view plain copy class A public void Show cout lt lt A Show lt lt endl void Fun cout lt lt A Fun lt lt e
  • [CVPR2020] DoveNet: Deep Image Harmonization via Domain Verification 论文解读

    论文地址和代码 数据库和代码已公布 https github com bcmi Image Harmonization Datasets 论文地址 https arxiv org abs 1911 13239 一 简介 图像合成 image
  • python selenium定位元素报错:‘WebDriver‘ object has no attribute ‘find_element_by_id

    标题问题的解决办法参考了这篇文章 然后成功了 1条消息 关于新版本selenium定位元素报错 WebDriver object has no attribute find element by id 等问题 selenium新版本定位 热
  • ADC各参数含义

    文章目录 前言 一 ADC工作原理 二 详细参数 1 分辨率 2 转换率 采样率 转化时间 3 最低有效位 LSB 最高有效位 MSB 4 量化误差 5 SNR 6 失调误差 7 增益误差 8 微分非线性 DNL 9 积分非线性 INL 1
  • 《数据分析思维》:分析方法与业务知识

    小飞象 读书会 生活从来不会刻意亏欠谁 它给你一块阴影 必会在不远处撒下阳光 读书交流 3期 数据分析方法与业务知识 data analysis 分享人 木兮 欢迎大家参加这次读书会的直播分享 本次分享由学委木兮来带大家梳理一下 数据分析思
  • 【STM32篇】步进电机之S型曲线

    使用步进电机的S曲线算法的目的是为了使电机缓慢加速到目标转速或从高转速减速到0 防止电机在高转速时立即停止而对电机造成损伤 减少电机的使用寿命 本文主要讲述S型算法的使用 对于具体的原理 可通过其他博主的文章学习 图1 S算法加减速图 如图
  • libvirt笔记 PCI设备直通

    PCI设备直通功能允许将主机上的物理PCI设备直接分配给来宾机 客户操作系统驱动程序可以直接使用设备硬件 而无需依赖主机操作系统的任何驱动程序功能 在使用PCI设备直通时需要注意一些事项 当将PCI设备直接分配给客户机时 如果不首先从客户机
  • 静态代码和动态代码的区别_动态改变的代码设计思想

    前言 我看过很多书 知道那些作者在推广什么 相对书里的东西落地 还是一个不容易的过程 我看过很多行业的代码 持续多年的积累 聊过一些老板 对代码改造的看法 为什么 程序员这么难 我将用4个方向 来解释代码设计中的设计思路和方法 给大家展现一
  • Android登录 之 GooglePlay登录

    想法 写了好久代码了 但是都没有具体的总结下 总是用到的时候 再去翻看资料 一看就是一堆 到最后都不一定能管用 近期做了一些项目 涉及到了登录授权 细回想下 自己接过的登录授权还真心不少 不同的授权都会一一列举 以后方便自己 也希望能够方便
  • Fatal error: Cannot declare class ***, because the name is already in use in D:\www\test.php

    报错信息 Fatal error Cannot declare class because the name is already in use in D www test php on line 5 报错原因 该class 文件在其他引入
  • Tomcat控制台打印输出中文时的乱码问题

    这个问题我参考了许多网上的解决方法 这个方法是比较迅速与方便的 产生这个问题的原因是Windows默认编码集为GBK 由于使用startup bat启动Tomcat时 它会读取catalina bat的代码并打开一个新窗口运行 打开的cmd
  • 信号量详细解说

    1 信号量概述 进化版的互斥锁 1 gt N 由于互斥锁的粒度比较大 如果我们希望在多个线程间对某一对象的部分数据进行共享 使用互斥锁是没有办法实现的 只能将整个数据对象锁住 这样虽然达到了多线程操作共享数据时保证数据正确性的目的 却无形中
  • webdriver安装

    首先了解自己所需使用的浏览器 这里以谷歌浏览器为例 火狐 Edge安装方式相同 找相应webdriver即可 查看自己浏览器的版本 去官网找对应版本的webdriver 网址 http npm taobao org mirrors chro
  • SD3.0协议解读三

    SD卡功能描述 所有主机和SD卡间的通信都是由主机控制的 这和USB是一致的 例如 U盘并没有主动通知USB控制器的能力 USB鼠标也没有主动通知USB控制器的能力 当然 SD卡也是没有主动通知SD控制器的能力的 主机发送的命令有两种 一种
  • 几个开发模式

    瀑布模型 开发过程顺序进行 过程规范 有质量保证 但是不能够应对需求变化 更改代价较高 灵活性较低 V模型 基于瀑布模型 但是更强调测试过程 有质量保证 显示出不同层次上的测试方法 并且将测试阶段与开发阶段相联系 原型模型 主要解决核心问题
  • @RabbitListener起作用的原理

    一 前言 在spring中 定义rabbitMq的消费者可以相当方便 只需要在消息处理类或者类方法加上 RabbitListener注解 指定队列名称即可 如下代码 Component public class RabbitMqListen