How to audit methods in Java Spring Boot - java

I am writing a Spring Boot Application. I want to audit methods with my annotation #AuditMetod: For example I have method foo() with the annotation:
#AuditMetod(name = "SomeValue")
foo() {...}
I want to handle and audit such methods like this (the simplest example):
auditMethod(Method method) {
if (method.hasAnnotation(AuditMethod.class)) {
System.out.println (method.getName() + " was called at " + new Date())
}
}
upd
Thanks to #Karthikeyan #Swapnil Khante and #misha2048 I understood, that I need to use AOP. But I have 2 problems:
The only method in Aspect class in not being called and I don't see the inscription "----------ASPECT METHOD IS CALLED-----------" in log
How can I check in aspect method what method it is intercepting. To get an instance of Method class.
Now I have the following code:
Controller:
#PostMapping
#LoggingRest(executor = "USER", method = "CREATE", model = "SUBSCRIPTION")
public ResponseEntity<?> create(#Valid #RequestBody SubscriptionRequestDto dto) {
...
}
Aspect:
`#Aspect
#Slf4j
#Component
public class AuditAspect {
#Pointcut(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
log.info("----------ASPECT METHOD IS CALLED------------");
}`
And annotation:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface LoggingRest {
String executor() default "SYSTEM";
String method() default "";
String model() default "";
}

Auditing is a cross-cutting concern and can be handled using AOP.
Another solution would be to use a low-level solution by writing a custom annotation and using a Spring interceptorto write your business logic.
To use the Spring interceptor you will need to implement the HandlerInterceptor interface
Example of the annotation
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Audit {
boolean active() default true;
}
Interceptor example
#Component
public class AuditInterceptor implements HandlerInterceptor {
#Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
Audit annotation = handlerMethod.getMethodAnnotation(Audit.class);
if (annotation != null && annotation.active()) {
// your business logic
}
}
HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
}
check this interceptor example

