Transaction does not commit when advise starts its execution - spring-boot

I am writing an aspect which will send email on save/update/delete operations with old and updated data as the email information. I am using JpaRepository provided by the spring-boot-starter-data-jpa dependency.
I am using #Transactional on these operations! I want that the aspect i have written for email should be called after the transaction is committed. But its not working that way. Aspect is also executed within the transaction boundary.
Below is the aspect.
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class EmailAspect {
private EmailSender emailSender;
public EmailAspect( EmailSender emailSender) {
this.emailSender = emailSender;
}
#Around("execution(* com.company.app.service.*Repository.save*(..))")
public void sendEmail(ProceedingJoinPoint joinPoint) throws Throwable {
Object myCustomObject= joinPoint.proceed();
emailSender.send("Testing from app - Subject","Testing from app - Body", "test#test.com");
}
}

Related

Spring - micrometer + opentelemetry-exporter-otlp

Is it possible to configure micrometer to send traces to otel container in spring?
I easily configured sending spans to Zipkin and Wavefront:
https://docs.spring.io/spring-boot/docs/3.1.0-SNAPSHOT/reference/htmlsingle/#actuator.micrometer-tracing.tracers but there is nothing about exporting to Otel.
Micrometer documentation also does not mention about exporting spans to otel container https://micrometer.io/docs/tracing#_using_micrometer_tracing_directly
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.exporter.otlp.trace.OtlpGrpcSpanExporter;
import io.opentelemetry.sdk.trace.export.SpanExporter;
/**
* As of SpringBoot 3.0.2 the inclusion of io.micrometer:micrometer-tracing-bridge-otel and
* io.opentelemetry:opentelemetry-exporter-otlp is not sufficient to bootstrap the SpanExporter. Adding
* io.opentelemetry:opentelemetry-sdk-extension-autoconfigure also does not help. Hence this solution which will
* probably be redundant one day.
*/
#Configuration
public class OpenTelemetryConfig {
#Value("${otel.exporter.otlp.traces.endpoint:http://localhost:4317}")
private String tracesEndpoint;
#Bean
public SpanExporter spanExporter() {
return OtlpGrpcSpanExporter.builder().setEndpoint(tracesEndpoint).build();
}
}
I also found the following necessary if you want to use io.opentelemetry.instrumentation:opentelemetry-jdbc (and probably others) as it relies on GlobalOpenTelemetry.get(). This is forcing it to be the instance produced by the micrometer-tracing-bridge-otel.
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.annotation.Configuration;
import io.opentelemetry.api.GlobalOpenTelemetry;
import io.opentelemetry.api.OpenTelemetry;
#Configuration
public class GlobalOpenTelemetrySetter implements BeanPostProcessor {
#Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
if (bean instanceof OpenTelemetry openTelemetry) {
GlobalOpenTelemetry.set(openTelemetry);
}
return bean;
}
#Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
}
I worry that this could have startup race conditions but is working for me at the moment. I hope the Spring team can provide proper clarification at some point.

can #Pointcut and #Around in different classes?

I am new to AOP.I made a generic logging API with the help of AOP in which advices are defined.
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
#Component
#Aspect
public class LogExecutionTime {
private static final String LOG_MESSAGE_FORMAT = "%s.%s execution time: %dms";
private static final Log LOG = LogFactory.getLog(LogExecutionTime.class);
#Around("myPointCut()")
public Object logTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside aAdvice LogExecutionTime");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = joinPoint.proceed();
stopWatch.stop();
logExecutionTime(joinPoint, stopWatch);
return retVal;
}
private void logExecutionTime(ProceedingJoinPoint joinPoint, StopWatch stopWatch) {
String logMessage = String.format(LOG_MESSAGE_FORMAT, joinPoint.getTarget().getClass().getName(), joinPoint.getSignature().getName());
LOG.info(logMessage.toString());
}
}
Now I have included this jar in my application code and written pointcuts.
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Component
#Aspect
public class ButterflyPointCut {
#Pointcut("execution(* com.*.Abc.methodName(..))")
public void myPointCut(){
System.out.println("Executed");
}
}
It is giving following error.I want to know whether it is because of different classes.I have put the package of jar in component scan.
Caused by: java.lang.IllegalArgumentException: error at ::0 can't find referenced pointcut myPointCut
at org.aspectj.weaver.tools.PointcutParser.parsePointcutExpression(PointcutParser.java:317)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.buildPointcutExpression(AspectJExpressionPointcut.java:217)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.checkReadyToMatch(AspectJExpressionPointcut.java:190)
at org.springframework.aop.aspectj.AspectJExpressionPointcut.getClassFilter(AspectJExpressionPointcut.java:169)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:220)
at org.springframework.aop.support.AopUtils.canApply(AopUtils.java:279)
at org.springframework.aop.support.AopUtils.findAdvisorsThatCanApply(AopUtils.java:311)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findAdvisorsThatCanApply(AbstractAdvisorAutoProxyCreator.java:119)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.findEligibleAdvisors(AbstractAdvisorAutoProxyCreator.java:89)
at org.springframework.aop.framework.autoproxy.AbstractAdvisorAutoProxyCreator.getAdvicesAndAdvisorsForBean(AbstractAdvisorAutoProxyCreator.java:70)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.wrapIfNecessary(AbstractAutoProxyCreator.java:346)
at org.springframework.aop.framework.autoproxy.AbstractAutoProxyCreator.postProcessAfterInitialization(AbstractAutoProxyCreator.java:298)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyBeanPostProcessorsAfterInitialization(AbstractAutowireCapableBeanFactory.java:422)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1588)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
... 57 common frames omitted
You can also use the full qualified name of the pointcut method (which you have defined in a separate class) with around annotation.
for example :
here you need to mention the full qualified name of the pointcut method with around annotation
for eg. #Around("domain.package.class.myPointCutMethodName()")
#Around("domain.package.className.myPointCut()")
public Object logTimeMethod(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Inside aAdvice LogExecutionTime");
StopWatch stopWatch = new StopWatch();
stopWatch.start();
Object retVal = joinPoint.proceed();
stopWatch.stop();
logExecutionTime(joinPoint, stopWatch);
return retVal;
}
Yes the exception is due to the annotated methods in different
classes.
Keep #Pointcut annotated method and #Around annotated method in a same class, Other wise use point cut expression inside #Around annotation itself like.
#Around("execution(* com.*.Abc.methodName(..))")

