天天看點

SpringAOP和AspectJ詳解一、AOP二、AspectJ

一、AOP

@作者:溫濤
@時間:2017-9-21
           

1.1 AOP簡介

a、面向切面程式設計(Aspect Oriented Programming):
    通過預編譯方式和運作期動态代理實作程式功能的統一維護的一種技術。AOP是OOP(面向對象程式設計)的延續,
是軟體開發中的一個熱點,也是Spring架構中的一個重要内容,是函數式程式設計的一種衍生範型。
利用AOP可以對業務邏輯的各個部分進行隔離,進而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,同時提高了開發的效率。
b、AOP 是一個概念,并沒有設定具體語言的實作,它能克服那些隻有單繼承特性語言的缺點,spring2.0 之後整合 AspectJ 第三方 AOP 技術。
c、AOP采取橫向抽取機制,取代了傳統縱向繼承體系重複性代碼。
d、AspectJ 是一個面向切面的架構,它擴充了 Java 語言。AspectJ 定義了 AOP 文法是以它有一個專門的編譯器用來生成遵守 Java 位元組編碼規範的 Class 檔案。
e、經典應用:事務管理、性能監視、安全檢查、緩存 、日志等
           

1.2 AOP 與 與 OOP 差別

OOP(面向對象程式設計):針對業務處理過程的實體及其屬性和行為進行抽象封裝,以獲得更加清晰高效的邏輯單元劃分。
AOP: 則是針對業務處理過程中的切面進行提取,它所面對的是處理過程中的某個步驟或階段,以獲得邏輯過程中各部分之間低耦合性的隔離效果。這兩種設計思想在目标上有着本質的差異。
換而言之,OOD/OOP 面向名詞領域,AOP 面向動詞領域。
           

1.3 AOP實作原理

靜态 AOP:将切面代碼直接編譯到 Java 類檔案中。
動态 AOP:是指将切面代碼進行動态織入實作的 AOP。

aop底層将采用代理機制進行實作:
    1.接口 + 實作類 :spring采用 jdk 的動态代理Proxy。
    2.實作類:spring 采用 cglib位元組碼增強。
           

1.4 AOP術語

1.target:目标類,需要被代理的類。例如:UserService
2.Joinpoint(連接配接點):所謂連接配接點是指那些可能被攔截到的方法。例如:所有的方法
3.PointCut 切入點:已經被增強的連接配接點。例如:addUser()
4.advice 通知/增強,增強代碼。例如:after、before
5.Weaving(織入):是指把增強advice應用到目标對象target來建立新的代理對象proxy的過程.
6.proxy 代理類
7.Aspect(切面): 是切入點pointcut和通知advice的結合
    一個線是一個特殊的面。
    一個切入點和一個通知,組成成一個特殊的面。
           

1.5 Proxy動态代理工廠

public class MyProxy {
    public static BookService createProxy(){
            //目标類
        final BookService bookService = new BookServiceImpl();
        //切面類
        final MyAspcet1 myAspect = new MyAspcet1();
        //代理類
        BookService bookProxy =(BookService)Proxy.newProxyInstance(
                MyProxy.class.getClassLoader(),
                bookService.getClass().getInterfaces(),
                new InvocationHandler() {

        public Object invoke(Object proxy, Method method, Object[] args)
                            throws Throwable {
                        myAspect.before();
                        Object invoke = method.invoke(bookService, args);
                        myAspect.after();
                        return invoke;
                    }
                });
        return bookProxy;
    }
}
解析:
    代理類:将目标類(切入點)和 切面類(通知) 結合 --> 切面
     *  Proxy.newProxyInstance
     *      參數1:loader ,類加載器,動态代理類
     *          運作時建立,任何類都需要類加載器将其加載到記憶體。
     *          一般情況:
     *                  目前類.class.getClassLoader();
     *              目标類執行個體.getClass().get...
     *      參數2:Class[] interfaces 代理類需要實作的所有接口
     *          方式1:目标類執行個體.getClass().getInterfaces();
     *          注意:隻能獲得自己接口,不能獲得父元素接口
     *          方式2:new Class[]{UserService.class}   
     *          例如:jdbc 驅動  --> DriverManager  獲得接口 Connection
     *      參數3:InvocationHandler 處理類,接口,必須進行實作類,一般采用匿名内部類
     *          提供 invoke 方法,代理類的每一個方法執行時,都将調用一次invoke
     *              參數31:Object proxy :代理對象
     *              參數32:Method method : 代理對象目前執行的方法的描述對象(反射)
     *                  執行方法名:method.getName()
     *                  執行方法:method.invoke(對象,實際參數)
     *              參數33:Object[] args :方法實際參數
           

