一、基本概念 1.AOP簡介 DI能夠讓相互協作的軟體組件保持鬆散耦合;而面向切麵編程(aspect-oriented programming,AOP)允許你把遍佈應用各處的功能分離出來形成可重用的組件。把這些橫切關註點與業務邏輯相分離正是面向切麵編程(AOP)所要解決的問題 常見場景:日誌、安全、 ...
一、基本概念
1.AOP簡介
DI能夠讓相互協作的軟體組件保持鬆散耦合;而面向切麵編程(aspect-oriented programming,AOP)允許你把遍佈應用各處的功能分離出來形成可重用的組件。把這些橫切關註點與業務邏輯相分離正是面向切麵編程(AOP)所要解決的問題
常見場景:日誌、安全、事物、緩存
2.AOP用到的一些術語
項目中每個模塊的核心功能都是為特定業務領域提供服務,但是這些模塊都需要類似的輔助功能,例如安全和事務管理,這時候需要引入AOP的概念。
通知定義了切麵是什麼以及何時使用, Spring切麵可以應用5種類型的通知:
- 前置通知(Before):在目標方法被調用之前調用通知功能;
- 後置通知(After):在目標方法完成之後調用通知,此時不會關心方法的輸出是什麼;
- 返回通知(After-returning):在目標方法成功執行之後調用通知;
- 異常通知(After-throwing):在目標方法拋出異常後調用通知;
- 環繞通知(Around):通知包裹了被通知的方法,在被通知的方法調用之前和調用之後執行自定義的行為。
連接點(join potint)是在應用執行過程中能夠插入切麵的一個點。這個點可以是調用方法時、拋出異常時、甚至修改一個欄位時。切麵代碼可以利用這些點插入到應用的正常流程之中,並添加新的行為
切點(poincut)的定義會匹配通知所要織入的一個或多個連接點。我們通常使用明確的類和方法名稱,或是利用正則表達式定義所匹配的類和方法名稱來指定這些切點
二、準備service模塊
1.service bean
public class CategoryService1 { public void add(int id) { System.out.println("CategoryService1.add()"); } } public class CategoryService2{ public void add(int id) { System.out.println("CategoryService2.add()"); } }
2.配置bean
<?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:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.2.xsd"> <bean id="categoryServiceImpl" class="service.CategoryService1"></bean> <bean id="CategoryServiceImpl2" class="service.CategoryService2"></bean> </beans>
3.單元測試
@Test public void test(){ ApplicationContext context=new ClassPathXmlApplicationContext("aop.xml"); CategoryService1 service1=context.getBean(CategoryService1.class); service1.add(1); CategoryService2 service2=context.getBean(CategoryService2.class); service2.add(2); }
運行結果:
CategoryService1.add()
CategoryService2.add()
三、XML方式聲明AOP
Spring所創建的通知都是用標準的Java類編寫的, 定義通知所應用的切點通常會使用註解或在Spring配置文件里採用XML來編寫,這兩種語法對於Java開發者來說都是相當熟悉的。
註意Spring只支持方法級別的連接點。
切入點表達式
execution指示器是我們在編寫切點定義時最主要使用的指示器
Demo
我們要實現的一個簡單示例是:在service方法調用前和調用後列印日誌“write log”。
public class LogHandler { public void log(){ System.out.println("write log."); } }
aop.xml添加配置:
<bean id="logHandler" class="pointcut.LogHandler"></bean> <aop:config> <aop:aspect id="log" ref="logHandler"> <aop:pointcut id="addLog" expression="execution(* service.*.*(..))"></aop:pointcut> <aop:before method="log" pointcut-ref="addLog"></aop:before> <aop:after method="log" pointcut-ref="addLog"></aop:after> </aop:aspect> </aop:config>
單元測試:
public class AopTests { @Test public void test() { ApplicationContext context = new ClassPathXmlApplicationContext("aop.xml"); CategoryService1 service1 = context.getBean(CategoryService1.class); service1.add(1); CategoryService2 service2 = context.getBean(CategoryService2.class); service2.add(2); } }
運行報錯:
org.aspectj.weaver.reflect.ReflectionWorld$ReflectionWorldException
原來是忘了pom依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency>
運行結果:
write log.
CategoryService1.add()
write log.
write log.
CategoryService2.add()
write log.
<?xml version="1.0" encoding="UTF-8"?> <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>DemoStore</groupId> <artifactId>DemoAOP</artifactId> <version>1.0-SNAPSHOT</version> <properties> <spring.version>4.3.5.RELEASE</spring.version> </properties> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>RELEASE</version> </dependency> </dependencies> </project>完整的pom.xml
四、aop:around
通過使用環繞通知,可以實現前置通知和後置通知所實現的功能,而且只需要在一個方法中實現。
public class LogTimeHandler { public void log(ProceedingJoinPoint jp) throws Throwable { try { System.out.println("1.before log "+new Date().getTime());//記錄開始時間 jp.proceed(); System.out.println("2.after log "+new Date().getTime());//記錄結束時間 }catch (Exception e){ System.out.println("log fail "); } } }
在aop1.xml中配置aop:round通知
<?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" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <bean id="categoryService" class="service.CategoryService1"></bean> <bean id="logHanlder" class="pointcut.LogTimeHandler"></bean> <aop:config> <aop:aspect id="log" ref="logHanlder"> <aop:pointcut id="addlog" expression="execution(* service.*.*(..))"></aop:pointcut> <aop:around method="log" pointcut-ref="addlog"></aop:around> </aop:aspect> </aop:config> </beans>
單元測試:
public class AopTest1 { @Test public void test(){ ApplicationContext context=new ClassPathXmlApplicationContext("aop1.xml"); CategoryService1 service1=context.getBean(CategoryService1.class); service1.add(1); } }
運行結果:
1.before log 1489990832246 CategoryService1.add() 2.after log 1489990832263
五、註解方式創建AOP
定義切麵需要給類添加@Aspect註解。然後需要給方法添加註解來聲明通知方法,各通知類型對應的註解:
- @After 通知方法會在目標方法返回或拋出異常後
- @AfterReturning 通知方法會在目標方法返回後調用
- @AfterThrowing 通知方法會在目標方法拋出異常後調用
- @Around 通知方法會將目標方法封裝起來
- @Before 通知方法會在目標方法調用之前執行
@Component @Aspect public class LogHelper3 { @Before("execution(* service.*.*(..))") public void logStart(){ System.out.println("log start "+new Date().getTime()); } }
然後定義JavaConfig類,註意需要給類添加@EnableAspectJAutoProxy註解啟用自動代理功能。
@Configuration @EnableAspectJAutoProxy @ComponentScan(basePackageClasses = {service.CategoryService3.class,pointcut.LogHelper3.class}) public class BeanConfig { }
單元測試:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = BeanConfig.class) public class AopTest3 { @Autowired CategoryService3 service; @Test public void testConfigAop(){ service.add(100); } }
運行結果:
log start 1489990977264 add category id=100
結尾:
參考:《spring實戰》
源碼下載:https://github.com/cathychen00/learnjava/tree/master/DemoAOP