I think one of the solutions here, as #Karthikeyan mentioned, is to use Spring AOP.
If you are not aware a brief introduction - spring-aop module implements the aspect oriented programming paradigm. We extract some common functionality, that we generally want to apply to some subset of functions/methods, to an entity called Aspect (see class annotated with #Aspect). This class will contain out cross-cutting functionality - such as auditing, for instance we want to audit the methods execution time, lets say. We just put the code to be executed, the condition, which tell the spring what exact beans methods should be affect by this aspect, see below.
For example, if I can audit the method execution duration with the following very simple example (in my case I said that any public method, returning void inside the Class com.example.stackoverflow.BusinessLogicClass must be inspected by this Aspect):
#SpringBootApplication
#EnableAspectJAutoProxy
public class StackoverflowApplication implements ApplicationRunner {
#Autowired
private BusinessLogicClass businessLogicClass;
public static void main(String[] args) {
SpringApplication.run(StackoverflowApplication.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
businessLogicClass.test();
}
}
#Aspect
#Component
class MyAspectLogicClass {
#Around("execution(public void com.example.stackoverflow.BusinessLogicClass.*(..))")
public Object hangAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
long before = System.currentTimeMillis();
Object returnedValue = proceedingJoinPoint.proceed();
long after = System.currentTimeMillis();
System.out.printf("Retruned in '%s' ms %n", (after - before));
return returnedValue;
}
}
#Component
class BusinessLogicClass {
public void test() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
In my case, I will get the time before method execution, then by the means of
proceedingJoinPoint.proceed() call I delegate the execution to the real method, and then, once I get the response back, I will get the current system time and calculate the execution time, fairly simple.
I hope I have at least directed you somewhere, if you are looking for documentation, this are the resources I suggest you should look for:
https://docs.spring.io/spring-framework/docs/2.5.x/reference/aop.html offical spring doc (stale a bit, but there are some valuable things to learn)
https://docs.spring.io/spring-framework/docs/4.3.15.RELEASE/spring-framework-reference/html/aop.html is more fresh doc
Hope it helped :)

The problem was in right annotation. In Aspect class I tried #Around and everything works as I need.
#Aspect
#Slf4j
#Component
public class AuditAspect {
#Around(value = "#annotation(com.aspect.annotations.LoggingRest)")
public void auditMethod(ProceedingJoinPoint proceedingJoinPoint) {
var method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
log.info("----------ASPECT METHOD IS CALLED------------");
}
}
For getting a Method instance I use fallowing code
Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();

Related

AOP and annotation 'inheritance'

let's consider the following situation.
#interface LoggedMethodInvocation{}
#LoggedMethodInvocation
#interface MonitoredMethodInvocation{}
I would like the #MonitoredMethodInvocation annotation implying the #LoggedMethodInvocation annotation.
class LoggingAOPConfig {
#Pointcut("#annotation(LoggedMethodInvocation)")
public void servicePointcut() {
}
#Around("servicePointcut()")
public Object logMethodInvocation(ProceedingJoinPoint pjp) throws Throwable {
// log the method invocation...
}
}
class MonitoringAOPConfig {
#Pointcut("#annotation(MonitoredMethodInvocation)")
public void servicePointcut() {
}
#Around("servicePointcut()")
public Object monitorResponseTime(ProceedingJoinPoint pjp) throws Throwable {
// add some meters to the method invocation
}
}
Now I would like to introduce some method, which shall be both monitored and logged. And I would like to annotate the method only with one annotation, namely #MonitoredMethodInvocation.
class SomeService {
#MonitoredMethodInvocation
Object someMethod(Object requestPayload) {
// ...
return responsePayload;
}
}
However it doesn't play, the logging aspect is not taken into the account.
There is spring's AnnotationUtils.findAnnotation which offers the needed functionality (of recognizing, whether the #LoggedMethodInvocation shall be considered). However, I don't know how to put this into the pointcut configuration.
How shall I modify the logging AOP config so it will recognize the logging annotation even if it is hidden behind the #MonitoredMethodInvocation?

Using #EnableBinding in DemoController.class result in spring aop fail

In my application, using #EnableBinding upon DemoController.class result in my Log Aspect fail,have log records no more.
And, if remove #EnableBinding, the log aspect work.
UspeController.class like this:
#RestController
#RequestMapping("/yeah/user")
#EnableBinding({OpenFileSystemOutput.class})
public class UspeController {
#Autowired
OpenFileSystemOutput openFileSystemOutput;
#PutMapping(value = "/applyAccount")
public Result<?> applyAccount() throws Exception {
UserMessage userMessage = new UserMessage(UserInfoContext.getUserId());
openFileSystemOutput.output().send(MessageBuilder.withPayload(userMessage).build());
return ResultUtils.success("");
}
}
LOG Aspect like this:
#Aspect
public class LogAspect {
#Pointcut("within(com.yeah..*) && #target(org.springframework.web.bind.annotation.RestController) ")
public void executionService() {
}
#Before(value = "executionService()")
public void doBefore(JoinPoint joinPoint) {
log.info("Api Interface: [{}], parameters: {}", request.getRequestURI(), JSON.toJSONString(getSerializableObject(joinPoint)));
}
}
Using EnableBinding is deprecated as of 3.1.x versions of Spring Cloud Stream. If you can, please upgrade your code to use the latest functional model. See the programming model section from the docs for more details.

Java Spring AOP: Can I ignore Xlint:invalidAbsoluteTypeName error which caused by pointcuts to classes which I don't use?

I have an issue concerning a generic component and one (of a dozen) application(s). My component has point cuts to many annotations, which could be used within classes and methods in my apps. When all annotations are present on the classpath, everything works fine. But not in all my apps I have these dependencies. The quick fix is, of course, add them, but that gives my app a lot of code which I don't need in that app. I'm searching for a way to ignore the Xlint:invalidAbsoluteTypeName error as stated here: Xlint:invalidAbsoluteTypeName
So what I have:
I have many apps with Soap/JMS connections, and all are annotated with the #Annotation org.springframework.ws.server.endpoint.annotation.Endpoint.
I have my pointcut in my generic component (jar):
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
And the result is:
All apps having the Spring WS dependency along with my generic component have no issues
Apps which don't have the annotation, cannot start due to java.lang.IllegalArgumentException: warning no match for this type name: org.springframework.ws.server.endpoint.annotation.Endpoint [Xlint:invalidAbsoluteTypeName] (which is obvious, see the link)
So the problem looks like Xlint:invalidAbsoluteTypeName BUT I don't want to add Spring dependencies which I'm not using. I just want this AOP pointcut ignored. Other workarounds like splitting up the pointcuts to different jars imho give too much overhead. Is there any way to have Spring AOP just ignore this pointcut, or e.g. set the pointcut to st like if-exists(class)?
To show why I think separating is causing way too much overhead have a look at my aspect structure:
#Aspect
public class PerformanceLoggingAspect {
private LogWriter logWriter;
#Inject
public PerformanceLoggingAspect(LogWriter logWriter) {
this.logWriter = logWriter;
}
#Around("within(#org.springframework.web.bind.annotation.RestController *)")
public Object withinARestController(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.REST);
}
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
#Around("within(#javax.inject.Named *)")
public Object withinAService(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.SERVICE);
}
private Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... Working code (performance logging) if the annotation is on the classpath...)
}
}
Update: I tried creating a #NeedsClass("any.package.Class") which is a #Conditional annotation from spring-context. The condition class is a ClasspathCondition which checked if the classloader could load that given class. But the error occurs before the condition gets evaluated so I'm afraid this is a dead end. But if you're curious:
The #NeedsClass annotation I tried
#Retention(RetentionPolicy.RUNTIME)
#Target({ElementType.TYPE, ElementType.METHOD})
#Documented
#Conditional(ClasspathCondition.class)
public #interface NeedsClass {
String[] value();
}
The Condition implementation. I had logging here, which never got written
public class ClasspathCondition implements Condition {
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
try {
String[] classes = (String[]) metadata.getAnnotationAttributes(NeedsClass.class.getName()).get("classes");
for (String clazz : classes) {
ClassUtils.resolveClassName(clazz, context.getClassLoader());
}
return true;
} catch (Throwable t) { /* noOp() */}
return false;
}
}
For now I have a workaround:
I created a superclass with the method:
protected Object proceedWithLogging(ProceedingJoinPoint pjp, String metingType) throws Throwable {
(... code which adds performance logging ...)
}
I created 4 subclasses with each the #Aspect annotation, and 1 method calling the super. For example this one targets JMS:
#Aspect
public class JmsPerformanceLogger extends PerformanceLoggingAspect {
#Inject
private LogWriter logWriter;
#Around("within(#org.springframework.ws.server.endpoint.annotation.Endpoint *)")
public Object withinAnEndpoint(ProceedingJoinPoint pjp) throws Throwable {
return proceedWithLogging(pjp, MetingType.BERICHT);
}
}
As a downside I have to configure all different beans which I need within my app, and I cannot add one simple configuration file as shown below, with all beans preconfigured:
#Configuration
public class PerformanceloggingConfig {
#Bean
public LogWriter performanceLogWriter(){
return new DefaultLogWriter();
}
#Bean
public JmsPerformanceLogger jmsPerformanceLogger(){
return new JmsPerformanceLogger();
}
#Bean
public RestPerformanceLogger restPerformanceLogger(){
return new RestPerformanceLogger();
}
#Bean
public ServicesPerformanceLogger servicesPerformanceLogger(){
return new ServicesPerformanceLogger();
}
#Bean
public DaoPerformanceLogger daoPerformanceLogger(){
return new DaoPerformanceLogger();
}
}
And therefore also not the handy annotation to autoconfig the class:
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.TYPE)
#Import(PerformanceloggingConfig.class)
public #interface EnablePerformanceLogging {
}
But for now adding these 4 beans when I need them, makes it possible to differentiate per app. But of course this is still a workaround, as I want to use #EnablePerformanceLogging and be done with it. If anyone has a better answer, pls tell me

