天天看點

【Spring】AOP及動态代理

AOP之前學習過但是一直沒有記下了,今天趁機會趕緊寫一篇:

AOP:

AOP利用”橫切”技術,剖解開封裝的對象内部,将那些影響了多個類的公共行為封裝到一個可重用子產品,并将其命名為”Aspect”,即切面。所謂”切面”,簡單說就是那些與業務無關,卻為業務子產品所共同調用的邏輯或責任封裝起來,便于減少系統的重複代碼,降低子產品之間的耦合度,并有利于未來的可操作性和可維護性。

———————————————————————————————————————

使用”橫切”技術,AOP把軟體系統分為兩個部分:核心關注點和橫切關注點。業務處理的主要流程是核心關注點,與之關系不大的部分是橫切關注點。橫切關注點的一個特點是,他們經常發生在核心關注點的多處,而各處基本相似,比如權限認證、日志、事物。AOP的作用在于分離系統中的各種關注點,将核心關注點和橫切關注點分離開來。

【Spring】AOP及動态代理

接下來寫一個切面類:

/**
     * 定義名稱為addMethod的pointcut,隻是一個辨別,不進行調用
     * (* add*(..)) 比對沒有傳回值,以add開頭的方法 、且參數任意
     */
    @Pointcut("execution(* add*(..))")
    private void addMethod(){};

    /**
     * 利用上面的辨別:addMethod
     * 表示在調用(* add*(..))方法Before要執行這個方法
     * 在advice中添加JoinPoint參數可以擷取用戶端的方法名及參數
     */
    @Before("addMethod()")
    private void checkSecurity(JoinPoint joinPoint){
        for (int i = ; i < joinPoint.getArgs().length; i++) {
            System.out.println(joinPoint.getArgs()[i]);//可以擷取參數
        }
        joinPoint.getSignature().getName();//方法名
        System.out.println("你好—————————————");
    }
           

切面類中的切入點可以靈活配置:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern)throws-pattern?)
execution(<修飾符模式>?<傳回類型模式><方法名模式>(<參數模式>)<異常模式>?) 
除了傳回類型模式、方法名模式和參數模式外,其它項都是可選的;
  ·傳回類型模式決定了方法的傳回類型,必須一次比對一個連接配接點
  .名字模式比對的是方法名,可用通配符*
  .參數模式()不接受參數、(..)接受任意數量的參數方法、(*)接受一個任何類型的參 數的方法

//execution 傳回值類型  包名/方法名
 @Pointcut("execution(* add*(..)) || execution(* com.mjx.net.*.del*(..))")
 private void showPointcutTags(){}
           

白話Spring(基礎篇)—AOP(execution表達式)

Spring AOP中pointcut expression表達式解析 及比對多個條件

通過applicationContext.xml檔案将類加載到spring容器中

<!--啟用aspect對annotation的支援-->
    <aop:aspectj-authoproxy/>
    <!--業務處理-->
    <bean id="userManagerImpl" class="com.mjx.study.UserManagerImpl"/>
    <!--切面,含切點和方法-->
    <bean id="annotationAOP" class="com.mjx.study.AnnotationAOP"/>
           

業務處理類:add*

public class UserManagerImpl implements UserManager {
    public void addUser(String usrname, String password) {
        System.out.println("add--------add------");
    }
}
           

測試類:

@Test
    public void test() {
        ClassPathXmlApplicationContext factory = new ClassPathXmlApplicationContext("applicationContext.xml");//讀取配置檔案
        UserManager userManager = (UserManager) factory.getBean("userManager");//從容器中擷取userManager對應的類
        userManager.addUser("張三","124");//執行方法,切面切入
    }
           

補充:

<!--方式二,将類中的切面、方法寫到配置檔案中-->
    <aop:config>
        <aop:aspect id="securityAspect" ref="annotationAOP">
            <aop:pointcut id = "addMethod" expression="execution(* add*(..))"/>
            <aop:before method="checkSecutiry" pointbut-ref="addMethod"/>
        </aop:aspect>
    </aop:config>
           

AOP和動态代理的結合:

JDK動态代理:

      在運作時使用位元組碼動态生成的代理類(代理類的位元組碼在運作時生成并載入目前代理的ClassLoader)

      通過反射類Proxy以及InvocationHandler回調接口實作的 == 隻能對該類所實作接口中定義的方法進行代理

