1. 程式人生 > >Spring基於註解的AOP配置

Spring基於註解的AOP配置

Joinpoint(連線點):

所謂連線點是指那些被攔截到的點。在spring中,這些點指的是方法,因為spring只支援方法型別的連線點。指的是所有可以被增強的方法。

Pointcut(切入點):

所謂切入點是指我們要對哪些Joinpoint進行攔截的定義。指的是已經決定即將要增強的方法。比如我決定要對saveAccount方法進行增強了,那麼該方法就是切入點。

Advice(通知/增強):

所謂通知是指攔截到Joinpoint之後所要做的事情就是通知。通知的型別:前置通知,後置通知,異常通知,最終通知,環繞通知。增強的程式碼所在的方法。如果我們要用Logger類中的pringtLog方法對saveAccount()進行增強,那麼printLog方法就是通知或者說是增強。

Introduction(引介):-瞭解

引介是一種特殊的通知在不修改類程式碼的前提下, Introduction可以在執行期為類動態地新增一些方法或Field。通知是方法層面的,而引介是針對類層面的,可以動態的新增一個類的屬性或者方法。

Target(目標物件):

代理的目標物件。將要對哪個物件進行增強。

Weaving(織入):

是指把增強應用到目標物件來建立新的代理物件的過程。spring採用動態代理織入,而AspectJ採用編譯期織入和類裝載期織入。將通知應用到切入點的過程。

Proxy(代理):

一個類被AOP織入增強後,就產生一個結果代理類。根據被代理物件的情況的不同,可能會生成一個基於介面的代理物件,也可能會生成一個基於子類的代理物件。

Aspect(切面):

是切入點和通知(引介)的結合。多個通知和多個切入的組合。

使用xml配置AOP:

<bean id="logger" class="com.demon.utils.Logger"/>
<aop:config>
    <aop:aspect id="logAdvice" ref="logger">
        <!-- 配置前置通知
             method:配置通知方法(即具體進行增強的方法)
             pointcut:配置AspectJ表示式,即將通知增強到哪個方法
             execution:使用AspectJ的切入點表示式
                     execution(修飾符 返回值型別 包名.類名.方法名(引數列表))
        -->
        <aop:before method="beforePrintLog" pointcut="execution(public void com.demon.service.impl.AccountServiceImpl.saveAccount())"/>
        <aop:after-returning method="afterReturningPrintLog" pointcut="execution(public void com.demon.service.impl.AccountServiceImpl.updateAccount(int))"
        <aop:after-throwing method="afterThrowingPrintLog" pointcut="execution(public void com.demon.service.impl.AccountServiceImpl.updateAccount(int)))"/
        <aop:after method="afterPrintLog" pointcut="execution(public int com.demon.service.impl.AccountServiceImpl.deleteAccount())"/>
    </aop:aspect>
</aop:config>

AspectJ切入點表示式說明

配置切入點表示式:aspectj表示式     1、修飾符可以省略:表示對所有方法進行增強         execution(void cn.itcast.service.impl.AccountServiceImpl.saveAccount())     2、返回值型別可以使用*代替:表示任意返回值型別         execution( * cn.itcast.service.impl.AccountServiceImpl.saveAccount())     3、包名可以使用*代替:表示任意名稱的包名         execution( * *.*.*.*.AccountServiceImpl.saveAccount())     4、如果有多個包名,可以使用*..代替:表示任意多個包名         execution( * *..AccountServiceImpl.saveAccount())     5、類名可以使用*代替:表示任意名稱的類         execution( * *..*.saveAccount())     6、方法名稱可以使用*代替:表示任意名稱的方法         execution( * *..*.*())     7、引數可以使用*代替:表示任意型別的引數,但至少有一個引數         execution( * *..*.*(*))     8、引數使用..代替:表示任意多個,任意型別的引數         execution( * *..*.*(..))          最終推薦的表示式的寫法:         execution(* com.demon.service.impl.*.*(..))

使用註解配置AOP:

第一步:建立配置類

@Configuration
@ComponentScan(value = {"com.demon"})
@EnableAspectJAutoProxy
public class SpringConfiguration {

}

第二步:註解裝配service實現類

@Service("accountService")
public class AccountServiceImpl implements IAccountService {

    @Override
    public void saveAccount() {
        System.out.println("儲存賬戶");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("更新賬戶");
    }

    @Override
    public int deleteAccount() {
        System.out.println("刪除賬戶");
        return 0;
    }
}

第三步:在通知類上使用@Aspect註解宣告為切面

@Component
@Aspect//表示當前類是一個切面類(也可以稱之為通知類)
public class Logger {}

第四步:使用註解配置通知型別

配置前置通知、後置通知、異常通知、最終通知

@Component
@Aspect
public class Logger {

    /**
     * 指定切入點表示式
     */
    @Pointcut("execution(* com.demon.service.impl.*.*(..))")
    public void pt1() {
    }

    /**
     * 前置通知
     * value:用於指定切入點表示式,還可以指定切入點表示式的引用。
     */
    @Before("pt1()")
    public void beforePrintLog(){
        System.out.println("前置通知執行了");
    }

    /**
     * 後置通知
     * value:用於指定切入點表示式,還可以指定切入點表示式的引用
     */
    @AfterReturning("pt1()")
    public void afterReturningPrintLog(){
        System.out.println("後置通知執行了");
    }

    /**
     * 異常通知
     * value:用於指定切入點表示式,還可以指定切入點表示式的引用
     */
    @AfterThrowing("pt1()")
    public void afterThrowingPrintLog(){
        System.out.println("異常通知執行了");
    }

    /**
     * 最終通知
     * value:用於指定切入點表示式,還可以指定切入點表示式的引用
     */
    @After("pt1()")
    public void afterPrintLog(){
        System.out.println("最終通知執行了");
    }
}

配置環繞通知

/**
 * 指定切入點表示式
 */
@Pointcut("execution(* com.demon.service.impl.*.*(..))")
public void pt1() {
}

/**
 * 環繞通知
 * value:用於指定切入點表示式,還可以指定切入點表示式的引用。
 * spring框架為我們提供了一個介面,該介面可以作為環繞通知的方法引數來使用
 * ProceedingJoinPoint。當環繞通知執行時,spring框架會為我們注入該介面的實現類。
 * 它有一個方法proceed(),就相當於invoke,明確的業務層方法呼叫
 * spring的環繞通知:
 *   它是spring為我們提供的一種可以在程式碼中手動控制增強方法何時執行的方式。
 */
@Around("pt1()")
public Object around(ProceedingJoinPoint pjp) {
    Object proceed = null;
    try {
        beforePrintLog();
        proceed = pjp.proceed();
        afterReturningPrintLog();
    } catch (Throwable throwable) {
        throwable.printStackTrace();
        afterThrowingPrintLog();
    } finally {
        afterPrintLog();
    }
    return proceed;
}

第五步:編寫測試類

public class IAccountServiceTest {

    private IAccountService accountService;

    @Before
    public void setUp() throws Exception {
        ApplicationContext ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        accountService = (IAccountService) ac.getBean("accountService");
    }

    @Test
    public void saveAccount() {
        accountService.saveAccount();
    }

    @Test
    public void updateAccount() {
        accountService.updateAccount(1);
    }

    @Test
    public void deleteAccount() {
        accountService.deleteAccount();
    }
}