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