從零開始實現一個簡易的Java MVC框架(四)--實現AOP

前言

AOP全稱是Aspect Oriented Programming,叫作面向切面編程,和麪向對象編程(OOP)同樣也是一種編程思想,也是spring中一個重要的部分。java

其實現基於代理模式,對原來的業務進行加強。好比說原來的功能是增刪改查,想要不修改源代碼的狀況下加強原來的功能,那麼就能夠對原來的業務類生成一個代理的對象,在代理對象中實現方法對原來的業務加強。git

而代理又分靜態代理和動態代理,一般咱們都是用動態代理,由於靜態代理都是硬編碼,不適合拿來用在實現框架這種需求裏。在java中一般有兩種代理方式,一個是jdk自帶的代理,另外一個是cglib實現的代理方式,這兩個代理各有特色,不大瞭解的話能夠自行查找資料看看。github

在spring的底層這兩種代理方式都支持,在默認的狀況下,若是bean實現了一個接口,spring會使用jdk代理,不然就用cglib代理。spring

在doodle框架裏用了cglib代理的方式,由於這種方式代理的類不用實現接口,實現更靈活編程

實現準備

在具體實現AOP功能前,先作一些準備。框架

由於cglib代理不是jdk自帶的,因此先在pom.xml引入cglib。ide

<properties>
    ...
    <cglib.version>3.2.6</cglib.version>
</properties>
<dependencies>
    ...
    <!-- cglib -->
    <dependency>
        <groupId>cglib</groupId>
        <artifactId>cglib</artifactId>
        <version>${cglib.version}</version>
    </dependency>
</dependencies>

而後在zbw.aop包下建立一個annotation包,而後再建立一個Aspect註解。這個註解是用於標記在''切面''中,即實現代理功能的類上面。函數

package com.zbw.aop.annotation;
import ...;

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /**
     * 目標代理類的範圍
     */
    Class<? extends Annotation> target();
}

接着在zbw.aop包下建立一個advice包,這個包下放一系列的通知接口(Advice)。其中包括:測試

  • 基礎通知接口Advice,全部通知接口都要繼承這個接口
  • 前置通知接口MethodBeforeAdvice,繼承這個通知接口並實現其前置方法,能夠前置加強目標類,即目標方法執行前會先執行這個前置方法。
  • 後置通知接口AfterReturningAdvice,繼承這個通知接口並實現其返回後方法,能夠後置加強目標類,即目標方法執後並放回結果時,會執行這個返回方法。
  • 異常通知接口ThrowsAdvice,繼承這個通知接口並實現其異常方法,能夠加強目標類的異常,即目標方法發生異常時,會執行這個異常方法。
  • 環繞通知接口AroundAdvice,這個接口繼承了MethodBeforeAdvice,AfterReturningAdvice,ThrowsAdvice這三個接口,至關於這三個接口的合集。

在spring中還有其餘幾種的通知,這裏暫時就不一一實現,咱們就實現這幾種相對來講最經常使用的。優化

/**
 * 通知接口
 */
public interface Advice {
}


/**
 * 前置通知接口
 */
public interface MethodBeforeAdvice extends Advice {
    /**
     * 前置方法
     */
    void before(Class<?> clz, Method method, Object[] args) throws Throwable;
}


/**
 * 返回通知接口
 */
public interface AfterReturningAdvice extends Advice {
    /**
     * 返回後方法
     */
    void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable;
}

/**
 * 異常通知接口
 */
public interface ThrowsAdvice extends Advice {
    /**
     * 異常方法
     */
    void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e);
}



/**
 * 環繞通知接口
 */
public interface AroundAdvice extends MethodBeforeAdvice, AfterReturningAdvice, ThrowsAdvice {
}

實現AOP

剛纔實現了幾種通知接口,咱們先將這些通知接口使用起來,實現代理類。

package com.zbw.aop;
import ...

/**
 * 代理通知類
 */
@Slf4j
@AllArgsConstructor
@NoArgsConstructor
@Data
public class ProxyAdvisor {

    /**
     * 通知
     */
    private Advice advice;

    /**
     * 執行代理方法
     */
    public Object doProxy(Object target, Class<?> targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        Object result = null;

        if (advice instanceof MethodBeforeAdvice) {
            ((MethodBeforeAdvice) advice).before(targetClass, method, args);
        }
        try {
            //執行目標類的方法
            result = proxy.invokeSuper(target, args);
            if (advice instanceof AfterReturningAdvice) {
                ((AfterReturningAdvice) advice).afterReturning(targetClass, result, method, args);
            }
        } catch (Exception e) {
            if (advice instanceof ThrowsAdvice) {
                ((ThrowsAdvice) advice).afterThrowing(targetClass, method, args, e);
            } else {
                throw new Throwable(e);
            }
        }
        return result;
    }
}

這個類就是代理類ProxyAdvisor,即到時候咱們的目標類執行的時候,實際上就是執行咱們這個代理類。在ProxyAdvisor中有屬性Advice即是剛纔編寫的通知接口,而後在目標方法執行的時候,就會執行doProxy()方法,經過斷定Advice接口的類型來執行在接口中實現的方法。

執行的順序就是 MethodBeforeAdvice@before() -> MethodProxy@invokeSuper() -> AfterReturningAdvice@afterReturning(),若是目標方法出現異常則會執行ThrowsAdvice@afterThrowing()方法。

接下來就是實現AOP的執行器

package com.zbw.aop;
import ...

/**
 * Aop執行器
 */
