How to fail Spring application startup if AOP Pointcut expression was not matched? - spring

I have 2 datasources in my Spring Boot app. Whenever I take a connection and there is a user's principal within Security Context, I would like to set user's id in the context of database package by invoking procedure.
To achieve this I created an AOP advice like this:
#Configuration
#Aspect
class SqlAuthAopConfig {
#AfterReturning(
value = "bean(myDataSource) && execution(java.sql.Connection javax.sql.DataSource+.getConnection(..))",
returning = "connection")
fun initUser(connection: Connection) {
val principal = SecurityContextHolder.getContext().authentication.principal as? MyUser ?: return
connection.prepareStatement("BEGIN P_AUTH.SET_ID(?);END;").use { ps ->
ps.setLong(1, principal.id)
ps.execute()
}
}
}
As you can see I used beans pointcut designator (because I have 2 datasources). This does not seem to be type-safe. If DS bean name will change in future, the pointcut expression won't match any bean, but the app will be silently started. How can I configure this aspect to fail application startup if pointcut expression was not matched?

You can use #AfterThrowing spring annotation then you can intercept by following way:
#AfterThrowing(value = "bean(...) && execution(...)", throwing = "ex")
public void interceptDataSourceErrors(Exception ex) {
// Doing something here with exception.
logger.debug( ex.getCause().getMessage());
}

Related

Spring Retry with Transactional Annotation

