1.AspectJ簡介
AspectJ是一個基於Java語言的AOP框架。
Spring2.0以后新增了對AdpectJ切點表達式的支持。
@AspectJ是AspectJ1.5新增功能,通過JDK5注解技術,允許直接在Bean類中定義切面。
新版本Spring框架,建議使用AspectJ方式來開發AOP。
使用AspectJ需要導入Spring AOP和AspectJ相關jar包。
2.語法簡介
(1)@AspectJ提供不同的通知類型
@Before 前置通知,相當於BeforeAdvice
@AfterReturning 后置通知,相當於AfterReturningAdvice
@Around 環繞通知,相當於MethodInterceptor
@AfterThrowing異常拋出通知,相當於ThrowAdvice
@After 最終final通知,不管是否異常,該通知都會執行
@DeclareParents 引介通知,相當於IntroductionInterceptor
(2)在通知中通過value屬性定義切點
通過execution函數,可以定義切點的方法切入。
語法:
execution(<訪問修飾符>?<返回類型><方法名>(<參數>)<異常>)
例如:
匹配所有類public方法 execution(public * *(..)) 第一個*任意方法返回值,第二個*任意參數 ..表示任意參數
匹配指定包下所有類方法(不包含子包) execution(* com.ikidana.dao.*(..)) 訪問修飾符可以沒有,第一個*返回值類型 第二個*方法名稱 ..表示任意參數
匹配指定包下所有類方法(包含子包) 第一個..*表示包、子孫包下所有類
匹配指定類所有方法 execution(* com.ikidana.service.UserService.*(..))
匹配實現特定接口所有類方法 execution(* com.imooc.dao.GenericDAO+.*(..))
匹配所有save開頭的方法 execution(* save*(..))
3.簡單案例
導入依賴包:
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.1.12</version> </dependency> <!--引入Spring的基本開發包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-core</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-beans</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-expression</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>aopalliance</groupId> <artifactId>aopalliance</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.8.9</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.12</version> <scope>compile</scope> </dependency>
創建XML配置文件,開啟自動代理:
<aop:aspectj-autoproxy/>
1)前置通知
a.創建一個實例類,並創建許多方法,現在我需要增強這個類中的方法
public class ProductDAO { public void save(){ System.out.println("ProductDAO save"); } public void delete(){ System.out.println("ProductDAO delete"); } public void update(){ System.out.println("ProductDAO update"); } public void find(){ System.out.println("ProductDAO find"); } }
b.添加一個切面
@Aspect //代表一個切面 public class MyAspectAnno { @Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))") //ProductDao所有方法 public void before(){ System.out.println("前置通知"); } }
c.在XML中聲明注冊
<!--目標類,屬性注入--> <bean id="productDao" class="com.imooc.aspectJ.demo1.ProductDAO"/> <!--定義切面,不需要引用,所以不需要方法--> <bean class="com.imooc.aspectJ.demo1.MyAspectAnno"/>
d.屬性注入、目標注入、通知測試
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration("classpath:applicationContext.xml") public class SpringDemo1 { @Resource(name="productDao") //目標注入 private ProductDAO productDAO; @Test public void demo1(){ productDAO.save(); productDAO.delete(); productDAO.update(); productDAO.find(); } }
e.測試結果
前置通知
ProductDAO save
前置通知
ProductDAO delete
前置通知
ProductDAO update
前置通知
ProductDAO find
我們可以發現,ProductDAO類下面的所有方法,都添加了前置通知。
如果我們這樣定義切面:
@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))")
那么只會在save前面添加前置通知
可以在方法中傳入JoinPoint對象,用來獲得切點信息。
@Before(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.*(..))") //ProductDao所有方法 public void before(JoinPoint joinPoint){ System.out.println("前置通知" + joinPoint) ; }
切點信息類似如下:
execution(void com.imooc.aspectJ.demo1.ProductDAO.save())
2)后置通知
添加一個切面:
@AfterReturning(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.update(..))",returning = "ret") public void afterReturing(Object ret){ System.out.println("后置通知" + ret); } 結果: ProductDAO save ProductDAO delete ProductDAO update 后置通知 ProductDAO update 返回值 ProductDAO find
通過returning屬性,可以定義方法返回值。
3)環繞通知
around方法的返回值就是目標代理方法執行的返回值。
可以通過ProceedingJoinPoint可以調用攔截目標方法執行。
a.添加一個環繞通知的切面
public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("環繞前通知"); Object obj = joinPoint.proceed(); //執行目標方法,如果不調用這句話,那么目標方法將不會執行 System.out.println("環繞后通知"); return obj; } //結果: ProductDAO save 環繞前通知 ProductDAO delete 環繞后通知 ProductDAO update ProductDAO find
4)異常拋出通知
通過設置throwing屬性,可以設置發生異常對象參數。
@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))") public void afterThrowing(){ System.out.println("異常拋出通知" ); }
當然我們還可以打印異常:
@AfterThrowing(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))",throwing = "e") public void afterThrowing(Throwable e){ System.out.println("異常拋出通知" + " " + e); }
5)最終通知
無論是否出現異常,最終通知總是會被執行的。
@After(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.find(..))") public void after(){ System.out.println("最終通知"); }
4.切點命中
通過@Pointcut為切點命名。
在每個通知內定義切點,會造成工作量大,不易維護,對於重復的切點,可以使用@Pointcut進行定義。
切點方法:private void 無參方法,方法名為切點名。
當通知多個切點時,可以使用||進行連接。
大概意思就是,如果同一個切點value被很多地方引用,改起來就不太方便。
@Pointcut(value = "execution(* com.imooc.aspectJ.demo1.ProductDAO.save(..))") private void myPointcut1(){} @AfterReturning(value = "myPointcut1()") public void afterReturing(){ System.out.println("后置通知"); }
相當於創建了別名,修改了別名,就相當於修改了所有的引用。
5.基於AspectJ的XML方式的AOP開發
1)前置通知
//創建接口類和實例類 public interface CustomerDao { public void save(); public void update(); public void delete(); public void find(); } public class CustomerDaoImpl implements CustomerDao { public void save() { System.out.println("c-save"); } public void update() { System.out.println("c-update"); } public void delete() { System.out.println("c-delete"); } public void find() { System.out.println("c-find"); } } //創建通知 public class MyAspectXml { //前置通知 public void before(){ System.out.println("XML方式的前置通知"); } } //配置增強 <!--XML配置的方式完成AOP開發--> <!--配置目標類--> <bean id="customerDao" class="com.imooc.aspectJ.demo2.CustomerDaoImpl"/> <!--配置切面類--> <bean id="myAspectXml" class="com.imooc.aspectJ.demo2.MyAspectXml"/> <!--AOP相關配置--> <aop:config> <!--定義切入點:那些類的那些方法需要應用增強--> <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/> <!--配置AOP切面:使用那些增強--> <aop:aspect ref="myAspectXml"> <!--配置前置增強 pointcut-ref哪個切點增強 method使用那個增強方法 aop:before增強類型--> <aop:before method="before" pointcut-ref="pointcut1"/> </aop:aspect> </aop:config> //測試 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(value = "classpath:applicationContext2.xml") public class SpringDemo2 { @Resource(name = "customerDao") private CustomerDao customerDao; @Test public void demo1(){ customerDao.save(); customerDao.delete(); customerDao.find(); customerDao.update(); } } //結果 XML方式的前置通知 c-save c-delete c-find c-update
2)其他通知
public class MyAspectXml { //前置通知 public void before(){ //一樣可以打印JoinPoint連接點 System.out.println("XML方式的前置通知"); } //后置通知 public void afterReturing(){ System.out.println("XML方式的后置通知"); } //環繞通知 public Object around(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("環繞前"); Object obj = joinPoint.proceed(); //執行目標方法 System.out.println("環繞后"); return obj; } } <aop:config> <!--定義切入點:那些類的那些方法需要應用增強--> <aop:pointcut id="pointcut1" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.save(..))"/> <aop:pointcut id="pointcut2" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.find(..))"/> <aop:pointcut id="pointcut3" expression="execution(* com.imooc.aspectJ.demo2.CustomerDao.delete(..))"/> <!--配置AOP切面:使用那些增強--> <aop:aspect ref="myAspectXml"> <!--配置前置增強 pointcut-ref哪個切點增強 method使用那個增強方法 aop:before增強類型--> <aop:before method="before" pointcut-ref="pointcut1"/> <!--配置后置通知--> <aop:after method="afterReturing" pointcut-ref="pointcut2"/> <!--環繞通知--> <aop:around method="around" pointcut-ref="pointcut3"/> </aop:aspect> </aop:config>
在現在企業中,spring在進行AOP開發的時候,都不會使用傳統方式,都會基於AspectJ的AOP開發。
傳統AOP開發需要手動開發代理層,這樣工作量會稍大。