Spring Cloud Contracts and Spring Security issues

I am using Spring Cloud Contracts in projects to test microservices, everything is ok. But when I added Spring Security in the producer side, the GET return 401 status code instead of 200.
#Autowired
WebApplicationContext context;
#Before
public void setup() {
RestAssuredMockMvc.webAppContextSetup(this.context);
}
My question is:
I have to avoid Security settings in the contract tests?
If I want to consider the security configuration, how to make it work.
I successfully used a custom annotation on the base class, as documented here test-method-withsecuritycontext
#Retention(RetentionPolicy.RUNTIME)
#Inherited
#WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class)
public #interface WithMockCustomUserDetails {
String username() default "email#example.com";
String role() default "DEFAULT_ROLE";
String password() default "123456";
}
and then
#WithMockCustomUserDetails
class AccountBase {
...
}
Two options AFAIK.
A) Use authorization header
request {
method 'POST'
urlPath '/check'
headers {
contentType(applicationJsonUtf8())
header(authorization(), "Bearer eyJhb.... ")
}
}
B)
Add #WithMockUser in my base test
import io.restassured.module.mockmvc.RestAssuredMockMvc;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.web.context.WebApplicationContext;
#SpringBootTest
#WithMockUser //this will ensure a mock user will be injected to all requests
public abstract class BaseTestCloudContract {
#Autowired
private WebApplicationContext context;
#BeforeEach
public void setup() {
RestAssuredMockMvc.webAppContextSetup(context);
}
}

Intercepting third party class using AspectJ

I am trying to intercept spring's class using my implemented aspect.
My aspect looks like below:
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class BatchExceptionInterceptor {
#Around("execution(* org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslator.doTranslate(..))")
public Object serviceMethodIntercept(ProceedingJoinPoint pjp)
throws Throwable {
System.out.println("I am here..");
return pjp.proceed();
}
}
Also, I have used below tag in my Springs context file:
<tx:annotation-driven proxy-target-class="true"/>
I am able to intercept my own classes this way. But not sure why SQLErrorCodeSQLExceptionTranslator.doTranslate() is not being intercepted even program controller goes through this method. My aspect is being initialized properly. Any idea?

Spring JsonExceptionMapper ExceptionMapper handling.

I am getting following error in my weblogic console when i am starting my server.
SEVERE: Missing dependency for constructor
public com.test.mine.exception.JsonExceptionMapper(java.lang.String,com.fasterxml.jackson.core.JsonLocation) at parameter index 0
SEVERE: Missing dependency for constructor public com.test.mine.exception.JsonExceptionMapper(java.lang.String,com.fasterxml.jackson.core.JsonLocation) at parameter index 1
Below is my java code.
package com.test.mine.exception;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import javax.ws.rs.ext.ExceptionMapper;
import javax.ws.rs.ext.Provider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import com.fasterxml.jackson.core.JsonLocation;
import com.fasterxml.jackson.core.JsonParseException;
#Provider
#Service
public class JsonExceptionMapper extends JsonParseException implements ExceptionMapper {
public JsonExceptionMapper(String msg, JsonLocation loc) {
super(msg, loc);
// TODO Auto-generated constructor stub
}
private static final Logger LOGGER = LoggerFactory.getLogger(JsonExceptionMapper.class);
protected Logger getLogger() {
return LOGGER;
}
public Status getStatus(JsonParseException thr) {
return Status.BAD_REQUEST;
}
#Override
public Response toResponse(Throwable arg0) {
// TODO Auto-generated method stub
return Response.status(Status.BAD_REQUEST).type(MediaType.APPLICATION_JSON_TYPE).build();
}
}
The annotation #Service tells spring to create a singleton of the annotated class. At startup spring tries to create that instance and to provide the required constructor args String msg, JsonLocation loc which it does not find, so the exception.
JsonExceptionMapper does not look like a service, and it should not be a singleton. Instead it must be created whenever an exception is created.
I have never worked with that class, so sorry, cannot give you any advice on how to do that.
I bumped into a similar problem while configuring swagger to work with Jersey. After searching various forums found that Jersey scanning require a constructor without parameters. I added a a constructor and it worked for me.

Resources