Method invocation based on custom annotation in Spring?

I have a custom annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface Controller {
EventType[] events() default EventType.MESSAGE;
}
And there are methods in class B using them like below:
#Controller(events = {EventType.MESSAGE, EventType.DIRECT_MESSAGE})
public void onMessage(Message msg) { }
#Controller(events = {EventType.STAR_ADDED})
public void onStarAdded(Message msg) { }
Now, I want to invoke the above methods based on the annotation events value from another class A. In other words, when class A receives an event of type STAR_ADDED, I want to invoke all methods in class B with annotation #Controller(events = {EventType.STAR_ADDED}).
I know how to do this in Java but does Spring provide any API to do this? If yes, a code snippet would be helpful too.
Solution 1:
You could also do something like this:
enum EventType {
MESSAGE {
#Override
public void handleMessage(Service service, Message message) {
service.onMessage(message);
}
},
STAR_ADDED {
#Override
public void handleMessage(Service service, Message message) {
service.onStarAdded(message);
}
public abstract void handleMessage(Service service, Message message);
}
}
In your other class, where you know what is the "active" event:
yourEvent.handleMessage(service, message);
Solution 2:
I don't know if spring has anything precisely for that, otherwise you could also use reflection. Here's an example using reflection (I much prefer the solution above => enum without reflection):
for(Method method: Service.class.getDeclaredMethods()){
Controller annotation = m.getAnnotation(Controller.class);
for(EventType event: annotation.events()){
if(event.equals(yourActiveEventType)){
method.invoke(service, message);
}
return ...
}
}
Hint (not a solution) 3:
I really don't think the following applies for your scenario, but I thought I'd mention it... Spring AOP lets you trigger some code when an annotated method is called (it's kind of the opposite of your scenario), check this answer, but it may be worth the read for you: aspectj-pointcut-for-all-methods-of-a-class-with-specific-annotation
#Around("execution(#Controller * com.exemple.YourService.*(..))")
public Object aroundServiceMethodAdvice(final ProceedingJoinPoint pjp)
throws Throwable {
// perform actions before
return pjp.proceed();
// perform actions after
}
Solution 4: (added after comments)
Using org.reflections
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.9.10</version>
</dependency>
example:
Service service = ...;
Message message = ...;
Set<Method> methods =
ReflectionUtils.getMethods(Service.class, ReflectionUtils.withAnnotation(Controller.class),ReflectionUtils.withParametersAssignableTo(Message.class));
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
m.invoke(service, message);
}
}
}
This assumes that you already hold the reference to the Service object (where your methods are).
Since you are using Spring, if your 'Services' are spring managed, you may get the instance from spring's context, you'll have to try it out for yourself, as this is somewhat bound to your design:
#Autowired
private ApplicationContext appContext;
Reflections r = new Reflections(new MethodAnnotationsScanner(), "com.your.package");
Set<Method> methods = r.getMethodsAnnotatedWith(Controller.class);
for(Method m: methods){
Controller controller = m.getAnnotation(Controller.class);
for(EventType eventType: controller.value()){
if(EventType.MESSAGE.equals(eventType)){
String className = m.getDeclaringClass().getSimpleName();
className = className.replaceFirst(className.substring(0,1), className.substring(0,1).toLowerCase());
Object service = appContext.getBean(className);
m.invoke(service, message);
}
}
}
This works if your Class is spring managed and is added to the context using its default camelcase name.
You may simplify the logic, but I believe the principal elements are there.

