1. 程式人生 > >Spring系列之AOP實現的兩種方式

Spring系列之AOP實現的兩種方式

部分 靜態常量 cep value conf tar import enc ble

AOP常用的實現方式有兩種,一種是采用聲明的方式來實現(基於XML),一種是采用註解的方式來實現(基於AspectJ)

首先復習下AOP中一些比較重要的概念:

Joinpoint(連接點):程序執行時的某個特定的點,在Spring中就是某一個方法的執行 。
Pointcut(切點):說的通俗點,spring中AOP的切點就是指一些方法的集合,而這些方法是需要被增強、被代理的。一般都是按照一定的約定規則來表示的,如正則表達式等。切點是由一類連接點組成。
Advice(通知):還是說的通俗點,就是在指定切點上要幹些什麽。
Advisor(通知器):其實就是切點和通知的結合 。

一、基於XML配置的Spring AOP

采用聲明的方式實現(在XML文件中配置),大致步驟為:配置文件中配置pointcut, 在java中用編寫實際的aspect 類, 針對對切入點進行相關的業務處理。

業務接口:

技術分享
package com.spring.service;

public interface IUserManagerService {
    //查找用戶
    public String findUser();
    
    //添加用戶
    public void addUser();
}
技術分享

業務實現:

技術分享
package com.spring.service.impl;

import com.spring.service.IUserManagerService;

public class UserManagerServiceImpl implements IUserManagerService{
    
private String name;
    
    public void setName(String name){
        this.name=name;
    }
    
    public String getName(){
        return this.name;
    }
    
    public String findUser(){
        System.out.println("============執行業務方法findUser,查找的用戶是:"+name+"=============");
        return name;
    }
    
    public void addUser(){
        System.out.println("============執行業務方法addUser=============");
        //throw new RuntimeException();
    }
}
技術分享

切面類:

技術分享
package com.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

public class AopAspect {
    
    /**
     * 前置通知:目標方法調用之前執行的代碼
      * @param jp
     */
    public void doBefore(JoinPoint jp){
        System.out.println("===========執行前置通知============");
    }
    
    /**
     * 後置返回通知:目標方法正常結束後執行的代碼
      * 返回通知是可以訪問到目標方法的返回值的
      * @param jp
     * @param result
     */
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========執行後置通知============");
        System.out.println("返回值result==================="+result);
    }
    
    /**
     * 最終通知:目標方法調用之後執行的代碼(無論目標方法是否出現異常均執行)
      * 因為方法可能會出現異常,所以不能返回方法的返回值
      * @param jp
     */
    public void doAfter(JoinPoint jp){
        System.out.println("===========執行最終通知============");
    }
    
    /**
     * 
     * 異常通知:目標方法拋出異常時執行的代碼
      * 可以訪問到異常對象
      * @param jp
     * @param ex
     */
    public void doAfterThrowing(JoinPoint jp,Exception ex){
        System.out.println("===========執行異常通知============");
    }
    
    /**
     * 環繞通知:目標方法調用前後執行的代碼,可以在方法調用前後完成自定義的行為。
      * 包圍一個連接點(join point)的通知。它會在切入點方法執行前執行同時方法結束也會執行對應的部分。
      * 主要是調用proceed()方法來執行切入點方法,來作為環繞通知前後方法的分水嶺。
      * 
     * 環繞通知類似於動態代理的全過程:ProceedingJoinPoint類型的參數可以決定是否執行目標方法。
      * 而且環繞通知必須有返回值,返回值即為目標方法的返回值
      * @param pjp
     * @return
     * @throws Throwable
     */
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
        System.out.println("======執行環繞通知開始=========");
         // 調用方法的參數
        Object[] args = pjp.getArgs();
        // 調用的方法名
        String method = pjp.getSignature().getName();
        // 獲取目標對象
        Object target = pjp.getTarget();
        // 執行完方法的返回值
        // 調用proceed()方法,就會觸發切入點方法執行
        Object result=pjp.proceed();
        System.out.println("輸出,方法名:" + method + ";目標對象:" + target + ";返回值:" + result);
        System.out.println("======執行環繞通知結束=========");
        return result;
    }
}
技術分享

Spring配置:

技術分享
<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <!-- 聲明一個業務類 -->
    <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
        <property name="name" value="lixiaoxi"></property>
    </bean>  
    
     <!-- 聲明通知類 -->
    <bean id="aspectBean" class="com.spring.aop.AopAspect" />

    <aop:config>
     <aop:aspect ref="aspectBean">
        <aop:pointcut id="pointcut" expression="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))"/>
        
        <aop:before method="doBefore" pointcut-ref="pointcut"/> 
        <aop:after-returning method="doAfterReturning" pointcut-ref="pointcut" returning="result"/>
        <aop:after method="doAfter" pointcut-ref="pointcut" /> 
        <aop:around method="doAround" pointcut-ref="pointcut"/> 
        <aop:after-throwing method="doAfterThrowing" pointcut-ref="pointcut" throwing="ex"/>
      </aop:aspect>
   </aop:config>
</beans>
技術分享

測試類:

技術分享
package com.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.service.IUserManagerService;

public class TestAop {

    public static void main(String[] args) throws Exception{
        
        ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext3.xml");
         IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
         userManager.findUser();
         System.out.println("\n");
         userManager.addUser();
    }
}
技術分享

測試結果:

技術分享

二、使用註解配置AOP

采用註解來做aop, 主要是將寫在spring 配置文件中的連接點寫到註解裏面。

業務接口和業務實現與上邊一樣,具體切面類如下:

技術分享
package com.spring.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;

@Aspect
public class AopAspectJ {
    
