萬字長文!自己手寫一個AOP動態代理框架(1)

前面提到過動態代理的有兩種簡單的實現方式,就是JDK動態代理和CGLIB。
不懂先看前文:SpringAOP的實現之代理模式
自己寫嘛當然是乞丐版了,爲了實現簡單 但是又具有一定的通用性(拒絕對被代理類有實現接口的強硬要求)。所以採用CGLIB的方式實現AOP。
工程目錄結構。
在這裏插入圖片描述

首先要實現註解一樣的 AOP,要定義與AOP相關的註解。

定義2個AOP相關的註解標籤。

//作用在類上
@Target(ElementType.TYPE)
//運行時有效
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    //初期版本僅僅支持按照註解分類AOP
    //比如只切Controller Controller.class
    Class<? extends Annotation> value();
}

如果對一個代理類有多個AOP,那麼需要用戶規定代理執行的順序,那麼就需要Order註解。

/**
 * 定義切面順序
 */
//作用在類上
@Target(ElementType.TYPE)
//運行時有效
@Retention(RetentionPolicy.RUNTIME)
public @interface Order {
    //越小優先級別越高
    int value();
}

定義好相關注解,需要定義AOP的基本骨架。這裏用到了類似於模板模式。 這個骨架定義了究竟支持哪幾種通知方式(前置,後置等等)。

模板模式的妙用:Spring源碼(3)-Spring中的ApplicationContext
通過此骨架的定義,可以讓子類按需實現自己想要實現的通知。

public abstract class DefaultAspect {
    /**
     * @param targetClass 被代理的目標類型
     * @param method 被代理的方法
     * @param args 方法的參數
     * @throws Throwable
     */
    public void before(Class<?> targetClass, Method method,Object[] args) throws Throwable {}

    /**
     *
     * @param targetClass 被代理的目標類型
     * @param method 被代理的方法
     * @param args 方法的參數
     * @param returnValue 返回值
     * @return 修改後返回值
     * @throws Throwable
     */
    public Object afterReturning(Class<?> targetClass,Method method
            ,Object[] args,Object returnValue) throws Throwable{
        return returnValue;
    }

    /**
     *
     * @param targetClass 被代理的目標類型
     * @param method 被代理的方法
     * @param args 方法的參數
     * @param e 異常
     * @throws Throwable 拋出被代理方法的 異常
     */
    public void afterThrowing(Class<?> targetClass,
                              Method method,Object[] args,Throwable e) throws Throwable{}
}

只支持了前置通知,方法返回後通知,異常時通知。Aroud可以將這三個聯合起來就行。
因爲三個方法在抽象類裏面都是默認的空實現,不會讓用戶強制實現,可以按需實現。

SpringAOP的實現之代理模式提到,使用CGLIB就需要實現MethodInterceptor去複用自己的增強邏輯,而在這裏也是一樣的。但是又有點不一樣,因爲我們可能(一定)會遇到一個被代理類有多個代理,所以這裏是一對多的關係。因此不能照搬原來的代碼。需要做出額外的操作來輔助完成多個AOP增強一個被代理類。

所以這裏需要聲明兩個屬性。

    //被代理的class
    private Class<?> targetClass;
    //切面信息集合 照顧多個AOP的 情況
    private List<AspectInfo> sortedAspectInfoList;

而且,多個AOP的執行順序是類似於同心圓的穿梭操作。所以多個before和多個after執行的順序不盡相同。需要做額外的工作。

上面出現的切面信息類應該定義如下。

package com.framework.aop;

/**
 * @Desc
 * @Author FuYouJ
 * @date 2020/5/31 21:55
 */

import com.framework.aop.aspect.DefaultAspect;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * 接受註解的值
 */
@Data
public class AspectInfo {
  private int orderIndex;
  private DefaultAspect aspectObject;
  public AspectInfo() {
  }
  public AspectInfo(int orderIndex, DefaultAspect aspectObject) {
    this.orderIndex = orderIndex;
    this.aspectObject = aspectObject;
  }

}

