AspectJ — самая мощная инфраструктура AOP в пространстве Java; Spring является самой мощной средой разработки корпоративных приложений в пространстве Java. Не удивительно, что объединение этих двух элементов должно привести к чудесным вещам … В этой статье я собираюсь показать очень простой, но полный пример, где я внедрил аспект AspectJ с помощью Spring bean-компонента.
Настройка проекта Maven
Во-первых, давайте посмотрим на Maven 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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>uk.co.jemos.experiments</groupId>
<artifactId>aspectj-test</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>aspectj-test</name>
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<spring.version>3.0.5.RELEASE</spring.version>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.3.1</version>
<executions>
<execution>
<id>waves-sources</id>
<goals>
<goal>compile</goal>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<mainClass>uk.co.jemos.experiments.Main</mainClass>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2.1</version>
<executions>
<execution>
<id>make-assembly</id> <!-- this is used for inheritance merges -->
<phase>install</phase> <!-- bind to the packaging phase -->
<goals>
<goal>assembly</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/main/assembly/executable.xml</descriptor>
<descriptor>src/main/assembly/project.xml</descriptor>
</descriptors>
<outputDirectory>${project.build.directory}/executable</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.6.10</version>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>jsr250-api</artifactId>
<version>1.0</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.version}</version>
</dependency>
</dependencies>
</project>
Бизнес-классы
Пример основан на примере, показанном в AspectJ в книге действий, 3-е издание. Это очень просто.
Существует бизнес-служба, которая отвечает за доставку сообщений. Реализация выглядит так:
package uk.co.jemos.experiments;
import org.springframework.stereotype.Component;
@Component(MessageCommunicator.BEAN_NAME)
public class MessageCommunicator {
public static final String BEAN_NAME = "uk.co.jemos.experiments.MessageCommunicator";
public void deliver(String message) {
System.out.println(message);
}
public void deliver(String person, String message) {
System.out.println(person + ", " + message);
}
}
Я сказал, что это было очень просто! Единственное, на что следует обратить внимание, это то, что я определил его как компонент Spring.
Тогда есть основной класс:
/**
*
*/
package uk.co.jemos.experiments;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
/**
* @author mtedone
*
*/
public class Main {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"classpath:appCtx.xml");
MessageCommunicator communicator = ctx
.getBean(MessageCommunicator.class);
communicator.deliver("Hello World");
}
}
Основной класс загружает контекст приложения Spring, он получает компонент MessageCommunicator и вызывает для него бизнес-метод.
Конфигурационный файл Spring
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="uk.co.jemos.experiments" />
</beans>
Исполнение без аспектов
Теперь, если бы я должен был выполнить код как есть, вывод был бы что-то вроде:
16-Jul-2011 18:50:19 org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing [...] Spring stuff... Hello World
Добавление аспекта аутентификации
Допустим, мы хотим, чтобы пользователь класса MessageCommunicator проходил аутентификацию каждый раз, когда вызывается метод delivery (…). Сначала я создаю службу аутентификации:
package uk.co.jemos.experiments;
import org.springframework.stereotype.Component;
@Component(Authenticator.BEAN_NAME)
public class Authenticator {
public static final String BEAN_NAME = "uk.co.jemos.experiments.Authenticator";
public void authenticate() {
System.out.println("User is authenticated");
}
}
Ничего особенного … Опять же, Аутентификатор был объявлен как Бин, и это важно, так как мы хотим внедрить наш Аспект с таким Аутентификатором через Spring.
Then I create the AspectJ aspect:
package uk.co.jemos.experiments;
import javax.annotation.Resource;
public aspect SecurityAspect {
@Resource(name = Authenticator.BEAN_NAME)
private Authenticator authenticator;
pointcut secureAccess() : execution(* MessageCommunicator.deliver(..));
before() : secureAccess() {
if (null == authenticator) {
throw new IllegalStateException("Authenticator should not be null");
}
System.out.println("Checking and authenticating user...");
authenticator.authenticate();
}
}
Здесь мы начинаем видеть несколько интересных вещей, главным образом, что этот аспект был введен с помощью компонента Authenticator.
Пересмотренный контекст Spring теперь выглядит следующим образом:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:component-scan base-package="uk.co.jemos.experiments" />
<bean id="securityAspect" class="uk.co.jemos.experiments.SecurityAspect" factory-method="aspectOf" />
</beans>
Все, что я сделал, это объявил аспект как компонент: это возможно, потому что каждый аспект объявляет статический метод aspectOf, который возвращает экземпляр самого аспекта. Это путь к внедрению аспектов AspectJ в Spring-бины.
Запуск модифицированной версии
Если я сейчас запускаю класс Main, я получаю следующий вывод:
16-Jul-2011 18:57:22 org.springframework.context.support.AbstractApplicationContext prepareRefresh INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@41ac1fe4: startup date [Sat Jul 16 18:57:22 BST 2011]; root of context hierarchy 16-Jul-2011 18:57:22 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions INFO: Loading XML bean definitions from class path resource [appCtx.xml] 16-Jul-2011 18:57:22 org.springframework.beans.factory.support.DefaultListableBeanFactory preInstantiateSingletons INFO: Pre-instantiating singletons in org.springframework.beans.factory.support.DefaultListableBeanFactory@66100363: defining beans [uk.co.jemos.experiments.Authenticator,uk.co.jemos.experiments.MessageCommunicator,org.springframework.context.annotation.internalConfigurationAnnotationProcessor,org.springframework.context.annotation.internalAutowiredAnnotationProcessor,org.springframework.context.annotation.internalRequiredAnnotationProcessor,org.springframework.context.annotation.internalCommonAnnotationProcessor,securityAspect]; root of factory hierarchy Checking and authenticating user... User is authenticated Hello World
Как вы можете видеть, аспект вступил в действие, и объект Authenticator действительно не был нулевым во время выполнения, что означает, что внедрение Spring сработало.
От http://tedone.typepad.com/blog/2011/07/inject-beans-in-aspectj.html