自定義Aspect風格的AOP框架

本篇博客參考《架構探險--從零開始寫java web框架》4.3章節 1代理接口:java

package smart.myaop.framework;

public interface Proxy {
    /**
     * 執行鏈式調用
     */
    Object doProxy(ProxyChain proxyChain) throws Throwable;
}

2代理鏈(責任鏈模式,同一個對象能夠被多個Proxy層層代理):web

package smart.myaop.framework;

import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

/**
 * 代理鏈
 */
public class ProxyChain {
    private final Class<?> targetClass; //目標類
    private final Object targetObject; //目標對象
    private final Method targetMethod; //目標方法
    private final MethodProxy methodProxy; //方法代理,cglib提供的方法代理對象
    private final Object[] methodParams; //方法參數

    private List<Proxy> proxyList = new ArrayList<>(); //代理列表
    private int proxyIndex = 0; //代理索引

    public ProxyChain(Class<?> targetClass, Object targetObject, Method targetMethod, MethodProxy methodProxy, Object[] methodParams, List<Proxy> proxyList) {
        this.targetClass = targetClass;
        this.targetObject = targetObject;
        this.targetMethod = targetMethod;
        this.methodProxy = methodProxy;
        this.methodParams = methodParams;
        this.proxyList = proxyList;
    }

    public Class<?> getTargetClass() {
        return targetClass;
    }

    public Method getTargetMethod() {
        return targetMethod;
    }

    public Object[] getMethodParams() {
        return methodParams;
    }

    /**
     * 在Proxy接口的實現中提供相應橫切邏輯並調用doProxyChain方法
     * methodProxy的invokeSuper方法執行目標對象的業務邏輯
     * @return
     * @throws Throwable
     */
    public Object doProxyChain() throws Throwable {
        Object methodResult;
        if(proxyIndex < proxyList.size()) {
            methodResult = proxyList.get(proxyIndex++).doProxy(this);
        } else {
            methodResult = methodProxy.invokeSuper(targetObject, methodParams);
        }
        return methodResult;
    }
}

3建立代理對象的工具類:spring

package smart.myaop.framework;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.List;

/**
 * 代理管理器,輸入目標類和一組proxy接口實現,建立一個代理對象並輸出
 * 由切面類來調用ProxyManager建立代理鏈,切面類在目標方法調用先後進行加強
 *
 * 在框架裏使用ProxyManager建立代理對象並放入ioc容器,而後將代理對象注入到其它對象中
 */
public class ProxyManager {
    public static <T> T createProxy(final Class<?> targetClass, final List<Proxy> proxyList) {
        return (T) Enhancer.create(targetClass, new MethodInterceptor() {
            @Override
            public Object intercept(Object targetObject, Method targetMethod, Object[] methodParams, MethodProxy methodProxy) throws Throwable {
                return new ProxyChain(targetClass, targetObject, targetMethod, methodProxy, methodParams, proxyList);
            }
        });
    }
}

4Proxy接口的抽象實現,模板方法模式:架構

package smart.myaop.framework;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Method;

/**
 * 切面代理
 * 該抽象類提供模板方法,由其子類擴展相應的抽象方法
 */
public abstract class AspectProxy implements Proxy {
    private static final Logger logger = LoggerFactory.getLogger(AspectProxy.class);
    @Override
    public Object doProxy(ProxyChain proxyChain) throws Throwable {
        Object result = null;
        Class<?> cls = proxyChain.getTargetClass();
        Method method = proxyChain.getTargetMethod();
        Object[] params = proxyChain.getMethodParams();
        /**
         * 從proxyChain中獲取目標類,目標方法和目標參數,經過try ... catch ...finally代碼塊調用代理框架
         */
        begin();
        try {
            if(intercept(cls, method, params)) {
                before(cls, method, params);
                result = proxyChain.doProxyChain();
                after(cls, method, result);
            }
        } catch (Exception e) {
            logger.error("proxy failure", e);
            error(cls, method, params, e);
        } finally {
            end();
        }
        return result;
    }

    /**
     * 下面幾個都是鉤子方法,可在子類中有選擇性的實現,能夠有選擇性的實現,因此不定義成抽象方法
     */
    public boolean intercept(Class<?> cls, Method method, Object[] params) {
        return true;
    }

    public void before(Class<?> cls, Method method, Object[] params) {}

    public void after(Class<?> cls, Method method, Object result) {}

    public void begin() {}

    public void end() {}

    public void error(Class<?> cls, Method method, Object[] params, Exception e) {}
}

5舉例某個具體的Proxy實現:框架

package smart.myaop;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import smart.myaop.framework.AspectProxy;

import java.lang.reflect.Method;

/**
 * 該示例類繼承AspectProxy類,指定攔截Controller全部方法,並在方法先後加日誌並記錄執行時間
 */
@Aspect(Controller.class)
public class ControllerAspect extends AspectProxy {
    private static final Logger logger = LoggerFactory.getLogger(ControllerAspect.class);
    private long begin;
    @Override
    public void before(Class<?> cls, Method method, Object[] params) {
        logger.debug("----------begin----------");
        logger.debug(String.format("class: %s"), cls.getName());
        logger.debug(String.format("method: %s"), method.getName());
        begin = System.currentTimeMillis();
    }
    @Override
    public void after(Class<?> cls, Method method, Object result) {
        logger.debug(String.format("time: %dms"), System.currentTimeMillis() - begin);
        logger.debug("----------end----------");
    }
}

6自定義註解@Aspect,做爲代理標記:ide

package smart.myaop;

import java.lang.annotation.*;

/**
 * 切面註解
 */
@Target(ElementType.TYPE) //只能用在類上
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /**
     * 註解,註解類,用來定義註解
     * @return
     */
    Class<? extends Annotation> value();
}