創建一個AspectListExecutor類繼承於MethodInterceptor,該類主要的工作就是往被代理的對象添加橫切邏輯。

@Data
@NoArgsConstructor
public class AspectListExecutor implements MethodInterceptor {
  //被代理的class
    private Class<?> targetClass;
    // targetCalss切面信息集合 照顧多個AOP的 情況
    private List<AspectInfo> sortedAspectInfoList;
    public AspectListExecutor(Class<?> targetClass,List<AspectInfo> aspectInfos){
        this.targetClass = targetClass;
        //直接先排序再賦值
        this.sortedAspectInfoList = sort(aspectInfos);
    }
    //因爲需要按照order的順序執行aop,所以需要對 List<AspectInfo>排序。
        private List<AspectInfo> sort(List<AspectInfo> aspectInfos) {
        //升序排列
       Collections.sort(aspectInfos, new Comparator<AspectInfo>() {
            @Override
            public int compare(AspectInfo o1, AspectInfo o2) {
                return o1.getOrderIndex() - o2.getOrderIndex();
            }
        });
       return aspectInfos;
    }
   /**
     * 按照oder的順序升序執行
     * @param method
     * @param args
     */
    private void invokeBeforeAdvices(Method method, Object[] args) throws Throwable {
        for (AspectInfo info : sortedAspectInfoList) {
            info.getAspectObject().before(targetClass,method,args);
        }
    }
        /**
     * 發生異常的時候執行 降序
     * @param method
     * @param args
     * @param e
     */
    private void invokeAfterThrowingAdvices(Method method, Object[] args, Exception e) throws Throwable {
        for (int i = sortedAspectInfoList.size() -1; i >=0 ; i--) {
            sortedAspectInfoList.get(i).getAspectObject()
                    .afterThrowing(targetClass,method,args,e);
        }
    }

    /**
     * 如果代理方法正常返回,降序執行afterAdvice
     * @param method
     * @param args
     * @param returnValue
     * @return
     */
    private Object invokeAfterReturningAdvices(Method method, Object[] args, Object returnValue) throws Throwable {
        Object res = null;
        for (int i = sortedAspectInfoList.size()-1; i >=0 ; i--) {
           res =  sortedAspectInfoList.get(i).getAspectObject().afterReturning(targetClass, method, args, returnValue);
        }
        return res;
    }
//織入邏輯的方法
  /**
     *
     * @param o 被增強的對象 
     * @param method 需要攔截的方法
     * @param args 方法參數
     * @param methodProxy 代理方法
     * @return 返回值
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        Object returnValue = null;
        if (ValidationUtil.isEmpty(sortedAspectInfoList)){
            //就算爲空也要執行原來的方法啊
            return methodProxy.invokeSuper(o,args);
        }
        //1.按照order的順序執行完畢所有切面的before方法
        invokeBeforeAdvices(method,args);
        try {
            //2。 執行被代理的方法
            returnValue = methodProxy.invokeSuper(o,args);
            //3. 如果被代理方法正常返回,按照order的順序 逆序執行afterReturning
            returnValue =  invokeAfterReturningAdvices(method,args,returnValue);
        }catch (Exception e){
            //執行異常時
            invokeAfterThrowingAdvices(method,args,e);
        }
        return returnValue;
    }

有了一個能往被代理對象裏面織入邏輯的武器,還需要使用這個武器的使用者纔可以。在CGLIB已經有這樣的使用者。就是Enhancer,它可以根據MethodInterceptor和目標類生成代理對象。

創建一個工具類,利用Enhancer創建一個代理現象。

public class ProxyCreator {
    /**
     *創建代理 對象並返回
     * @param targetClass
     * @param interceptor
     * @return
     */
    public static Object createProxy(Class<?> targetClass, MethodInterceptor interceptor){
        Object o = Enhancer.create(targetClass, interceptor);
        return o;
    }
}

