I'm using Java 8 Spring boot. I have below method.
public hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
// I need to retry the same hello() method for three times as in 10sec, 20sec and 25sec.
}
}
}
I need to call the same method three times for retrying whenever it hits the catch block.
How can I do this asynchronously?
I found below code but it didn't work.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
Appreciate your help.
You can use #Async annotation from Spring to achieve that.
You have to create a config like this:
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {}
When you want to use Async with Retry you have to decorate the method with Async which is trying to call a Retryable method. Also, you have to make sure that you are returning Future<> or similar because you are sending that piece of code for a toss in the background
I have also implemented fallback mechanism otherwise the request will terminate with 500 exception.
If you run the code below you can see that the main request is executed on thread http-nio-8080-exec-1 while your Async code is executed on a different thread task-1.
I tried to explain this with a sample service method, but the concept will be same for local or remote service call.
A detailed exmaple is given below:
package com.example.silentsudo.springcloudssamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
#SpringBootApplication
public class SpringCloudsSamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudsSamplesApplication.class, args);
}
}
#RequestMapping(path = "sample")
#RestController
class SampleController {
private final GreetService greetService;
SampleController(GreetService greetService) {
this.greetService = greetService;
}
#GetMapping
public String hello() {
System.out.println(Thread.currentThread().getName());
return "Hello!";
}
#GetMapping(path = "greet")
public String greet(#RequestParam(value = "name", defaultValue = "John") String name) {
return greetService.greet(name);
}
#Async
#GetMapping(path = "greet-async")
public CompletableFuture<String> greetAsync(#RequestParam(value = "name", defaultValue = "John") String name) {
return CompletableFuture.completedFuture(greetService.greet(name));
}
}
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {
}
#Service
class GreetService {
private final UngaBungaService ungaBungaService;
GreetService(UngaBungaService ungaBungaService) {
this.ungaBungaService = ungaBungaService;
}
#Retryable(maxAttempts = 5, value = GreetException.class, backoff = #Backoff(value = 3000L))
public String greet(String name) {
return ungaBungaService.lol(name);
}
#Recover
public String recoverGreetException(GreetException greetException) {
return greetException.getMessage();
}
}
#Service
class UngaBungaService {
public String lol(String name) {
System.out.println(Thread.currentThread().getName());
throw new GreetException("Called greet for " + name);
}
}
class GreetException extends RuntimeException {
public GreetException(String message) {
super(message);
}
}
For retry mechanisms, you can to use the #Retryable(value = RestClientException.class)
For this to trigger, you need to actually throw this exception (or something that extends from RestClientException). Because of your catch statement, no exception is actually thrown, so the retry mechanism doesn't kick in.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
public void hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
throw new RestClientException("meaningfull message");
}
}
}
If you want to run some catch code after the 3 retries failed, you can make use of the #Recover annotation on a recovery method.
If you want some more info on the retry mechanism, you could look here
Also don't forget to add #EnableRetry in your config so that the annotations are used.
Full code example with spring boot
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
context.getBean(TestService.class).hello();
context.close();
}
#Configuration
#EnableRetry
public class AppConfig {
}
#Service
public class TestService {
#Retryable(value = {IllegalArgumentException.class}, maxAttempts = 4, backoff = #Backoff(delay = 1000, multiplier = 4))
public void hello() {
try {
int a = Integer.parseInt(null);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Exception triggered");
}
}
}
Related
When I create jetty websocket, I register my endpoint handler like this:
public class MyWebSocketEndpoint extends WebSocketServlet {
#Override
public void configure(WebSocketServletFactory webSocketServletFactory) {
webSocketServletFactory.register(MyEndpointHandler.class);
}
}
for MyEndpoingHandler class, I can't define a constructor with some parameters, or it will got runtime exception. How can I pass some parameters when create the MyEndpointHandler instance?
Use a WebSocketCreator.
When you call WebSocketServletFactory.register(MyEndpoingHandler.class) all that's happening internally is the equivalent of ...
#Override
public void register(Class<?> websocketPojo)
{
this.setCreator(new SingleEndpointCreator(websocketPojo));
}
Complete example on WebSocketCreator:
package websocket;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerList;
import org.eclipse.jetty.servlet.DefaultServlet;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
import org.eclipse.jetty.websocket.api.annotations.WebSocket;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeRequest;
import org.eclipse.jetty.websocket.servlet.ServletUpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
public class DemoWebSocketCreator
{
public static class MyWebSocketServlet extends WebSocketServlet
{
#Override
public void configure(WebSocketServletFactory wsFactory)
{
wsFactory.setCreator(new MyWebSocketCreator());
}
}
public static class MyWebSocketCreator implements WebSocketCreator
{
private AtomicInteger idGen = new AtomicInteger(0);
#Override
public Object createWebSocket(ServletUpgradeRequest servletUpgradeRequest, ServletUpgradeResponse servletUpgradeResponse)
{
String id = "ws" + idGen.incrementAndGet();
return new MyWebSocket(id);
}
}
#WebSocket
public static class MyWebSocket
{
private final String id;
public MyWebSocket(String id)
{
this.id = id;
}
#OnWebSocketMessage
public void onMessage(Session session, String msg)
{
try
{
session.getRemote().sendString("Hello, my id is [" + id + "]: You said: " + msg);
}
catch (IOException e)
{
e.printStackTrace();
}
}
}
public static void main(String[] args) throws Exception
{
Server server = new Server(8080);
ServletContextHandler context = new ServletContextHandler();
context.setContextPath("/");
context.addServlet(MyWebSocketServlet.class, "/ws/");
// always last, and on default pathspec
context.addServlet(DefaultServlet.class, "");
HandlerList handlers = new HandlerList();
handlers.addHandler(context);
handlers.addHandler(new DefaultHandler());
server.setHandler(handlers);
server.start();
server.join();
}
}
I have a spring boot application that uses spring-JMS. Is there any way to tell the test method to wait the jms lister util it finishes executing without using latches in the actual code that will be tested?
Here is the JMS listener code:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jms.annotation.JmsListener;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyListener {
#Autowired
MyProcessor myProcessor;
#JmsListener(destination = "myQueue", concurrency = "1-4")
private void onMessage(Message message, QueueSession session) {
myProcessor.processMessage(message, session);
}
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.jms.Message;
import javax.jms.QueueSession;
#Component
public class MyProcessor {
public void processMessage(Message msg, QueueSession session) {
//Here I have some code.
}
}
import org.apache.activemq.command.ActiveMQTextMessage;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.QueueSession;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
#SpringBootTest
#ExtendWith(SpringExtension.class)
#ActiveProfiles("test")
public class IntegrationTest {
#Autowired
private JmsTemplate JmsTemplate;
#Test
public void myTest() throws JMSException {
Message message = new ActiveMQTextMessage();
jmsTemplate.send("myQueue", session -> message);
/*
Here I have some testing code. How can I tell the application
to not execute this testing code until all JMS lister threads
finish executing.
*/
}
}
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.activemq.broker.BrokerService;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.jms.annotation.EnableJms;
import org.springframework.jms.core.JmsTemplate;
import org.springframework.util.SocketUtils;
import javax.jms.ConnectionFactory;
#EnableJms
#Configuration
#Profile("test")
public class JmsTestConfig {
public static final String BROKER_URL =
"tcp://localhost:" + SocketUtils.findAvailableTcpPort();
#Bean
public BrokerService brokerService() throws Exception {
BrokerService brokerService = new BrokerService();
brokerService.setPersistent(false);
brokerService.addConnector(BROKER_URL);
return brokerService;
}
#Bean
public ConnectionFactory connectionFactory() {
return new ActiveMQConnectionFactory(BROKER_URL);
}
#Bean
public JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {
JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);
return jmsTemplate;
}
}
Note: Is it applicable to solve this without adding testing purpose code to the implementation code (MyListener and MyProcessor).
Proxy the listener and add an advice to count down a latch; here's one I did for a KafkaListener recently...
#Test
public void test() throws Exception {
this.template.send("so50214261", "foo");
assertThat(TestConfig.latch.await(10, TimeUnit.SECONDS)).isTrue();
assertThat(TestConfig.received.get()).isEqualTo("foo");
}
#Configuration
public static class TestConfig {
private static final AtomicReference<String> received = new AtomicReference<>();
private static final CountDownLatch latch = new CountDownLatch(1);
#Bean
public static MethodInterceptor interceptor() {
return invocation -> {
received.set((String) invocation.getArguments()[0]);
return invocation.proceed();
};
}
#Bean
public static BeanPostProcessor listenerAdvisor() {
return new ListenerWrapper(interceptor());
}
}
public static class ListenerWrapper implements BeanPostProcessor, Ordered {
private final MethodInterceptor interceptor;
#Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
public ListenerWrapper(MethodInterceptor interceptor) {
this.interceptor = interceptor;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Listener) {
ProxyFactory pf = new ProxyFactory(bean);
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor(this.interceptor);
advisor.addMethodName("listen");
pf.addAdvisor(advisor);
return pf.getProxy();
}
return bean;
}
}
(but you should move the countDown to after the invocation proceed()).
A method annotated with #JmsListener deletes the message after it finishes, so a good option is to read the queue for existing messages and assume the queue is empty after your method is done. Here is the piece of code for counting the messages from the queue.
private int countMessages() {
return jmsTemplate.browse(queueName, new BrowserCallback<Integer>() {
#Override
public Integer doInJms(Session session, QueueBrowser browser) throws JMSException {
return Collections.list(browser.getEnumeration()).size();
}
});
}
Following is the code for testing the countMessages() method.
jmsTemplate.convertAndSend(queueName, "***MESSAGE CONTENT***");
while (countMessages() > 0) {
log.info("number of pending messages: " + countMessages());
Thread.sleep(1_000l);
}
// continue with your logic here
I've based my solution on the answer given by Gary Russell, but rather put the CountDownLatch in an Aspect, using Spring AOP (or the spring-boot-starter-aop variant).
public class TestJMSConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(TestJMSConfiguration.class);
public static final CountDownLatch countDownLatch = new CountDownLatch(1);
#Component
#Aspect
public static class LatchCounterAspect {
#Pointcut("execution(public void be.infrabel.rocstdm.application.ROCSTDMMessageListener.onMessage(javax.jms.TextMessage))")
public void onMessageMethod() {};
#After(value = "onMessageMethod()")
public void countDownLatch() {
countDownLatch.countDown();
LOGGER.info("CountDownLatch called. Count now at: {}", countDownLatch.getCount());
}
}
A snippet of the test:
JmsTemplate jmsTemplate = new JmsTemplate(this.embeddedBrokerConnectionFactory);
jmsTemplate.convertAndSend("AQ.SOMEQUEUE.R", message);
TestJMSConfiguration.countDownLatch.await();
verify(this.listenerSpy).putResponseOnTargetQueueAlias(messageCaptor.capture());
RouteMessage outputMessage = messageCaptor.getValue();
The listenerSpy is a #SpyBean annotated field of the type of my MessageListener. The messageCaptor is a field of type ArgumentCaptor<MyMessageType> annotated with #Captor. Both of these are coming from mockito so you need to run/extend your test with both MockitoExtension (or -Runner) along with the SpringExtension (or -Runner).
My code puts an object on an outbound queue after processing the incoming message, hence the putResponseOnTargetQueueAlias method. The captor is to intercept that object and do my assertions accordingly. The same strategy could be applied to capture some other object in your logic.
application.properties
zuul.routes.commonservice.path=/root/path/commonservice/**
zuul.routes.commonservice.service-id=commonservice
zuul.routes.customer.path=/root/path/customer/**
zuul.routes.customer.service-id=customer
zuul.routes.student.path=/root/path/student/**
zuul.routes.student.service-id=student
and below is my custom filter
import com.netflix.zuul.ZuulFilter;
import com.netflix.zuul.context.RequestContext;
import com.openreach.gateway.common.constant.CommonConstant;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
#Component
public class HeaderFilter extends ZuulFilter {
private static final Logger log = LoggerFactory.getLogger(HeaderFilter.class);
#Override
public String filterType() {
return "pre";
}
#Override
public int filterOrder() {
return 1;
}
#Override
public boolean shouldFilter() {
return true;
}
#Override
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
HttpSession httpSession = context.getRequest().getSession();
String idOrEmail = context.getRequest().getHeader("coustom");
if (httpSession.getAttribute("someAttributes") == null) {
if (idOrEmail != null) {
//call the common-service and get details and set it first
//then call the customer service with common-service details
} else {
//call the customer service
}
} else {
log.info("data excits");
// routrs the request to the backend with the excisting data details
}
context.addZuulResponseHeader("Cookie", "JSESSIONID=" + httpSession.getId());
return null;
}
}
I'm using the ribbon load balancer with zuul. My problem is that how should I call the common-service first? I need all my requests to check the header value and then call the actual service end point.
First, use the #LoadBalanced qualifier to create your RestTemplate bean which is load balanced.
#LoadBalanced
#Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
And Inject the bean into the filter
#Autowired
RestTemplate restTemplate;
Then you can get result by restTemplate's method like below
String result = restTemplate.postForObject("http://commonservice/url", object, String.class);
ref: http://cloud.spring.io/spring-cloud-static/spring-cloud.html#_spring_resttemplate_as_a_load_balancer_client
The way I would have put together a Netty Tcp Server before reactor netty was to create the server bootsrap and add my custom pipeline class.
With Reactor-Netty there is the TcpServer.create(), but seems that I have to create a new functional interface that takes NettyInbound and NettyOutbound and returns a Mono.
However if I want to add a ChannelInitializer that builds my pipeline, I have to block to get the NettyContext.
The incoming message is received by the functional interface and I can send a response, but nothing go through my pipeline.
Is there a way to make us of Reactor Netty and have the message flow through a customized pipeline?
Returning the Mono.just("Hi") with neverComplete() successfully sends 'Hi' to the client when a connection is made and when a message is received, but I need to rather offload this to the pipeline and then have the result feed back to the client.
public void startServer() throws InterruptedException{
EventLoopGroup group = new NioEventLoopGroup(1);
try {
final TcpServer server = TcpServer.create(opts -> opts
.eventLoopGroup(group)
.listen(tcpSocketAddress));
server
.newHandler((in, out) -> {
in.receive()
.take(1)
.log(ApolloApplicationTests.class.getName())
.subscribe(data -> {
log.info("Server Received: {}", data.toString(CharsetUtil.UTF_8));
latch.countDown();
});
return out.sendString(Mono.just("Hi")).neverComplete();
})
.block().addHandler(clientEndPoint)
.channel()
.closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.MessageToMessageDecoder;
import reactor.util.Logger;
import reactor.util.Loggers;
#Configurable
#Component
public class ClientEndPoint extends ChannelInitializer<Channel> {
final Logger log = Loggers.getLogger(ApolloApplication.class);
private ChannelPipeline pipeline;
#Autowired
private ChannelHandlerAdapter messageInterchange;
#Autowired
private LengthFieldBasedFrameDecoder lowOrderVliDecoder;
#Autowired
private MessageToMessageDecoder<ByteBuf> messageDecoder;
#Autowired
private LengthFieldPrepender vliEncoder;
#Autowired
#Qualifier("inBound")
List<ChannelHandler> inBoundHandlers;
#Autowired
#Qualifier("outBound")
List<ChannelHandler> outBoundHandlers;
#Override
protected void initChannel(Channel sc) throws Exception {
this.pipeline = sc.pipeline();
this.pipeline.addLast("lowOrderVliDecoder", this.lowOrderVliDecoder);
this.pipeline.addLast("messageDecoder", this.messageDecoder);
this.pipeline.addLast("vliEncoder", this.vliEncoder);
for (ChannelHandler handler : this.inBoundHandlers) {
this.pipeline.addLast(handler);
}
this.pipeline.addLast("messageInterchange", this.messageInterchange);
for (ChannelHandler handler : this.outBoundHandlers) {
this.pipeline.addLast(handler);
}
}
public void accept(Channel sc) {
this.pipeline = sc.pipeline();
this.pipeline.addLast("lowOrderVliDecoder", this.lowOrderVliDecoder);
this.pipeline.addLast("messageDecoder", this.messageDecoder);
this.pipeline.addLast("vliEncoder", this.vliEncoder);
for (ChannelHandler handler : this.inBoundHandlers) {
this.pipeline.addLast(handler);
}
this.pipeline.addLast("messageInterchange", this.messageInterchange);
for (ChannelHandler handler : this.outBoundHandlers) {
this.pipeline.addLast(handler);
}
}
}
So this I figured out
public Mono<? extends NettyContext> initializeServer() throws InterruptedException {
this.log.debug("Server Initializing");
BiFunction<? super NettyInbound, ? super NettyOutbound, ? extends Publisher<Void>> serverHandler = (in,
out) -> {
in.receive().asString().subscribe(data -> {
this.log.debug("Received " + data + " on " + in);
});
return Flux.never();
};
TcpServer server = TcpServer.create(opts -> opts.afterChannelInit(pipeline).listen(tcpSocketAddress));
return server.newHandler(serverHandler);
}
where pipeline is the class that implements Consumer and builds the pipeline in the accept method as a typical netty pipeline.
Then I start the server
private void startServer(Mono<? extends NettyContext> connected) {
ChannelFuture f = connected.block(Duration.ofSeconds(5)).channel()
.closeFuture();
final CountDownLatch channelLatch = new CountDownLatch(1);
f.addListener(new ChannelFutureListener() {
#Override
public void operationComplete(ChannelFuture cf) throws Exception {
log.debug("Channel Disconnected");
}
});
f.awaitUninterruptibly();
// Now we are sure the future is completed.
assert f.isDone();
if (f.isCancelled()) {
this.log.warn("Connection Cancelled");
} else if (!f.isSuccess()) {
if (f.cause() != null) {
f.cause().printStackTrace();
} else {
this.log.warn("Connection not successful");
}
} else {
channelLatch.countDown();
this.log.info("Server Start Successful");
}
try {
channelLatch.await();
} catch (InterruptedException ex) {
throw new CancellationException("Interrupted while waiting for streaming " + "connection to arrive.");
}
}
I was implement 2 ControllersAdvice to. handle exception
CommonAdvice and UserAdvice
Common Advice
#ControllerAdvice(annotations = RestController.class)
public class CommonAdvice {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionBean> handleException(Exception e) {
ExceptionBean exception = new ExceptionBean(Causes.ANOTHER_CAUSE);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UserAdvice
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
public class UserAdvice {
#ExceptionHandler(NotUniqueUserLoginException.class)
public ResponseEntity<ExceptionBean> handleAlreadyFound(NotUniqueUserLoginException e) {
System.out.println("this is me : " + Causes.USER_ALREADY_EXIST.toString());
ExceptionBean exception = new ExceptionBean(Causes.USER_ALREADY_EXIST);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
And now, when I throw NotUniqueUserException, this is a CommonAdvice which handle and exception.
I tested and UserAdvice works fine.
There is the way to set priority on this classes ?
#Edit - add Controllel Mapping
#RequestMapping(value = "add", method = RequestMethod.POST)
public ResponseEntity<GT_User> addUser(#RequestBody GT_User newUser) throws NotUniqueUserLoginException, Exception {
if (this.userService.exist(newUser.getLogin())) {
throw new NotUniqueUserLoginException(Causes.USER_ALREADY_EXIST.toString());
} else {
GT_User addesUser = this.userService.addUser(newUser);
return new ResponseEntity<GT_User>(addesUser, HttpStatus.OK);
}
}
To set Higher priority to an ControllerAdvice on add :
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.HIGHEST_PRECEDENCE)
public class UserAdvice {
...
}
To set Lower priority to an ControolerAdvice on add
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.LOWEST_PRECEDENCE)
public class CommonAdvice {
...
}