I was asked in our group to set up the environment and show how to use AspectJ aspects and integrate it with Spring.
It seemed to me that this could also be interesting for the HF community.
I will not talk here about what AspectJ is, who knows - it will be useful for that, I note only that aspects are an opportunity to add some functionality to the classes at the compilation or runtime stage, which was not there before. Or change the existing one.
Next: project configuration and 3 example aspects.
')
Let's start with the maven configuration:
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.habloexample</groupId> <artifactId>aspects</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.3</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> <maven.compiler.source>1.6</maven.compiler.source> <maven.compiler.target>1.6</maven.compiler.target> </properties> </project>
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.habloexample</groupId> <artifactId>aspects</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.3</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> <maven.compiler.source>1.6</maven.compiler.source> <maven.compiler.target>1.6</maven.compiler.target> </properties> </project>
<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/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.habloexample</groupId> <artifactId>aspects</artifactId> <packaging>jar</packaging> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.5.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjrt</artifactId> <version>1.6.7</version> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.0.2</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> <artifactId>aspectj-maven-plugin</artifactId> <version>1.3</version> <configuration> <source>${maven.compiler.source}</source> <target>${maven.compiler.target}</target> </configuration> <executions> <execution> <goals> <goal>compile</goal> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> <properties> <org.springframework.version>3.0.5.RELEASE</org.springframework.version> <maven.compiler.source>1.6</maven.compiler.source> <maven.compiler.target>1.6</maven.compiler.target> </properties> </project> |
Further, in Intellij IDEA:
AspectJ Plugin: enable
Plugin AspectJ Weaver: disable
Settings / Compile / JavaCompiler: Ajc
Example 1, AspectJ with annotations, Spring with annotations:
(example taken from
www.javacodegeeks.com/2010/07/aspect-oriented-programming-with-spring.html )
Create a class:
package org.habr.springaspectj.services; import org.springframework.stereotype.Service; @Service("greetingService") public class GreetingService { public static final String HELLO_FROM_GREETING_SERVICE = "Hello from Greeting Service"; public String sayHello() { return HELLO_FROM_GREETING_SERVICE; } }
package org.habr.springaspectj.services; import org.springframework.stereotype.Service; @Service("greetingService") public class GreetingService { public static final String HELLO_FROM_GREETING_SERVICE = "Hello from Greeting Service"; public String sayHello() { return HELLO_FROM_GREETING_SERVICE; } }
package org.habr.springaspectj.services; import org.springframework.stereotype.Service; @Service("greetingService") public class GreetingService { public static final String HELLO_FROM_GREETING_SERVICE = "Hello from Greeting Service"; public String sayHello() { return HELLO_FROM_GREETING_SERVICE; } } |
Create an aspect:
package org.habr.springaspectj.aspects; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class GreetingAspect { private String message; public void setMessage(String message) { this.message = message; } @Around("execution(* org.habr.springaspectj.services.GreetingService.*(..))") public Object advice(ProceedingJoinPoint pjp) throws Throwable { String serviceGreeting = (String) pjp.proceed(); return message + " and " + serviceGreeting; } }
package org.habr.springaspectj.aspects; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class GreetingAspect { private String message; public void setMessage(String message) { this.message = message; } @Around("execution(* org.habr.springaspectj.services.GreetingService.*(..))") public Object advice(ProceedingJoinPoint pjp) throws Throwable { String serviceGreeting = (String) pjp.proceed(); return message + " and " + serviceGreeting; } }
package org.habr.springaspectj.aspects; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; @Aspect public class GreetingAspect { private String message; public void setMessage(String message) { this.message = message; } @Around("execution(* org.habr.springaspectj.services.GreetingService.*(..))") public Object advice(ProceedingJoinPoint pjp) throws Throwable { String serviceGreeting = (String) pjp.proceed(); return message + " and " + serviceGreeting; } } |
Create a test-aspectj.xml for Spring:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:component-scan base-package="org.habr.springaspectj" /> <bean class="org.habr.springaspectj.aspects.GreetingAspect" factory-method="aspectOf"> <property name="message" value="Hello from Greeting Aspect"/> </bean> <beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:component-scan base-package="org.habr.springaspectj" /> <bean class="org.habr.springaspectj.aspects.GreetingAspect" factory-method="aspectOf"> <property name="message" value="Hello from Greeting Aspect"/> </bean> <beans>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:context="http://www.springframework.org/schema/context" xmlns:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:task="http://www.springframework.org/schema/task" xsi:schemaLocation=" http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <context:component-scan base-package="org.habr.springaspectj" /> <bean class="org.habr.springaspectj.aspects.GreetingAspect" factory-method="aspectOf"> <property name="message" value="Hello from Greeting Aspect"/> </bean> <beans> |
We write the test:
package org.habr.springaspectj; import org.habr.springaspectj.services.GreetingService; import org.habr.springaspectj.services.IncrementService; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; public class AspectAnnotationTest { ApplicationContext context ; @Before public void init() { context = new ClassPathXmlApplicationContext("test-aspectj.xml"); } @Test public void testAnnotationService() { GreetingService greetingService = (GreetingService) context.getBean("greetingService"); assertTrue(greetingService.sayHello().contains(GreetingService.HELLO_FROM_GREETING_SERVICE)); assertTrue(greetingService.sayHello().length()>GreetingService.HELLO_FROM_GREETING_SERVICE.length()); } }
package org.habr.springaspectj; import org.habr.springaspectj.services.GreetingService; import org.habr.springaspectj.services.IncrementService; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; public class AspectAnnotationTest { ApplicationContext context ; @Before public void init() { context = new ClassPathXmlApplicationContext("test-aspectj.xml"); } @Test public void testAnnotationService() { GreetingService greetingService = (GreetingService) context.getBean("greetingService"); assertTrue(greetingService.sayHello().contains(GreetingService.HELLO_FROM_GREETING_SERVICE)); assertTrue(greetingService.sayHello().length()>GreetingService.HELLO_FROM_GREETING_SERVICE.length()); } }
package org.habr.springaspectj; import org.habr.springaspectj.services.GreetingService; import org.habr.springaspectj.services.IncrementService; import org.junit.Before; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertTrue; public class AspectAnnotationTest { ApplicationContext context ; @Before public void init() { context = new ClassPathXmlApplicationContext("test-aspectj.xml"); } @Test public void testAnnotationService() { GreetingService greetingService = (GreetingService) context.getBean("greetingService"); assertTrue(greetingService.sayHello().contains(GreetingService.HELLO_FROM_GREETING_SERVICE)); assertTrue(greetingService.sayHello().length()>GreetingService.HELLO_FROM_GREETING_SERVICE.length()); } } |
Service and aspect go to the corresponding packages in src / main / java
Test in src / test / java
The test checks that something has been added to the original message.
You can print greetingService.sayHello () and see that a line has been added, which is passed in the xml aspect.
Example 2. Without annotations, changing the behavior of the method.
Add service:
package org.habr.springaspectj.services; public class IncrementService { public int inc(int i){ return i+1; } }
package org.habr.springaspectj.services; public class IncrementService { public int inc(int i){ return i+1; } }
package org.habr.springaspectj.services; public class IncrementService { public int inc(int i){ return i+1; } } |
Add an aspect:
package org.habr.springaspectj.aspects; public aspect DecrementAspect { pointcut incMethod(): execution(public int inc(int)); int around(int number): incMethod() && args(number) { return proceed(number) - 1; } }
package org.habr.springaspectj.aspects; public aspect DecrementAspect { pointcut incMethod(): execution(public int inc(int)); int around(int number): incMethod() && args(number) { return proceed(number) - 1; } }
package org.habr.springaspectj.aspects; public aspect DecrementAspect { pointcut incMethod(): execution(public int inc(int)); int around(int number): incMethod() && args(number) { return proceed(number) - 1; } } |
Add a service to xml (promised - this time without annotations):
<bean name="inc-bean" class="org.habr.springaspectj.services.IncrementService"/>
<bean name="inc-bean" class="org.habr.springaspectj.services.IncrementService"/>
<bean name="inc-bean" class="org.habr.springaspectj.services.IncrementService"/> |
Add a method to the unit test:
@Test public void testBeanAndAJService() { IncrementService service = context.getBean(IncrementService.class); int i = 10; assertEquals(i,service.inc(i)); }
@Test public void testBeanAndAJService() { IncrementService service = context.getBean(IncrementService.class); int i = 10; assertEquals(i,service.inc(i)); }
@Test public void testBeanAndAJService() { IncrementService service = context.getBean(IncrementService.class); int i = 10; assertEquals(i,service.inc(i)); } |
Hooray! do increment, but the number does not increase!
Note: Aspect support in IDEA is not perfect. The above aspect will be marked in red, but it will be compiled with both maven and IDEA.
Example 3: Adding a non-existing method
Service from the previous example, add an aspect:
package org.habr.springaspectj.aspects; public aspect AddingAspect { public String org.habr.springaspectj.services.IncrementService.myCall(String name) { return "Cognac " + name; } }
package org.habr.springaspectj.aspects; public aspect AddingAspect { public String org.habr.springaspectj.services.IncrementService.myCall(String name) { return "Cognac " + name; } }
package org.habr.springaspectj.aspects; public aspect AddingAspect { public String org.habr.springaspectj.services.IncrementService.myCall(String name) { return "Cognac " + name; } } |
Add a method to the test:
@Test public void testAddAMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { IncrementService service = context.getBean(IncrementService.class); assertEquals(service.myCall("Hennesy"),"Cognac Hennesy"); }
@Test public void testAddAMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { IncrementService service = context.getBean(IncrementService.class); assertEquals(service.myCall("Hennesy"),"Cognac Hennesy"); }
@Test public void testAddAMethod() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { IncrementService service = context.getBean(IncrementService.class); assertEquals(service.myCall("Hennesy"),"Cognac Hennesy"); } |
Despite the absence of the myCall method in the class of service, IDEA does not complain and everything compiles
Findings:
Aspects to put it mildly do not increase the understanding, readability of the code, I would not advise using them without special need.
But if you really need - these examples can help. In the comments they advised to change the conclusion ...
Aspects need to be applied in several areas: logging, auditing, transactions.
And very carefully they should be used in other areas. With aspects, it is easy to do so that you read one in the code, but something completely different works.