7初始化框架並建立代理對象放入ioc容器:工具

package smart.myaop;

import smart.myaop.framework.AspectProxy;
import smart.myaop.framework.Proxy;
import smart.myaop.framework.ProxyManager;

import java.lang.annotation.Annotation;
import java.util.*;

/**
 * 初始化時獲取全部目標類和其被攔截的切面類實例,獲取AspectProxy抽象類的全部子類和@Aspect註解的全部類
 * 調用ProxyManager#createProxy方法建立代理對象放入ioc容器
 * AopHelper在框架初始化時調用,要在初始化bean以後,ioc容器初始化以前調用,這樣ioc容器作屬性注入時候才能拿到相應的代理對象
 */
public class AopHelper {
    private static Set<Class<?>> allClassSet = new HashSet<>(); //項目啓動時把指定路徑下的全部class文件加載爲class對象集合,過程略

    static {
        try {
            Map<Class<?>, Set<Class<?>>> proxyMap = createProxyMap();
            Map<Class<?>, List<Proxy>> targetMap = createTargetMap(proxyMap);
            for (Map.Entry<Class<?>, List<Proxy>> targetEntry : targetMap.entrySet()) {
                Class<?> targetClass = targetEntry.getKey();
                List<Proxy> proxyList = targetEntry.getValue();
                Object proxy = ProxyManager.createProxy(targetClass, proxyList);
                //todo 放入targetClass爲鍵,proxy爲值放入ioc容器,在ioc容器作屬性注入的時候經過class對象拿到的就是代理對象了
            }
        } catch (Exception e) {
            System.out.println("aop failure");
            e.printStackTrace();
        }
    }

    /**
     * 目標類與代理對象列表之間的映射關係,如一個業務類被多個@Aspect註解修飾的AspectProxy子類代理,這裏獲得這樣的1對n映射關係
     * @param proxyMap
     * @return
     * @throws Exception
     */
    private static Map<Class<?>, List<Proxy>> createTargetMap(Map<Class<?>, Set<Class<?>>> proxyMap) throws Exception {
        Map<Class<?>, List<Proxy>> targetMap = new HashMap<>();
        for (Map.Entry<Class<?>, Set<Class<?>>> proxyEntry : proxyMap.entrySet()) {
            Class<?> proxyClass = proxyEntry.getKey();
            Set<Class<?>> targetClassSet = proxyEntry.getValue();
            for (Class<?> targetClass : targetClassSet) {
                Proxy proxy = (Proxy) proxyClass.newInstance();
                if(targetMap.containsKey(targetClass)) {
                    targetMap.get(targetClass).add(proxy);
                } else {
                    List<Proxy> proxyList = new ArrayList<>();
                    proxyList.add(proxy);
                    targetMap.put(targetClass, proxyList);
                }
            }
        }
        return targetMap;
    }

    /**
     * 給createTargetMap方法用
     * 代理類(切面類)與目標類集合之間的一對多映射關係
     * 在所有class對象集合中搜索知足1是AspectProxy子類,2被@Aspect註解,這樣的類(代理類),根據@Aspect註解指定的註解屬性去獲取該註解對應的目標類集合
     * 而後創建代理類與目標類集合之間的映射關係,據此分析出目標類與代理對象列表之間的映射關係
     * @return
     * @throws Exception
     */
    private static Map<Class<?>, Set<Class<?>>> createProxyMap() throws Exception {
        Map<Class<?>, Set<Class<?>>> proxyMap = new HashMap<>();
        Set<Class<?>> proxyClassSet = new HashSet<>();
        for (Class<?> aClass : allClassSet) {
            if(AspectProxy.class.isAssignableFrom(aClass) && !AspectProxy.class.equals(aClass)) {
                proxyClassSet.add(aClass); //獲取AspectProxy子類class對象集合
            }
        }
        for (Class<?> aClass : proxyClassSet) {
            if(aClass.isAnnotationPresent(Aspect.class)) {
                Aspect aspect = aClass.getAnnotation(Aspect.class);
                Set<Class<?>> targetClassSet = createTargetClassSet(aspect);
                proxyMap.put(aClass, targetClassSet);
            }
        }
        return proxyMap;
    }

    /**
     * 給createProxyMap方法用
     * 獲取被指定aspect註解的全部類對象
     * @param aspect
     * @return
     * @throws Exception
     */
    private static Set<Class<?>> createTargetClassSet(Aspect aspect) throws Exception {
        Set<Class<?>> targetClassSet = new HashSet<>();
        Class<? extends Annotation> annotation = aspect.value();
        if(annotation != null && annotation.equals((Aspect.class))) {
            for (Class<?> aClass : allClassSet) {
                if(aClass.isAnnotationPresent(annotation)) {
                    targetClassSet.add(aClass); //這裏從全部的class集合中挑出被annotation類型註解的class對象集合
                }
            }
        }
        return targetClassSet;
    }
}

注意:代碼從1寫到7,從7到1理解有助於瞭解總體工做流程,整個用了責任鏈模式、模板方法模式,CGLIB動態代理。書中叫Proxy和ProxyChain叫作代理和代理鏈,改叫加強和加強鏈更容易理解,每個Proxy就是對目標類的方法的一次功能加強。this

使用舉例:debug

能夠提供一個@Login註解,能夠用在方法或類上,而後實現一個AuthzAnnotationAspect繼承AspectProxy,用@Aspect(Controller.class)註解,在before方法中判斷目標方法或目標類是否被@Login註解了,若是是,使用Shiro的代碼判斷用戶是否登陸:
PrincipalCollection principals = SecurityUtils.getSubject().getPrincipals();
if(principals == null || principals.isEmpty()) {
    throw new Exception(「當前用戶未登陸!」);
}
相關文章
相關標籤/搜索