有了以上工作,下面只需要從容器中篩選所有對象,用代理對象替換容器裏面的被代理對象。 容器的實現
這裏不贅述。後面我會新開文章專門寫實現容器。這裏 直接貼出 容器的實現代碼 。

容器必須是單例,單例模式我以前寫過一個不是很好的文章,有心的可以看看幫助理解。
JAVA中N種單例模式簡單概括(防反射,克隆,序列化,實例化,多線程安全)

容器實現代碼

/**
 * 容器類 單例模式  (線程安全,防止反射 ,序列化)
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class BeanContainer {
    //判斷實例是否已經被加載過
    private boolean loaded = false;
    private final ConcurrentHashMap<Class<?>,Object> beanMap = new ConcurrentHashMap<>();
    /**
     * 加載 bean的註解列表
     * 當一個類被一下幾個註解修飾的時候,那麼就歸容器管理
     */
    private static final List<Class<? extends Annotation>> BEAN_ANNOTATION
            = Arrays.asList(Component.class, Controller.class
            ,Service.class, Repository.class
            ,Aspect.class);
    /**
     * 獲取容器實例
     * @return
     */
    public static BeanContainer getInstance(){
        return ContainerHolder.HOLDER.instance;
    }
    private enum  ContainerHolder{
        //一個實例
        HOLDER;
        //一個成員
        private BeanContainer instance;
        private ContainerHolder(){
            instance = new BeanContainer();
        }
    }
    /**
     * 掃描加載所有的bean
     *避免 多線程同時加載
     */
    public synchronized void loadBeans(String packageName){
        if (isLoaded()){
            log.warn("容器已經被加載過了");
            return;
        }
        Set<Class<?>> classSet = ClassUtil.extractPackageClass(packageName);
        if (ValidationUtil.isEmpty(classSet)) {
            log.warn("沒有提取到任何的類"+packageName);
            return;
        }
        for (Class<?> clazz : classSet) {
            for (Class<? extends Annotation> annotation : BEAN_ANNOTATION) {
                //如果有這個註解
                if (clazz.isAnnotationPresent(annotation)) {
                    //將m目標類本省作爲建 ,目標類的實例作爲 值,放在容器中
                    beanMap.put(clazz,ClassUtil.newInstance(clazz,true));
                }
            }
        }
        loaded = true;
    }

    public boolean isLoaded() {
        return loaded;
    }

    public void setLoaded(boolean loaded) {
        this.loaded = loaded;
    }
    public int size(){
        return beanMap.size();
    }


    //增加一個實例
   public Object addBean(Class<?>clazz, Object bean){
       return beanMap.put(clazz,bean);
    }
    //刪除
   public Object remove(Class<?>clazz){
       return beanMap.remove(clazz);
    }

    /**
     * 類獲取實例
     * @param clazz
     * @return
     */
   public Object getBean(Class<?>clazz){
        return beanMap.get(clazz);
    }

    /**
     * 獲取所有的 beanclss集合
     * @return
     */
    public Set<Class<?>> getClasses(){
        return beanMap.keySet();
    }

    /**
     * 獲取所有實例
     * @return
     */
   public Set<Object> getBeans(){
        return new HashSet<>(beanMap.values());
    }

    /**
     * 獲取某註解 的class
     * @param annotation
     * @return
     */
  public Set<Class<?>> getClassesByAnnotation(Class<? extends Annotation> annotation){
       //獲取所有class
        Set<Class<?>> keySet = getClasses();
        if (ValidationUtil.isEmpty(keySet)){
            log.warn("容器爲空");
            return null;
        }
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> clazz : keySet) {
            if (clazz.isAnnotationPresent(annotation)){
                classSet.add(clazz);
            }
        }
        return classSet.size() > 0?classSet:null;
    }
  public Set<Class<?>> getClassesBySuper(Class<?> interfaceOrClass){
      //獲取所有class
      Set<Class<?>> keySet = getClasses();
      if (ValidationUtil.isEmpty(keySet)){
          log.warn("容器爲空");
          return null;
      }
      //通過接口篩選
      Set<Class<?>> classSet = new HashSet<>();
      for (Class<?> clazz : keySet) {
          //判斷是否是我的子類
          if (interfaceOrClass.isAssignableFrom(clazz)){
              classSet.add(clazz);
              //TODO
          }
      }
      return classSet.size()>0?classSet:null;
  }
}

