Aspect for class or subclass annotated with - java

I want to be able to track methods that are anotated with #RequestMapping that are annotated with a certain annotation (for instance #LooseController).
I have two pointcuts: requestMappings() and looseController()
this works well if the method that is annotated with #RequestMapping is in a class that has #LooseController but not if the #LooseController is in a subclass
for instance, when update(id) is called on Controller1 it is not caught by this aspect
Update to include more information:
package de.scrum_master.app;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
private static void requestMapping() {}
#Pointcut("#within(de.scrum_master.app.LooseController)")
private static void looseController() {}
// #Pointcut("#this(de.scrum_master.app.LooseController)")
// private static void looseController() {}
#Before("requestMapping() && looseController()")
public void myAdvice(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestMapping;
//#LooseController
public abstract class PutController {
#RequestMapping("/{id}")
public void update(String id) {
}
}
package de.scrum_master.app;
import java.lang.annotation.Retention;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#Retention(RUNTIME)
public #interface LooseController {
}
package de.scrum_master.app;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
#Component
#LooseController
#RequestMapping("/something")
public class Controller1 extends PutController {
}
package de.scrum_master.app;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
#SpringBootApplication
public class AspectApplication implements CommandLineRunner {
#Autowired
private Controller1 controller1;
#Autowired
private ConfigurableApplicationContext context;
public static void main(String[] args) {
SpringApplication.run(AspectApplication.class, "--logging.level.root=WARN", "--spring.main.banner-mode=off");
}
#Override
public void run(String... strings) throws Exception {
controller1.update("test");
context.close();
}
}
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>aspects</groupId>
<artifactId>aspects</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
</dependencies>
</project>

