如果我们为“localhost:8080/actuator/health”定义自己的请求映射,它会被注册吗?它会调用 Spring 执行器吗?
我正在使用带有 KafkaTransactionManager 和 DefaultErrorHandler 的 Spring Kafka 3.0.5。
在我的消费者配置中:
确认模式设置为 BATCH
监听器类型为@KafkaListener(基于记录)
事务已启用(KafkaTransactionManager)
重试配置为 DefaultErrorHandler.setRetryListeners(...) 和 10 次重试
在对一批消息进行测试时,我观察到了以下情况:
如果轮询批次中的最后一条记录失败,则整个事务将回滚,并且偏移量不会提交 - 正如预期的那样。
如果最后一条记录成功,即使批次中的先前记录失败但重试成功,整个事务也会提交,并且消费者滞后变为 0。
这意味着批次中最后一条记录的最终结果似乎决定了事务是否被提交。
问题:
这是预期的行为吗——最后一条记录的结果决定了事务提交?
这在 Spring Kafka 或 Kafka 自己的事务行为中是否有记录?
有没有办法让每个记录的事务提交更加明确,或者将其与最终记录状态分离?
如能提供任何说明或文档链接,我们将不胜感激。
我在生成 jooq 类时遇到了问题。我使用的是 jooq 3.20.3,带有 maven 和一个在后台运行的数据库。
我的配置是这样的:
<build>
<plugins>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>${jooq.version}</version>
<executions>
<execution>
<id>jooq-codegen</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<jdbc>
<driver>org.postgresql.Driver</driver>
<url>jdbc:postgresql://localhost:5432/my_db</url>
<user>my_user</user>
<password>my_password</password>
</jdbc>
<generator>
<database>
<name>org.jooq.meta.postgres.PostgresDatabase</name>
</database>
<target>
<packageName>com.example.domain</packageName>
<directory>src/main/java_generated</directory>
</target>
</generator>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
我不确定哪些是必需的,哪些是可选的。另外,我使用了“入门指南”中提供的 jooq 配置。当然,my_db
等的值是我的数据库的值(稍后我会使用一些环境变量)。
但每次运行时mvn jooq-codegen:generate
都会出现以下错误:
[ERROR] Incorrect configuration of jOOQ code generation tool
[ERROR]
The jOOQ-codegen-maven module's generator configuration is not set up correctly.
This can have a variety of reasons, among which:
- Your pom.xml's <configuration> contains invalid XML according to jooq-codegen-3.20.1.xsd
- There is a version or artifact mismatch between your pom.xml and your commandline
但我没有发现错误
例如,当用户即将访问管理员页面时,我想捕获 spring boot 访问被拒绝异常。
我的 GlobalExceptionHandler (@ControllerAdvice) 中有这个异常处理程序
@ExceptionHandler(AccessDeniedException.class)
@ResponseStatus(HttpStatus.FORBIDDEN)
public String handleAccessDenied(AccessDeniedException e, Model model) {
model.addAttribute("errorMessage", e.getMessage());
return "error";
}
我尝试使用 CustomAccessDeniedHandler:
@Component
public class CustomAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
throw accessDeniedException;
}
}
在我的 SecurityConfig 中我将其添加到我的 filterChain 中:
.exceptionHandling(eh -> eh.accessDeniedHandler(customAccessDeniedHandler))
现在我的终端出现“访问被拒绝”:org.springframework.security.authorization.AuthorizationDeniedException:访问被拒绝
我想用我的 GlobalExceptionHandler 捕获此异常(尝试使用 ExceptionHandler 中的 AuthorizationDeniedException)
注意:我不使用 JWT
我错过了什么?
我正在将 Spring Boot Batch App 从版本 2.7.1 迁移到 3.2.10,并收到以下错误。文档没有建议如何解决该问题。有人可以提出建议吗?
错误:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jobLauncherApplicationRunner' defined in class path resource [org/springframework/boot/autoconfigure/batch/BatchAutoConfiguration.class]: Job name must be specified in case of multiple jobs
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1806) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:975) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:971) ~[spring-context-6.1.13.jar:6.1.13]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625) ~[spring-context-6.1.13.jar:6.1.13]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:754) ~[spring-boot-3.2.10.jar:3.2.10]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:456) ~[spring-boot-3.2.10.jar:3.2.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:335) ~[spring-boot-3.2.10.jar:3.2.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1363) ~[spring-boot-3.2.10.jar:3.2.10]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1352) ~[spring-boot-3.2.10.jar:3.2.10]
at com.example.demo.Application.main(Application.java:11) ~[classes/:na]
Caused by: java.lang.IllegalArgumentException: Job name must be specified in case of multiple jobs
at org.springframework.util.Assert.isTrue(Assert.java:111) ~[spring-core-6.1.13.jar:6.1.13]
at org.springframework.boot.autoconfigure.batch.JobLauncherApplicationRunner.afterPropertiesSet(JobLauncherApplicationRunner.java:115) ~[spring-boot-autoconfigure-3.2.10.jar:3.2.10]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853) ~[spring-beans-6.1.13.jar:6.1.13]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802) ~[spring-beans-6.1.13.jar:6.1.13]
... 15 common frames omitted
Process finished with exit code 1
ChilJob配置.java
package com.example.demo;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class ChildJobConfiguration {
@Autowired
private JobRepository jobRepository;
@Autowired
private PlatformTransactionManager transactionManager;
@Bean
public Step step1a() {
return new StepBuilder("step1a", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println("\t>>This is step 1a");
return RepeatStatus.FINISHED;
}, transactionManager).build();
}
@Bean
public Job childJob() {
return new JobBuilder("childJob", jobRepository)
.start(step1a())
.build();
}
}
父作业配置.java
package com.example.demo;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.job.builder.JobBuilder;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobRepository;
import org.springframework.batch.core.step.builder.JobStepBuilder;
import org.springframework.batch.core.step.builder.StepBuilder;
import org.springframework.batch.repeat.RepeatStatus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.PlatformTransactionManager;
@Configuration
public class ParentJobConfiguration {
@Autowired
private JobRepository jobRepository;
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private Job childJob;
@Autowired
private JobLauncher jobLauncher;
@Bean
public Step step1() {
return new StepBuilder("step1", jobRepository)
.tasklet((contribution, chunkContext) -> {
System.out.println(">> This is step 1");
return RepeatStatus.FINISHED;
}, transactionManager).build();
}
@Bean
public Job parentJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
Step childJobStep = new JobStepBuilder(new StepBuilder("childJobStep", jobRepository))
.job(childJob)
.launcher(jobLauncher)
.build();
return new JobBuilder("parentJob", jobRepository)
.start(step1())
.next(childJobStep)
.build();
}
}
主应用程序
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
应用程序.属性
spring.datasource.driverClassName=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.schema=schema-mysql.sql
spring.batch.job.names=parentJob
当我创建IntegrationFlow
设置时sendTimeout()
,值SourcePollingChannelAdapterSpec
被忽略,导致轮询线程阻塞。如果我以编程方式创建,则不会SourcePollingChannelAdapter
发生这种情况。例如,以编程方式使用@Configuration
类:
@Configuration
public class JdbcPollingConfig {
@Bean
protected MessageChannel jdbcInboundChannel() {
return new QueueChannel(1);
}
@Bean
public ThreadPoolTaskScheduler jdbcTaskExecutor() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setPoolSize(1);
taskScheduler.initialize();
return taskScheduler;
}
@Bean
protected JdbcPollingChannelAdapter jdbcPollingChannelAdapter(final DataSource dataSource) {
final JdbcPollingChannelAdapter adapter =
new CustomJdbcPollingChannelAdapter(dataSource, "select dp_id, dp_payload from data_packets");
return adapter;
}
@Bean
protected SourcePollingChannelAdapter sourcePollingChannelAdapter(
final JdbcPollingChannelAdapter adapter,
final MessageChannel jdbcInboundChannel,
final ThreadPoolTaskScheduler jdbcTaskExecutor) {
final SourcePollingChannelAdapter spcAdapter = new SourcePollingChannelAdapter();
spcAdapter.setSource(adapter);
spcAdapter.setOutputChannel(jdbcInboundChannel);
spcAdapter.setSendTimeout(500); // Sets the send-to-channel timeout - throws exception on timeout though.
spcAdapter.setTaskScheduler(jdbcTaskExecutor);
spcAdapter.setTrigger( new PeriodicTrigger(Duration.ofSeconds(2L)));
return spcAdapter;
}
}
请注意,该类CustomJdbcPollingChannelAdapter
是我自己的,它只是进行了扩展,以便我可以doReceive()
使用一些日志调用来覆盖该方法:
@Log4j2
public class CustomJdbcPollingChannelAdapter extends JdbcPollingChannelAdapter {
public CustomJdbcPollingChannelAdapter(DataSource dataSource, String selectQuery) {
super(dataSource, selectQuery);
}
public CustomJdbcPollingChannelAdapter(JdbcOperations jdbcOperations, String selectQuery) {
super(jdbcOperations, selectQuery);
}
@Override
protected Object doReceive() {
log.info("Jdbc Polling.");
return super.doReceive();
}
}
在配置了通道队列的情况下,此配置会按预期抛出异常,因为没有通道的消费者jdbcInboundChannel
。对比一下使用和IntegrationFlow
配置:
@Configuration
public class JdbcDSLConfig {
@Bean
public QueueChannelSpec jdbcInboundChannel() {
return MessageChannels.queue(1);
}
@Bean
public MessageSource<Object> jdbcMessageSource(final DataSource dataSource) {
return new CustomJdbcPollingChannelAdapter(dataSource, "select dp_id, dp_payload from data_packets");
}
@Bean
public ThreadPoolTaskExecutor jdbcExecutor() {
final ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(1);
taskExecutor.setMaxPoolSize(1);
taskExecutor.initialize();
return taskExecutor;
}
@Bean
public IntegrationFlow jdbcInboundFlow(final MessageSource<Object> jdbcMessageSource,
final QueueChannelSpec jdbcInboundChannel,
@Qualifier("jdbcExecutor") final ThreadPoolTaskExecutor jdbcExecutor) {
return IntegrationFlow.from(jdbcMessageSource,
c -> c.poller(Pollers
.fixedDelay(2000)
.sendTimeout(1) // this has no effect.
.taskExecutor(jdbcExecutor)))
.channel(jdbcInboundChannel)
.get();
}
}
即使在配置中设置了 sendTimeout,此配置也会在数据库的第二次轮询时阻塞。
作为学习曲线的一部分,我正在尝试使用 Spring Integrations 向 Mosquitto MQTT 发布一条消息。我已经设置了一个配置(主要来自我通过谷歌搜索找到的部分,但 Listener 部分运行良好):
@Configuration
@RequiredArgsConstructor
@IntegrationComponentScan
public class MqttConfig {
private final MqttProperties properties;
private final ObjectMapper objectMapper;
private List<String> sensorTopics;
@PostConstruct
void init() {
sensorTopics = properties.getSensors().stream()
.map(MqttProperties.Sensor::getTopic)
.toList();
}
@Bean
public IntegrationFlow sensorDataFlow(HumilityAndTempSensorMessageHandler humilityAndTempSensorMessageHandler) {
return IntegrationFlow.from(
new MqttPahoMessageDrivenChannelAdapter(
properties.getUrl(),
"iotBridge",
"#")
)
.filter(getSensorTopicFilter())
.transform(getTempSensorTransformer())
.handle(humilityAndTempSensorMessageHandler::handleMessage)
.get();
}
@Bean
public MessageChannel mqttOutboundChannel() {
return new DirectChannel();
}
@Bean
public MqttPahoClientFactory mqttClientFactory() {
DefaultMqttPahoClientFactory factory = new DefaultMqttPahoClientFactory();
MqttConnectOptions options = new MqttConnectOptions();
options.setServerURIs(new String[]{properties.getUrl()});
options.setUserName(properties.getUsername());
options.setPassword(properties.getPassword().toCharArray());
factory.setConnectionOptions(options);
return factory;
}
@Bean
@ServiceActivator(inputChannel = "mqttOutboundChannel")
public MessageHandler mqttOutbound() {
MqttPahoMessageHandler messageHandler =
new MqttPahoMessageHandler("exoluse", mqttClientFactory());
messageHandler.setAsync(true);
messageHandler.setDefaultTopic("topic");
return messageHandler;
}
@MessagingGateway(defaultRequestChannel = "mqttOutboundChannel")
public interface PublishGateway {
void send(String message, @Header(MqttHeaders.TOPIC) String topic);
}
private GenericTransformer<String, TempSensorReading> getTempSensorTransformer() {
return source -> {
try {
return objectMapper.readValue(source, TempSensorReading.class);
} catch (Exception e) {
System.out.println("failed to resolve input");
}
return null;
};
}
private GenericHandler<String> getSensorTopicFilter() {
return (payload, headers) -> {
String topic = Optional.ofNullable(headers)
.map(item -> item.get("mqtt_receivedTopic"))
.map(String::valueOf)
.orElse(null);
return sensorTopics.contains(topic);
};
}
我正在尝试传达一条信息:
@Service
//@RequiredArgsConstructor
@Slf4j
public class SocketGateway {
@Autowired
private MqttConfig.PublishGateway publishGateway;
@PostConstruct
void t() {
// ${SHELLY_ID}/command/switch:0 -m toggle
String topic = "xxx/command/switch:0";
System.out.println(">>>>>> "+publishGateway);
publishGateway.send("toggle", topic);
}
}
我遇到了Dispatcher has no subscribers
异常。
依赖版本:
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>6.4.1</version>
</dependency>
我做错什么了?
UPD:完整 pom
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.1.2</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>15</maven.compiler.source>
<maven.compiler.target>15</maven.compiler.target>
<javafx.version>17.0.1</javafx.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.28</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-mqtt</artifactId>
<version>6.4.1</version>
</dependency>
</dependencies>
</project>
错误的完整堆栈跟踪:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'socketGateway': Invocation of init method failed
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:222) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsBeforeInitialization(AbstractAutowireCapableBeanFactory.java:419) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1762) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:598) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:520) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:973) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:942) ~[spring-context-6.0.11.jar:6.0.11]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:608) ~[spring-context-6.0.11.jar:6.0.11]
at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:146) ~[spring-boot-3.1.2.jar:3.1.2]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:734) ~[spring-boot-3.1.2.jar:3.1.2]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:436) ~[spring-boot-3.1.2.jar:3.1.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:312) ~[spring-boot-3.1.2.jar:3.1.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1306) ~[spring-boot-3.1.2.jar:3.1.2]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1295) ~[spring-boot-3.1.2.jar:3.1.2]
at org.draakz.iotbridge.Application.main(Application.java:9) ~[classes/:na]
Caused by: org.springframework.messaging.MessageDeliveryException: Dispatcher has no subscribers for channel 'application.mqttOutboundChannel'.
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:76) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.channel.AbstractMessageChannel.sendInternal(AbstractMessageChannel.java:373) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.channel.AbstractMessageChannel.send(AbstractMessageChannel.java:327) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:187) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:166) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.messaging.core.GenericMessagingTemplate.doSend(GenericMessagingTemplate.java:47) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.send(AbstractMessageSendingTemplate.java:109) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:151) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.messaging.core.AbstractMessageSendingTemplate.convertAndSend(AbstractMessageSendingTemplate.java:143) ~[spring-messaging-6.0.11.jar:6.0.11]
at org.springframework.integration.gateway.MessagingGatewaySupport.send(MessagingGatewaySupport.java:466) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.gateway.GatewayProxyFactoryBean.sendOrSendAndReceive(GatewayProxyFactoryBean.java:669) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invokeGatewayMethod(GatewayProxyFactoryBean.java:584) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.gateway.GatewayProxyFactoryBean.doInvoke(GatewayProxyFactoryBean.java:550) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.gateway.GatewayProxyFactoryBean.invoke(GatewayProxyFactoryBean.java:540) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184) ~[spring-aop-6.0.11.jar:6.0.11]
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:244) ~[spring-aop-6.0.11.jar:6.0.11]
at jdk.proxy2/jdk.proxy2.$Proxy83.send(Unknown Source) ~[na:na]
at org.draakz.iotbridge.mqtt.publisher.SocketGateway.t(SocketGateway.java:26) ~[classes/:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77) ~[na:na]
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
at java.base/java.lang.reflect.Method.invoke(Method.java:568) ~[na:na]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMethod.invoke(InitDestroyAnnotationBeanPostProcessor.java:457) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor$LifecycleMetadata.invokeInitMethods(InitDestroyAnnotationBeanPostProcessor.java:401) ~[spring-beans-6.0.11.jar:6.0.11]
at org.springframework.beans.factory.annotation.InitDestroyAnnotationBeanPostProcessor.postProcessBeforeInitialization(InitDestroyAnnotationBeanPostProcessor.java:219) ~[spring-beans-6.0.11.jar:6.0.11]
... 18 common frames omitted
Caused by: org.springframework.integration.MessageDispatchingException: Dispatcher has no subscribers
at org.springframework.integration.dispatcher.UnicastingDispatcher.doDispatch(UnicastingDispatcher.java:139) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.dispatcher.UnicastingDispatcher.dispatch(UnicastingDispatcher.java:106) ~[spring-integration-core-6.1.2.jar:6.1.2]
at org.springframework.integration.channel.AbstractSubscribableChannel.doSend(AbstractSubscribableChannel.java:72) ~[spring-integration-core-6.1.2.jar:6.1.2]
... 42 common frames omitted
我有一个使用 Spring Security OAuth2 保护的 Spring Boot 3.4 服务。
我的安全配置是标准的:
@Bean
public SecurityFilterChain oauth2FilterChain(HttpSecurity http) throws Exception {
http.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(sm -> sm.sessionCreationPolicy(STATELESS))
.exceptionHandling(eh -> eh.authenticationEntryPoint(handleException()));
http.securityMatcher("/api/**")
.authorizeHttpRequests(auth ->
auth.requestMatchers("/api/**").authenticated())
.oauth2ResourceServer(resourceServer ->
resourceServer.jwt(jwt -> jwt.decoder(jwtDecoder())));
return http.build();
}
我的端点带有以下注释:@PreAuthorize(hasAuthority('SCOPE_MY_SCOPE'))
。
此外,在我的 API 模型上,我使用 Jakarta Validation 3 进行验证(例如,在字段上使用注释@NotNull
)。
鉴于此配置,我有以下场景:我使用没有所需范围的安全承载令牌发出无效请求。
服务器响应 400,因为验证约束失败。但是,我预计这个请求会返回 403;我认为 Spring 向未经授权调用我的端点的人公开了有关我的验证约束的不必要信息。如果我使用有效的请求,它确实会返回 403,所以问题在于检查的顺序:我希望先看到安全检查,然后是验证检查,但似乎情况正好相反。
Kotlin 中的方法或类默认为 final。但是当我将 Service 类中的方法标记为 时@Transactional
,我意识到服务类及其所有方法都是开放的。因为要使@Transactional
方面发挥作用,服务类需要被代理,那么它必须是一个开放类,以便代理类可以扩展原始类。
(正如教程Kotlin 中的打开关键字所指出的,kotlin 默认是 final 的。
根据Spring文档AOP,因为我的服务类继承自抽象类而不是接口,所以代理是CGLIB代理。)
我是否误解了什么或者它是如何工作的?
@Service
class SomeService(...): SomeAbstractClass(...){
@Transactional
fun someMethod(...){...}
}
我正在使用 Spring Boot 创建一个 REST 服务,但在将 JSON 请求/DTO 转换为相应实体时遇到了麻烦,特别是当一个实体包含对另一个实体的引用时。例如,假设我们有这些对象:
data class BookEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int?,
val name: String,
@ManyToOne
@JoinColumn(name = "author_id")
val author: AuthorEntity,
)
data class AuthorEntity(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Int?,
val name: String,
)
data class BookRequestDto(
val name: String,
val authorId: Int,
)
该应用程序具有控制器-服务-存储库层,据我所知,我应该在控制器层中执行 DTO -> 实体转换,然后将生成的实体传递给服务层。但是,为了将图书请求 DTO 转换为实体,我首先必须根据给定的从存储库中获取适当的 Author 实体authorId
。我的问题是:我到底应该在哪里做这件事?
鉴于服务层应该只接受实体,看来我应该在控制器层中获取作者。但这意味着 Book 控制器需要访问 Author 服务层,我不确定这是否是好的做法。
这就是我的意思(使用明显的扩展函数来执行 DTO/实体转换)
@RestController
@RequestMapping(path = ["/books"])
class BookController(
private val bookService: BookService,
private val authorService: AuthorService,
) {
@PostMapping
fun createBook(@RequestBody bookDto: BookRequestDto): ResponseEntity<BookResponseDto> {
val author = authorService.get(bookDto.authorId)
val bookEntity = bookDto.toBookEntity(author = author)
val createdBook = bookService.create(bookEntity)
return ResponseEntity(createdBook.toBookResponseDto(), HttpStatus.CREATED)
}
}
这是通常的做法吗?还是在控制器中混合多个服务是个坏主意?我显然必须在某个地方访问作者存储库,但我不知道最佳位置在哪里。有没有更好的方法?