What I want is to have an aspect around all methods annotated with #RabbitHandler so that AssertionErrors won't kill the handler thread.
I just want to wrap them inside RuntimeExceptions and rethrow.
Motivation: there is additional error handling that I want to use which works well except for these AssertionErrors.
I could add a try-catch for AssertionErrors in each method but there are too many places and instead I was thinking of using aspects.
#Aspect
public class RabbitAssertionErrorHandlerAspect {
#Around("#annotation(org.springframework.amqp.rabbit.annotation.RabbitHandler)")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
try {
return pjp.proceed();
} catch (AssertionError e) {
throw new RuntimeException(e);
}
}
}
All nice and elegant but it doesn't get called. I assume this has something to do with the way these methods are discovered in the first place.
Any reasonable workarounds anyone sees?
The #RabbitListener on the class with those #RabbitHandlers can be configured with:
/**
* Set an {#link org.springframework.amqp.rabbit.listener.RabbitListenerErrorHandler}
* to invoke if the listener method throws an exception.
* #return the error handler.
* #since 2.0
*/
String errorHandler() default "";
So, consider to go the custom RabbitListenerErrorHandler way instead.
It works with Spring AOP...
#SpringBootApplication
public class So48324210Application {
public static void main(String[] args) {
SpringApplication.run(So48324210Application.class, args);
}
#Bean
public MethodInterceptor interceptor() {
return i -> {
try {
System.out.println("here");
return i.proceed();
}
catch (AssertionError e) {
throw new RuntimeException(e);
}
};
}
#Bean
public static BeanNameAutoProxyCreator proxyCreator() {
BeanNameAutoProxyCreator pc = new BeanNameAutoProxyCreator();
pc.setBeanNames("foo");
pc.setInterceptorNames("interceptor");
return pc;
}
#Bean
public Foo foo() {
return new Foo();
}
public static class Foo {
#RabbitListener(queues = "one")
public void listen(Object in) {
System.out.println(in);
}
}
}
or, as Artem said, a custom error handler will work too.
Related
I've create this pointcut:
#Pointcut(value = "execution(* net.space.service.RepositoryService.createDocumentFromBytes(..))")
public void groupBytesMethod() {
}
and this advice:
#Around("groupBytesMethod()")
public Object groupMetrics(ProceedingJoinPoint point) throws Throwable {
Object result = point.proceed();
}
I've set a breakpoint, but it's never reached.
package net.space.service;
#Service
public class RepositoryService {
private Reference createDocumentFromBytes(String id, byte[] content) throws IOException {...}
public Reference groupDocuments(#NotNull RepositoryGroupForm groupForm) {
return this.createDocumentFromBytes("id", new byte[10]);
}
}
There are many ways to make this work, I would suggest use an annotation,something like :
#Documented
#Target(ElementType.METHOD)
#Inherited
#Retention(RetentionPolicy.RUNTIME)
public #interface GroupBytesMethod {
}
Then
#Aspect
#Component
public class MyAspect {
#Around("#annotation(GroupBytesMethod)") // use full package name of annotation
public Object groupMetrics(ProceedingJoinPoint point) throws Throwable {
// do something before
Object result = null;
try {
result = point.proceed();
}
catch (Throwable t) {
// handle an exception
}
// do something after
return result;
}
}
Example here
I'm using Spring for the first time and am trying to implement a shared queue wherein a Kafka listener puts messages on the shared queue, and a ThreadManager that will eventually do something multithreaded with the items it takes off the shared queue. Here is my current implementation:
The Listener:
#Component
public class Listener {
#Autowired
private QueueConfig queueConfig;
private ExecutorService executorService;
private List<Future> futuresThread1 = new ArrayList<>();
public Listener() {
Properties appProps = new AppProperties().get();
this.executorService = Executors.newFixedThreadPool(Integer.parseInt(appProps.getProperty("listenerThreads")));
}
//TODO: how can I pass an approp into this annotation?
#KafkaListener(id = "id0", topics = "bose.cdp.ingest.marge.boseaccount.normalized")
public void listener(ConsumerRecord<?, ?> record) throws InterruptedException, ExecutionException
{
futuresThread1.add(executorService.submit(new Runnable() {
#Override public void run() {
try{
queueConfig.blockingQueue().put(record);
// System.out.println(queueConfig.blockingQueue().take());
} catch (Exception e){
System.out.print(e.toString());
}
}
}));
}
}
The Queue:
#Configuration
public class QueueConfig {
private Properties appProps = new AppProperties().get();
#Bean
public BlockingQueue<ConsumerRecord> blockingQueue() {
return new ArrayBlockingQueue<>(
Integer.parseInt(appProps.getProperty("blockingQueueSize"))
);
}
}
The ThreadManager:
#Component
public class ThreadManager {
#Autowired
private QueueConfig queueConfig;
private int threads;
public ThreadManager() {
Properties appProps = new AppProperties().get();
this.threads = Integer.parseInt(appProps.getProperty("threadManagerThreads"));
}
public void run() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(threads);
try {
while (true){
queueConfig.blockingQueue().take();
}
} catch (Exception e){
System.out.print(e.toString());
executorService.shutdownNow();
executorService.awaitTermination(1, TimeUnit.SECONDS);
}
}
}
Lastly, the main thread where everything is started from:
#SpringBootApplication
public class SourceAccountListenerApp {
public static void main(String[] args) {
SpringApplication.run(SourceAccountListenerApp.class, args);
ThreadManager threadManager = new ThreadManager();
try{
threadManager.run();
} catch (Exception e) {
System.out.println(e.toString());
}
}
}
The problem
I can tell when running this in the debugger that the Listener is adding things to the queue. When the ThreadManager takes off the shared queue, it tells me the queue is null and I get an NPE. It seems like autowiring isn't working to connect the queue the listener is using to the ThreadManager. Any help appreciated.
This is the problem:
ThreadManager threadManager = new ThreadManager();
Since you are creating the instance manually, you cannot use the DI provided by Spring.
One simple solution is implement a CommandLineRunner, that will be executed after the complete SourceAccountListenerApp initialization:
#SpringBootApplication
public class SourceAccountListenerApp {
public static void main(String[] args) {
SpringApplication.run(SourceAccountListenerApp.class, args);
}
// Create the CommandLineRunner Bean and inject ThreadManager
#Bean
CommandLineRunner runner(ThreadManager manager){
return args -> {
manager.run();
};
}
}
You use SpringĀ“s programatic, so called 'JavaConfig', way of setting up Spring beans (classes annotated with #Configuration with methods annotated with #Bean). Usually at application startup Spring will call those #Bean methods under the hood and register them in it's application context (if scope is singleton - the default - this will happen only once!). No need to call those #Bean methods anywhere in your code directly... you must not, otherwise you will get a separate, fresh instance that possibly is not fully configured!
Instead, you need to inject the BlockingQueue<ConsumerRecord> that you 'configured' in your QueueConfig.blockingQueue() method into your ThreadManager. Since the queue seems to be a mandatory dependency for the ThreadManager to work, I'd let Spring inject it via constructor:
#Component
public class ThreadManager {
private int threads;
// add instance var for queue...
private BlockingQueue<ConsumerRecord> blockingQueue;
// you could add #Autowired annotation to BlockingQueue param,
// but I believe it's not mandatory...
public ThreadManager(BlockingQueue<ConsumerRecord> blockingQueue) {
Properties appProps = new AppProperties().get();
this.threads = Integer.parseInt(appProps.getProperty("threadManagerThreads"));
this.blockingQueue = blockingQueue;
}
public void run() throws InterruptedException {
ExecutorService executorService = Executors.newFixedThreadPool(threads);
try {
while (true){
this.blockingQueue.take();
}
} catch (Exception e){
System.out.print(e.toString());
executorService.shutdownNow();
executorService.awaitTermination(1, TimeUnit.SECONDS);
}
}
}
Just to clarify one more thing: by default the method name of a #Bean method is used by Spring to assign this bean a unique ID (method name == bean id). So your method is called blockingQueue, means your BlockingQueue<ConsumerRecord> instance will also be registered with id blockingQueue in application context. The new constructor parameter is also named blockingQueue and it's type matches BlockingQueue<ConsumerRecord>. Simplified, that's one way Spring looks up and injects/wires dependencies.
I am trying to leverage both the retry and circuit breaker mechanism of spring-retry.
I tried to use both annotations(#Retryable and #CircuitBreaker) in a particular function(like below), but Circuit Breaker was not working.
#Service
public class CommandAndRetry {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
#Retryable(
value = {TypeOneException.class},
maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("SampleRetryService.recover");
throw t;
}
}
Then I tried dividing the functionality into two different functions, both having #Retryable and #CircuitBreaker respectively. In this case, retry mechanism was not working. Please find below code snippet.
PS: exec method(Circuit Breaker method) is invoked from a controller.
#Service
public class CommandAndRetry {
private static final Logger LOGGER = LoggerFactory.getLogger(SampleRetryService.class);
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000)
public void exec() throws TypeOneException {
retryWhenException();
}
#Retryable(
value = {TypeOneException.class},
maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("SampleRetryService.recover");
throw t;
}
}
Can anyone please tell why it's behaving like this.
Also please advise if there exists a better way to implement both retry and circuit-breaker.
PS: I neither want to use resilience4j nor retryTemplate.
If you want retry within circuit breaker, they must be in different beans. If you call one #Retryable directly from another, in the same bean, you will bypass the interceptor.
This works fine for me...
#SpringBootApplication
#EnableRetry
public class So52193237Application {
public static void main(String[] args) {
SpringApplication.run(So52193237Application.class, args);
}
#Bean
public ApplicationRunner runner(Foo foo) {
return args -> {
try {
foo.exec();
}
catch (Exception e) {
try {
foo.exec();
}
catch (Exception ee) {
Thread.sleep(11000);
try {
foo.exec();
}
catch (Exception eee) {
}
}
}
};
}
#Component
public static class Foo {
private static final Logger LOGGER = LoggerFactory.getLogger(Foo.class);
private final Bar bar;
public Foo(Bar bar) {
this.bar = bar;
}
#CircuitBreaker(maxAttempts = 1, openTimeout = 10000, resetTimeout=10000)
public void exec() throws TypeOneException {
LOGGER.info("Foo.circuit");
this.bar.retryWhenException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("Foo.recover");
throw t;
}
}
#Component
public static class Bar {
private static final Logger LOGGER = LoggerFactory.getLogger(Bar.class);
#Retryable(value = { TypeOneException.class }, maxAttempts = 3, backoff = #Backoff(2000))
public void retryWhenException() throws TypeOneException {
LOGGER.info("Retrying");
throw new TypeOneException();
}
#Recover
public void recover(Throwable t) throws Throwable {
LOGGER.info("Bar.recover");
throw t;
}
}
}
I'm trying to handle exceptions with AOP approach in my Spring/Swing Application and I couldn't make it work.
Main Class:
public class MainFrame extends JFrame {
private JPanel mainPanel;
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
MainFrame frame = new MainFrame();
frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
public MainFrame() {
initializeMainPanel();
}
private void initializeMainPanel() {
exitLabel.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent arg0) {
throw new Exception("test");
}
});
}
}
Aspect Class:
#Aspect
public class AspectTest{
#AfterThrowing(pointcut = "execution(* com.test.MainFrame.*(..))", throwing = "ex")
public void logError(Exception ex) throws Throwable {
// ex.printStackTrace();
}
}
So, I throw an exception within my Mouse Listener and expect to catch it in my AspectTest class' AfterThrowing method but it does not work.
Can someone please help me to understand what I'm missing here?
#AfterThrowing cannot catch exceptions, only notice them and log them or do something similar. If you want to handle exceptions in an aspect you need to use an #Around advice.
I'm having a hard time to work with jersey test framework.
I have a root resource.
#Path("sample")
public class SampleResource {
#GET
#Path("path")
#Produces({MediaType.TEXT_PLAIN})
public String readPath() {
return String.valueOf(path);
}
#Inject
private java.nio.file.Path path;
}
I prepared a factory providing the path.
public class SamplePathFactory implements Factory<Path> {
#Override
public Path provide() {
try {
return Files.createTempDirectory(null);
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
}
#Override
public void dispose(final Path instance) {
try {
Files.delete(instance);
} catch (final IOException ioe) {
throw new RuntimeException(ioe);
}
}
}
And a binder.
public class SamplePathBinder extends AbstractBinder {
#Override
protected void configure() {
bindFactory(SamplePathFactory.class).to(Path.class);
}
}
And, finally, my test class.
public class SampleResourceTest extends ContainerPerClassTest {
#Override
protected Application configure() {
final ResourceConfig resourceConfig
= new ResourceConfig(SampleResource.class);
resourceConfig.register(SamplePathBinder.class);
return resourceConfig;
}
}
When I tried to test, I got.
org.glassfish.hk2.api.UnsatisfiedDependencyException: There was no object available for injection at SystemInjecteeImpl(requiredType=Path,parent=SampleResource,qualifiers={},position=-1,optional=false,self=false,unqualified=null,1916953383)
What did I do wrong?
Your AbstractBinders should be registered as an instance, not as a class. So make the change
resourceConfig.register(new SamplePathBinder());
and it should work