I have a doubt in Spring AOP ( aspectj )
Let's say one of the Advice threw unexpected/Unchecked exception while executing some piece of logic ( here After Advice having problem )
#Aspect
public class DataBaseLoggingAspect {
#After("allGetters()")
public void printAfterAspect(JoinPoint joinPoint) throws Exception {
System.out.println("after logger storing in DB ");
Triangle t = (Triangle) joinPoint.getTarget();
t.getName();
// consider below logic is persisting data into DB and throw SQL Exception
throw new SQLException();
}
#Before("allGetters()")
public void printBeforeAspect(JoinPoint joinPoint) throws SQLException {
System.out.println("before logger storing in DB");
throw new SQLException();
}
#Pointcut("execution(public String room.aop.model.*.*())")
public void allGetters() {
}
}
Main Method :
public class Main {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-context.xml");
ShapeService service = applicationContext.getBean("shapeService",ShapeService.class);
service.getTraingle().getName();
System.out.println("calling another method");
}
}
Due to exception in #After Advice below error is thrown, but my expectation is to continue the business logic which is to print "calling another method" in the console.
before logger storing in DB
after logger storing in DB
triangle shape
Exception in thread "main" java.lang.reflect.UndeclaredThrowableException
at room.aop.model.Triangle$$EnhancerBySpringCGLIB$$460db113.getName(<generated>)
at room.aop.client.Main.main(Main.java:16)
Caused by: java.sql.SQLException
at room.aop.aspects.DataBaseLoggingAspect.printAfterAspect(DataBaseLoggingAspect.java:22)
My thoughts to fix above problem as below, but how this can be handled in Spring :
Aspects has to run in different thread.
Run the Aspects in fire and forget principle ( Asynchronous )
Related
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;
}
}
I currently have two ControllerAdvice in my application, I'm supposed to merge them into one.
But I need to test them before and after the merge, test the exception and the object that the controller return me.
I'm trying to make a jUnit test with Mockito but it seems impossible to test the exceptions without any context, without a controller, etc ...
Does anyone know how can I proceed to achieve what I'm trying to do ?
I also try to throw manually an exception but obviously it wasn't catched by the ControllerAdvice.
So basically here is what i'm trying to do:
Manually throw an exception
This exception is handled by my ControllerAdvice
Check the returned object (code & message)
Here is a sample of code I have:
#Before
public void setup() {
...
mockMvc = MockMvcBuilders.standaloneSetup(getController())
.setControllerAdvice(new GlobalControllerExceptionHandler())
.setCustomArgumentResolvers(resolver, resolver_0, resolver_1)
.setHandlerExceptionResolvers(exceptionResolver).build();
}
#Controller
#RequestMapping("/tests")
public static class RestProcessingExceptionThrowingController {
#RequestMapping(value = "/exception", method = GET)
public #ResponseBody String find() {
throw new EntityNotFoundException();
}
}
#Test
public void testHandleException() throws Exception {
mockMvc.perform(get("/tests/exception"))
.andExpect(new ResultMatcher() {
#Override
public void match(MvcResult result) throws Exception {
result.getResponse().getContentAsString().contains("global_error_test");
}
})
.andExpect(status().isNotFound());
}
I have the good status code at the end but it doesn't use my ControllerAdvice (I try with the debugger)
You can just call handler method directly
#ControllerAdvice
MyAdvice{
#ExceptionHandeler(listOfExxcetpions)
public ResponseEntity someOfMyExceptionsHandler(Exception e){
.....
}
}
and in test
MuTest{
private MyAdvice advice=new MyAdvice();
#Test
public void oneOfTests(){
Exception e=new SomeSortOfExceptionToTest();
resp=advice.someOfMyExceptionsHandler(e)
assertThat(resp).....dostuff;
}
}
If you want to test how spring integrates with your handlers - if your annotations are correct, ordering serialization etc - well that will be an integration test and you have to boot up test context - then you can throw exceptions directly from controller methods.
I currently have a spring application with hibernate and a PlataformTransactionManager running on Jboss/wildfly.
Some of the methods that manipulate the database also call a bean which contains a LinkedBlockingQueue. This queue stores logging messages that are periodically dispatched to someplace else on another thread (using simple spring #Scheduler).
Would it be possible to make my queue (inside a bean) transactional? ie. if the transaction rollback would I be able to "undo" any operations made on my Collection? What's the best strategy to implement this ?
So, in short something like:
#Service
#Transactional
public PersonService {
#Autowired
EntityManager EM;
#Autowired
LoggingBuffer logger;
public void addPerson(String name) {
EM.persist(new Person(.....));
logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
}
#Component
public class LoggingBuffer {
private Queue<String> q= new LinkedBlockingQueue<String>();
public add(String msg){
q.add(msg);
}
}
Try something like this
#Transactional
public void addPerson(String name) {
EM.persist(new Person(.....));
//logger.add("New person!");
// A rollback here via some thrown exception would not affect the queue
}
public void wrapAddPerson(String name){
List<String> localBuffer = new ArrayList<>();
try{
addPerson(name);
localBuffer.add(".....");
}catch(Exception e)
{
localBuffer.clear();
}
finally{
localBuffer.forEach(logger::add);
}
}
I have a Spring Boot application and want to cover my REST controllers by integration test.
Here is my controller:
#RestController
#RequestMapping("/tools/port-scan")
public class PortScanController {
private final PortScanService service;
public PortScanController(final PortScanService portScanService) {
service = portScanService;
}
#GetMapping("")
public final PortScanInfo getInfo(
#RequestParam("address") final String address,
#RequestParam(name = "port") final int port)
throws InetAddressException, IOException {
return service.scanPort(address, port);
}
}
In one of test cases I want to test that endpoint throws an exception in some circumstances. Here is my test class:
#RunWith(SpringRunner.class)
#WebMvcTest(PortScanController.class)
public class PortScanControllerIT {
#Autowired
private MockMvc mvc;
private static final String PORT_SCAN_URL = "/tools/port-scan";
#Test
public void testLocalAddress() throws Exception {
mvc.perform(get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53")).andExpect(status().isInternalServerError());
}
}
What is the best way to do that? Current implementation doesn't handle InetAddressException which is thrown from PortScanController.getInfo() and when I start test, I receive and error:
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported
It is not possible to specify expected exception in #Test annotation since original InetAddressException is wrapped with NestedServletException.
Spring Boot Test package comes with AssertJ that has very convenient way of verifying thrown exceptions.
To verify cause:
#Test
public void shouldThrowException() {
assertThatThrownBy(() -> methodThrowingException()).hasCause(InetAddressException .class);
}
There are also few more methods that you may be interested in. I suggest having a look in docs.
In order to test the wrapped exception (i.e., InetAddressException), you can create a JUnit Rule using ExpectedException class and then set the expectMessage() (received from NestedServletException's getMessage(), which contains the actual cause), you can refer the below code for the same:
#Rule
public ExpectedException inetAddressExceptionRule = ExpectedException.none();
#Test
public void testLocalAddress() {
//Set the message exactly as returned by NestedServletException
inetAddressExceptionRule.expectMessage("Request processing failed; nested exception is com.handytools.webapi.exceptions.InetAddressException: Site local IP is not supported");
//or you can check below for actual cause
inetAddressExceptionRule.expectCause(org.hamcrest.Matchers.any(InetAddressException.class))
//code for throwing InetAddressException here (wrapped by Spring's NestedServletException)
}
You can refer the ExpectedException API here:
http://junit.org/junit4/javadoc/4.12/org/junit/rules/ExpectedException.html
You could define an exception handler
#ExceptionHandler(InetAddressException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public Response handledInvalidAddressException(InetAddressException e)
{
log e
return getValidationErrorResponse(e);
}
and then in your test you could do
mvc.perform(get(PORT_SCAN_URL)
.param("address", "192.168.1.100")
.param("port", "53"))
.andExpect(status().isBadRequest())
.andExpect(jsonPath("$.response").exists())
.andExpect(jsonPath("$.response.code", is(400)))
.andExpect(jsonPath("$.response.errors[0].message", is("Site local IP is not supported")));
I had the same issue and i fix it with org.assertj.core.api.Assertions.assertThatExceptionOfType :
#Test
public void shouldThrowInetAddressException() {
assertThatExceptionOfType(InetAddressException.class)
.isThrownBy(() -> get(PORT_SCAN_URL).param("address", "192.168.1.100").param("port", "53"));
}
I hope it's help you !
My application receives messages, extracts data and persists the extracted data to a database. Data is received via a Apache Camel channel, added to a FIFO. The following code takes the next message from the FIFO and processes it. However, in order to do this it needs to get a bean from the Spring application context:
private static void dispatch(Message msg) {
if (msg == null) {
return;
}
// TODO: This really violates IoC.
DomainObjectFactory factory = (DomainObjectFactory) ApplicationContextProvider.getApplicationContext().getBean("domainObjectFactoryBean", DomainObjectFactory.class);
// do something with message
This is the service class:
#Service
public class ApplicationContextProvider implements ApplicationContextAware {
private static final Logger log = LoggerFactory.getLogger(ApplicationContextProvider.class);
private static ApplicationContext ctx = null;
public static ApplicationContext getApplicationContext() {
return getCtx();
}
#Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException {
log.debug("Loading context");
ApplicationContextProvider.setCtx(ctx);
}
public static ApplicationContext getCtx() {
return ctx;
}
public static void setCtx(ApplicationContext ctx) {
ApplicationContextProvider.ctx = ctx;
}
}
reading a message from the FIFO:
void process(Object obj) {
Message msg = (Message) obj;
try {
Dispatcher.process(msg);
} catch (Exception e) {
e.printStackTrace();
}
}
This is really weak code, but I can't work out how to avoid it? That is how to use Spring IoC to link the removal of a message from the FIFO to the message processing without having to retrieve the bean from the context.
Any advice / guidance appreciated
Naturally, your code would look a whole lot nicer, if you would use annotation or xml-based injection. This would be easier, if your dispatch method was not static. Still, you can inject Spring beans into static fields utilizing a MethodInvokingFactoryBean. Look here for more information:
How can I inject local variables of a static method inside an abstract class using Spring?