Is the below code the correct way to use Spring Retry with Transactional?
Or do I need to take care of anything extra ? I am using latest Spring Boot version
Is retry tried after the failed transaction is closed ?
#Repository
public class MyRepository {
#Retryable( value = CustomRetryAbleException.class, maxAttempts = 2, backoff = #Backoff(delay = 30000))
#Transactional
Employee updateAndGetEmployee(String date) throw CustomRetryAbleException;
{
try{
jdbcTemplate.exceute( ....) ; //Call Stored Proc
}
catch(CustomRetryAbleException c )
{
throw CustomRetryAbleException (" Retry this Exception " );
}
}
'This is the way.'
Do not forget to put the #EnableRetry annotation on either your config-class (annotated with #Configuration) or your application-class (annotated with #SpringBootApplication).
Read this for more information.
You can just log something and intentionally make it fail to see if it gets logged again after the delay.

error is prompted when I use #target in spring aop

I find the same question in there but didn`t find a useful answer, so I support more details. My code is the following.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DS {
String value();
}
public class AnnotationAspect {
#Around("#target(com.yh.application.DS)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsName = getDataSourceAnnotation(joinPoint).value();
System.out.println("enter in aspect:" + dsName);
return joinPoint.proceed();
}
here is a demo,
just run the application you can see the error stack trace
Unable to proxy interface-implementing method
[public final void org.springframework.boot.web.servlet.RegistrationBean.onStartup
(javax.servlet.ServletContext) throws javax.servlet.ServletException]
because it is marked as final: Consider using interface-based JDK proxies instead!
seems I need to change the aop proxy type to JDK, but when I did this, another error is prompted.
The bean 'dispatcherServlet' could not be injected as a 'org.springframework.web.servlet.DispatcherServlet' because it is a JDK dynamic proxy
Does anyone help me? thank you!
R.G's solution is correct, you ought to limit the pointcut scope. BTW, looking at your aspect code, I noticed this contrived way of getting the annotation value:
private DS getDataSourceAnnotation(ProceedingJoinPoint joinPoint) {
Class<?> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
}
else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
I suggest you just bind the annotation to an advice method parameter like this:
package com.yh.application;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AnnotationAspect {
#Before("#target(ds) && within(com.yh..*)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}
Update:
I forgot to explain why you were getting the error in the first place: Pointcuts like this(), target(), #this(), #target() can only be determined dynamically during runtime because they access active object instances. Hence, all possible Spring components (also internal ones) are being aspect-woven, which is also the reason why the workaround to limit the aspect scope by using statically evaluated pointcut designators like within() help you avoid the problem.
But actually, using a statically evaluated pointcut designator in the first place, if it is a viable alternative, is the best idea. It is also faster than weaving the world, creating dozens or hundreds of proxies, and then to dynamically evaluate pointcuts over and over again. Luckily, in this case such an alternative exists: #within().
#Aspect
#Component
public class AnnotationAspect {
#Before("#within(ds)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}

JoinPoint to match EntityManager methods

I am trying to intercept calls to the find method in EntityManager.
public Map<String, String> get() {
Map<String, String> map = new HashMap<>();
DleTestData data = em.find(DleTestData.class, "1");
map.put(data.getId(), data.getName() + " : " + data.getRegion());
return map;
}
I have an advice written like this:
#Aspect
#Configuration
public class MyAdvice {
#Around("execution(* javax.persistence.EntityManager.*(..))")
public Object aroundFind(ProceedingJoinPoint joinPoint) {
System.err.println("before em find called : " + joinPoint);
Object o = null;
try {
o = joinPoint.proceed();
System.err.println("after em find advice called : " + joinPoint);
} catch (Throwable e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return o;
}
}
The output show calls intercepted but the find method doesn't get matched in the pointcut.
Can you suggest what am I doing wrong here?
output:
before em find called : execution(Metamodel
javax.persistence.EntityManager.getMetamodel()) after em find advice
called : execution(Metamodel
javax.persistence.EntityManager.getMetamodel())
The Spring AOP manual states that Spring AOP only works for Spring beans/components.
The same manual also describes how you can apply AOP to non-Spring classes via full AspectJ via LTW (load-time weaving). It is pretty easy to configure.
If you experience any problems weaving into a basic class from the javax..* package because maybe the class is loaded before LTW is activated (even though you should be able to do that if you use javaagent:/path/to/aspectjweaver.jar), you can still switch from execution() to call() pointcut. As long as the calls are in your own application code it should be easy to intercept via AspectJ. But you do need AspectJ for it, not Spring AOP, because the latter neither supports non-Spring beans (as mentioned above) nor call() pointcut (as mentioned in the Spring manual).
Update after OP's comment:
I just checked the EntityManager Javadoc for you: Method getMetaModel() is part of the interface while get() is not. Consequently, the pointcut fails to find it.

logging at package level using spring AOP

I am trying to implement logging and exception framework for my application using spring AOP. I have defined beans for classes in ApplicationContext and using interceptors to log entry, exit and exceptions if any.
My query is : Do I need to create bean for every class in my ApplicationContext or is it possible to generalise this at a package level. If I have 30 classes in my package, I should create 30 beans in my AppContext, rather I would prefer doing it at a package level if it is possible. Kindly help me in this regard.
You need to use #Around advice. Here define the pointcut such that each class and method of the package are covered / intercepted. See sample below -
#Aspect
public class AllMethodLogging {
#Around(execution("* com.x.y..*.*(..)"))
public Object intercept(ProceddingJoinPoint pjp){
String name = pjp.getSignature().getName();
try {
logger.info("started " + name);
Object obj = pjp.proceed();
logger.info("finished " + name + " successfully");
return obj;
} catch (Throwable t) {
logger.error(name + " finished with Exception - " + t.getMessage());
throw t;
} finally {
// do some more logging if required else skip
}
}
}
The pointcut execution("* com.x.y..*.*(..)") means that any method within com.x.y package, and its sub-package, with all visibility should be intercepted by this advice.
See docs here for more examples to customize your pointcut as per your needs.

How to prevent developer call repository method from #Controller or scriptlet

I'm using spring to build Controller -> Service -> Repository architecture.
However, others developer can autowired repository in controller or anywhere.
I don't want others to violate the 3 layer architecture.
So, I using aspectj to check caller for Repository.
if caller is Service -> pass;
else throw exception.
#Aspect
public class ModelAdvice {
private Pattern pattern = Pattern.compile("^demo\\.services\\..*");
#Before("execution(* demo.repositories..*Repository.*(..))")
public void protectRepositories(JoinPoint joinPoint) {
StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
for (StackTraceElement element : stElements) {
Matcher matcher = pattern.matcher(element.getClassName());
if (matcher.matches()) {
return;
}
}
throw new RuntimeException("security violation!");
}
}
It work, but ugly and may be slow.
Is spring have some elegant way to do this?

Resources