Invoke other method instead

I have two methods and one of them with an annotation, let's say:
#ReplacingMethod(bar)
public void foo() { ... }
public void bar { ... }
Is it possible to invoke bar instead of foo whenever foo is called, without jumping into the body of foo? I did some research on this and were not able to set a return value via reflections. Any suggestions?
You can achieve this using Aspect Oriented Programming, e.g. with Spring AOP. I don't think you can change method implementation in pure Java without AOP.
Let me give you an example how to achieve what you asked for with Spring AOP. First, define your annotation:
#Target(ElementType.METHOD)
#Retention(RetentionPolicy.RUNTIME)
public #interface ReplacingMethod {
String value();
}
Then define an aspect that will do the actual replacing of method:
#Aspect // aspect is a module encapsulating your replacing functionality
public class ReplacingAspect {
// pointcut gives an expression selecting the "joint points" to be intercepted
#Pointcut("#annotation(example.annotation.ReplacingMethod)")
public void methodToBeReplaced() { }
// advice defining the code executed at joint points selected by given pointcut;
// in our case #Around is executed instead of the method call selected by pointcut methodToBeReplaced()
#Around("methodToBeReplaced()")
public void replaceMethodCall(ProceedingJoinPoint pjp) throws Throwable {
// get reference to the method to be replaced
MethodSignature signature = (MethodSignature) pjp.getSignature();
Method method = signature.getMethod();
// extract the name of the method to be called from ReplacingMethod annotation
ReplacingMethod replacingMethodAnnotation = method.getAnnotation(ReplacingMethod.class);
String methodToCallName = replacingMethodAnnotation.value();
// use reflection to call the method
Method methodToCall = pjp.getTarget().getClass().getMethod(methodToCallName);
methodToCall.invoke(pjp.getTarget());
}
}
Now, assuming you have class TestClass where you have applied your #ReplacingMethod annotation,
public class TestClass {
#ReplacingMethod("bar")
public void foo() { System.out.println("foo"); }
public void bar() { System.out.println("bar"); }
}
the last missing piece is to get create your instance of TestClass with AOP enabled and your ReplacingAspect applied:
public class Main {
public static void main(String... args) throws Exception {
ApplicationContext context = new AnnotationConfigApplicationContext(TestConfiguration.class); // create Spring context that enables AOP under the hood
TestClass testObject = context.getBean(TestClass.class); // we get reference to TestClass instance from context; calling on a plain new instance wouldn't work
testObject.foo(); // prints "bar" !
}
#EnableAspectJAutoProxy // enables AOP support
#Configuration
public static class TestConfiguration {
#Bean public TestClass testClass() { return new TestClass(); }
#Bean public ReplacingAspect aspect() { return new ReplacingAspect(); } // enables our ReplacingAspect
}
}
You can check out the whole working example at GitHub.
Reflection cannot change the schema of a class and not its behaviour. It can only call (possibly hidden) features.
If you want to replace a method call by another try out a byte code library as asm or javassist. These tools allow you to change class definitions and behaviour (even at runtime with some restrictions).
The approach with AOP is easier, but it is not as flexible and its classpath footprint is larger.

Categories