1.6 cglib動态代理工廠

public class MyProxy {
    public static BookService createCglib(){
        //目标類
        final BookServiceImpl bookServiceImpl = new BookServiceImpl();
        //增強類
        final MyAspcet1 myAspcet = new MyAspcet1();
        //代理類 ,采用cglib,底層建立目标類的子類
        //核心類
        Enhancer enhancer = new Enhancer();
        //确定父類
        enhancer.setSuperclass(bookServiceImpl.getClass());
        //設定回調函數 , MethodInterceptor接口 等效 jdk InvocationHandler接口
        //intercept() 等效 jdk  invoke()
        //  參數1、參數2、參數3:跟invoke一樣
        //  參數4:methodProxy 方法的代理
        enhancer.setCallback(new MethodInterceptor() {

            public Object intercept(Object proxy, Method method, Object[] args,
                    MethodProxy arg3) throws Throwable {
                myAspcet.before();

                //執行目标類
                Object invoke = method.invoke(bookServiceImpl, args);
                //執行代理類的父類 ,執行目标類 (目标類和代理類 父子關系)
                methodProxy.invokeSuper(proxy, args);

                myAspcet.after();
                return invoke;
            }
        });
        //建立代理
        BookServiceImpl create = (BookServiceImpl)enhancer.create();
        return create;
    }
}
           

1.7 spring 采用的動态機制

如果目标對象,有接口,優先使用 jdk 動态代理(接口+實作類)。
如果目标對象,無接口,使用 cglib 動态代理(實作類)。

但是,可通過配置,讓spring總是使用cglib 動态代理。

關系:
    proxy動态代理是父子關系
    cglib動态代理是兄弟關系
           

1.8 AOP聯盟通知類型

AOP聯盟為通知Advice定義了org.aopalliance.aop.Advice
Spring按照通知Advice在目标類方法的連接配接點位置,可以分為5類:
•   前置通知 org.springframework.aop.MethodBeforeAdvice
•   在目标方法執行前實施增強

•   後置通知 org.springframework.aop.AfterReturningAdvice
•   在目标方法執行後實施增強

•   環繞通知 org.aopalliance.intercept.MethodInterceptor
•   在目标方法執行前後實施增強

•   異常抛出通知 org.springframework.aop.ThrowsAdvice
•   在方法抛出異常後實施增強

•   引介通知 org.springframework.aop.IntroductionInterceptor
•   在目标類中添加一些新的方法和屬性

用法:增強類實作對應的接口。
           

1.9 spring AOP全自動程式設計配置(了解,一般配合AspectJ架構使用)

<?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">
    <!-- 1 建立目标類 -->
    <bean id="userService" class="com.itheima.c_spring_aop.UserServiceImpl"></bean>
    <!-- 2 建立切面類(通知,切面類實作MethodInterceptor) -->
    <bean id="myAspect" class="com.itheima.c_spring_aop.MyAspect"></bean>
    <!-- 3 aop程式設計 
        3.1 導入命名空間
        3.2 使用 <aop:config>進行配置
                proxy-target-class="true" 聲明時使用cglib代理
            <aop:pointcut> 切入點 ,從目标對象獲得具體方法
            <aop:advisor> 特殊的切面,隻有一個通知 和 一個切入點
                advice-ref 通知引用
                pointcut-ref 公共切入點引用
                pointcut 自己的切入點表達式
        3.3 切入點表達式
            execution(* com.itheima.c_spring_aop.*.*(..))
            選擇方法:傳回值任意   包   類名任意   方法名任意   參數任意
    -->
    <aop:config proxy-target-class="true">
        <aop:pointcut expression="execution(* com.itheima.c_spring_aop.*.*(..))" id="myPointCut"/>
        <aop:advisor advice-ref="myAspect" pointcut-ref="myPointCut"/>
    </aop:config>