As I said in my comment, I have to speculate:
It begins with your exact pointcut (which you do not show)
and goes on with the question whether you use Spring AOP or full AspectJ via LTW (load-time weaving).
In the former case you do not explain if your target classes are actual Spring beans because Spring AOP can only proxy Spring beans/components.
In the latter case you have more options but have to configure your application in a different way.
You also do not show your own annotation's implementation, especially not if it has the required runtime retention.
My hypothesis for now is that
you use proxy-based Spring AOP and
all classes and aspects are #Components or otherwise declared as Spring beans in your configuration.
But in order to show you what happens I will use a stand-alone Java example with AspectJ, not a Spring application. I only put spring-web.jar and sprint-context.jar on my classpath so as to resolve the Spring annotations.
Annotation:
package de.scrum_master.app;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#Retention(RetentionPolicy.RUNTIME)
public #interface LooseController {}
Abstract base class:
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestMapping;
public abstract class PutController {
#RequestMapping("/{id}")
public void update(String id) {}
}
Subclass + main method:
package de.scrum_master.app;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.annotation.RequestMapping;
#Component
#LooseController
#RequestMapping("/something")
public class Controller1 extends PutController {
public static void main(String[] args) {
new Controller1().update("foo");
}
}
The aspect which I guess you might use:
Please note that your own pointcut within(#blabla.LooseController) is syntactically invalid, this is why I changed it to #within(blabla.LooseController).
package de.scrum_master.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class MyAspect {
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
private static void requestMapping() {}
#Pointcut("#within(de.scrum_master.app.LooseController)")
private static void looseController() {}
#Before("requestMapping() && looseController()")
public void myAdvice(JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
Console log when running Controller1.main:
staticinitialization(de.scrum_master.app.Controller1.<clinit>)
call(void de.scrum_master.app.Controller1.update(String))
Now you see the problem: AspectJ can intercept a few joinpoints for the given pointcut, but neither call nor staticinitialization are supported by Spring AOP according to the documentation.
So either you need to switch to AspectJ with LTW or devise another pointcut strategy.
To be continued, I have to interrupt the answer here for a while because I have an appointment, but will add some more info later.
Update: Okay, here is your problem and a solution: #within(de.scrum_master.app.LooseController) looks inside classes with the #LooseController annotation, but the parent class with the annotated method you are trying to intercept does not have that annotation. Thus, #within() is not the right pointcut type for you. What you want to express is that the instance, i.e. the current target object upon which the method is called, belongs to an annotated class. Therefore, you need a #target() pointcut:
#Pointcut("#target(de.scrum_master.app.LooseController)")
private static void looseController() {}
Alternatively you could also use this workaround (a little ugly, but works too):
#Pointcut("target(#de.scrum_master.app.LooseController Object)")
private static void looseController() {}
Now the console log says:
execution(void de.scrum_master.app.PutController.update(String))
This should also work in Spring AOP because execution() is a supported pointcut type there.

Related

Aspect Method is not triggered

I'm trying to get my Aspect class to work but it gets completely ignored.
I have following files:
MyAnnotation.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
}
MyAspect.java
package annotations;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
#Aspect
public class MyAspect {
#Before("#annotation(annotations.MyAnnotation)*")
public void interceptMethods(final JoinPoint thisJoinPoint) {
System.out.println(thisJoinPoint);
}
}
MyClass.java
package annotations;
import org.springframework.stereotype.Service;
#Service
public class MyClass {
#MyAnnotation
public int myMethod(final int i) {
System.out.println("method: " + i);
return i;
}
}
MyRestController.java
package annotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyRestController {
#Autowired
MyClass myClass;
#GetMapping("/myClass")
private int callMyMethod() {
return myClass.myMethod(1);
}
}
Application.java
package annotations;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>experiments</groupId>
<artifactId>annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
</dependencies>
</project>
Does anyone see the problem and how to fix it?
I've tried multiple Before expressions ("execution(* annotations..(..))") but I just can't see to get it working.
I've tried Around instead of Before.
I've tried Pointcuts with Before.
I've been through articles:
AspectJ #Before annotation issue
Spring AspectJ, pointcut before method execution where method OR class is annotated
I finally did it. I'll leave the solution here for other people in need for a basic example.
These are the files:
MyAnnotation.java
package annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Retention(RetentionPolicy.RUNTIME)
#Target(ElementType.METHOD)
public #interface MyAnnotation {
}
MyAspect.java - it's tricky because:
annotation #Aspect is not sufficient. The annotation #Component also needs to be added to aspect class. If annotation #Component is omitted, the application will ignore the class.
in #Before annotation, there needs to be "and args(*)". If the args are omitted, there will be null pointer exception.
Code:
package annotations;
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 MyAspect {
#Before("execution(* annotations.*.*(..)) and args(*)")
public void beforeExecution(final JoinPoint joinPoint) {
System.out.println("5");
}
}
MyClass.java
package annotations;
import org.springframework.stereotype.Service;
#Service
public class MyClass {
#MyAnnotation
public int myMethod(final int i) {
System.out.println("method: " + i);
return i;
}
}
MyRestController
package annotations;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyRestController {
#Autowired
MyClass myClass;
#GetMapping("/myClass")
private int callMyMethod() {
return myClass.myMethod(1);
}
}
Application.java
package annotations;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class Application {
public static void main(final String[] args) {
SpringApplication.run(Application.class, args);
}
}
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>experiments</groupId>
<artifactId>annotations</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.2.11.RELEASE</version>
</dependency>
</dependencies>

Why the Pointcut expression is not working

If i keep #Pointcut("within(org.example.ShoppingCart.*)") in AuthenticationAspect.java then the authenticate method is NOT getting invoked BUT when i change to #Pointcut("within(org.example..*)") then it does get invoked.
Doesn't the 2nd PointCut below automatically include the first one?
#Pointcut("within(org.example.ShoppingCart.*)")
#Pointcut("within(org.example..*)")
i believe both of these should work.
Following is the code :
ShoppingCart.java
package org.example;
import org.springframework.stereotype.Component;
#Component
public class ShoppingCart {
public void checkout(String status)
{
System.out.println("Checkout method called");
}
}
AuthenticationAspect.java
package org.example;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AuthenticationAspect {
#Pointcut("within(org.example.ShoppingCart.*)")
public void authenticationPointCut()
{
}
#Before("authenticationPointCut()")
public void authenticate()
{
System.out.println("Authentication is being performed");
}
}
Main.java
package org.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
ShoppingCart cart = context.getBean(ShoppingCart.class);
cart.checkout("CANCELLED");
}
}
BeanConfig.java
package org.example;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
#Configuration // Indicates this is a configuration class
#ComponentScan(basePackages = "org.example") // Indicates where to find the components/beans
#EnableAspectJAutoProxy // Enables the AspectJ features in project
public class BeanConfig {
}
i am learning spring aspect and unsure why the pointcut expression is not working .
Since the within pointcut designator limits matching to join points of certain types, the problem is your pointcut expression is matching types within ShoppingCart because of .* in the end. If you remove those characters it should work.
#Pointcut("within(org.example.ShoppingCart)")
public void authenticationPointCut(){}

Spring Boot Autowired is throwing NullPointerException in Main() when using field [duplicate]

