logging at package level using spring AOP - spring

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.

Related

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());
}
}

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

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());
}

Logging not working in web application using Spring AOP

using Spring AOP, I'm trying to put logging in my web application for an object called corelation like below :-
LoggingCorrelationEnrichingAspect.java:-
#Aspect
#Component
public class LoggingCorrelationEnrichingAspect {
private static final Logger logger = getLogger(LoggingCorrelationEnrichingAspect.class);
#Around("#annotation(Correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
}
Correlated.java:-
#Inherited
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Correlated {}
In my main REST Controller operation, using #Correlated annotation, I'm trying to log this corellation like below :-
#Correlated
#GetMapping(path = "/products}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<ProductBeanResponse> getProducts(
#RequestHeader(name = Test.HttpHeaders.TENANT_ID, required = true) UUID tId,
#RequestHeader(name = Test.HttpHeaders.CORRELATION_ID, required = true) UUID correlationId
----
---
}
However, when I test my service using PostMan tool and see the applicaiton logs, the corelation id is never logged :-
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
Please advise is this a configuration issue in Spring AOP.
Thanks
This can get working in either of below two ways
Provide fully qualified name of Correlated in the pointcut definition as #Around("#annotation(com.x.y.z.Correlated)")
Update the Aspect method signature to include the Correlated as second argument
#Around("#annotation(correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint, Correlated correlated ) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
Let know in comments if anything else is required.
P.S.: Also as pointed out by M. Deinum make sure to remove object cast.

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.

Event not working in Message Driven Bean

I'm trying to generate and handle an event when my MDB receives a message. Here is what I'm doing:
public class MDBBooks implements MessageListener {
#Inject
private Event<Update> messageReceived;
public MDBLibri() {
}
#Override
public void onMessage(Message message) {
System.out.println("Message received");
try {
Update u = message.getBody(Update.class);
messageReceived.fire(u);
if(u != null){
... stuff
}
} catch (JMSException ex) {
System.out.println("JMSException: " + ex.getMessage());
}
}
public void eventHandler(#Observes Update up) {
System.out.println("There was an update");
}
}
But it just does not work, the string "There was an update" it's not printed in the glassfish console. I can't really tell what's the problem, my textbook does it the same way pretty much. I'm assuming the event fires fine, but the event handler isn't notified.
You are correct that the observer method does not get notified. In fact, CDI doesn't even know it exists. The reason is that in CDI, message-driven beans are non-contextual objects. To simplify, they are not considered CDI beans, but you can still inject into them and intercept them.
Now, for CDI to recognize an observer method, you have to place it in a managed bean or a session bean. Quoting the spec:
An observer method is a non-abstract method of a managed bean class or session bean class (or of an extension, as defined in Container lifecycle events).
So a solution for you would be to place your observer method in another class, which is either a managed bean or a session bean.

Resources