Java——基於AspectJ的AOP開發


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開發需要手動開發代理層,這樣工作量會稍大。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM