I've been trying to create a consumer and a producer using the Spring Integration TCP. I've achived some success on the listening part (I got the message sended by a telnet normally), but when I try to send this same message to the terminal client, nothing happens.
These are my classes:
#EnableIntegration
#IntegrationComponentScan
#Configuration
public class TcpIntegration {
#Value("${tcp.port}")
private Integer port;
#MessagingGateway(defaultRequestChannel="toTcp")
public interface Gateway {
String viaTcp(String in);
}
#Bean
#ServiceActivator(inputChannel="toTcp")
public TcpSendingMessageHandler tcpOutGate(AbstractClientConnectionFactory connectionFactory) {
TcpSendingMessageHandler gate = new TcpSendingMessageHandler();
gate.setConnectionFactory(connectionFactory);
return gate;
}
#Bean
public TcpReceivingChannelAdapter tcpInGate(AbstractServerConnectionFactory connectionFactory) {
TcpReceivingChannelAdapter inGate = new TcpReceivingChannelAdapter();
inGate.setConnectionFactory(connectionFactory);
inGate.setOutputChannel(fromTcp());
return inGate;
}
#Bean
public MessageChannel fromTcp() {
return new DirectChannel();
}
#Bean
public AbstractClientConnectionFactory clientCF() {
return new TcpNetClientConnectionFactory("localhost", this.port);
}
#Bean
public AbstractServerConnectionFactory serverCF() {
return new TcpNetServerConnectionFactory(this.port);
}
}
TcpListener
#MessageEndpoint
#AllArgsConstructor
public class TcpListener {
private final Gateway gateway;
#ServiceActivator(inputChannel = "fromTcp")
public void convert(String payload) {
System.out.println(payload);
gateway.viaTcp(payload);
}
}
Why doesn't it work?
You need to tell the adapter which socket to send the messsage on by setting the ip_connnetionId header.
#ServiceActivator(inputChannel = "fromTcp")
public void convert(String payload, #Header(IpHeaders.CONNECTION_ID String cid) {
System.out.println(payload);
gateway.viaTcp(payload, cid);
}
#MessagingGateway(defaultRequestChannel="toTcp")
public interface Gateway {
#Gateway(#GatewayHeader(IPHeaders.CONNECTION_ID, "#args[1]"))
String viaTcp(String in, String cid);
}
EDIT
My syntax was a little bit off; here's a working example...
#SpringBootApplication
public class So65597331Application {
public static void main(String[] args) {
SpringApplication.run(So65597331Application.class, args);
}
#MessagingGateway(defaultRequestChannel = "toTcp")
public interface Gate {
#Gateway(payloadExpression = "#args[0]",
headers = #GatewayHeader(name = IpHeaders.CONNECTION_ID, expression = "#args[1]"))
String viaTcp(String in, String cid);
}
#Bean
#ServiceActivator(inputChannel = "toTcp")
public TcpSendingMessageHandler tcpOutGate(AbstractServerConnectionFactory connectionFactory) {
TcpSendingMessageHandler gate = new TcpSendingMessageHandler();
gate.setConnectionFactory(connectionFactory);
return gate;
}
#Autowired
private Gate gateway;
#ServiceActivator(inputChannel = "fromTcp")
public void convert(String payload, #Header(IpHeaders.CONNECTION_ID) String cid) {
System.out.println(payload);
gateway.viaTcp(payload, cid);
}
#Bean
public TcpReceivingChannelAdapter tcpInGate(AbstractServerConnectionFactory connectionFactory) {
TcpReceivingChannelAdapter inGate = new TcpReceivingChannelAdapter();
inGate.setConnectionFactory(connectionFactory);
inGate.setOutputChannel(fromTcp());
return inGate;
}
#Bean
public MessageChannel fromTcp() {
return new DirectChannel();
}
#Bean
public AbstractServerConnectionFactory serverCF() {
return new TcpNetServerConnectionFactory(1234);
}
}
$ telnet localhost 1234
Trying ::1...
Connected to localhost.
Escape character is '^]'.
foo
foo
Connection closed by foreign host.
Related
If I send multiple commands to the same aggregate, only the first is handled.
Is this a configuration problem, or am I missing something?
The message I am getting after the 2nd command is send:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is org.axonframework.commandhandling.CommandExecutionException: Cannot invoke "Object.hashCode()" because "key" is null
The service method where I do my sending of the command is:
public void maakAanvraag() {
UUID aanvraagId = UUID.randomUUID();
commandGateway.sendAndWait(
VerwerkAanvraag.builder()
.aanvraagId(aanvraagId)
.build()
);
commandGateway.sendAndWait(
VerwerkPersoonsgegevensVastgesteld.builder()
.aanvraagId(aanvraagId)
.build()
);
commandGateway.sendAndWait(
VerwerkOrganisatiegegevensVastgesteld.builder()
.aanvraagId(aanvraagId)
.organisatieId(organisatieView.getOrganisatieId())
.rolOrganisatie(rolOrganisatie)
.build()
);
commandGateway.sendAndWait(
VerwerkBeperkingErkenningsdoelGematcht.builder()
.aanvraagId(aanvraagId)
.build());
}
The aggregate I am using is:
#Aggregate
#Getter
#NoArgsConstructor
public class Aanvraag {
public static final String META_DATA_ZAAKNUMMER = "aanvraag_zaaknummer";
#AggregateIdentifier
private UUID aanvraagId;
#CommandHandler
public Aanvraag(VerwerkAanvraag command) {
AanvraagGeregistreerd aanvraagGeregistreerd =
AanvraagGeregistreerd.builder()
.aanvraagId(command.getAanvraagId())
.build();
apply(aanvraagGeregistreerd, MetaData.with(META_DATA_ZAAKNUMMER, "123456789"));
}
#EventSourcingHandler
public void on(AanvraagGeregistreerd event) {
aanvraagId = event.getAanvraagId();
}
#CommandHandler
public void verwerkOrganisatiegegevensVastgesteld(VerwerkOrganisatiegegevensVastgesteld command) {
OrganisatiegegevensVastgesteld persoonsgegevensVastgesteld =
OrganisatiegegevensVastgesteld.builder()
.aanvraagId(command.getAanvraagId())
.build();
apply(persoonsgegevensVastgesteld);
}
#EventSourcingHandler
public void on(OrganisatiegegevensVastgesteld event) {
aanvraagId = event.getAanvraagId();
}
#CommandHandler
public void verwerkPersoonsgegevensVastgesteld(VerwerkPersoonsgegevensVastgesteld command) {
PersoonsgegevensVastgesteld persoonsgegevensVastgesteld =
PersoonsgegevensVastgesteld.builder()
.aanvraagId(command.getAanvraagId())
.build();
apply(persoonsgegevensVastgesteld);
}
#EventSourcingHandler
public void on(PersoonsgegevensVastgesteld event) {
aanvraagId = event.getAanvraagId();
}
#CommandHandler
public void verwerkBeperkingErkenningsdoelGematcht(VerwerkBeperkingErkenningsdoelGematcht command) {
BeperkingErkenningsdoelGematcht beperkingErkenningsdoelGematcht =
BeperkingErkenningsdoelGematcht.builder()
.aanvraagId(command.getAanvraagId())
.build();
apply(beperkingErkenningsdoelGematcht);
}
#EventSourcingHandler
public void on(BeperkingErkenningsdoelGematcht event) {
aanvraagId = event.getAanvraagId();
}
}
The project uses Spring Boot 2.6.6 with axon-spring-boot-starter 4.5.9
It al runs with Java Temurin 17.0.3
We solved the problem.
Issue had nothing to do with Axon.
The problem was an Logging interceptor.
After removing this log interceptor the Axon worked as expected.
Just wanted to make sure that whether messages are processed in correct way or not. When the message gets received at listener, it will be always processed by a new thread( defined the processor bean as prototype). is this implementation correct ? (i have Considered the listener is not thread safe, so for this reason the prototype scope of bean to process the message has been used)
(Input : TestTopic- 5 partitions - 1 consumer) or (Input : TestTopic- 5 partitions - 5 consumers)
public class EventListener {
#Autowired
private EventProcessor eventProcessor;
#KafkaListener(topics = "TestTopic", containerFactory = "kafkaListenerContainerFactory",
autoStartup = "true")
public void onMessage(
#Payload List<ConsumerRecord<String, String>> consumerRecords, Acknowledgment acknowledgment) {
eventProcessor.processAndAcknowledgeBatchMessages(consumerRecords, acknowledgment);
}
}
//event processor
#Slf4j
#Component
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
#NoArgsConstructor
#SuppressWarnings("unused")
public class EventProcessorImpl implements EventProcessor {
#Autowired
private KafkaProducerTemplate kafkaProducerTemplate;
#Autowired
private ObjectMapper localObjectMapper;
#Autowired
private Dao dao;
public void processAndAcknowledgeBatchMessages(
List<ConsumerRecord<String, String>> consumerRecords, Acknowledgment acknowledgment) {
long start = System.currentTimeMillis();
consumerRecords.forEach( consumerRecord -> {
Event event = localObjectMapper.readValue(consumerRecord.value(), Event.class);
dao.save(process(event));
});
acknowledgment.acknowledge();
}
}
No it is not correct; you should not execute on another thread; it will cause problems with committing offsets and error handling.
Also, making the EventProcessorImpl a prototype bean won't help. That just means a new instance is used each time the bean is referenced.
Since it is #Autowired it is only referenced once, during initialization. To get a new instance for each request, you would need to call getBean() on the application context each time.
It is better to make your code thread-safe.
EDIT
There are (at least) a couple of ways to deal with a not thread-safe service defined in prototype scope.
Use a ThreadLocal:
#SpringBootApplication
public class So68447863Application {
public static void main(String[] args) {
SpringApplication.run(So68447863Application.class, args);
}
private static final ThreadLocal<NotThreadSafeService> SERVICES = new ThreadLocal<>();
#Autowired
ApplicationContext context;
#KafkaListener(id = "so68447863", topics = "so68447863", concurrency = "5")
void listen(String in) {
NotThreadSafeService service = SERVICES.get();
if (service == null) {
service = this.context.getBean(NotThreadSafeService.class);
SERVICES.set(service);
}
service.process(in);
}
#EventListener
void removeService(ConsumerStoppedEvent event) {
System.out.println("Consumer stopped; removing TL");
SERVICES.remove();
}
#Bean
NewTopic topic() {
return TopicBuilder.name("so68447863").partitions(10).replicas(1).build();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
NotThreadSafeService service() {
return new NotThreadSafeService();
}
}
class NotThreadSafeService {
void process(String msg) {
System.out.println(msg + " processed by " + this);
}
}
Use a pool of instances.
#SpringBootApplication
public class So68447863Application {
public static void main(String[] args) {
SpringApplication.run(So68447863Application.class, args);
}
private static final BlockingQueue<NotThreadSafeService> SERVICES = new LinkedBlockingQueue<>();
#Autowired
ApplicationContext context;
#KafkaListener(id = "so68447863", topics = "so68447863", concurrency = "5")
void listen(String in) {
NotThreadSafeService service = SERVICES.poll();
if (service == null) {
service = this.context.getBean(NotThreadSafeService.class);
}
try {
service.process(in);
}
finally {
SERVICES.add(service);
}
}
#Bean
NewTopic topic() {
return TopicBuilder.name("so68447863").partitions(10).replicas(1).build();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
NotThreadSafeService service() {
return new NotThreadSafeService();
}
}
class NotThreadSafeService {
void process(String msg) {
System.out.println(msg + " processed by " + this);
}
}
I am trying to write kafka consumer using spring-kafka version 2.3.0.M2 library.
To handle run time errors I am using SeekToCurrentErrorHandler.class with DeadLetterPublishingRecoverer as my recoverer. This works fine only when my consumer code throws exception, but fails when unable to deserialize the message.
I tried implementing ErrorHandler myself and I was successful but with this approach I myself end up writing DLT code to handle error messages which I do not want to do.
Below are my kafka properties
spring:
kafka:
consumer:
bootstrap-servers: localhost:9092
group-id: group_id
auto-offset-reset: latest
key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
properties:
spring.json.trusted.packages: com.mypackage
spring.deserializer.key.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
spring.deserializer.value.delegate.class: org.apache.kafka.common.serialization.StringDeserializer
public ConcurrentKafkaListenerContainerFactory kafkaListenerContainerFactory(
ConcurrentKafkaListenerContainerFactoryConfigurer configurer,
ConsumerFactory<Object, Object> kafkaConsumerFactory,
KafkaTemplate<Object, Object> template) {
ConcurrentKafkaListenerContainerFactory<Object, Object> factory = new ConcurrentKafkaListenerContainerFactory<>();
configurer.configure(factory, kafkaConsumerFactory);
factory.setErrorHandler(new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template), maxFailures));}
It works fine for me (note that Boot will auto-configure the error handler)...
#SpringBootApplication
public class So56728833Application {
public static void main(String[] args) {
SpringApplication.run(So56728833Application.class, args);
}
#Bean
public SeekToCurrentErrorHandler errorHandler(KafkaTemplate<String, String> template) {
SeekToCurrentErrorHandler eh = new SeekToCurrentErrorHandler(new DeadLetterPublishingRecoverer(template), 3);
eh.setClassifier( // retry for all except deserialization exceptions
new BinaryExceptionClassifier(Collections.singletonList(DeserializationException.class), false));
return eh;
}
#KafkaListener(id = "so56728833"
+ "", topics = "so56728833")
public void listen(Foo in) {
System.out.println(in);
if (in.getBar().equals("baz")) {
throw new IllegalStateException("Test retries");
}
}
#KafkaListener(id = "so56728833dlt", topics = "so56728833.DLT")
public void listenDLT(Object in) {
System.out.println("Received from DLT: " + (in instanceof byte[] ? new String((byte[]) in) : in));
}
#Bean
public NewTopic topic() {
return TopicBuilder.name("so56728833").partitions(1).replicas(1).build();
}
#Bean
public NewTopic dlt() {
return TopicBuilder.name("so56728833.DLT").partitions(1).replicas(1).build();
}
public static class Foo {
private String bar;
public Foo() {
super();
}
public Foo(String bar) {
this.bar = bar;
}
public String getBar() {
return this.bar;
}
public void setBar(String bar) {
this.bar = bar;
}
#Override
public String toString() {
return "Foo [bar=" + this.bar + "]";
}
}
}
spring:
kafka:
consumer:
auto-offset-reset: earliest
enable-auto-commit: false
key-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
value-deserializer: org.springframework.kafka.support.serializer.ErrorHandlingDeserializer2
properties:
spring.json.trusted.packages: com.example
spring.deserializer.key.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
spring.deserializer.value.delegate.class: org.springframework.kafka.support.serializer.JsonDeserializer
spring.json.value.default.type: com.example.So56728833Application$Foo
producer:
key-serializer: org.springframework.kafka.support.serializer.JsonSerializer
value-serializer: org.springframework.kafka.support.serializer.JsonSerializer
logging:
level:
org.springframework.kafka: trace
I have 3 records in the topic:
"badJSON"
"{\"bar\":\"baz\"}"
"{\"bar\":\"qux\"}"
I see the first one going directly to the DLT, and the second one goes there after 3 attempts.
I write request mapping at interface but it's not working.
Request / response body POJOs:
public class ReqAa {
private String firstValue;
private String secondValue;
public String getFirstValue() {
return firstValue;
}
public ReqAa setFirstValue(String firstValue) {
this.firstValue = firstValue;
return this;
}
public String getSecondValue() {
return secondValue;
}
public ReqAa setSecondValue(String secondValue) {
this.secondValue = secondValue;
return this;
}
}
public class RespAa{
private String status;
RespAa(String status){
this.status = status;
}
public String getStatus() {
return status;
}
public RespAa setStatus(String status) {
this.status = status;
return this;
}
}
Interfaces:
public interface A{
interface Aa{
#PostMapping("/do/something")
RespAa doSomething(#RequestBody ReqAa);
}
interface Ab{
#PostMapping("/do/another")
RespAb doAnother(#RequestBody ReqAb);
}
}
#PreAuthorize("hasAuthority('admin')")
#RequestMapping("/api/admin")
public interface IClient extends A.Aa{
}
Rest Controller:
#RestController
public class Client implements IClient{
#Override
public RespAa doSomething(ReqAa reqAa) {
return new RespAa("SUCCESS");
}
}
Spring boot #RequestBody could not mapping as Body. Its took it as parameter.
Example:
generated request: /api/admin/do/something?firstValue=fv&secondValue=sv
expected mapping: /api/admin/do/something
requestBody: { "firstValue":"fv","secondValue":"sv"}
its work when i used #RequestBody in implementaion method.
#RestController
public class Client implements IClient{
#Override
public RespAa doSomething(#RequestBody ReqAa reqAa) {
return new RespAa("SUCCESS");
}
}
I used spring boot version: 1.5.10.
I solved my problem using interface default method.
Working code here:
Interfaces:
public interface A{
interface Aa{
RespAa doSomething(ReqAa);
#PostMapping("/do/something")
default RespAa dDoSomething(#RequestBody ReqAa){
return doSomething(ReqAa);
}
}
interface Ab{
#PostMapping("/do/another")
RespAb doAnother(#RequestBody ReqAb);
}
}
#PreAuthorize("hasAuthority('admin')")
#RequestMapping("/api/admin")
public interface IClient extends A.Aa{
}
Rest Controller:
#RestController
public class Client implements IClient{
#Override
public RespAa doSomething(ReqAa reqAa) {
return new RespAa("SUCCESS");
}
}
I have some ASP.NET Core MVC middleware to catch unhandled exceptions that I would like to return a response from.
While it is easy to just httpContext.Response.WriteAsync to write a string and e.g. use JsonSerializer to serialise an object to a string, I would like to use the standard serialisation settings and content negotiation so that if I change my default output formatting to XML or a text/xml accept header is sent when I have multiple output formatters configured then XML is returned, as it does if I return an ObjectResult from a controller.
Does anyone know how this can be achieved in middleware?
Here is my code so far which only writes JSON:
public class UnhandledExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly IOutputFormatter _outputFormatter;
private readonly IHttpResponseStreamWriterFactory _streamWriterFactory;
public UnhandledExceptionMiddleware(RequestDelegate next, JsonOutputFormatter outputFormatter, IHttpResponseStreamWriterFactory streamWriterFactory)
{
_next = next;
_outputFormatter = outputFormatter;
_streamWriterFactory = streamWriterFactory;
}
public async Task Invoke(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
await HandleExceptionAsync(context, ex);
}
}
private async Task HandleExceptionAsync(HttpContext context, Exception exception)
{
var error = new ErrorResultModel("Internal Server Error", exception.Message, exception.StackTrace);
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
await _outputFormatter.WriteAsync(new OutputFormatterWriteContext(context, _streamWriterFactory.CreateWriter, typeof(ErrorResultModel), error));
}
}
where ErrorResultModel is defined as:
public class ErrorResultModel
{
public string ResultMessage { get; };
public string ExceptionMessage { get; };
public string ExceptionStackTrace { get; };
public ErrorResultModel(string resultMessage, string exceptionMessage, string exceptionStackTrace)
{
ResultMessage = resultMessage;
ExceptionMessage = exceptionMessage;
ExceptionStackTrace = exceptionStackTrace;
}
}
This is not possible in ASP.NET Core 2.0 MVC.
This will be possible in 2.1:
public static class HttpContextExtensions
{
private static readonly RouteData EmptyRouteData = new RouteData();
private static readonly ActionDescriptor EmptyActionDescriptor = new ActionDescriptor();
public static Task WriteResultAsync<TResult>(this HttpContext context, TResult result)
where TResult : IActionResult
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var executor = context.RequestServices.GetService<IActionResultExecutor<TResult>>();
if (executor == null)
{
throw new InvalidOperationException($"No result executor for '{typeof(TResult).FullName}' has been registered.");
}
var routeData = context.GetRouteData() ?? EmptyRouteData;
var actionContext = new ActionContext(context, routeData, EmptyActionDescriptor);
return executor.ExecuteAsync(actionContext, result);
}
}
public class Program : StartupBase
{
public static Task Main(string[] args)
{
return BuildWebHost(args).RunAsync();
}
public static IWebHost BuildWebHost(string[] args)
{
return new WebHostBuilder().UseStartup<Program>().UseKestrel().Build();
}
public override void ConfigureServices(IServiceCollection services)
{
services.AddMvcCore().AddJsonFormatters();
}
public override void Configure(IApplicationBuilder app)
{
app.Use((ctx, next) =>
{
var model = new Person("Krisian", "Hellang");
var result = new ObjectResult(model);
return ctx.WriteResultAsync(result);
});
}
}
public class Person
{
public Person(string firstName, string lastName)
{
FirstName = firstName;
LastName = lastName;
}
public string FirstName { get; }
public string LastName { get; }
}