1、如果目标對象實作了接口,預設情況下才有JDK的動态代理AOP,也可以強制使用CGLIB生成的代理實作APP

  • 強制使用CGLIB
引入jar包、配置檔案中:
<aop:aspectj-autoproxy proxy-target-class="true"/>
           

示例:

public class TestCglib {
    public static void main(String[] args) {
        //位元組碼增強器,http://blog.sina.com.cn/s/blog_790c59140102w7y3.html
        // 在Java位元組碼生成之後,對其進行修改,增強其功能,這種方式相當于對應用程式的二進制檔案進行修改
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(TargetObject.class);//被代理類TargetObject設定成父類

        //方法攔截器
        CallbackFilter callbackFilter = new TargetMethodCallbackFilter();
        Callback noopCb = NoOp.INSTANCE;//即什麼操作也不做,代理類直接調用被代理的方法不進行攔截。
        Callback callback = (Callback) new TargetInterceptor();//調用目标攔截器
        Callback fixedValue = (Callback) new TargetResultFixed ();//鎖定方法傳回值:無論被代理類的方法傳回什麼值,回調方法都傳回固定值
        Callback[] callbacks = new Callback[]{callback,noopCb,fixedValue};
        enhancer.setCallback(new TargetInterceptor());//設定回調
        enhancer.setCallbacks((net.sf.cglib.proxy.Callback[]) callbacks);
        enhancer.setCallbackFilter(callbackFilter);
        TargetObject targetObject2 = (TargetObject) enhancer.create();//生成代理對象

        enhancer.setCallback(new TargetInterceptor());//設定攔截器TargetInterceptor
        TargetObject targetObject = (TargetObject) enhancer.create();//動态生成一個代理類,轉型成父類型TargetObject
        System.out.println(targetObject);//在代理類上調用方法時會被攔截器攔截
        System.out.println(targetObject.method1("mmm"));
        System.out.println(targetObject.method2());
        System.out.println(targetObject.method2());

    }
}
           

目标攔截器:

public class TargetInterceptor implements MethodInterceptor {

    /**
     * 重寫方法攔截
     *
     * @param o           目标對象,動态生成的代理類執行個體
     * @param method      目标方法,上文中實體類所調用的被代理的方法引用
     * @param objects     參數
     * @param methodProxy 代理對象,生成的代理類對方法的代理引用
     * @return
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("調用前");
        Object result = methodProxy.invokeSuper(o, objects);//目标對象和參數
        System.out.println("調用後" + result);
        return result;
    }
}
           

鎖定方法傳回值:

/**
 * 表示鎖定方法傳回值,無論被代理類的方法傳回什麼值,回調方法都傳回固定值
 * Created by phoebeM on 2018/04/15.
 */
public class TargetResultFixed implements FixedValue {
    public Object loadObject() throws Exception {
        System.out.println("鎖定結果");
        Object object = ;
        return object;
    }
}
           

申請的callback長什麼樣呐?

【Spring】AOP及動态代理

一個接口?一個接口!

關鍵都在綠色箭頭上,很多實作這個接口的方法

2、如果目标對象沒有實作接口,必須引入CGLIB,spring會在JDK的動态代理和CGLIB代理間切換

JDK和CGLIB的差別:

1、JDK動态代理 處理 對實作了接口的類

2、CGLIB代理可以對類代理,為指定的類生成子類(繼承,目标最好不要用final聲明)

說了這麼些,什麼是CGLIB呐?

        一個代碼生成包,如上所說其會動态生成一個要代理類的子類,子類重寫要代理的類的所有不是final的方法,然後在子類中采用方法攔截技術攔截所有父類方法的調用,順勢織入橫切邏輯。比使用java反射的JDK動态代理要快(底層采用ASM位元組碼生成架構)。

    ASM是java位元組碼操縱架構,從類檔案中讀入資訊後,能夠改變類行為,分析類資訊,甚至能夠根據使用者要求生成新類(動态生成類或者增強既有類的功能)。

後語:

很簡單的一個例子,出去前置還有後置與環繞,想起了裝飾者模式,spring真是很強大,查資料看到一句話、分享給大家[一知半解,就是給自己挖坑]

感謝分享:

獨具匠心:Spring AOP詳解