第七篇 - 手寫Aop(面向切面編程)

在這裏插入圖片描述
在這裏插入圖片描述
Github源碼下載地址:https://github.com/chenxingxing6/sourcecode/tree/master/code-springaop


一、前言

AOP爲Aspect Oriented Programming的縮寫,意爲:面向切面編程,通過預編譯方
式和運行期動態代理實現程序功能的統一維護的一種技術。AOP是OOP的延續,是軟件開發中的一個熱點,也是Spring框架中的一個重要內容,是函數式編程的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程序的可重用性,同時提高了開發的效率。
將日誌記錄,性能統計,安全控制,事務處理,異常處理等代碼從業務邏輯代碼中劃分出來,通過對這些行爲的分離,我們希望可以將它們獨立到非指導業務邏輯的方法中,進而改變這些行爲的時候不影響業務邏輯的代碼。

Aop相關專業術語:

  1. Aspect(切面): Aspect 聲明類似於 Java 中的類聲明,在 Aspect 中會包含着一些 Pointcut 以及相應的Advice。
  2. Jointpoint(連接點):表示在程序中明確定義的點,典型的包括方法調用,對類成員的訪問以及異常處理程序塊的執行等等,它自身還可以嵌套其它joint point。
  3. Pointcut(切點):表示一組 joint point,這些 joint point或是通過邏輯關係組合起來,或是通過通配、正則表達式等方式集中起來,它定義了相應的 Advice 將要發生的地方。
  4. Advice(增強):Advice 定義了在 Pointcut 裏面定義的程序點具體要做的操作,它通before、after 和around 來區別是在每個 joint point 之前、之後還是代替執行的代碼。 Target(目標對象):織入 Advice的目標對象.。
  5. Weaving(織入):將 Aspect 和其他對象連接起來, 並創建 Adviced object 的過程。

AOP底層使用動態代理實現。包括兩種方式:

使用JDK動態代理實現。
使用cglib來實現

通知類型

前置通知:在方法之前執行
後置通知:在方法之後執行
異常通知:方法出現異常執行
最終通知:在後置之後執行
環繞通知:在方法之前和之後執行​​​​​​​


二、自己實現AOP代理(jdk代理)

2.1需求

1.前置通知,後置返回通知,環繞通知,後置通知,異常通知
2.自己實現一個簡單版IOC容器
3.通過動態代理 + 通知註解類實現
4.定義切入點,正則表達式 @pointcut

2.2實現思路

服務啓動時,將項目中的bean注入到容器裏面,對應有切面進行代理的bean,重新生成代理對象,替換原理的對象。

①被代理類;
②被代理類要實現的接口;
③代理類;
④動態創建“代理類的對象”的類;
⑤註解類:
 a. 切面註解類,註解在類上:@Aspect
 b. 各種通知註解,註解在方法上:
   __@Before
   __@AfterReturning
   __@After
   __@AfterThrowing
   __@Around
⑥IOC容器:BeanFactory

2.3項目結構

在這裏插入圖片描述

2.4如何配置,測試項目

本項目配置切面,只支持@MyPointcut方式,支持正則表達式;然後將pointcut放到對應切面通知上。

1.編寫Aspect切面
2.測試
只支持jdk動態代理

package com.demo1;

import com.aop.annotation.*;
import com.aop.aspect.AbstractAspect;
import com.aop.aspect.JoinPoint;
import com.ioc.annotation.MyComponent;

/**
 * @Author: cxx
 * @Date: 2019/10/3 11:25
 * 日誌切面
 */
@MyAspect
@MyComponent
public class LogAspect extends AbstractAspect{

    @MyPointcut("com.demo1.*")
    private void myPointcut(){
    }

    @MyAround(value = "myPointcut()")
    public Object around(JoinPoint joinPoint){
        Long start = System.currentTimeMillis();
        System.out.println("環繞通知start....");
        Object obj= joinPoint.proceed();
        System.out.println("環繞通知end....");
        Long end = System.currentTimeMillis();
        System.out.println("執行方法耗時:" + String.valueOf(end - start));
        return obj;
    }
}

2.5 核心代碼AopBeanContext
package com.aop.core;

import com.aop.aspect.AspectHandler;
import com.aop.aspect.CheckResult;
import com.aop.aspect.IAspectHandler;
import com.ioc.core.MyIoc;
import javafx.util.Pair;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: cxx
 * @Date: 2019/10/3 13:45
 */
public class AopBeanContext {
    private static IAspectHandler aspectHandler = null;

    static {
        init();
    }