@Slf4j
public class Aop {

    /**
     * Bean容器
     */
    private BeanContainer beanContainer;

    public Aop() {
        beanContainer = BeanContainer.getInstance();
    }

    public void doAop() {
        beanContainer.getClassesBySuper(Advice.class)
                .stream()
                .filter(clz -> clz.isAnnotationPresent(Aspect.class))
                .forEach(clz -> {
                    final Advice advice = (Advice) beanContainer.getBean(clz);
                    Aspect aspect = clz.getAnnotation(Aspect.class);
                    beanContainer.getClassesByAnnotation(aspect.target())
                            .stream()
                            .filter(target -> !Advice.class.isAssignableFrom(target))
                            .filter(target -> !target.isAnnotationPresent(Aspect.class))
                            .forEach(target -> {
                                ProxyAdvisor advisor = new ProxyAdvisor(advice);
                                Object proxyBean = ProxyCreator.createProxy(target, advisor);
                                beanContainer.addBean(target, proxyBean);
                            });
                });
    }
}

和上一節實現IOC的執行器的時候相似,先在AOP執行器的構造函數獲取到單例化得BeanContainer容器。

而後在doAop()方法中實現AOP功能。

  • 遍歷在BeanContainer容器被Aspect註解的Bean,並找到實現了Advice接口的類,這些類即是切面
  • 獲取切面上的註解Aspecttarget()的值,這個值就是要被代理的類的註解。好比說有個切面的註解爲@Aspect(target = Controller.class),那麼這個切面會做用在被Controller註解的類上。
  • 遍歷BeanContainer容器被aspect.target()的值註解的Bean,找到目標代理類
  • 建立ProxyAdvisor代理類並經過cglib建立出這個代理類的實例,並把這個類實例放回到BeanContainer容器中。

在方法中有一個代理類創造器ProxyCreator,他就是經過cglib來建立代理類的,最後實現一下這個創造器。

package com.zbw.aop;

import ...

/**
 * 代理類建立器
 */
public final class ProxyCreator {

    /**
     * 建立代理類
     */
    public static Object createProxy(Class<?> targetClass, ProxyAdvisor proxyAdvisor) {
        return Enhancer.create(targetClass,
                (MethodInterceptor) (target, method, args, proxy) ->
                        proxyAdvisor.doProxy(target, targetClass, method, args, proxy));
    }
}

以上咱們最基本的AOP功能就實現了,可是目前來講,咱們的Advice實現類是不會被Bean容器BeanContainer加載的,全部要在Bean容器的BEAN_ANNOTATION屬性添加@Aspect註解

//BeanContainer
...

/**
* 加載bean的註解列表
*/
private static final List<Class<? extends Annotation>> BEAN_ANNOTATION 
= Arrays.asList(Component.class, Controller.class, Service.class, Repository.class,Aspect.class);

...

測試用例

在上一篇文章從零開始實現一個簡易的Java MVC框架(三)--實現IOC中的測試用例的基礎上,在實現一個DoodleAspect切面,這切面實現了AroundAdvice的通知接口並實現其中的三個方法。

package com.zbw.bean;
import ...

@Slf4j
@Aspect(target = Controller.class)
public class DoodleAspect implements AroundAdvice {

    @Override
    public void before(Class<?> clz, Method method, Object[] args) throws Throwable {
        log.info("Before  DoodleAspect ----> class: {}, method: {}", clz.getName(), method.getName());
    }

    @Override
    public void afterReturning(Class<?> clz, Object returnValue, Method method, Object[] args) throws Throwable {
        log.info("After  DoodleAspect ----> class: {}, method: {}", clz, method.getName());
    }

    @Override
    public void afterThrowing(Class<?> clz, Method method, Object[] args, Throwable e) {
        log.error("Error  DoodleAspect ----> class: {}, method: {}, exception: {}", clz, method.getName(), e.getMessage());
    }
}

而後再編寫AopTest的測試用例,這裏要注意,Aop執行器必需要在Ioc執行器以前執行,否則注入到Bean中的實例將可能不是代理類。

package com.zbw.aop;
import ...

@Slf4j
public class AopTest {
    @Test
    public void doAop() {
        BeanContainer beanContainer = BeanContainer.getInstance();
        beanContainer.loadBeans("com.zbw");
        new Aop().doAop();
        new Ioc().doIoc();
        DoodleController controller = (DoodleController) beanContainer.getBean(DoodleController.class);
        controller.hello();
    }
}

能夠看到在執行DoodleController@hello()方法的先後分別執行了DoodleAspect@before()DoodleAspect@afterReturning()方法。說明AOP的功能已經完成了。

目前缺陷

雖然完成了AOP功能,可是仍是有幾個比較嚴重的缺陷的

  • 對目標類的篩選不是很便捷,如今是用Aspect.target()的值,來篩選出被這個值註解的類,這樣太籠統了。假如Aspect.target()=Controller.class,那麼全部被Controller註解的controller裏的左右方法都要被代理。咱們但願可以像spring那樣如execution(* com.zbw.*.service..*Impl.*(..)),用一些表達式來篩選目標類。
  • 一個目標類只能被一個切面做用。目前來講好比有DoodleAspect1DoodleAspect2兩個切面,都做用於DoodleController上,只有一個切面能生效,這也不合理。

因此在後面的章節會完善實現這兩個問題。


源碼地址:doodle

原文地址:從零開始實現一個簡易的Java MVC框架(四)--實現AOP

相關文章
相關標籤/搜索