public interface InventoryService {public Inventory create(Inventory inventory);public List<inventory> list();public Inventory findByVin(String vin);public Inventory update(Inventory inventory);public boolean delete(Long id);public Inventory compositeUpdateService(String vin, String newMake);
}
及其默认实现:
public class DefaultInventoryService implements InventoryService{@Overridepublic Inventory create(Inventory inventory) {logger.info("Create Inventory called");inventory.setId(1L);return inventory; }@Overridepublic List<inventory> list(){return new ArrayList<inventory>();}@Overridepublic Inventory update(Inventory inventory) {return inventory;}@Overridepublic boolean delete(Long id) {logger.info("Delete Inventory called");return true;}
....
这只是一项服务。 假设此项目中还有更多服务。
因此,现在,如果需要记录每种服务方法所花费的时间,则没有AOP的选项将遵循以下内容。 为服务创建一个装饰器:
public class InventoryServiceDecorator implements InventoryService{private static Logger logger = LoggerFactory.getLogger(InventoryServiceDecorator.class);private InventoryService decorated;@Overridepublic Inventory create(Inventory inventory) {logger.info("before method: create");long start = System.nanoTime();Inventory inventoryCreated = decorated.create(inventory);long end = System.nanoTime();logger.info(String.format("%s took %d ns", "create", (end-start)) );return inventoryCreated;}
该修饰器实质上将代表修饰的对象拦截该调用,记录将方法委派给修饰对象时该方法调用所花费的时间。
想象一下对项目中的所有方法和所有服务执行此操作。 这是AOP解决的方案,它为交叉问题(服务方法的记录时间要求例如)提供了一种模块化的方式–单独包装而不会污染类的核心。
为了结束会话,实现装饰器的另一种方法是使用Java的动态代理功能:
public class AuditProxy implements java.lang.reflect.InvocationHandler {private static Logger logger = LoggerFactory.getLogger(AuditProxy.class);private Object obj;public static Object newInstance(Object obj) {return java.lang.reflect.Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), new AuditProxy(obj));}private AuditProxy(Object obj) {this.obj = obj;}public Object invoke(Object proxy, Method m, Object[] args) throws Throwable {Object result;try {logger.info("before method " + m.getName());long start = System.nanoTime();result = m.invoke(obj, args);long end = System.nanoTime();logger.info(String.format("%s took %d ns", m.getName(), (end-start)) );} catch (InvocationTargetException e) {throw e.getTargetException();} catch (Exception e) {throw new RuntimeException("unexpected invocation exception: " + e.getMessage());} finally {logger.info("after method " + m.getName());}return result;}
}
因此,现在,当创建InventoryService实例时,我将通过AuditProxy动态代理创建它:
InventoryService inventoryService = (InventoryService)AuditProxy.newInstance(new DefaultInventoryService());
重写的java.lang.reflect.InvocationHandler调用方法将拦截以这种方式创建的对InventoryService的所有调用,其中记录了审核方法调用时间的交叉问题。 这样,将跨领域关注点模块化到一个位置(AuditProxy),但是在实例化InventoryService时,仍然需要InventoryService的客户端明确地知道它。
现在,我将展示如何使用Spring AOP来实现跨领域的关注– Spring提供了多种实现Aspects的方式–基于XML配置,基于@AspectJ。 在此特定示例中,我将使用基于XML配置文件的方式定义方面
Spring AOP在Spring容器的上下文中工作,因此在上一个会话中定义的服务实现必须是Spring Bean,我使用@Service批注定义了它:
@Service
public class DefaultInventoryService implements InventoryService{
...
}
现在,我想记录我的DefaultInventoryService的每个方法调用所花费的时间–我首先将其模块化为“建议”:
package org.bk.inventory.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class AuditAdvice {private static Logger logger = LoggerFactory.getLogger(AuditAdvice.class);public void beforeMethod() {logger.info("before method");}public void afterMethod() {logger.info("after method");}public Object aroundMethod(ProceedingJoinPoint joinpoint) {try {long start = System.nanoTime();Object result = joinpoint.proceed();long end = System.nanoTime();logger.info(String.format("%s took %d ns", joinpoint.getSignature(), (end - start)));return result;} catch (Throwable e) {throw new RuntimeException(e);}}}
预计该建议将捕获DefaultInventoryService中的方法所花费的时间。 因此,现在将此建议连接到DefaultInventoryService Spring bean:
<bean id="auditAspect" class="org.bk.inventory.aspect.AuditAdvice" /><aop:config><aop:aspect ref="auditAspect"><aop:pointcut id="serviceMethods" expression="execution(* org.bk.inventory.service.*.*(..))" /><aop:before pointcut-ref="serviceMethods" method="beforeMethod" /> <aop:around pointcut-ref="serviceMethods" method="aroundMethod" /><aop:after-returning pointcut-ref="serviceMethods" method="afterMethod" /> </aop:aspect></aop:config>
这是通过首先定义“切入点”(即在本例中为服务方法的位置)添加横切关注点(在本例中为捕获方法执行时间)添加的。 在这里,我使用切入点表达式进行了定义–
execution(* org.bk.inventory.service.*.*(..))
,这实际上是选择org.bk.inventory.service包中所有类型的所有方法。 定义切入点后,它使用表达式定义围绕切入点(建议)要做的事情:
<aop:around pointcut-ref="serviceMethods" method="aroundMethod" />
这基本上就是说,围绕任何服务类型的每个方法,执行前面定义的AspectAdvice的aroundMethod。 现在,如果执行了服务方法,我将看到在方法执行期间调用建议,以下是如果调用DefaultInventoryService,createInventory方法的示例输出:
org.bk.inventory.service.InventoryService - Create Inventory called
org.bk.inventory.aspect.AuditAdvice - Inventory org.bk.inventory.service.InventoryService.create(Inventory) took 82492 ns
Spring的AOP实现通过在运行时基于定义的切入点为所有目标bean生成动态代理来工作。
定义方面的另一种方法是使用@AspectJ注释-Spring本身理解:
package org.bk.inventory.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
public class AuditAspect {private static Logger logger = LoggerFactory.getLogger(AuditAspect.class);@Pointcut("execution(* org.bk.inventory.service.*.*(..))")public void serviceMethods(){//}@Before("serviceMethods()")public void beforeMethod() {logger.info("before method");}@Around("serviceMethods()")public Object aroundMethod(ProceedingJoinPoint joinpoint) {try {long start = System.nanoTime();Object result = joinpoint.proceed();long end = System.nanoTime();logger.info(String.format("%s took %d ns", joinpoint.getSignature(), (end - start)));return result;} catch (Throwable e) {throw new RuntimeException(e);}}@After("serviceMethods()")public void afterMethod() {logger.info("after method");}
}
类上的@Aspect批注将其标识为方面定义。 首先定义切入点:
@Pointcut("execution(* org.bk.inventory.service.*.*(..))")public void serviceMethods(){}
上面的代码基本上标识了org.bk.inventory.service包中所有类型的所有方法,该切入点通过放置注释的方法的名称(在本例中为“ serviceMethods”)进行标识。 接下来,使用@Before(serviceMethods()),@ After(serviceMethods())和@Around(serviceMethods())注释定义建议,而需要发生的细节是带有这些注释的方法的主体。 如果此Aspect被定义为bean,则Spring AOP可以自然地理解@AspectJ批注:
<bean id="auditAspect" class="org.bk.inventory.aspect.AuditAspect" />
Spring将创建一个动态代理,以将建议应用于所有标识为切入点符号的目标Bean。
定义方面的另一种方法–这次使用本机AspectJ表示法。
package org.bk.inventory.aspect;import org.bk.inventory.types.Inventory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public aspect AuditAspect {private static Logger logger = LoggerFactory.getLogger(AuditAspect.class);pointcut serviceMethods() : execution(* org.bk.inventory.service.*.*(..));pointcut serviceMethodsWithInventoryAsParam(Inventory inventory) : execution(* org.bk.inventory.service.*.*(Inventory)) && args(inventory);before() : serviceMethods() {logger.info("before method");}Object around() : serviceMethods() {long start = System.nanoTime();Object result = proceed();long end = System.nanoTime();logger.info(String.format("%s took %d ns", thisJoinPointStaticPart.getSignature(),(end - start)));return result;}Object around(Inventory inventory) : serviceMethodsWithInventoryAsParam(inventory) {Object result = proceed(inventory);logger.info(String.format("WITH PARAM: %s", inventory.toString()));return result;}after() : serviceMethods() {logger.info("after method");}
}
这映射到先前定义的 @AspectJ表示法
由于这是专门用于定义方面的DSL,因此Java编译器无法理解。 AspectJ提供了一个工具(ajc)来编译这些本机的Aspectj文件并将其编织到目标切入点中。 Maven提供了一个在编译时无缝调用ajc的插件:
<plugin><groupId>org.codehaus.mojo</groupId><artifactId>aspectj-maven-plugin</artifactId><version>1.0</version><dependencies><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>${aspectj.version}</version></dependency><dependency><groupId>org.aspectj</groupId><artifactId>aspectjtools</artifactId><version>${aspectj.version}</version></dependency></dependencies><executions><execution><goals><goal>compile</goal><goal>test-compile</goal></goals></execution></executions><configuration><outxml>true</outxml><aspectLibraries><aspectLibrary><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId></aspectLibrary></aspectLibraries><source>1.6</source><target>1.6</target></configuration></plugin>
这将是AOP简介的总结,并提供一个示例,该示例将全面应用前几节中介绍的概念。
用例很简单,我将定义一个自定义批注PerfLog,我希望对使用此批注进行批注的方法的调用进行计时和记录。 让我首先定义注释:
package org.bk.annotations;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface PerfLog {}
现在使用此注释来注释一些服务方法:
@Service
public class DefaultInventoryService implements InventoryService{private static Logger logger = LoggerFactory.getLogger(InventoryService.class);@Overridepublic Inventory create(Inventory inventory) {logger.info("Create Inventory called");inventory.setId(1L);return inventory; }@Overridepublic List<Inventory> list() {return new ArrayList<Inventory>();}@Override@PerfLogpublic Inventory update(Inventory inventory) {return inventory;}@Overridepublic boolean delete(Long id) {logger.info("Delete Inventory called");return true;}@Override@PerfLogpublic Inventory findByVin(String vin) {logger.info("find by vin called");return new Inventory("testmake", "testmodel","testtrim","testvin" );}@Override@PerfLogpublic Inventory compositeUpdateService(String vin, String newMake) {logger.info("composite Update Service called");Inventory inventory = findByVin(vin);inventory.setMake(newMake);update(inventory);return inventory;}
}
在这里,已使用@PerfLog批注对DefaultInventoryService的三种方法进行了批注– update,findByVin,compositeUpdateService,它们在内部调用方法findByVin和update。
现在,对于Aspect,它将拦截对所有使用@PerfLog注释的方法的调用,并记录该方法调用所花费的时间:
package org.bk.inventory.aspect;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;@Aspect
public class AuditAspect {private static Logger logger = LoggerFactory.getLogger(AuditAspect.class);@Pointcut("execution(@org.bk.annotations.PerfLog * *.*(..))")public void performanceTargets(){}@Around("performanceTargets()")public Object logPerformanceStats(ProceedingJoinPoint joinpoint) {try {long start = System.nanoTime();Object result = joinpoint.proceed();long end = System.nanoTime();logger.info(String.format("%s took %d ns", joinpoint.getSignature(), (end - start)));return result;} catch (Throwable e) {throw new RuntimeException(e);}}
}
这里的切入点表达–
@Pointcut("execution(@org.bk.annotations.PerfLog * *.*(..))")
选择所有使用@PerfLog注释注释的方法,并且方面方法logPerformanceStats记录方法调用所花费的时间。
要对此进行测试:
package org.bk.inventory;import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;import org.bk.inventory.service.InventoryService;
import org.bk.inventory.types.Inventory;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:/testApplicationContextAOP.xml")
public class AuditAspectTest {@Autowired InventoryService inventoryService;@Testpublic void testInventoryService() {Inventory inventory = this.inventoryService.create(new Inventory("testmake", "testmodel","testtrim","testvin" ));assertThat(inventory.getId(), is(1L));assertThat(this.inventoryService.delete(1L), is(true));assertThat(this.inventoryService.compositeUpdateService("vin","newmake").getMake(),is("newmake"));}}
调用此测试时,输出如下:
2011-09-08 20:54:03,521 org.bk.inventory.service.InventoryService - Create Inventory called
2011-09-08 20:54:03,536 org.bk.inventory.service.InventoryService - Delete Inventory called
2011-09-08 20:54:03,536 org.bk.inventory.service.InventoryService - composite Update Service called
2011-09-08 20:54:03,536 org.bk.inventory.service.InventoryService - find by vin called
2011-09-08 20:54:03,536 org.bk.inventory.aspect.AuditAspect - Inventory org.bk.inventory.service.DefaultInventoryService.findByVin(String) took 64893 ns
2011-09-08 20:54:03,536 org.bk.inventory.aspect.AuditAspect - Inventory org.bk.inventory.service.DefaultInventoryService.update(Inventory) took 1833 ns
2011-09-08 20:54:03,536 org.bk.inventory.aspect.AuditAspect - Inventory org.bk.inventory.service.DefaultInventoryService.compositeUpdateService(String, String) took 1371171 ns
正确调用了findByVin,update和CompositeUpdateService的建议。
该示例可在以下位置获得:git://github.com/bijukunjummen/AOP-Samples.git
参考: all和其他博客中的JCG合作伙伴 Biju Kunjummen 对AOP的简单介绍 。
翻译自: https://www.javacodegeeks.com/2012/06/simple-introduction-to-aop.html