This question already has answers here:
Why is my Spring #Autowired field null?
(21 answers)
Closed 4 months ago.
Note: I'm updating this since I've found a solution.
This question is similar to Why is my Spring #Autowired field null?, but I'm using the main() method here. The solution is to mark Foo with #Component and autowire it into Main through a #PostConstruct method. I learned about this through How to use autowired (#Autowired) references from main(String[] args) method?
On PostContruct: Why use #PostConstruct?
#PostConstruct makes sure all beans are fully initialized before executing (including the bean class you're actively editing).
But the bigger issue was me doing Foo foo = new Foo(). foo is outside the scope of Spring's context and 100% of it's lifecycle is managed by me. Put everything back in Spring's hands by autowiring everything:
New Main.java
package org.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import javax.annotation.PostConstruct;
#SpringBootApplication
public class Main {
#Autowired
Foo foo;
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
#PostConstruct
public void test() {
foo.message();
}
}
New Foo.java
package org.example;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Component
#Slf4j
public class Foo {
#Autowired
Bar bar;
public Foo() {
log.info("Foo bean created");
}
public void message() {
log.info(bar.getWord());
}
}
The original question is below:
I've followed so many Spring tutorials that do it this way, and it never works on my end. No clue what I'm doing wrong, but I seriously need help figuring this out.
Main.java
package org.example;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
#SpringBootApplication
#ComponentScan
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
Foo foo = new Foo();
foo.message();
}
}
Foo.java
package org.example;
import org.springframework.beans.factory.annotation.Autowired;
public class Foo {
#Autowired
Bar bar;
public void message() {
System.out.println(bar.getWord());
}
}
Bar.java
package org.example;
import org.springframework.stereotype.Component;
#Component
public class Bar {
String word = "word";
public String getWord() {return word;}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>untitled</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.7.5</version>
</dependency>
</dependencies>
</project>
When running from Main(), I get:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "org.example.Bar.getWord()" because "this.bar" is null
at org.example.Foo.message(Foo.java:11)
at org.example.Main.main(Main.java:14)
These are all in the same directory/package. I've been at this for two hours and have no clue why this isn't working. Any help on this would be greatly appreciated.
What I tried:
Using #Autowired to create a bean that I can use in another class.
What I expected to happen:
The String word to be printed to the console.
My understanding is that a class that has #Component is up for grabs by Spring to be a bean if you're using #Autowired to inject it. I have met that criteria and I am not getting my expected result.
What actually resulted:
A NullPointerException
Autowired only works when you inject the component. You will have to make Foo a component (or define a bean) too and Autowire that into your main class for this to work. Alternatively, you can use an instance of the ApplicationContext.
You also don't need that #ComponentScan annotation since the #SpringBootApplication already has it.
See: https://docs.spring.io/spring-boot/docs/2.0.x/reference/html/using-boot-using-springbootapplication-annotation.html

Error Field Repository in Controller.Controller required a bean of type 'Controller.Repository' that could not be found

I'm trying to connect my Controller to Repository in one of the spring application but I'm getting an error saying "Field tweetRepository in TweetsController.TweetsController required a bean of type 'TweetsController.TweetRepository' that could not be found."
Can someone help me with this? Thanks in advance. I've attached code samples as well.
TwitterApplication.java
package SpringMVC.Twitter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
#SpringBootApplication
#ComponentScan("AuthController")
#ComponentScan("TweetsController")
public class TwitterApplication {
public static void main(String[] args) {
SpringApplication.run(TwitterApplication.class, args);
}
}
TwitterController.java
package TweetsController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
#RestController
public class TweetsController {
#Autowired
private TweetRepository tweetRepository;
#RequestMapping("/tweets")
public Iterable<TweetsContent> getAllTweets() {
return tweetRepository.findAll();
}
#RequestMapping("tweet/{id}")
public Optional<TweetsContent> getTweet(#PathVariable int id) {
return tweetRepository.findById(id);
}
#RequestMapping(method = RequestMethod.POST, value = "/tweets")
public boolean addTweet(#RequestBody TweetsContent tweet) {
TweetsContent t = tweetRepository.save(new TweetsContent(tweet.getTitle(), tweet.getContent()));
if (t != null)
return true;
else
return false;
}
}
TwitterRepository.java
package TweetsController;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface TweetRepository extends CrudRepository<TweetsContent, Integer> { }
Add the spring data dependency on your pom:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
Add this too.
#ComponentScan("TweetRepository")
You don't need to use #ComponentScan("AuthController") and #ComponentScan("TweetsController") because if you use #SpringBootApplication, scanning will occur from the package of the class that declares this annotation. But you need to put TwitterApplication like this:
You don't have to specify #ComponentScan in your TwitterApplication, because spring boot application is scanning its own directory at the startup. and remove the #Repository
annotation from your repository class, it is also not needed.

Spring Boot AOP not working for AfterThrow

package test.aop;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.velocity.VelocityAutoConfiguration;
import org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
#Component
#Aspect
class LoggingMonitor {
#AfterThrowing(
pointcut = "execution(* test.aop..foo(..))",
throwing = "error")
public void logAfterThrowing(JoinPoint joinPoint, Throwable error) {
System.out.println(joinPoint);
}
}
#Service
class MyBean {
void foo() {
throw new RuntimeException("run error");
}
}
#SpringBootApplication
public class App {
#Autowired
MyBean myBean;
#Bean
CommandLineRunner runner() {
return r -> {
System.out.println("Run");
myBean.foo();
};
}
public static void main(String[] args) {
SpringApplication.run(App.class);
}
}
I don't understand why the code above not working, but when I changed my pointcut to "* org.springframework.boot.CommandLineRunner.run(..)", it works.
Spring AOP works on public methods only. Make your method public, or revert to AspectJ.
Due to the proxy-based nature of Spring’s AOP framework, protected
methods are by definition not intercepted, neither for JDK proxies
(where this isn’t applicable) nor for CGLIB proxies (where this is
technically possible but not recommendable for AOP purposes). As a
consequence, any given pointcut will be matched against public methods
only! If your interception needs include protected/private methods or
even constructors, consider the use of Spring-driven native AspectJ
weaving instead of Spring’s proxy-based AOP framework. This
constitutes a different mode of AOP usage with different
characteristics, so be sure to make yourself familiar with weaving
first before making a decision.
Source

Categories