AOP的简单介绍
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); }
及其默認(rèn)實現(xiàn):
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;} ....這只是一項服務(wù)。 假設(shè)此項目中還有更多服務(wù)。
因此,現(xiàn)在,如果需要記錄每種服務(wù)方法所花費的時間,則沒有AOP的選項將遵循以下內(nèi)容。 為服務(wù)創(chuàng)建一個裝飾器:
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;}該修飾器實質(zhì)上將代表修飾的對象攔截該調(diào)用,記錄將方法委派給修飾對象時該方法調(diào)用所花費的時間。
想象一下對項目中的所有方法和所有服務(wù)執(zhí)行此操作。 這是AOP解決的方案,它為交叉問題(服務(wù)方法的記錄時間要求例如)提供了一種模塊化的方式–單獨包裝而不會污染類的核心。
為了結(jié)束會話,實現(xiàn)裝飾器的另一種方法是使用Java的動態(tài)代理功能:
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;} }因此,現(xiàn)在,當(dāng)創(chuàng)建InventoryService實例時,我將通過AuditProxy動態(tài)代理創(chuàng)建它:
InventoryService inventoryService = (InventoryService)AuditProxy.newInstance(new DefaultInventoryService());重寫的java.lang.reflect.InvocationHandler調(diào)用方法將攔截以這種方式創(chuàng)建的對InventoryService的所有調(diào)用,其中記錄了審核方法調(diào)用時間的交叉問題。 這樣,將跨領(lǐng)域關(guān)注點模塊化到一個位置(AuditProxy),但是在實例化InventoryService時,仍然需要InventoryService的客戶端明確地知道它。
現(xiàn)在,我將展示如何使用Spring AOP來實現(xiàn)跨領(lǐng)域的關(guān)注– Spring提供了多種實現(xiàn)Aspects的方式–基于XML配置,基于@AspectJ。 在此特定示例中,我將使用基于XML配置文件的方式定義方面
Spring AOP在Spring容器的上下文中工作,因此在上一個會話中定義的服務(wù)實現(xiàn)必須是Spring Bean,我使用@Service批注定義了它:
@Service public class DefaultInventoryService implements InventoryService{ ... }現(xiàn)在,我想記錄我的DefaultInventoryService的每個方法調(diào)用所花費的時間–我首先將其模塊化為“建議”:
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);}}}預(yù)計該建議將捕獲DefaultInventoryService中的方法所花費的時間。 因此,現(xiàn)在將此建議連接到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>這是通過首先定義“切入點”(即在本例中為服務(wù)方法的位置)添加橫切關(guān)注點(在本例中為捕獲方法執(zhí)行時間)添加的。 在這里,我使用切入點表達(dá)式進(jìn)行了定義–
execution(* org.bk.inventory.service.*.*(..)),這實際上是選擇org.bk.inventory.service包中所有類型的所有方法。 定義切入點后,它使用表達(dá)式定義圍繞切入點(建議)要做的事情:
<aop:around pointcut-ref="serviceMethods" method="aroundMethod" />這基本上就是說,圍繞任何服務(wù)類型的每個方法,執(zhí)行前面定義的AspectAdvice的aroundMethod。 現(xiàn)在,如果執(zhí)行了服務(wù)方法,我將看到在方法執(zhí)行期間調(diào)用建議,以下是如果調(diào)用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 nsSpring的AOP實現(xiàn)通過在運行時基于定義的切入點為所有目標(biāo)bean生成動態(tài)代理來工作。
定義方面的另一種方法是使用@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批注將其標(biāo)識為方面定義。 首先定義切入點:
@Pointcut("execution(* org.bk.inventory.service.*.*(..))")public void serviceMethods(){}上面的代碼基本上標(biāo)識了org.bk.inventory.service包中所有類型的所有方法,該切入點通過放置注釋的方法的名稱(在本例中為“ serviceMethods”)進(jìn)行標(biāo)識。 接下來,使用@Before(serviceMethods()),@ After(serviceMethods())和@Around(serviceMethods())注釋定義建議,而需要發(fā)生的細(xì)節(jié)是帶有這些注釋的方法的主體。 如果此Aspect被定義為bean,則Spring AOP可以自然地理解@AspectJ批注:
<bean id="auditAspect" class="org.bk.inventory.aspect.AuditAspect" />Spring將創(chuàng)建一個動態(tài)代理,以將建議應(yīng)用于所有標(biāo)識為切入點符號的目標(biāo)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文件并將其編織到目標(biāo)切入點中。 Maven提供了一個在編譯時無縫調(diào)用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簡介的總結(jié),并提供一個示例,該示例將全面應(yīng)用前幾節(jié)中介紹的概念。
用例很簡單,我將定義一個自定義批注PerfLog,我希望對使用此批注進(jìn)行批注的方法的調(diào)用進(jìn)行計時和記錄。 讓我首先定義注釋:
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 {}現(xiàn)在使用此注釋來注釋一些服務(wù)方法:
@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的三種方法進(jìn)行了批注– update,findByVin,compositeUpdateService,它們在內(nèi)部調(diào)用方法findByVin和update。
現(xiàn)在,對于Aspect,它將攔截對所有使用@PerfLog注釋的方法的調(diào)用,并記錄該方法調(diào)用所花費的時間:
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);}} }這里的切入點表達(dá)–
@Pointcut("execution(@org.bk.annotations.PerfLog * *.*(..))")選擇所有使用@PerfLog注釋注釋的方法,并且方面方法logPerformanceStats記錄方法調(diào)用所花費的時間。
要對此進(jìn)行測試:
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"));}}調(diào)用此測試時,輸出如下:
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正確調(diào)用了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
總結(jié)
- 上一篇: 落宗是什么意思
- 下一篇: 过安检不能带什么 过安检不允许带什么