【從零寫javaweb框架】(九)開發AOP框架

上一篇我們完成搭建了一個簡單的MVC框架,使其可以通過DispatchServlet來處理所有的請求【從零寫javaweb框架】(八)請求轉發器,這一篇會實現AOP功能。

一.什麼是AOP

AOP(Aspect-Oriented Programming)是對面向對象編程(OOP)的一種補充,可以譯爲面向切面編程。其中切面是AOP的一個術語,表示從業務邏輯中分離出來的橫切邏輯,比如性能監控,日誌記錄、權限控制等,這些邏輯都可從核心的業務邏輯代碼中抽離出去。也就是說,通過AOP可以解決代碼耦合問題,是職責更加單一。
我個人的理解:AOP的作用通俗一點說就是可以在方法的前後附加上其他代碼,每次調用此方法之前或之後(又或者前後都有),都會先後執行附加的代碼。

二.設計模式之代理模式

如果想繼續深入瞭解AOP的實現,我們需要先了解靜態代理模式和動態代理模式,這裏附上我以前寫的兩篇博文(如果之前已經瞭解過代理模式了,那麼可以直接跳過看下一步):
靜態代理模式:設計模式_4:代理模式


三.開發AOP框架

緊接着我之前寫的框架,現在要開始實現AOP了,首先定義一個切面註解:
package org.smart4j.framework.annotation;

import java.lang.annotation.*;

/**
 * desc : 切面註解
 * Created by Lon on 2018/2/3.
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {

    /**
     * 註解
     */
    Class<? extends Annotation> value();

}

這個註解的value是一個class,表示我們要對哪個標有此註解類裏的方法進行切入(其實就是要代理哪個類),我們可以註解在一個類上面,表示此類是一個切面類。用法:@Aspect(Controller.class),它表示我們要對標有Controller(這個註解是我們之前編寫的,如果忘記了可以回頭看一看)註解的類進行切入。

然後繼續在框架中添加一個名爲Proxy的接口:
package org.smart4j.framework.proxy;

/**
 * desc : 代理接口
 * Created by Lon on 2018/2/3.
 */
public interface Proxy {

    /**
     * 執行鏈式代理
     */
    Object doProxy(ProxyChain proxyChain) throws Throwable;

}
這個Proxy接口中有一個doProxy方法,參數是一條代理鏈proxyChain,這條代理鏈的作用是把多個代理串起來,並一個個依次執行,執行順序取決於添加到鏈上的先後順序,ProxyChain代碼:
package org.smart4j.framework.proxy;

import net.sf.cglib.proxy.MethodProxy;

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

/**
 * desc : 代理鏈
 * Created by Lon on 2018/2/3.
 */
public class ProxyChain {
    
    // 要代理的目標類
    private final Class<?> targetClass;
    // 要代理的目標對象
    private final Object targetObject;
    // 要代理的目標方法
    private final Method targetMethod;
    // 目標方法的代理(這個是CGLib爲我們提供的)
    private final MethodProxy methodProxy;
    // 要代理的目標方法的方法參數
    private final Object[] methodParams;

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

    // ProxyChain構造函數
    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;
    }

    /**
     * 如果尚未達到proxyList上限,則從proxyList上拿取相應的對象,執行它的doProxy方法,
     * 在Proxy接口的實現中會提供相應的橫切邏輯,並調用doProxyChain方法,隨後將再次調用
     * 當前ProxyChain對象的doProxyChain方法,知道proxyIndex達到proxyList上限爲止,
     * 到最後再調用methodProxy的invokeSuper方法,執行目標對象的業務邏輯。
     */
    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;
    }

}
我們下面一步還用到了動態代理,所以要往maven加入CGLib:
    <!-- CGLib -->
    <dependency>
      <groupId>cglib</groupId>
      <artifactId>cglib</artifactId>
      <version>2.2.2</version>
    </dependency>
現在寫一個類,用它來動態創建代理對象,命名爲ProxyManager:
package org.smart4j.framework.proxy;

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;

/**
 * desc : 代理管理器
 * Created by Lon on 2018/2/5.
 */
public class ProxyManager {

    @SuppressWarnings("unchecked")
    public static <T> T createProxy(final Class<?> targetClass, final List<Proxy> proxyList){
        return (T) Enhancer.create(targetClass, new MethodInterceptor() {
            public Object intercept(Object targetObject, Method targetMethod,
                                    Object[] methodParams, MethodProxy methodProxy) throws Throwable {
                return new ProxyChain(targetClass,targetObject, targetMethod,methodProxy,methodParams,proxyList).doProxyChain();
            }
        });
    }

}

接下來需要通過切面類來調用ProxyManager,寫一個抽象類,讓它提供一個模板方法,並在該抽象類的具體實現中擴展相應的抽象方法:

package org.smart4j.framework.proxy;

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

import java.lang.reflect.Method;

/**
 * desc : 切面代理類
 * Created by Lon on 2018/2/6.
 */
public abstract class AspectProxy implements Proxy{

    private static final Logger LOGGER = LoggerFactory.getLogger(AspectProxy.class);

    public Object doProxy(ProxyChain proxyChain) throws Throwable{
        Object result = null;

        Class<?> cls = proxyChain.getTargetClass();
        Method method = proxyChain.getTargetMethod();
        Object[] params = proxyChain.getMethodParams();

        begin();
        try {
            if (this.intercept(cls, method, params)){
                this.before(cls, method, params);
                result = proxyChain.doProxyChain();
                this.after(cls, method, params, result);
            } else {
                result = proxyChain.doProxyChain();
            }
        } catch (Throwable e){
            LOGGER.error("proxy failure", e);
            error(cls, method, params, e);
            throw e;
        } finally {
            end();
        }
        return result;
    }

    public void begin(){
    }

    public boolean intercept(Class<?> cls, Method method, Object[] params) throws Throwable{
        return true;
    }


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

    public void after(Class<?> cls, Method method, Object[] params, Object result) throws Throwable{
    }

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

    public void end(){

    }

}
在doProxy方法中,我們從proxyChain參數中獲取了目標類、目標方法與方法參數、之後用try/catch/finally代碼塊來實現調用框架,從框架中抽象出一系列的鉤子方法,這些抽象方法可在AspectProxy的子類中有選擇性地進行實現。

下一篇會寫加載AOP框架


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