Spring Cloud Stream 源码解析
Spring Cloud Stream 是一个消息驱动微服务的框架。
应用程序通过inputs 或者outputs 来与 Spring Cloud Stream 中binder 交互,通过我们配置来 binding ,而 Spring Cloud Stream 的 binder 负责与消息中间件交互。所以,我们只需要搞清楚如何与 Spring Cloud Stream 交互就可以方便使用消息驱动的方式。
通过使用Spring Integration来连接消息代理中间件以实现消息事件驱动。Spring Cloud Stream 为一些供应商的消息中间件产品提供了个性化的自动化配置实现,引用了发布-订阅、消费组、分区的三个核心概念。目前仅支持RabbitMQ、Kafka。
下面开始进行分析,首先引入pom文件。
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>eureka.stream</groupId>
<artifactId>stream</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springstream</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Brixton.SR5</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
复制代码
其中spring-cloud-starter-stream-rabbit就是引入的stream的框架,也可以支持spring-cloud-starter-stream-kafka。这里以rabbit做分析。
首先我们先创建一个简单的例子。
先创建消息input、output管道类StreamSendClient。
import org.springframework.cloud.stream.annotation.Input;
import org.springframework.cloud.stream.annotation.Output;
import org.springframework.cloud.stream.messaging.Sink;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.SubscribableChannel;
public interface StreamSendClient {
@Output("testMessage")
MessageChannel output();
@Input("testMessage")
MessageChannel input();
}
复制代码
再创建一个消息处理类SinkReceiver。上面加上@EnableBinding注解。注解定义的类为StreamSendClient。
@EnableBinding({StreamSendClient.class})
public class SinkReceiver {
@StreamListener("testMessage")
public void reveive(Object payload){
System.out.println("Received:" + payload);
}
}
复制代码
创建启动类StreamApplication。
@SpringBootApplication
public class StreamApplication {
public static void main(String[] args) {
ConfigurableApplicationContext run = SpringApplication.run(StreamApplication.class, args);
StreamSendClient streamClient = (StreamSendClient)run.getBean("com.springcloud.eurekaclient.StreamSendClient");
streamClient.output().send(MessageBuilder.withPayload("from streamClient").build());
}
}
复制代码
执行之后变可以在控制台发现打印信息:Received:from streamClient。同时也可以看到rabbitmq控制台中队列包含了testMessage。
下面开始分析。
首先启动类没有新添任何注解,在SinkReceiver上面有@EnableBinding注解。
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Configuration
@Import({ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class,
SpelExpressionConverterConfiguration.class})
@EnableIntegration
public @interface EnableBinding {
/**
* A list of interfaces having methods annotated with {@link Input} and/or
* {@link Output} to indicate bindable components.
*/
Class<?>[] value() default {};
}
复制代码
可以知道:
1、该类是一个@Component类
2、该类导入了ChannelBindingServiceConfiguration.class, BindingBeansRegistrar.class, BinderFactoryConfiguration.class, SpelExpressionConverterConfiguration.class类。
3、开启了EnableIntegration注解。Spring Integration的定位是一种企业服务总线 ESB(Enterprise Service Bus),在Spring Integration中,通道被抽象成两种表现形式:PollableChannel和SubscribableChannel,都是继承了MessageChannel。
ChannelBindingServiceConfiguration类分析:
@Configuration
@EnableConfigurationProperties(ChannelBindingServiceProperties.class)
public class ChannelBindingServiceConfiguration {
private static final String ERROR_CHANNEL_NAME = "error";
@Autowired
private MessageBuilderFactory messageBuilderFactory;
@Autowired(required = false)
private ObjectMapper objectMapper;
/**
* User defined custom message converters
*/
@Autowired(required = false)
private List<AbstractFromMessageConverter> customMessageConverters;
@Bean
// This conditional is intentionally not in an autoconfig (usually a bad idea) because
// it is used to detect a ChannelBindingService in the parent context (which we know
// already exists).
@ConditionalOnMissingBean(ChannelBindingService.class)
public ChannelBindingService bindingService(ChannelBindingServiceProperties channelBindingServiceProperties,
BinderFactory<MessageChannel> binderFactory) {
return new ChannelBindingService(channelBindingServiceProperties, binderFactory);
}
@Bean
public BindableChannelFactory channelFactory(CompositeMessageChannelConfigurer compositeMessageChannelConfigurer) {
return new DefaultBindableChannelFactory(compositeMessageChannelConfigurer);
}
@Bean
public CompositeMessageChannelConfigurer compositeMessageChannelConfigurer(
MessageConverterConfigurer messageConverterConfigurer) {
List<MessageChannelConfigurer> configurerList = new ArrayList<>();
configurerList.add(messageConverterConfigurer);
return new CompositeMessageChannelConfigurer(configurerList);
}
@Bean
@DependsOn("bindingService")
public OutputBindingLifecycle outputBindingLifecycle() {
return new OutputBindingLifecycle();
}
@Bean
@DependsOn("bindingService")
public InputBindingLifecycle inputBindingLifecycle() {
return new InputBindingLifecycle();
}
@Bean
@DependsOn("bindingService")
public ContextStartAfterRefreshListener contextStartAfterRefreshListener() {
return new ContextStartAfterRefreshListener();
}
@Bean
public static StreamListenerAnnotationBeanPostProcessor bindToAnnotationBeanPostProcessor(
@Lazy BinderAwareChannelResolver binderAwareChannelResolver,
@Lazy MessageHandlerMethodFactory messageHandlerMethodFactory) {
return new StreamListenerAnnotationBeanPostProcessor(binderAwareChannelResolver,
messageHandlerMethodFactory);
}
}
复制代码
ChannelBindingServiceConfiguration装载了重要的Bean:
1、ChannelBindingService:负责创建生产者、消费者的MessageChannel,以及RabbitMQ中的交换器(Exchange)、Queue等。
2、inputBindingLifecycle、outputBindingLifecycle:主要负责启动后,调用ChannelBindingService进行创建。
3、StreamListenerAnnotationBeanPostProcessor:负责方法上有@StreamListener注解的方法和RabbitMQ消费channel创建关联关系。即当有rabbitmq消息推送过来,执行方法上有@StreamListener注解的方法。
BindingBeansRegistrar类分析:
BindingBeansRegistrar主要对实现@EnableBinding(StreamSendClient.class)中的class进行分析。
public class BindingBeansRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata metadata,
BeanDefinitionRegistry registry) {
AnnotationAttributes attrs = AnnotatedElementUtils.getMergedAnnotationAttributes(
ClassUtils.resolveClassName(metadata.getClassName(), null),
EnableBinding.class);
for (Class<?> type : collectClasses(attrs, metadata.getClassName())) {
BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions(type,
type.getName(), registry);
BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions(
ClassUtils.resolveClassName(metadata.getClassName(), null), type,
registry);
}
}
private Class<?>[] collectClasses(AnnotationAttributes attrs, String className) {
EnableBinding enableBinding = AnnotationUtils.synthesizeAnnotation(attrs,
EnableBinding.class, ClassUtils.resolveClassName(className, null));
return enableBinding.value();
}
}
复制代码
通过collectClasses方法,获取EnableBinding中的enableBinding.value(),也就是之前例子中的StreamSendClient类。之后的BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions方法
public static void registerChannelBeanDefinitions(Class<?> type,
final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
ReflectionUtils.doWithMethods(type, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = getChannelName(input, method);
registerInputChannelBeanDefinition(input.value(), name,
channelInterfaceBeanName, method.getName(), registry);
}
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = getChannelName(output, method);
registerOutputChannelBeanDefinition(output.value(), name,
channelInterfaceBeanName, method.getName(), registry);
}
}
});
}
复制代码
public static void registerChannelBeanDefinitions(Class<?> type,
final String channelInterfaceBeanName, final BeanDefinitionRegistry registry) {
ReflectionUtils.doWithMethods(type, new MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException,
IllegalAccessException {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = getChannelName(input, method);
registerInputChannelBeanDefinition(input.value(), name,
channelInterfaceBeanName, method.getName(), registry);
}
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = getChannelName(output, method);
registerOutputChannelBeanDefinition(output.value(), name,
channelInterfaceBeanName, method.getName(), registry);
}
}
});
}
复制代码
public static void registerInputChannelBeanDefinition(String qualifierValue,
String name, String channelInterfaceBeanName,
String channelInterfaceMethodName, BeanDefinitionRegistry registry) {
registerChannelBeanDefinition(Input.class, qualifierValue, name,
channelInterfaceBeanName, channelInterfaceMethodName, registry);
}
private static void registerChannelBeanDefinition(
Class<? extends Annotation> qualifier, String qualifierValue, String name,
String channelInterfaceBeanName, String channelInterfaceMethodName,
BeanDefinitionRegistry registry) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition();
rootBeanDefinition.setFactoryBeanName(channelInterfaceBeanName);
rootBeanDefinition.setUniqueFactoryMethodName(channelInterfaceMethodName);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(qualifier,
qualifierValue));
registry.registerBeanDefinition(name, rootBeanDefinition);
}
复制代码
找到StreamSendClient类中的@input和@output注解,将两个方法注入成Bean。Bean的名称为StreamSendClient中@Input注解的值,也就是testMessage。并设置定义 BeanDefinition 的生成该Bean的工厂类为StreamSendClient,生成该Bean(testMessage)的方法setUniqueFactoryMethodName为input()。
BindingBeanDefinitionRegistryUtils.registerChannelsQualifiedBeanDefinitions方法:
public static void registerChannelsQualifiedBeanDefinitions(Class<?> parent,
Class<?> type, final BeanDefinitionRegistry registry) {
if (type.isInterface()) {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(
BindableProxyFactory.class);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
Bindings.class, parent));
rootBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(
type);
registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
}
else {
RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(type);
rootBeanDefinition.addQualifier(new AutowireCandidateQualifier(
Bindings.class, parent));
registry.registerBeanDefinition(type.getName(), rootBeanDefinition);
}
}
复制代码
注入一个beanName为StreamSendClient,Qualifier名为Bindings,BeanClass为BindableProxyFactory(Bindable)类型的Bean对象。BindableProxyFactory实现了Bindable接口。
我们这里在看一下BindableProxyFactory源码。
public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Object>, Bindable, InitializingBean {
private Map<String, ChannelHolder> inputHolders = new HashMap<>();
private Map<String, ChannelHolder> outputHolders = new HashMap<>();
//实现了动态代理,所以当获取input和output方法获取channel时,会通过这里获得
public synchronized Object invoke(MethodInvocation invocation) throws Throwable {
MessageChannel messageChannel = null;
Method method = invocation.getMethod();
if (MessageChannel.class.isAssignableFrom(method.getReturnType())) {
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
messageChannel = this.inputHolders.get(name).getMessageChannel();
}
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
messageChannel = this.outputHolders.get(name).getMessageChannel();
}
}
//ignore
return messageChannel;
}
//实现了InitializingBean,在Bean生成后,会将input和output两个注解生成对应的channel
//类,默认是DirectChannel类
public void afterPropertiesSet() throws Exception {
ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
Assert.notNull(channelFactory, "Channel Factory cannot be null");
Input input = AnnotationUtils.findAnnotation(method, Input.class);
if (input != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(input, method);
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
inputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
}
else {
inputHolders.put(name, new ChannelHolder(sharedChannel, false));
}
}
}
});
ReflectionUtils.doWithMethods(type, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException {
Output output = AnnotationUtils.findAnnotation(method, Output.class);
if (output != null) {
String name = BindingBeanDefinitionRegistryUtils.getChannelName(output, method);
validateChannelType(method.getReturnType());
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
outputHolders.put(name, new ChannelHolder(channelFactory.createSubscribableChannel(name), true));
}
else {
outputHolders.put(name, new ChannelHolder(sharedChannel, false));
}
}
}
});
}
private void validateChannelType(Class<?> channelType) {
Assert.isTrue(SubscribableChannel.class.equals(channelType) || MessageChannel.class.equals(channelType),
"A bound channel should be either a '" + MessageChannel.class.getName() + "', " +
" or a '" + SubscribableChannel.class.getName() + "'");
}
private MessageChannel locateSharedChannel(String name) {
return this.sharedChannelRegistry != null ?
this.sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
}
private String getNamespacePrefixedChannelName(String name) {
return this.channelNamespace + "." + name;
}
@Override
public synchronized Object getObject() throws Exception {
if (this.proxy == null) {
ProxyFactory factory = new ProxyFactory(this.type, this);
this.proxy = factory.getProxy();
}
return this.proxy;
}
@Override
public Class<?> getObjectType() {
return this.type;
}
@Override
public boolean isSingleton() {
return true;
}
public void bindInputs(ChannelBindingService channelBindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
String inputChannelName = channelHolderEntry.getKey();
ChannelHolder channelHolder = channelHolderEntry.getValue();
if (channelHolder.isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
}
channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
}
}
}
@Override
public void bindOutputs(ChannelBindingService channelBindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding outputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
ChannelHolder channelHolder = channelHolderEntry.getValue();
String outputChannelName = channelHolderEntry.getKey();
if (channelHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, outputChannelName));
}
channelBindingService.bindProducer(channelHolder.getMessageChannel(), outputChannelName);
}
}
}
@Override
public void unbindInputs(ChannelBindingService channelBindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding inputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
}
channelBindingService.unbindConsumers(channelHolderEntry.getKey());
}
}
}
@Override
public void unbindOutputs(ChannelBindingService channelBindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding outputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputHolders.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
}
channelBindingService.unbindProducers(channelHolderEntry.getKey());
}
}
}
复制代码
BinderFactoryConfiguration类分析
@Configuration
public class BinderFactoryConfiguration {
@Bean
@ConditionalOnMissingBean(BinderFactory.class)
public BinderFactory<?> binderFactory(BinderTypeRegistry binderTypeRegistry,
ChannelBindingServiceProperties channelBindingServiceProperties) {
Map<String, BinderConfiguration> binderConfigurations = new HashMap<>();
Map<String, BinderProperties> declaredBinders = channelBindingServiceProperties.getBinders();
boolean defaultCandidatesExist = false;
Iterator<Map.Entry<String, BinderProperties>> binderPropertiesIterator = declaredBinders.entrySet().iterator();
while (!defaultCandidatesExist && binderPropertiesIterator.hasNext()) {
defaultCandidatesExist = binderPropertiesIterator.next().getValue().isDefaultCandidate();
}
for (Map.Entry<String, BinderProperties> binderEntry : declaredBinders.entrySet()) {
BinderProperties binderProperties = binderEntry.getValue();
if (binderTypeRegistry.get(binderEntry.getKey()) != null) {
binderConfigurations.put(binderEntry.getKey(),
new BinderConfiguration(binderTypeRegistry.get(binderEntry.getKey()),
binderProperties.getEnvironment(), binderProperties.isInheritEnvironment(),
binderProperties.isDefaultCandidate()));
}
else {
Assert.hasText(binderProperties.getType(),
"No 'type' property present for custom binder " + binderEntry.getKey());
BinderType binderType = binderTypeRegistry.get(binderProperties.getType());
Assert.notNull(binderType, "Binder type " + binderProperties.getType() + " is not defined");
binderConfigurations.put(binderEntry.getKey(),
new BinderConfiguration(binderType, binderProperties.getEnvironment(),
binderProperties.isInheritEnvironment(), binderProperties.isDefaultCandidate()));
}
}
if (!defaultCandidatesExist) {
for (Map.Entry<String, BinderType> entry : binderTypeRegistry.getAll().entrySet()) {
binderConfigurations.put(entry.getKey(),
new BinderConfiguration(entry.getValue(), new Properties(), true, true));
}
}
DefaultBinderFactory<?> binderFactory = new DefaultBinderFactory<>(binderConfigurations);
binderFactory.setDefaultBinder(channelBindingServiceProperties.getDefaultBinder());
return binderFactory;
}
}
复制代码
主要就是创建一个DefaultBinderFactory的工厂。
ChannelBindingServiceConfiguration, BindingBeansRegistrar, BinderFactoryConfiguration三个装载Bean的内容大概介绍完毕了,现在开始说一下加载过程:
1、ChannelBindingServiceConfiguration类加载的Bean对象outputBindingLifecycle,inputBindingLifecycle。我们拿inputBindingLifecycle做分析,outputBindingLifecycle类似。
@Bean
@DependsOn("bindingService")
public OutputBindingLifecycle outputBindingLifecycle() {
return new OutputBindingLifecycle();
}
@Bean
@DependsOn("bindingService")
public InputBindingLifecycle inputBindingLifecycle() {
return new InputBindingLifecycle();
}
复制代码
inputBindingLifecycle类实现了SmartLifecycle接口,在spring启动后会执行start方法。
public class InputBindingLifecycle implements SmartLifecycle, ApplicationContextAware {
public void start() {
if (!running) {
// retrieve the ChannelBindingService lazily, avoiding early initialization
try {
ChannelBindingService channelBindingService = this.applicationContext
.getBean(ChannelBindingService.class);
Map<String, Bindable> bindables = this.applicationContext
.getBeansOfType(Bindable.class);
for (Bindable bindable : bindables.values()) {
//bindables.values即为@@EnableBinding({StreamSendClient.class})类,BeanClass为BindableProxyFactory
bindable.bindInputs(channelBindingService);
}
}
catch (BeansException e) {
throw new IllegalStateException(
"Cannot perform binding, no proper implementation found", e);
}
this.running = true;
}
}
}
BindableProxyFactory.bindInputs方法如下:
public void bindInputs(ChannelBindingService channelBindingService) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputHolders.entrySet()) {
String inputChannelName = channelHolderEntry.getKey();
ChannelHolder channelHolder = channelHolderEntry.getValue();
if (channelHolder.isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, inputChannelName));
}
//这里继续进入
channelBindingService.bindConsumer(channelHolder.getMessageChannel(), inputChannelName);
}
}
public Collection<Binding<MessageChannel>> bindConsumer(MessageChannel inputChannel, String inputChannelName) {
....................
validate(consumerProperties);
for (String target : channelBindingTargets) {
//继续进入binder.bindConsumer方法
Binding<MessageChannel> binding = binder.bindConsumer(target, channelBindingServiceProperties.getGroup(inputChannelName), inputChannel, consumerProperties);
bindings.add(binding);
}
this.consumerBindings.put(inputChannelName, bindings);
return bindings;
}
}
会进入RabbitMessageChannelBinder.bindConsumer方法
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
String prefix = properties.getExtension().getPrefix();
String exchangeName = applyPrefix(prefix, name);
TopicExchange exchange = new TopicExchange(exchangeName);
//创建交换器
declareExchange(exchangeName, exchange);
String queueName = applyPrefix(prefix, baseQueueName);
boolean partitioned = !anonymousConsumer && properties.isPartitioned();
boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
Queue queue;
......................
//创建队列
declareQueue(queueName, queue);
if (partitioned) {
String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
}
else {
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
}
Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
.................
return binding;
}
复制代码
可以看到通过inputBindingLifecycle创建了RabbitMq的交换器(Exchange)和队列。
同理通过outputBindingLifecycle启动后会创建生产者。
2、ChannelBindingServiceConfiguration类加载的Bean对象StreamListenerAnnotationBeanPostProcessor。StreamListenerAnnotationBeanPostProcessor实现了BeanPostProcessor接口。会执行postProcessAfterInitialization方法。
public class StreamListenerAnnotationBeanPostProcessor implements BeanPostProcessor, ApplicationContextAware, SmartInitializingSingleton {
public Object postProcessAfterInitialization(final Object bean, String beanName) throws BeansException {
Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean) : bean.getClass();
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(final Method method) throws IllegalArgumentException, IllegalAccessException {
// 步骤1
StreamListener streamListener = AnnotationUtils.findAnnotation(method, StreamListener.class);
if (streamListener != null) {
Method targetMethod = checkProxy(method, bean);
Assert.hasText(streamListener.value(), "The binding name cannot be null");
//步骤2
final InvocableHandlerMethod invocableHandlerMethod = messageHandlerMethodFactory.createInvocableHandlerMethod(bean, targetMethod);
if (!StringUtils.hasText(streamListener.value())) {
throw new BeanInitializationException("A bound component name must be specified");
}
if (mappedBindings.containsKey(streamListener.value())) {
throw new BeanInitializationException("Duplicate @" + StreamListener.class.getSimpleName() +
" mapping for '" + streamListener.value() + "' on " + invocableHandlerMethod.getShortLogMessage() +
" already existing for " + mappedBindings.get(streamListener.value()).getShortLogMessage());
}
mappedBindings.put(streamListener.value(), invocableHandlerMethod);
//步骤3
SubscribableChannel channel = applicationContext.getBean(streamListener.value(),
SubscribableChannel.class);
final String defaultOutputChannel = extractDefaultOutput(method);
if (invocableHandlerMethod.isVoid()) {
Assert.isTrue(StringUtils.isEmpty(defaultOutputChannel), "An output channel cannot be specified for a method that " +
"does not return a value");
}
else {
Assert.isTrue(!StringUtils.isEmpty(defaultOutputChannel), "An output channel must be specified for a method that " +
"can return a value");
}
//步骤4
StreamListenerMessageHandler handler = new StreamListenerMessageHandler(invocableHandlerMethod);
handler.setApplicationContext(applicationContext);
handler.setChannelResolver(binderAwareChannelResolver);
if (!StringUtils.isEmpty(defaultOutputChannel)) {
handler.setOutputChannelName(defaultOutputChannel);
}
handler.afterPropertiesSet();
//步骤5
channel.subscribe(handler);
}
}
});
return bean;
}
}
复制代码
postProcessAfterInitialization方法过程:
1、会将包含@StreamListener("testMessage")的方法找出来
2、创建一个InvocableHandlerMethod代理类,代理类执行我们创建的具体方法。
3、streamListener.value()的值为testMessage,该Bean实际获取的是工厂类为StreamSendClient,方法为input()获得的Bean对象testMessage。生成该Bean的具体方式上面已经说过在BindingBeanDefinitionRegistryUtils.registerChannelBeanDefinitions方法。将之前由BindableProxyFactory对象通过afterPropertiesSet方法生成的@input、@output注解对应的SubscribableChannel Bean查找出来。
PS:获取testMessage Bean对象时,先找StreamSendClient类,在调用input方法。因为前面设置了rootBeanDefinition.setFactoryBeanName(StreamSendClient);
rootBeanDefinition.setUniqueFactoryMethodName(input);
4、创建一个StreamListenerMessageHandler对象,构造方法中InvocableHandlerMethod作为入参。
5、将SubscribableChannel添加订阅StreamListenerMessageHandler。
这样当消息传过来的时候,就会由StreamListenerMessageHandler中的InvocableHandlerMethod,找到具体方法去处理了。
========分割线-Rabbit消息找对应方法源码深度解析=====================
我们知道刚才RabbitMessageChannelBinder.bindConsumer方法中
RabbitMessageChannelBinder.bindConsumer方法
public Binding<MessageChannel> doBindConsumer(String name, String group, MessageChannel inputChannel,
ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
String prefix = properties.getExtension().getPrefix();
String exchangeName = applyPrefix(prefix, name);
TopicExchange exchange = new TopicExchange(exchangeName);
//创建交换器
declareExchange(exchangeName, exchange);
String queueName = applyPrefix(prefix, baseQueueName);
boolean partitioned = !anonymousConsumer && properties.isPartitioned();
boolean durable = !anonymousConsumer && properties.getExtension().isDurableSubscription();
Queue queue;
......................
//创建队列
declareQueue(queueName, queue);
if (partitioned) {
String bindingKey = String.format("%s-%d", name, properties.getInstanceIndex());
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with(bindingKey));
}
else {
declareBinding(queue.getName(), BindingBuilder.bind(queue).to(exchange).with("#"));
}
Binding<MessageChannel> binding = doRegisterConsumer(baseQueueName, group, inputChannel, queue, properties);
.................
return binding;
}
复制代码
最后这句doRegisterConsumer方法就是找到RabbitMq消息对应处理方法的地方。
private Binding<MessageChannel> doRegisterConsumer(final String name, String group, MessageChannel moduleInputChannel, Queue queue,
final ExtendedConsumerProperties<RabbitConsumerProperties> properties) {
DefaultBinding<MessageChannel> consumerBinding;
SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(
this.connectionFactory);
listenerContainer.setAcknowledgeMode(properties.getExtension().getAcknowledgeMode());
listenerContainer.setChannelTransacted(properties.getExtension().isTransacted());
listenerContainer.setDefaultRequeueRejected(properties.getExtension().isRequeueRejected());
int concurrency = properties.getConcurrency();
concurrency = concurrency > 0 ? concurrency : 1;
listenerContainer.setConcurrentConsumers(concurrency);
int maxConcurrency = properties.getExtension().getMaxConcurrency();
if (maxConcurrency > concurrency) {
listenerContainer.setMaxConcurrentConsumers(maxConcurrency);
}
listenerContainer.setPrefetchCount(properties.getExtension().getPrefetch());
listenerContainer.setRecoveryInterval(properties.getExtension().getRecoveryInterval());
listenerContainer.setTxSize(properties.getExtension().getTxSize());
listenerContainer.setTaskExecutor(new SimpleAsyncTaskExecutor(queue.getName() + "-"));
listenerContainer.setQueues(queue);
int maxAttempts = properties.getMaxAttempts();
if (maxAttempts > 1 || properties.getExtension().isRepublishToDlq()) {
RetryOperationsInterceptor retryInterceptor = RetryInterceptorBuilder.stateless()
.maxAttempts(maxAttempts)
.backOffOptions(properties.getBackOffInitialInterval(),
properties.getBackOffMultiplier(),
properties.getBackOffMaxInterval())
.recoverer(determineRecoverer(name, properties.getExtension().getPrefix(), properties.getExtension().isRepublishToDlq()))
.build();
listenerContainer.setAdviceChain(new Advice[] { retryInterceptor });
}
listenerContainer.setAfterReceivePostProcessors(this.decompressingPostProcessor);
listenerContainer.setMessagePropertiesConverter(RabbitMessageChannelBinder.inboundMessagePropertiesConverter);
listenerContainer.afterPropertiesSet();
AmqpInboundChannelAdapter adapter = new AmqpInboundChannelAdapter(listenerContainer);
adapter.setBeanFactory(this.getBeanFactory());
DirectChannel bridgeToModuleChannel = new DirectChannel();
bridgeToModuleChannel.setBeanFactory(this.getBeanFactory());
bridgeToModuleChannel.setBeanName(name + ".bridge");
adapter.setOutputChannel(bridgeToModuleChannel);
adapter.setBeanName("inbound." + name);
DefaultAmqpHeaderMapper mapper = new DefaultAmqpHeaderMapper();
mapper.setRequestHeaderNames(properties.getExtension().getRequestHeaderPatterns());
mapper.setReplyHeaderNames(properties.getExtension().getReplyHeaderPatterns());
adapter.setHeaderMapper(mapper);
adapter.afterPropertiesSet();
consumerBinding = new DefaultBinding<MessageChannel>(name, group, moduleInputChannel, adapter) {
@Override
protected void afterUnbind() {
cleanAutoDeclareContext(properties.getExtension().getPrefix(), name);
}
};
ReceivingHandler convertingBridge = new ReceivingHandler();
convertingBridge.setOutputChannel(moduleInputChannel);
convertingBridge.setBeanName(name + ".convert.bridge");
convertingBridge.afterPropertiesSet();
bridgeToModuleChannel.subscribe(convertingBridge);
adapter.start();
return consumerBinding;
}
复制代码
1、可以看到Spring Stream的创建了SimpleMessageListenerContainer,该类的作用就是RabbitMQ接收消息并让对应方法处理,在SpringRabbit中是找到@RabbitListener注解,详细分析可以看之前的SpringBoot整合Rabbit的文章。
这里我们只需要知道SimpleMessageListenerContainer是负责和RabbitMQ和本地监听方法交互的一个容器就行。
2、接着创建了一个AmqpInboundChannelAdapter对象,入参是SimpleMessageListenerContainer。并创建了一个名为bridgeToModuleChannel的DirectChannel对象,设置Adapter的OutputChannel为DirectChannel。之后调用adapter的afterPropertiesSet方法。afterPropertiesSet方法会调用自身的onInit方法。
protected void onInit() {
this.messageListenerContainer.setMessageListener(new ChannelAwareMessageListener() {
@Override
public void onMessage(Message message, Channel channel) throws Exception {
Object payload = AmqpInboundChannelAdapter.this.messageConverter.fromMessage(message);
Map<String, Object> headers =
AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties());
if (AmqpInboundChannelAdapter.this.messageListenerContainer.getAcknowledgeMode()
== AcknowledgeMode.MANUAL) {
headers.put(AmqpHeaders.DELIVERY_TAG, message.getMessageProperties().getDeliveryTag());
headers.put(AmqpHeaders.CHANNEL, channel);
}
sendMessage(getMessageBuilderFactory().withPayload(payload).copyHeaders(headers).build());
}
});
this.messageListenerContainer.afterPropertiesSet();
super.onInit();
}
复制代码
onInit方法可以看到,将SimpleMessageListenerContainer的消息监听设置为实现ChannelAwareMessageListener接口的自定义方法。在SpringBoot整合RabbitMQ中,setMessageListener的对象为MessagingMessageListenerAdapter。
3、创建一个ReceivingHandler对象,ReceivingHandler对象实现了MessageHandler接口。并设置ReceivingHandler的OutputChannel为BindableProxyFactory创建的messagechannel。之前已经分析了,在StreamListenerAnnotationBeanPostProcessor对象的postProcessAfterInitialization中,已经将该messagechannel添加了StreamListenerMessageHandler,StreamListenerMessageHandler中又创建了具体的处理Bean和Method。
4、将bridgeToModuleChannel添加观察者ReceivingHandler。
下面分析当一条MQ消息到来时经历的步骤:
1、首先会调用SimpleMessageListenerContainer的onMessage方法,详细分析可以看之前的SpringBoot整合Rabbit的文章。
2、onMessage会调用sendMessage方法。
public void onMessage(Message message, Channel channel) throws Exception {
Object payload = AmqpInboundChannelAdapter.this.messageConverter.fromMessage(message);
Map<String, Object> headers =
AmqpInboundChannelAdapter.this.headerMapper.toHeadersFromRequest(message.getMessageProperties());
if (AmqpInboundChannelAdapter.this.messageListenerContainer.getAcknowledgeMode()
== AcknowledgeMode.MANUAL) {
headers.put(AmqpHeaders.DELIVERY_TAG, message.getMessageProperties().getDeliveryTag());
headers.put(AmqpHeaders.CHANNEL, channel);
}
sendMessage(getMessageBuilderFactory().withPayload(payload).copyHeaders(headers).build());
}
protected void sendMessage(Message<?> message) {
................
try {
//这里的OutputChannel就是之前的bridgeToModuleChannel
this.messagingTemplate.send(getOutputChannel(), message);
}
................
}
public void send(D destination, Message<?> message) {
doSend(destination, message);
}
protected final void doSend(MessageChannel channel, Message<?> message) {
......................
boolean sent = (timeout >= 0 ? channel.send(message, timeout) : channel.send(message));
..................
}
复制代码
channel.send(message)方法会调用AbstractMessageChannel的send方法
①:
public final boolean send(Message<?> message, long timeout) {
Assert.notNull(message, "message must not be null");
Assert.notNull(message.getPayload(), "message payload must not be null");
.............
sent = this.doSend(message, timeout);
if (countsEnabled) {
channelMetrics.afterSend(metrics, sent);
metricsProcessed = true;
}
if (debugEnabled) {
logger.debug("postSend (sent=" + sent + ") on channel '" + this + "', message: " + message);
}
if (interceptorStack != null) {
interceptors.postSend(message, this, sent);
interceptors.afterSendCompletion(message, this, sent, null, interceptorStack);
}
return sent;
....................
}
复制代码
这里的doSend方法会调用AbstractSubscribableChannel的doSend方法
②:
protected boolean doSend(Message<?> message, long timeout) {
try {
return getRequiredDispatcher().dispatch(message);
}
catch (MessageDispatchingException e) {
String description = e.getMessage() + " for channel '" + this.getFullChannelName() + "'.";
throw new MessageDeliveryException(message, description, e);
}
}
//UnicastingDispatcher类的dispatch方法
public final boolean dispatch(final Message<?> message) {
if (this.executor != null) {
Runnable task = createMessageHandlingTask(message);
this.executor.execute(task);
return true;
}
return this.doDispatch(message);
}
private boolean doDispatch(Message<?> message) {
if (this.tryOptimizedDispatch(message)) {
return true;
}
....................
return success;
}
protected boolean tryOptimizedDispatch(Message<?> message) {
MessageHandler handler = this.theOneHandler;
if (handler != null) {
try {
handler.handleMessage(message);
return true;
}
catch (Exception e) {
throw wrapExceptionIfNecessary(message, e);
}
}
return false;
}
复制代码
最后会调用handler.handleMessage方法。由刚才创建可知bridgeToModuleChannel的handler为ReceivingHandler。所以会调用ReceivingHandler.handleMessage方法。ReceivingHandler继承自AbstractReplyProducingMessageHandler,AbstractReplyProducingMessageHandler继承自AbstractMessageHandler。所以会调用AbstractMessageHandler.handleMessage方法。
③:
public final void handleMessage(Message<?> message) {
................
try {
............
this.handleMessageInternal(message);
............
}
catch (Exception e) {
...........
}
}
复制代码
之后会执行 AbstractReplyProducingMessageHandler.handleMessageInternal方法
④:
protected final void handleMessageInternal(Message<?> message) {
Object result;
if (this.advisedRequestHandler == null) {
result = handleRequestMessage(message);
}
else {
result = doInvokeAdvisedRequestHandler(message);
}
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
else if (!isAsync() && logger.isDebugEnabled()) {
logger.debug("handler '" + this + "' produced no reply for request Message: " + message);
}
}
复制代码
会执行ReceivingHandler.handleRequestMessage方法,对消息进行反序列化等。之后会执行sendOutputs。
protected void sendOutputs(Object result, Message<?> requestMessage) {
if (result instanceof Iterable<?> && shouldSplitOutput((Iterable<?>) result)) {
for (Object o : (Iterable<?>) result) {
this.produceOutput(o, requestMessage);
}
}
else if (result != null) {
this.produceOutput(result, requestMessage);
}
}
protected void produceOutput(Object reply, final Message<?> requestMessage) {
final MessageHeaders requestHeaders = requestMessage.getHeaders();
Object replyChannel = null;
if (getOutputChannel() == null) {
............
}
if (this.async && reply instanceof ListenableFuture<?>) {
.......................
}
else {
sendOutput(createOutputMessage(reply, requestHeaders), replyChannel, false);
}
}
复制代码
protected void sendOutput(Object output, Object replyChannel, boolean useArgChannel) {
MessageChannel outputChannel = getOutputChannel();
....................
if (replyChannel instanceof MessageChannel) {
if (output instanceof Message<?>) {
this.messagingTemplate.send((MessageChannel) replyChannel, (Message<?>) output);
}
else {
this.messagingTemplate.convertAndSend((MessageChannel) replyChannel, output);
}
}
..................
}
复制代码
此处ReceivingHandler.sendOutput的getOutputChannel就是BindableProxyFactory创建的messagechannel。
3、之后会调用this.messagingTemplate.send((MessageChannel) replyChannel, (Message<?>) output);也就是重复之前的①、②、③、④步骤。进入到AbstractReplyProducingMessageHandler.handleMessageInternal方法。
protected final void handleMessageInternal(Message<?> message) {
Object result;
if (this.advisedRequestHandler == null) {
result = handleRequestMessage(message);
}
else {
result = doInvokeAdvisedRequestHandler(message);
}
if (result != null) {
sendOutputs(result, message);
}
else if (this.requiresReply && !isAsync()) {
throw new ReplyRequiredException(message, "No reply produced by handler '" +
getComponentName() + "', and its 'requiresReply' property is set to true.");
}
else if (!isAsync() && logger.isDebugEnabled()) {
logger.debug("handler '" + this + "' produced no reply for request Message: " + message);
}
}
复制代码
但是此时的messagechannel是BindableProxyFactory创建的,此时的观察者是StreamListenerMessageHandler。
protected Object handleRequestMessage(Message<?> requestMessage) {
try {
return invocableHandlerMethod.invoke(requestMessage);
}
catch (Exception e) {
if (e instanceof MessagingException) {
throw (MessagingException) e;
}
else {
throw new MessagingException(requestMessage, "Exception thrown while invoking " + invocableHandlerMethod.getShortLogMessage(), e);
}
}
}
复制代码
由之前源码解析可知,invocableHandlerMethod已经封装了对应的Bean和Method。这样就完成了从RabbitMQ到Spring Stream找到对应方法的解析。
Spring Stream Rabbit消息找对应方法源码总结:
1、Spring Stream的创建了SimpleMessageListenerContainer,用于监听RabbitMQ服务器。
2、创建一个AmqpInboundChannelAdapter对象,入参是SimpleMessageListenerContainer。两者做关联,让SimpleMessageListenerContainer收到信息后,调用AmqpInboundChannelAdapter中onInit方法里创建的onMessage信息。
3、创建一个名为bridgeToModuleChannel的DirectChannel对象,设置Adapter的OutputChannel为DirectChannel。
4、创建一个观察者ReceivingHandler,用于观察bridgeToModuleChannel。并设置ReceivingHandler的OutputChannel为BindableProxyFactory创建的messagechannel。
5、RabbitMQ发送信息后,第一次会到ReceivingHandler.handleRequestMessage方法,对消息进行反序列化等。之后会执行sendOutputs。
6、再次sendOutputs后,会调用BindableProxyFactory创建的messagechannel的观察者StreamListenerMessageHandler。StreamListenerMessageHandler.handleRequestMessage方法会通过invocableHandlerMethod调用已经封装了对应的Bean和Method。从而找到对应的类方法。
以上就是Spring Cloud Stream的简单分析,Spring Stream的使用需要结合Spring Integration。
Spring Cloud Stream 源码解析的更多相关文章
- Feign 系列(05)Spring Cloud OpenFeign 源码解析
Feign 系列(05)Spring Cloud OpenFeign 源码解析 [TOC] Spring Cloud 系列目录(https://www.cnblogs.com/binarylei/p/ ...
- api网关揭秘--spring cloud gateway源码解析
要想了解spring cloud gateway的源码,要熟悉spring webflux,我的上篇文章介绍了spring webflux. 1.gateway 和zuul对比 I am the au ...
- spring cloud ribbon源码解析(一)
我们知道spring cloud中restTemplate可以通过服务名调接口,加入@loadBalanced标签就实现了负载均衡的功能,那么spring cloud内部是如何实现的呢? 通过@loa ...
- spring cloud ribbon源码解析(二)
在上一篇文章中主要梳理了ribbon的执行过程,这篇主要讲讲ribbon的负载均衡,ribbon的负载均衡是通过ILoadBalancer来实现的,对ILoadBalancer有以下几个类 1.Abs ...
- Spring Security 解析(七) —— Spring Security Oauth2 源码解析
Spring Security 解析(七) -- Spring Security Oauth2 源码解析 在学习Spring Cloud 时,遇到了授权服务oauth 相关内容时,总是一知半解,因 ...
- spring boot @Value源码解析
Spring boot 的@Value只能用于bean中,在bean的实例化时,会给@Value的属性赋值:如下面的例子: @SpringBootApplication @Slf4j public c ...
- Spring Security 访问控制 源码解析
上篇 Spring Security 登录校验 源码解析 分析了使用Spring Security时用户登录时验证并返回token过程,本篇分析下用户带token访问时,如何验证用户登录状态及权限问 ...
- 异步任务spring @Async注解源码解析
1.引子 开启异步任务使用方法: 1).方法上加@Async注解 2).启动类或者配置类上@EnableAsync 2.源码解析 虽然spring5已经出来了,但是我们还是使用的spring4,本文就 ...
- Spring @Import注解源码解析
简介 Spring 3.0之前,创建Bean可以通过xml配置文件与扫描特定包下面的类来将类注入到Spring IOC容器内.而在Spring 3.0之后提供了JavaConfig的方式,也就是将IO ...
- Spring的AOP源码解析(二)
Spring AOP 源码解析 目录 Spring AOP 源码解析 前言 本文使用的调试代码 IOC 容器管理 AOP 实例 ProxyFactory 详解 基于注解的 Spring AOP 源码分 ...
随机推荐
- RabbitMQ 快速复习
目录 RabbitMQ学习笔记 1.消息队列概述 1.1 为什么学习消息队列 1.2 什么是消息中间件 1.3 消息队列应用场景 1.3.1 异步处理 1.3.2 解耦服务 1.3.3 流量削峰 1. ...
- 使用 Docker 部署 Answer 问答平台
1)介绍 GitHub:https://github.com/apache/incubator-answer Answer 问答社区是在线平台,让用户提出问题并获得回答.用户可以发布问题并得到其他用户 ...
- 定时器之PWM
void PWM_Init(void) { RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); RCC_APB2PeriphClockCmd(RC ...
- 芯片公司Dialog产品调研简报
一 公司简介: Dialog半导体有限公司的总部位于伦敦,设有一个全球销售.研发和营销部.2013年,公司实现了9.10亿美元的营业收入,是欧洲增长速度最快的公共半导体公司之一. 二 芯片型号: ...
- 基于python中librosa的声音混音实例解析
一 概念 1.一些概念 Librosa是一个用于音频.音乐分析.处理的python工具包,一些常见的时频处理.特征提取.绘制声音图形等功能应有尽有,功能十分强大.本文主要介绍libros的基本用法 ...
- Spring Boot学习日记13
学习引入Thymeleaf Thymeleaf 官网:https://www.thymeleaf.org/ Thymeleaf 在Github 的主页:https://github.com/thyme ...
- 实时3D渲染它是如何工作的?可以在哪些行业应用?
随着新兴技术--3D渲染的发展,交互应用的质量有了极大的提高.用实时三维渲染软件创建的沉浸式数字体验,几乎与现实没有区别了.随着技术的逐步改进,在价格较低的个人工作站上渲染3D图像变得更加容易,设计师 ...
- 如何用数字人技术让课堂活起来?番职院和3DCAT实时云渲染给出答案
2023年4月20日,广州市第二届智慧教育成果巡展活动在番禺职业技术学院(下文简称番职院)举行,本次活动的主题是''智能AI助教-让课堂活起来''. 活动现场,瑞云科技受邀展示了其自主研发的瑞云数字人 ...
- Python配置文件使用教程
在 Python 应用程序开发过程中,配置文件扮演着重要的角色.配置文件可以用来存储应用程序的各种设置.选项和参数,使得程序更加灵活和可配置.本文将介绍 Python 中如何使用配置文件,并提供一些常 ...
- Mysql中的锁(case篇)
case1(表锁的读-写-读阻塞) 上篇文档中提到过 WRITE locks normally have higher priority than READ locks to ensure that ...