依賴注入代碼

/**
 * 依賴注入的服務
 */
@Slf4j
public class DependencyInjector {
    private BeanContainer beanContainer;
    public DependencyInjector(){
        beanContainer = BeanContainer.getInstance();
    }

    /**
     * 執行IOC
     */
    public void doIoc(){
        //遍歷容器class對象
        if (ValidationUtil.isEmpty(beanContainer.getClasses())){
            log.warn("容器爲空");
            return;
        }
        for (Class<?> clazz : beanContainer.getClasses()) {
            //遍歷每個對象的 成員變量
            Field[] fields = clazz.getDeclaredFields();
            if (ValidationUtil.isEmpty(fields)){
                continue;
            }
            //找到自動注入的註解
            for (Field field : fields) {
                if (field.isAnnotationPresent(Autowired.class)) {
                    Autowired autowired = field.getAnnotation(Autowired.class);
                    String autowiredValue = autowired.value();
                    //獲取類型
                    Class<?> fieldClass = field.getType();
                    //再容器中找到
                    Object fieldValue =  getFiledInstance(fieldClass,autowiredValue);
                    if (fieldValue == null){
                        throw new RuntimeException("注入失敗,容器中不存在該類型,名字:"+autowiredValue);
                    }else {
                        //注入
                        Object targetBean = beanContainer.getBean(clazz);
                        //通過反射注入
                        ClassUtil.setField(field,targetBean,fieldValue,true);
                    }
                }
            }
        }
    }

    /**
     * 根據class對象 獲取容器管理的類或者實現類
     * @param fieldClass
     * @param autowired
     * @return
     */
    private Object getFiledInstance(Class<?> fieldClass, String autowired) {
        Object fieldValue = beanContainer.getBean(fieldClass);
        //入如果直接獲取到了就返回
        if (fieldValue != null) {
            return fieldValue;
        }else {
            //找實現類
             Class<?> implClass =  getImplementClass(fieldClass,autowired);
             if (implClass != null){
                 return beanContainer.getBean(implClass);
             }else {
                 return null;
             }
        }
    }

    /**
     * 獲取接口的實現類
     * @param fieldClass
     * @param autowired
     * @return
     */
    private Class<?> getImplementClass(Class<?> fieldClass, String autowired) {
        Set<Class<?>> classsSet = beanContainer.getClassesBySuper(fieldClass);
        if (ValidationUtil.isEmpty(classsSet) == false){
            //如果有多個實現類呢? 通過 Qualifier 設置名稱
            if (ValidationUtil.isEmpty(autowired)){
                //說明沒有精確指定
                if (classsSet.size() == 1){
                    return classsSet.iterator().next();
                }else {
                    //拋出異常
                    throw  new RuntimeException("發現多個繼承類!卻又沒有指定名字"+fieldClass.getName());
                }
            }else {
                for (Class<?> clazz : classsSet) {
                    //如果類的類名等於指定的名字
                    if (autowired.equals(clazz.getSimpleName())){
                        return clazz;
                    }
                }
            }
        }
        return null;
    }
}

需要從容器裏面篩選出符合要被代理的對象,對其替換。