    /**  
     * 必須為final String類型的,註解裏要使用的變量只能是靜態常量類型的  
     */  
    public static final String EDP="execution(* com.spring.service.impl.UserManagerServiceImpl..*(..))";
    
    /**
     * 切面的前置方法 即方法執行前攔截到的方法
      * 在目標方法執行之前的通知
      * @param jp
     */
    @Before(EDP)
    public void doBefore(JoinPoint jp){
        
        System.out.println("=========執行前置通知==========");
    }
    
    
    /**
     * 在方法正常執行通過之後執行的通知叫做返回通知
      * 可以返回到方法的返回值 在註解後加入returning 
     * @param jp
     * @param result
     */
    @AfterReturning(value=EDP,returning="result")
    public void doAfterReturning(JoinPoint jp,String result){
        System.out.println("===========執行後置通知============");
    }
    
    /**
     * 最終通知:目標方法調用之後執行的通知(無論目標方法是否出現異常均執行)
      * @param jp
     */
    @After(value=EDP)
    public void doAfter(JoinPoint jp){
        System.out.println("===========執行最終通知============");
    }
    
    /**
     * 環繞通知:目標方法調用前後執行的通知,可以在方法調用前後完成自定義的行為。
      * @param pjp
     * @return
     * @throws Throwable
     */
    @Around(EDP)
    public Object doAround(ProceedingJoinPoint pjp) throws Throwable{

        System.out.println("======執行環繞通知開始=========");
        // 調用方法的參數
        Object[] args = pjp.getArgs();
        // 調用的方法名
        String method = pjp.getSignature().getName();
        // 獲取目標對象
        Object target = pjp.getTarget();
        // 執行完方法的返回值
        // 調用proceed()方法,就會觸發切入點方法執行
        Object result=pjp.proceed();
        System.out.println("輸出,方法名:" + method + ";目標對象:" + target + ";返回值:" + result);
        System.out.println("======執行環繞通知結束=========");
        return result;
    }
    
    /**
     * 在目標方法非正常執行完成, 拋出異常的時候會走此方法
      * @param jp
     * @param ex
     */
    @AfterThrowing(value=EDP,throwing="ex")
    public void doAfterThrowing(JoinPoint jp,Exception ex) {
        System.out.println("===========執行異常通知============");
    }
}
技術分享

spring的配置:

技術分享
<?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:p="http://www.springframework.org/schema/p"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
    http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
    http://www.springframework.org/schema/tx 
    http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
    http://www.springframework.org/schema/aop 
    http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
    
    <!-- [email protected] -->
    <aop:aspectj-autoproxy/>    
    
    <!-- 聲明一個業務類 -->
    <bean id="userManager" class="com.spring.service.impl.UserManagerServiceImpl">
        <property name="name" value="lixiaoxi"></property>
    </bean>   
    
     <!-- 聲明通知類 -->
    <bean id="aspectBean" class="com.spring.aop.AopAspectJ" />

</beans>
技術分享

測試類:

技術分享
package com.spring.test;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.spring.service.IUserManagerService;

public class TestAop1 {
    public static void main(String[] args) throws Exception{
        
        ApplicationContext act =  new ClassPathXmlApplicationContext("applicationContext4.xml");
         IUserManagerService userManager = (IUserManagerService)act.getBean("userManager");
         userManager.findUser();
         System.out.println("\n");
         userManager.addUser();
    }
}
技術分享

測試結果與上面相同。

註意:
1.環繞方法通知,環繞方法通知要註意必須給出調用之後的返回值,否則被代理的方法會停止調用並返回null,除非你真的打算這麽做。
2.只有環繞通知才可以使用JoinPoint的子類ProceedingJoinPoint,各連接點類型可以調用代理的方法,並獲取、改變返回值。

補充:
1.<aop:pointcut>如果位於<aop:aspect>元素中,則命名切點只能被當前<aop:aspect>內定義的元素訪問到,為了能被整個<aop:config>元素中定義的所有增強訪問,則必須在<aop:config>下定義切點。
2.如果在<aop:config>元素下直接定義<aop:pointcut>,必須保證<aop:pointcut>在<aop:aspect>之前定義。<aop:config>下還可以定義<aop:advisor>,三者在<aop:config>中的配置有先後順序的要求:首先必須是<aop:pointcut>,然後是<aop:advisor>,最後是<aop:aspect>。而在<aop:aspect>中定義的<aop:pointcut>則沒有先後順序的要求,可以在任何位置定義。
.<aop:pointcut>:用來定義切入點,該切入點可以重用;
.<aop:advisor>:用來定義只有一個通知和一個切入點的切面;
.<aop:aspect>:用來定義切面,該切面可以包含多個切入點和通知,而且標簽內部的通知和切入點定義是無序的;和advisor的區別就在此,advisor只包含一個通知和一個切入點。
3.在使用spring框架配置AOP的時候,不管是通過XML配置文件還是註解的方式都需要定義pointcut"切入點"
例如定義切入點表達式 execution(* com.sample.service.impl..*.*(..))
execution()是最常用的切點函數,其語法如下所示:
整個表達式可以分為五個部分:
(1)、execution(): 表達式主體。
(2)、第一個*號:表示返回類型,*號表示所有的類型。
(3)、包名:表示需要攔截的包名,後面的兩個句點表示當前包和當前包的所有子包,com.sample.service.impl包、子孫包下所有類的方法。
(4)、第二個*號:表示類名,*號表示所有的類。
(5)、*(..):最後這個星號表示方法名,*號表示所有的方法,後面括弧裏面表示方法的參數,兩個句點表示任何參數。

Spring系列之AOP實現的兩種方式