Spring AOP exception handler - execute only first aspect in ordering - spring

UPD I've updated code for Aspects to throw exception further
I have SpringBoot application, service class and I need to implement Exception Handler for my service (not MVC). The task is to log error and throw it further to the client.
I decided to use Aspect with #AfterThrowing advice. I'm gonna catch few exceptions (that extend RuntimeException) at AspectOne aspect. And for other cases I need to catch exceptions (that extend RuntimeException) at AspectTwo aspect.
So I did the following code:
public class MyOwnException extends RuntimeException {
}
#Aspect
#Order(0)
#Component
public class AspectOne {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(MyOwnException ex) {
System.out.println("MyOwnException has been thrown: " + ex.getMessage());
throw ex;
}
}
#Aspect
#Order(1)
#Component
public class AspectTwo {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(RuntimeException ex) {
System.out.println("Some unknown exception has been thrown: " + ex);
throw ex;
}
}
The problem is that AspectTwo is executed in both cases for MyOwnException and other ancestors of RuntimeException. How can I limit AspectTwo to be executed only when AspectOne haven't caught the exception?
Seems like #Order annotation works not as I expected.

How about a little hack to indicate an exception is already handled/adviced ?
Also note that , the order of execution to be AspectOne before AspectTwo here , the Order should be specified as 1 for AspectOne and 0 for AspectTwo.
From the reference documentation section : Advice Ordering
What happens when multiple pieces of advice all want to run at the
same join point? Spring AOP follows the same precedence rules as
AspectJ to determine the order of advice execution. The highest
precedence advice runs first "on the way in" (so, given two pieces of
before advice, the one with highest precedence runs first). "On the
way out" from a join point, the highest precedence advice runs last
(so, given two pieces of after advice, the one with the highest
precedence will run second).
Following code leverages the Throwable.addSuppressed() method to indicate an exception object is already handled/adviced.
--
Add an Exception class to be used as an indicator.
public class AlreadyAdvicedIndicator extends Exception {
private static final long serialVersionUID = 1L;
public AlreadyAdvicedIndicator(String message) {
super(message);
}
}
AspectOne with modified Order and logic to add a suppressed exception.
#Component
#Aspect
#Order(1)
public class AspectOne {
public static final String ALREADY_ADVICED_MSG="Adviced with AspectOne";
private static final AlreadyAdvicedIndicator alreadyAdviced = new AlreadyAdvicedIndicator(ALREADY_ADVICED_MSG);
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {}
#AfterThrowing(pointcut="logException()", throwing="ex")
public void logException(MyOwnException ex) {
System.out.println("MyOwnException has been thrown: " + ex.getMessage());
ex.addSuppressed(alreadyAdviced);
}
}
AspectTwo with modified Order and logic to check for already adviced.
#Component
#Aspect
#Order(0)
public class AspectTwo {
#Pointcut("execution(* com.test.MyService.*(..))")
public void logException() {
}
#AfterThrowing(pointcut = "logException()", throwing = "ex")
public void logException(RuntimeException ex) {
if (isAlreadyAdviced(ex)) {
System.out.println("Already Adviced : Skipping");
} else {
System.out.println("RuntimeException has been thrown: " + ex.getMessage());
}
}
private boolean isAlreadyAdviced(RuntimeException ex) {
for(Throwable e : ex.getSuppressed()) {
if(AspectOne.ALREADY_ADVICED_MSG.equals(e.getMessage())){
return true;
}
}
return false;
}
}

Related

Spring cloud stream : how to use #Transactional with new Consumer<> functional programming model

