返回顶部
首页 > 资讯 > 后端开发 > Python >解读@RabbitListener起作用的原理
  • 378
分享到

解读@RabbitListener起作用的原理

@RabbitListener作用@RabbitListener的原理 2023-03-21 11:03:18 378人浏览 泡泡鱼

Python 官方文档:入门教程 => 点击学习

摘要

目录一、前言二、源码分析总结一、前言 在spring中,定义RabbitMQ的消费者可以相当方便,只需要在消息处理类或者类方法加上@RabbitListener注解,指定队列名称即可

一、前言

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,所以看看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接口

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

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转换参数的源码~~

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: 解读@RabbitListener起作用的原理

本文链接: https://lsjlt.com/news/200665.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • 解读@RabbitListener起作用的原理
    目录一、前言二、源码分析总结一、前言 在spring中,定义rabbitMq的消费者可以相当方便,只需要在消息处理类或者类方法加上@RabbitListener注解,指定队列名称即可...
    99+
    2023-03-21
    @RabbitListener作用 @RabbitListener的原理
  • @RabbitListener起作用的原理是什么
    这篇文章主要讲解了“@RabbitListener起作用的原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“@RabbitListener起作用的原理是什么”吧!一、前言在spring...
    99+
    2023-07-05
  • @Transactional注解不起作用的原因分析及解决
    目录Transactional失效场景介绍第一种第二种第三种@Transactional注解不起作用原理分析第一种不创建代理对象不进行代理调用第二种第三种Transactional失...
    99+
    2024-04-02
  • css中hover不起作用的原因
    这篇“css中hover不起作用的原因”除了程序员外大部分人都不太理解,今天小编为了让大家更加理解“css中hover不起作用的原因”,给大家总结了以下内容,具有一定借鉴价值,内容详细步骤清晰,细节处理妥当,希望大家通过这篇文章有所收获,下...
    99+
    2023-06-06
  • css不起作用的原因有哪些
    这篇文章主要介绍css不起作用的原因有哪些,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完! css不起作用的原因:1、未关联外部样式表;2、样式表保存的编码...
    99+
    2024-04-02
  • CSS中margin不起作用的原因及怎么解决
    本篇内容介绍了“CSS中margin不起作用的原因及怎么解决”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!...
    99+
    2024-04-02
  • 如何解读MySQL的InnoDB引擎日志工作原理
    这篇文章主要介绍了如何解读MySQL的InnoDB引擎日志工作原理,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。当你使用UPDATE, IN...
    99+
    2024-04-02
  • Python 容器和索引:一起来了解它们的工作原理
    Python 是一种流行的编程语言,具有简单易学、功能强大、灵活等特点,因此在数据科学、人工智能、Web 开发等领域广泛应用。Python 提供了多种容器类型,例如列表、元组、集合、字典等,这些容器类型提供了不同的数据存储和操作方式。本文将...
    99+
    2023-09-21
    容器 ide 索引
  • Vue.use()的作用及原理解析
    目录前言Vue.use是什么?Vue.use() 的源码中的逻辑Vue.use()什么时候使用?前言 最近帮忙面试前端的时候,就随口一问,发现很多2年以上的vue开发者说不出vue....
    99+
    2024-04-02
  • php css不起作用的原因有哪些
    这篇文章主要介绍“php css不起作用的原因有哪些”,在日常操作中,相信很多人在php css不起作用的原因有哪些问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”php css不起作用的原因有哪些”的疑惑有所...
    99+
    2023-07-05
  • css不起作用是什么原因
    这篇文章主要讲解了“css不起作用是什么原因”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“css不起作用是什么原因”吧! css...
    99+
    2024-04-02
  • java读写锁的工作原理是什么
    读写锁是一种特殊的锁机制,允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。读写锁的工作原理如下: 当一个线程想要读取...
    99+
    2024-04-03
    java
  • Golang中channel的原理解读(推荐)
    数据结构 channel的数据结构在$GOROOT/src/runtime/chan.go文件下: type hchan struct { qcount uint ...
    99+
    2024-04-02
  • css的样式不起作用的原因是什么
    这篇文章给大家分享的是有关css的样式不起作用的原因是什么的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。css的全称是什么css的全称是Cascading Style Sheets(层叠样式表),它是一种用来表现...
    99+
    2023-06-14
  • 详解SpringBootStarter作用及原理
    目录前言什么是 StarterStarter 的作用spring 整合组件spring-boot 整合组件Starter 原理前言 有没有在入行后直接基于 SpringBoot 开发...
    99+
    2023-05-17
    SpringBoot Starter作用原理 SpringBoot Starter作用 SpringBoot Starter原理
  • @ResponseBody注解作用和原理
         @ResponseBody这个注解通常使用在控制层(controller)的方法上,其作用是将方法的返回值以特定的格式写入到response的body区域,进而将数据返回给客户端。当方法上面...
    99+
    2023-06-02
  • C++static的作用解读
    目录1. 隐藏2. 保持变量内容的持久3. 默认初始化为 04. 多个对象之间数据共享5. 注意总结1. 隐藏 当同时编译多个文件时,所有未加 static 前缀的全局变量和函数都具...
    99+
    2023-02-21
    C++ static C++ static的作用 static作用
  • 探讨Vue占位符不起作用的原因和解决方法
    Vue是一个流行的前端框架,用于构建现代化的Web应用程序。它提供了各种功能,例如数据绑定、组件化、路由、状态管理和构建工具等。在Vue中,占位符是一种常见的技术,用于展示模板中的数据或组件,但有时它可能不起作用。在本文中,我们将探讨Vue...
    99+
    2023-05-14
  • @JsonSerialize不起作用的解决方案
    目录@JsonSerialize不起作用在项目中 当字段实体类为Long类型时但是这里有个小坑@JsonSerialize正确使用1. 写一个负责转换的类2. 在实体类上需要装换的字...
    99+
    2024-04-02
  • Java中读写锁ReadWriteLock的原理与应用详解
    目录什么是读写锁?为什么需要读写锁?读写锁的特点读写锁的使用场景读写锁的主要成员和结构图读写锁的实现原理读写锁总结Java并发编程提供了读写锁,主要用于读多写少的场景,今天我就重点来...
    99+
    2024-04-02
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作