</beans>
           

二、AspectJ

2.1 簡介

a、AspectJ是一個基于Java語言的AOP架構
b、Spring2.0以後新增了對AspectJ切點表達式支援
c、@AspectJ 是AspectJ1.5新增功能,通過JDK5注解技術,允許直接在Bean類中定義切面
d、新版本Spring架構,建議使用AspectJ方式來開發AOP
e、主要用途:自定義開發
           

2.2 切入點表達式

1.execution()  用于描述方法 【掌握】
文法:execution(修飾符  傳回值  包.類.方法名(參數) throws異常)
    修飾符,一般省略
        public      公共方法
        *       任意
    傳回值,不能省略
        void        傳回沒有值
        String      傳回值字元串
            *       任意
    包,[省略]
        com.wentao.crm          固定包
        com.wentao.crm.*.service    crm包下面子包任意 (例如:com.wentao.crm.staff.service)
        com.wentao.crm..        crm包下面的所有子包(含自己)
        com.wentao.crm.*.service..  crm包下面任意子包,固定目錄service,service目錄任意包
    類,[省略]
        UserServiceImpl         指定類
            *Impl               以Impl結尾
            User*               以User開頭
                *               任意
    方法名,不能省略
        addUser             固定方法
        add*                以add開頭
            *Do             以Do結尾
            *               任意
    (參數)
        ()              無參
        (int)           一個整型
    (int ,int)          兩個
        (..)            參數任意
    throws ,可省略,一般不寫。

綜合1
    execution(* com.wentao.crm.*.service..*.*(..))
綜合2
    <aop:pointcut expression="execution(* com.wentao.*WithCommit.*(..)) || 
                          execution(* com.wentao.*Service.*(..))" id="myPointCut"/>
           

2.3 AspectJ 通知類型

差別:
    aop聯盟定義通知類型,具有特性接口,必須實作,進而确定方法名稱。
    aspectj 通知類型,隻定義類型名稱。已經方法格式。
個數:6種,知道5種,掌握1中。

before:前置通知(應用:各種校驗)
    在方法執行前執行,如果通知抛出異常,阻止方法運作
afterReturning:後置通知(應用:正常資料處理)
    方法正常傳回後執行,如果方法中抛出異常,通知無法執行
    必須在方法執行後才執行,是以可以獲得方法的傳回值。
around:環繞通知(應用:十分強大,可以做任何事情)
    方法執行前後分别執行,可以阻止方法的執行
    必須手動執行目标方法
afterThrowing:抛出異常通知(應用:包裝異常資訊)
    方法抛出異常後執行,如果方法沒有抛出異常,無法執行
after:最終通知(應用:清理現場)
    方法執行完畢後執行,無論方法中是否出現異常
           

2.4 AspectJ 基于xml

步驟:
1.目标類:接口 + 實作
2.切面類:編寫多個通知,采用aspectj 通知名稱任意(方法名任意)
3.aop程式設計,将通知應用到目标類
           

2.4.1 切面類寫法

public class MyAspect {
    public void myBefore(JoinPoint joinPoint){
        System.out.println("前置通知 : " + joinPoint.getSignature().getName());
    }

    public void myAfterReturning(JoinPoint joinPoint,Object ret){
        System.out.println("後置通知 : " + joinPoint.getSignature().getName() + " , -->" + ret);
    }

    public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
        System.out.println("前");
        //手動執行目标方法
        Object obj = joinPoint.proceed();

        System.out.println("後");
        return obj;
    }

    public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
        System.out.println("抛出異常通知 : " + e.getMessage());
    }

    public void myAfter(JoinPoint joinPoint){
        System.out.println("最終通知");
    }

}
           