public class AspectWeaver {
  private BeanContainer beanContainer;
  //獲取容器的單例
    public AspectWeaver(){
        beanContainer = BeanContainer.getInstance();
    }
    //AOP主要方法
     public  void doAop(){
     //獲取所有的切面類
      //1.獲取所有的切面類
        Set<Class<?>> aspectSet = beanContainer.getClassesByAnnotation(Aspect.class);
        //沒有需要代理的類,也就是沒有切面類。直接返回 此方法是非空驗證,可自行實現。
           if (ValidationUtil.isEmpty(aspectSet)){ return; }
        //2.將切面類按照不同的織入目標進行切分 比如切Controller的在一邊,切Service的 在一邊。用Map保存  比如 controller  有多個切面類要去增強他。所以用List保存
        Map<Class<? extends Annotation>, List<AspectInfo>> categorizedMap = new HashMap<>();
        //遍歷 所有的切面類
         for (Class<?> aspectClass : aspectSet) {
         //對切面類進行校驗,校驗切面的一般規範。比如 不能切自己(死循環)
            boolean b =  verifyAspect(aspectClass);
            if (b){
            //對於校驗通過的根據要切的目標去歸類保存
            //比如Service的 切面增強 有一個列表
            //比如Controller 切面增強 有一個列表
            //列表是因爲 往往 不止一個切面類要去增強某被代理類
                categorizeAspect(categorizedMap,aspectClass);
            }else {
            //對於不符合切面規範的,拋出異常
                throw  new RuntimeException("當前切面類必須持有@Aspect 和 @Order 和繼承自DefaultAspect");
            }
        }
        //Map都是空的 也沒啥做的 直接返回 無事可做
         if (ValidationUtil.isEmpty(categorizedMap)){return;}
//注意注意 這裏map百年裏的是KEY  假設 整個應用程序所有的切面只對service和controller進行增強,所以此時的key就是service和controller
//然後根據key獲取符合條件的切面類列表,全部進行織入。
         for (Class<? extends Annotation> aClass : categorizedMap.keySet()) {
            //執行織入 代理對象替換了容器的被代理對象
            //這樣字從 容器取得對象就是已經增強後的對象了
              List<AspectInfo> aspectInfoList = categorizedMap.get(aClass);
              //對controller 和service織入
              weaveByCategory(aClass, aspectInfoList);
        }
     }
 /**
     * 驗證切面 類是否滿足要求
     * 必須要有兩個切面標籤
     * 必須繼承自DefaultAspect.class 必須按照這個骨架執行啊
     * Aspect的屬性不能是他本身
     * @param aspectClass
     * @return
     */
    private boolean verifyAspect(Class<?> aspectClass) {
            return aspectClass.isAnnotationPresent(Aspect.class)
                    && aspectClass.isAnnotationPresent(Order.class)
                    && DefaultAspect.class.isAssignableFrom(aspectClass)
                    && aspectClass.getAnnotation(Aspect.class).value() != Aspect.class;
    }
/**
     *將切面類 按照不同的切面目標進行分類
     * @param categorizedMap
     * @param aspectClass
     */
    private void categorizeAspect(Map<Class<? extends Annotation>, List<AspectInfo>> categorizedMap, Class<?> aspectClass) {
        Order orderTag = aspectClass.getAnnotation(Order.class);
        Aspect aspectTag = aspectClass.getAnnotation(Aspect.class);
        DefaultAspect aspect = (DefaultAspect) beanContainer.getBean(aspectClass);
        AspectInfo aspectInfo = new AspectInfo(orderTag.value(),aspect);
        if (categorizedMap.containsKey(aspectTag.value()) == false) {
            //如果第一次出現
            List<AspectInfo> aspectInfoList = new ArrayList<>();
            aspectInfoList.add(aspectInfo);
            categorizedMap.put(aspectTag.value(),aspectInfoList);
        }else {
            //已有列表
            categorizedMap.get(aspectTag.value())
                    .add(aspectInfo);
        }
    }
/**
     * 
     * @param aspectAnnotationClass 要被切的註解所對應的類 比如@service的xxxxService
     * @param aspectInfos 他所對應的集合
     */
    private void weaveByCategory(Class<? extends Annotation> aspectAnnotationClass, List<AspectInfo> aspectInfos) {
     //獲取被例如 @service標記的所有類
        Set<Class<?>> aspectClassSet = beanContainer.getClassesByAnnotation(aspectAnnotationClass);
        if (ValidationUtil.isEmpty(aspectClassSet)){return;}
        //遍歷 爲每個被代理的類生成動態代理對象
        for (Class<?> targetClass : aspectClassSet) {
            AspectListExecutor aspectListExecutor = new AspectListExecutor(targetClass,aspectInfos);
            Object proxyBean = ProxyCreator.createProxy(targetClass, aspectListExecutor);
            //將代理對象放到容器 替換被代理的實例
            beanContainer.addBean(targetClass,proxyBean);
        }
    }
}

