我的编程空间,编程开发者的网络收藏夹
学习永远不晚

@RabbitListener起作用的原理是什么

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

@RabbitListener起作用的原理是什么

这篇文章主要讲解了“@RabbitListener起作用的原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“@RabbitListener起作用的原理是什么”吧!

一、前言

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

如下代码:

@Componentpublic 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消息的方法,入参可以那么随意?

有经验的程序员,可能会有这样的设想:

单纯看这些listener的代码,只是定义了由spring管理的bean,要能监听rabbitMq的消息,肯定需要有另外一个类,这个类会扫描所有加了@RabbitListener的bean,进行加工。

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

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配置类

@Configurationpublic 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方法的代码,大致流程为:

通过AopUtils得到bean代理的对象的class

判断缓存中是否有该class的类型元数据,如果没有则调用buildMetadata方法生成类型元数据并放入缓存

遍历加了@RabbitListener注解的方法,调用processAmqpListener方法进行处理

调用processMultiMethodListeners方法对加了@RabbitHandler的方法进行处理

关于buildMetadata方法:

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

关于processAmqpListener方法:

没有什么实际内容,就干两个事情:

检查一下是否使用jdk代理,使用jdk代理方式必须实现了接口

new一个MethodRabbitListenerEndpoint对象,交由processListener方法进行处理

关于processListener方法:

前面大半代码都是对MethodRabbitListenerEndpoint对象的属性设置:处理消息的bean、消息处理方法的工厂类、监听的队列名。。。。

其中要关注一下setMessageHandlerMethodFactory方法,查看MessageHandlerMethodFactory接口的源码

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

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

通过beanFactory获取RabbitListenerContainerFactory类的bean。

调用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,所以看看AbstractRabbitListenerEndpoint的createMessageListener方法

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接口

到这里,我们就能得出一些结论:

有@RabbitListener注解的方法,会生成MethodRabbitListenerEndpoint对象

通过MethodRabbitListenerEndpoint对象和SimpleRabbitListenerContainerFactory工厂bean,生成SimpleMessageListenerContainer对象

SimpleMessageListenerContainer对象保存了要监听的队列名,创建了用于处理消息的MessagingMessageListenerAdapter实例

MessagingMessageListenerAdapter持有@RabbitListener注解的对象和方法,起到一个适配器的作用

SimpleMessageListenerContainer是相当重要的一个类,,包装了整个mq消息消费需要的信息:

保存了要监听的队列名,启动的时候,根据队列名创建从服务器拉取消息的consumer&mdash;&mdash;BlockingQueueConsumer

创建了一个MessagingMessageListenerAdapter对象,当consumer从服务器拿到消息后,由MessagingMessageListenerAdapter进行处理

谁来做数据转换?

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

感谢各位的阅读,以上就是“@RabbitListener起作用的原理是什么”的内容了,经过本文的学习后,相信大家对@RabbitListener起作用的原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

@RabbitListener起作用的原理是什么

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

@RabbitListener起作用的原理是什么

这篇文章主要讲解了“@RabbitListener起作用的原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“@RabbitListener起作用的原理是什么”吧!一、前言在spring
2023-07-05

解读@RabbitListener起作用的原理

这篇文章主要介绍了解读@RabbitListener起作用的原理,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
2023-03-21

css的样式不起作用的原因是什么

这篇文章给大家分享的是有关css的样式不起作用的原因是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。css的全称是什么css的全称是Cascading Style Sheets(层叠样式表),它是一种用来表现
2023-06-14

CSS中overflow-y: visible;不起作用的原因是什么

这篇文章主要介绍CSS中overflow-y: visible;不起作用的原因是什么,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!场景最近要做的一个需求是移动端的h6页面,要求有一排可选择的卡片, 超出容器部分可以左
2023-06-08

bucket的原理及作用是什么

bucket的原理及作用是什么,针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。存储空间是您用于存储对象(Object)的容器,所有的对象都必须隶属于某个存储空间。您可以设置和
2023-06-03

nginx作用和工作原理是什么

nginx是一种高性能的开源Web服务器和反向代理服务器。它可以用于处理静态和动态的HTTP、HTTPS、SMTP、POP3和IMAP协议,并具有负载均衡、反向代理、缓存、压缩等功能。nginx的工作原理如下:1. 当客户端发送请求时,先到
2023-10-07

Git的工作原理是什么

这篇文章主要讲解了“Git的工作原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“Git的工作原理是什么”吧!本文图解Git中的最常用命令。如果你稍微理解Git的工作原理,这篇文章能够
2023-06-15

ipsec的工作原理是什么

IPSec(Internet Protocol Security)是一种用于保护网络通信安全的协议套件。它可以提供数据加密、数据完整性验证和身份验证等功能,以保护IP通信的安全性。IPSec的工作原理包括以下几个关键步骤:1. 认证阶段(A
2023-10-10

NTPD的工作原理是什么

今天小编给大家分享一下NTPD的工作原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Network Time Pro
2023-06-28

HashMap的工作原理是什么

原理介绍如下:HashMap是基于Hash算法实现的。(在线学习视频教程分享:java视频教程)在使用put(key,value)方法时,HashMap会根据key.hashCode()方法得出key的hash值,然后根据hash值找到对应的bucket位置,
HashMap的工作原理是什么
2019-10-28

docker的工作原理是什么

Docker的工作原理主要涉及以下几个方面:1. 镜像(Image):Docker利用镜像来构建容器,镜像是一个只读的文件系统,包含了运行应用所需的所有文件和设置。镜像可以通过Docker Hub获取,也可以通过Dockerfile自定义构
2023-09-27

Hadoop的工作原理是什么

Hadoop的工作原理是基于分布式存储和计算的概念。Hadoop由两个核心组件组成:Hadoop分布式文件系统(HDFS)和MapReduce计算框架。HDFS是一种分布式文件系统,它将大文件分割成多个小块,并在集群中的多个计算节点上进行
Hadoop的工作原理是什么
2024-03-11

RabbitMQ的工作原理是什么

这篇文章给大家介绍RabbitMQ的工作原理是什么,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。RabbitMQ简介在介绍RabbitMQ之前实现要介绍一下MQ,MQ是什么?MQ全称是Message Queue,可以理
2023-06-19

puppet的工作原理是什么

今天小编给大家分享一下puppet的工作原理是什么的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。Puppet 的介绍什么是P
2023-06-28

chatgpt的工作原理是什么

chatgpt的工作原理是以电子邮件和电子传真方式为用户提供多媒体信息传输,利用一个基于网络的、安全的点对点通讯系统为用户进行多人的声音、图像和数据交换。chatgpt的原理ChatGPT是一个基于网络的、支持多协议的、使用 Xchat 软
2023-02-09

HDFS的工作原理是什么

这篇文章将为大家详细讲解有关HDFS的工作原理是什么,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。HDFS的工作原理HDFS支持在计算节点之间快速传输数据。在开始阶段,它与MapReduce紧密耦合——M
2023-06-05

MapReduce的工作原理是什么

本篇文章为大家展示了MapReduce的工作原理是什么,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。开始聊mapreduce,mapreduce是hadoop的计算框架,我学hadoop是从hive
2023-06-03

fastdfs的工作原理是什么

FastDFS(Fast Distributed File System)是一个开源的分布式文件系统,主要用于解决大规模数据存储和访问的问题。它的工作原理可以分为以下几个步骤:客户端上传文件:客户端将文件分割成固定大小的块,并选择一个可用的
2023-10-24

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录