    public static Object getObject(Class clz){
        Object target = MyIoc.getObject(clz);
        if (target == null){
            throw new RuntimeException("容器中獲取不到實例");
        }
        Pair<Object, CheckResult> aspectResult = aspectHandler.getAspectInstance(clz.getTypeName());
        // 沒有切面
        if (aspectResult == null){
            return target;
        }
        // 創建代理類
        return ProxyBeanFactory.newProxyBean(target, aspectResult);
    }

    private static void init(){
        createProxyBeanContext(MyIoc.getBeanFactory());
    }

    // 根據切點,創建有代理類的bean容器
    public static void createProxyBeanContext(Map<String, Object> iocMap){
        aspectHandler = new AspectHandler(iocMap);
    }
}

2.6 核心代碼BeanInvocationHandler
package com.aop.core;

import com.aop.annotation.AdviceEnum;
import com.aop.aspect.AbstractAspect;
import com.aop.aspect.CheckResult;
import com.aop.aspect.JoinPoint;
import javafx.util.Pair;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: cxx
 * @Date: 2019/10/3 15:35
 * @Desc: 環繞通知先不實現
 */
public class BeanInvocationHandler implements InvocationHandler {
    // 目標類
    private Object target;
    // 切面相關信息
    private Pair<Object, CheckResult> acpectResult;

    public BeanInvocationHandler(Object target, Pair<Object, CheckResult> acpectResult) {
        this.target = target;
        this.acpectResult = acpectResult;
    }

    /**
     * 內部類實現環繞通知
     */
    class MyJoinPoint implements JoinPoint{
        private Method method;
        private Object[] args;

        public MyJoinPoint(Method method, Object[] args) {
            this.method = method;
            this.args = args;
        }

        @Override
        public Object proceed() {
            try {
                return method.invoke(target, args);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 切面類
        AbstractAspect aspcet = (AbstractAspect) acpectResult.getKey();
        // 通知方式
        CheckResult runType = acpectResult.getValue();

        boolean isAround = runType.isRunAround();
        JoinPoint joinPoint = null;
        if (isAround){
            joinPoint = new MyJoinPoint(method, args);
        }
        Object result = null;
        try {
            // 1.前置通知
            if (runType.isRunBefore()){
                runAspectInstance(AdviceEnum.BEFORE, aspcet, args);
            }
            // 2.環繞通知
            if (isAround){
                result = aspcet.around(joinPoint);
            }else {
                result = method.invoke(target, args);
            }
            // 3.返回通知
            if (runType.isRunAfterReturning()){
                runAspectInstance(AdviceEnum.AFTER_RETURNING, aspcet, args, result);
            }
            return result;
        }catch (Exception e){
            // 4.異常通知
            if (runType.isRunAfterThrowing()){
                runAspectInstance(AdviceEnum.AFTER_THROWING, aspcet, args, e);
            }
        }finally {
            // 5.後置通知
            if (runType.isRunAfter()){
                runAspectInstance(AdviceEnum.AFTER, aspcet, args);
            }
        }
        return result;
    }


    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args){
        this.runAspectInstance(adviceEnum, aspect, args, null, null);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Object result){
        this.runAspectInstance(adviceEnum, aspect, args, null, result);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e){
        this.runAspectInstance(adviceEnum, aspect, args, e, null);
    }

    /**
     * 執行切面實例
     * @param adviceEnum
     * @param aspect
     * @param args
     */
    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e, Object result){
        try {
            switch (adviceEnum){
                case BEFORE:{
                    aspect.before();
                    break;
                }
                case AFTER_RETURNING:{
                    aspect.afterReturning(result);
                    break;
                }
                case AFTER_THROWING:{
                    aspect.afterThrowable(e);
                    break;
                }
                case AFTER:{
                    aspect.after();
                    break;
                }
                default:{
                    break;
                }
            }
        }catch (Exception ee){
            ee.printStackTrace();
        }
    }
}


三、測試結果

package com.aop;

import com.aop.core.AopBeanContext;
import com.demo1.ILogService;
import com.demo1.LogService;

/**
 * @Author: cxx
 * @Date: 2019/10/3 11:34
 */
public class MainTest {
    public static void main(String[] args) {
        //  01.測試 修改ioc.properties scan.package=com.demo
        //IUserService userService = (IUserService) AopBeanContext.getObject(UserService.class);
        //userService.delete("100");

        // 02.環繞通知測試 修改ioc.properties scan.package=com.demo1
        ILogService logService = (ILogService) AopBeanContext.getObject(LogService.class);
        logService.printLog("test....");
    }
}

在這裏插入圖片描述


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章