以上就是一個簡單版本的根據註解來分類切面的實現。

測試

編寫一個測試的controller,具有返回值。

@Controller
public class TestAopController {
    public int say(){
        System.out.println("我是controller的本地方法");
        return 1;
    }
}

爲他編寫兩個AOP類。順序分別是0和1

package com.fuyouj.aspect.test;

import com.framework.aop.annotation.Aspect;
import com.framework.aop.annotation.Order;
import com.framework.aop.aspect.DefaultAspect;
import com.framework.core.annotation.Controller;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;

/**
 * @Desc
 * @Author FuYouJ
 * @date 2020/6/2 0:40
 */
@Aspect(value = Controller.class)
@Order(0)
@Slf4j
public class TestAopOne extends DefaultAspect {
    @Override
    public void before(Class<?> targetClass, Method method, Object[] args) throws Throwable {
       log.info("我是切面邏輯before order0,我增強的目標類是{},我增強的目標方法是,方法參數{}"
        ,targetClass.getSimpleName(),method.getName(),args);
    }

    @Override
    public Object afterReturning(Class<?> targetClass, Method method, Object[] args, Object returnValue) throws Throwable {
        Integer res = (Integer) returnValue;
        log.info("我是切面邏輯after order0,我增強的目標類是{},我增強的目標方法是,方法參數{}"
                ,targetClass.getSimpleName(),method.getName(),args);
        res++;
        return res;
    }
}
package com.fuyouj.aspect.test;

import com.framework.aop.annotation.Aspect;
import com.framework.aop.annotation.Order;
import com.framework.aop.aspect.DefaultAspect;
import com.framework.core.annotation.Controller;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;

/**
 * @Desc
 * @Author FuYouJ
 * @date 2020/6/2 0:40
 */
@Aspect(value = Controller.class)
@Order(1)
@Slf4j
public class TestAopTwo extends DefaultAspect {
    @Override
    public void before(Class<?> targetClass, Method method, Object[] args) throws Throwable {
       log.info("我是切面邏輯before order1,我增強的目標類是{},我增強的目標方法是{},方法參數{}"
        ,targetClass.getSimpleName(),method.getName(),args);
    }

    @Override
    public Object afterReturning(Class<?> targetClass, Method method, Object[] args, Object returnValue) throws Throwable {
        Integer res = (Integer) returnValue;
        log.info("我是切面邏輯after order1,我增強的目標類是{},我增強的目標方法是{},方法參數{}"
                ,targetClass.getSimpleName(),method.getName(),args);
        res++;
        return res;
    }
}

編寫測試類

public class TestAop {
    @Test
    public void testAopOne(){
        BeanContainer beanContainer = BeanContainer.getInstance();
        //加載了所有類
        beanContainer.loadBeans("com.fuyouj");
        //AOP
        new AspectWeaver().doAop();
        // ioc
        //這裏的細節 先AOP,再自動注入。
        new DependencyInjector().doIoc();
        TestAopController controller = (TestAopController) beanContainer.getBean(TestAopController.class);
        int say = controller.say();
        System.out.println(say+"======");
    }
}

按照邏輯,倆個切面類的順序分別是0和1,所以 執行順序因該是
0的beore,1的before,1的after,0的after.並且原來額返回結果經過兩次修改,應該是2。

在這裏插入圖片描述
附上Controller註解。


@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Controller {
}

後序預告
AOP2.0: 支持execution表達式
自己手寫一個Spring框架

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