I have StreamListener which I would like to replace using the new functional model and Consumer <>. Unfortunately, I don't know how to transfer #Transactional to new model:
#Transactional
#StreamListener(PaymentChannels.PENDING_PAYMENTS_INPUT)
public void executePayments(PendingPaymentEvent event) throws Exception {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
I have tired certain things. Sample code below. I added logging messages to a different queue for tests. Then I throw an exception to trigger a rollback. Unfortunately, messages are queued even though they are not there until the method is completed (I tested this using brakepoints). It seems that the transaction was automatically committed despite the error.
#Transactional
#RequiredArgsConstructor
#Component
public class functionalPayment implements Consumer<PendingPaymentEvent> {
private final PaymentsService paymentsService;
private final StreamBridge streamBridge;
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
streamBridge.send("log-out-0",event);
throw new RuntimeException("Test exception to rollback message from log-out-0");
}
}
Configuration:
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.bind-queue=true
spring.cloud.stream.rabbit.bindings.functionalPayment-in-0.consumer.transacted=true
spring.cloud.stream.source=log
spring.cloud.stream.bindings.log-out-0.content-type=application/json
spring.cloud.stream.bindings.log-out-0.destination=log_a
spring.cloud.stream.bindings.log-out-0.group=log_a
spring.cloud.stream.rabbit.bindings.log-out-0.producer.declare-exchange=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.bind-queue=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.queue-name-group-only=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.binding-routing-key=log
spring.cloud.stream.rabbit.bindings.log-out-0.producer.transacted=true
spring.cloud.stream.rabbit.bindings.log-out-0.producer.exchange-type=direct
spring.cloud.stream.rabbit.bindings.log-out-0.producer.routing-key-expression='log'
Have you tried something along the lines of
#Transactional
public class ExecutePaymentConsumer implements Consumer<PendingPaymentEvent> {
public void accept(PendingPaymentEvent event) {
paymentsService.triggerInvoicePayment(event.getInvoiceId());
}
}
. . .
#Bean
public ExecutePaymentConsumer executePayments() {
return new ExecutePaymentConsumer();
}

MockMvc not working for DataIntegrityViolationException