2.4.2 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:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context 
                           http://www.springframework.org/schema/context/spring-context.xsd">

        <!-- 1 建立目标類 -->
        <bean id="userService" class="com.wentao.service.UserServiceImpl"></bean>
        <!-- 2 建立切面類(通知) -->
        <bean id="myAspect" class="com.wentao.utils.MyAspect"></bean>
        <!-- 3 aop程式設計 
            <aop:aspect> 将切面類 聲明“切面”,進而獲得通知(方法)
                ref 切面類引用
            <aop:pointcut> 聲明一個切入點,所有的通知都可以使用。(一定别忘了)
                expression 切入點表達式
                id 名稱,用于其它通知引用
        -->
        <aop:config>
            <aop:aspect ref="myAspect">
                <!-- 切入點别忘記配置 -->
                <aop:pointcut expression="execution(* com.wentao.service.UserServiceImpl.*(..))" id="myPointCut"/>

                <!-- 3.1 前置通知 
                    <aop:before method="" pointcut="" pointcut-ref=""/>
                        method : 通知,及方法名
                        pointcut :切入點表達式,此表達式隻能目前通知使用。
                        pointcut-ref : 切入點引用,可以與其他通知共享切入點。
                    通知方法格式:public void myBefore(JoinPoint joinPoint){
                        參數1:org.aspectj.lang.JoinPoint  用于描述連接配接點(目标方法),獲得目标方法名等
                    例如:
                    <aop:before method="myBefore" pointcut-ref="myPointCut"/>
                -->

                <!-- 3.2後置通知  ,目标方法後執行,獲得傳回值
                    <aop:after-returning method="" pointcut-ref="" returning=""/>
                        returning 通知方法第二個參數的名稱
                    通知方法格式:public void myAfterReturning(JoinPoint joinPoint,Object ret){
                        參數1:連接配接點描述
                        參數2:類型Object,參數名 returning="ret" 配置的
                    例如:
                    <aop:after-returning method="myAfterReturning" pointcut-ref="myPointCut" returning="ret" />
                -->

                <!-- 3.3 環繞通知 *****
                    <aop:around method="" pointcut-ref=""/>
                    通知方法格式:public Object myAround(ProceedingJoinPoint joinPoint) throws Throwable{
                        傳回值類型:必須是Object
                        方法名:任意
                        參數:必須是org.aspectj.lang.ProceedingJoinPoint
                        抛出異常
                    執行目标方法:Object obj = joinPoint.proceed();
                    例如:
                    <aop:around method="myAround" pointcut-ref="myPointCut"/>
                -->
                <!-- 3.4 抛出異常
                    <aop:after-throwing method="" pointcut-ref="" throwing=""/>
                        throwing :通知方法的第二個參數名稱
                    通知方法格式:public void myAfterThrowing(JoinPoint joinPoint,Throwable e){
                        參數1:連接配接點描述對象
                        參數2:獲得異常資訊,類型Throwable ,參數名由throwing="e" 配置
                    例如:
                    <aop:after-throwing method="myAfterThrowing" pointcut-ref="myPointCut" throwing="e"/>
                -->
                <!-- 3.5 最終通知 -->           
                <aop:after method="myAfter" pointcut-ref="myPointCut"/>

            </aop:aspect>
        </aop:config>
    </beans>
           

2.5 AspectJ基于注解

2.5.1 注解用法

1.切面類
    @Aspect  聲明切面,修飾切面類,進而獲得 通知。
2.公共切入點(修飾方法 private void xxx(){} 之後通過“方法名”獲得切入點引用)
    //聲明公共切入點
    @Pointcut("execution(* com.itheima.d_aspect.b_anno.UserServiceImpl.*(..))")
    private void myPointCut(){
    }
3.通知
    @Before("execution(* com.itheima.d_aspect.b_anno.UserServiceImpl.*(..))")
    @AfterReturning(value="myPointCut()" ,returning="ret")
    @Around(value = "myPointCut()")
    @AfterThrowing(value="execution(* com.itheima.d_aspect.b_anno.UserServiceImpl.*(..))" ,throwing="e")
    @After("myPointCut()")
           

2.5.2 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:context="http://www.springframework.org/schema/context"
       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
                           http://www.springframework.org/schema/context 
                           http://www.springframework.org/schema/context/spring-context.xsd">

        <!-- 1.掃描 注解類 -->
        <context:component-scan base-package="com.wentao.aspectj"></context:component-scan>
        <!-- 2.确定 aop注解生效 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
</beans>