We Spring developers know that if one tries to delete an entity that has other associated entities, a DataIntegrityViolationException is thrown.
I wrote a delete method catching both EmptyResultDataAccessException and DataIntegrityViolationException exceptions, throwing custom service-level exceptions for each case:
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
repository.deleteById(id); // returns 204
}
catch (EmptyResultDataAccessException e) {
throw new ResourceNotFoundException("Id not found " + id); // returns 404
}
catch (DataIntegrityViolationException e) {
throw new DatabaseException("Integrity violation"); // returns 400
}
}
}
I've set all up so the first scenario returns 204, the second scenario returns 404, and the third scenario returns 400. Everything is working fine when I test it on Postman.
However, when I try to write an integrated test using MockMvc, the DataIntegrityViolationException scenario doesn't work! (the other two scenarios work).
#SpringBootTest
#AutoConfigureMockMvc
#Transactional
public class CityControllerIT {
#Autowired
private MockMvc mockMvc;
(...)
#Test
public void deleteShouldReturnBadRequestEventWhenDependentId() throws Exception {
mockMvc.perform(delete("/cities/{id}", 1L))
.andExpect(status().isBadRequest());
}
}
It's returning 204 instead of 400! I have printed some messages inside the try block and I have found that it is really not throwing an exception. The try block executes entirely, as there was no integrity violation.
#Service
public class CityService {
#Autowired
private CityRepository repository;
public void delete(Long id) {
try {
System.out.println("START");
repository.deleteById(id);
System.out.println("FINISH");
}
(...)
I am missing something about MockMvc fundamentals? Why integrity violation is being ignored when executing that MockMvc test?
I've saved a minimum-H2-just-clone-and-run project on Github:
https://github.com/acenelio/mockmvc-dataintegrity
DataIntegrityViolationException does NOT work properly with #Transactional, even in a common service method like:
#Service
public class MyService {
#Autowired
private MyRepository repository;
#Transactional
public void delete(Long id) {
try {
repository.deleteById(id);
}
catch (DataIntegrityViolationException e) {
// do something
}
}
}
Similarly, if you want to automate test a DataIntegrityViolationException scenario, you should NOT annotate your test class with #Transactional.
So if your writing transactional integrated tests (which rollback database for each test), you may want to create another test class without #Transactional annotation to test your DataIntegrityViolationException scenario.

How to limit an aspect to a bean specified with a name?

I have the following interface:
public interface Performance {
public void perform();
}
Implemented by the following class:
#Component
public class Woodstock implements Performance {
public void perform() {
System.out.println("Woodstock.perform()");
}
}
The aspect I want to apply is this one:
#Aspect
public class Audience {
#Pointcut("execution(* concert.Performance.perform(..)) "
+ "&& bean('woodstock')"
)
public void performance() {}
#Around("performance()")
public void watchPerformance(ProceedingJoinPoint jp) {
try {
System.out.println("Taking seats");
jp.proceed();
System.out.println("CLAP CLAP CLAP!!!");
} catch (Throwable e) {
System.out.println("Demanding a refund");
}
}
}
The configuration file for the program is declaring the following beans:
#Bean
public Audience audience(){
return new Audience();
}
#Bean
public Performance woodstock(){
return new Woodstock();
}
And the test I'm running is this:
#Autowired
ApplicationContext context;
#Autowired
#Qualifier("woodstock")
Performance performance;
#Test
public void test(){
printBeanNames();
performance.perform();
}
Now, in the Audience class I specified in the annotation that I want to apply the aspect for the perform() method from the interface concert.Performance (execution(* concert.Performance.perform(..))) and I am explicitly limiting the application of the aspect to the bean whose name is "woodstock" (&& bean('woodstock')).
But when I run it I see that the aspect is not applied to the woodstock bean, although that bean is the one being used (it prints Woodstock.perform())
If instead of bean() I use !bean() then it does work, so it seems that it cannot consider that bean as the one named "woodstock".
Is there something wrong in the code above?
The special Spring AOP bean() pointcut works like bean(myBean) or bean(*Bean), there are no inline single or double quotes around the bean name according to the Spring AOP documentation (scroll down a little bit in this chapter).

ControllerAdvice not triggering for specific exceptions

I have #RestControllerAdvice (spring boot 1.4.2) that looks like this
#RestControllerAdvice
public class GlobalControllerExceptionHandler {
#ExceptionHandler(value = { AvailabilityException.class })
public RestResponse availabilityException(AvailabilityException ex) {
//logic
}
#ExceptionHandler(value = { HrsException.class })
public RestResponse hrsException(HrsException ex) {
//logic
}
}
This class catches excpetions of type HrsException but does not catch exceptions of type AvailabilityException
HrsException
public class HrsException extends RuntimeException {
public Integer errorCode;
public String messageKey;
}
AvailabilityException
public class AvailabilityException extends HrsException {
}
So I'm guessing AvailabilityException is not being caught by the controller advice because it's extending HrsException, what's the explanation for this and how can I continue with this a design?
Basically I want to create a bunch of exceptions that inherits from HrsException (because I don't want duplicate code) and want to catch them in the controller advice.
There was a catch somewhere in the code that was interfering with the controller advice, if someone faces the issue make sure you have no catches in your code preventing the chain from reaching the controller advice.

Spring PostConstruct of a container

How can I run some code inside a Spring Container after all beans has been loaded? I know I can use #PostConstruct for a single bean, but I would like to run that piece of code after all PostConstructs are called.
Is is possibile?
---UPDATE---
I tried to follow the ApplicationListener way, this is the implementation:
#Component
public class PostContructListener implements ApplicationListener<ContextRefreshedEvent> {
private static Logger log = LoggerFactory.getLogger(PostContructListener.class);
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
Collection<Initializable> inits= contextRefreshedEvent.getApplicationContext().getBeansOfType(Initializable.class).values();
for (Initializable initializable : inits) {
try{
log.debug("Initialization {} ",initializable.getClass().getSimpleName());
initializable.init();
}catch(Exception e){
log.error("Error initializing {} ",initializable.getClass().getSimpleName(),e);
}
}
}
}
Applying "Initializable" interface to all services I got what I needed, how every this way I broke all autowires, I cannot understand why but seems to be connected to the new "Initializable" interface:
java.lang.IllegalArgumentException: Can not set com.service.MyService field com.controller.RestMultiController.myService to com.sun.proxy.$Proxy41
I think you need this.
public class SpringListener implements ApplicationListener<ContextRefreshedEvent>{
public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent ) {
// do things here
}
}
http://docs.spring.io/spring/docs/current/spring-framework-reference/htmlsingle/#context-functionality-events

Resources