從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點

前言

在上一節從零開始實現一個簡易的Java MVC框架(四)--實現AOP中咱們實現了AOP的功能,已經能夠生成對應的代理類了,可是對於代理對象的選擇只能經過指定的類,這樣確實不方便也不合理。這一節咱們就利用aspectj來實現功能更強大的切點。java

在spring初期的時候AOP功能使用起來也是很繁瑣麻煩的,到了後面整合了aspectj纔有瞭如今這麼方便的AOP功能,好比下面這樣的代碼,很簡便而且直觀的定義了切點。git

@Component
@Aspect
public class LogAspect {
	@Pointcut("execution(* com.zbw.*.service..*Impl.*(..)) && @annotation(Log)")
	public void logPointcut() {
	}

	@Before("logPointcut()")
    public void before() {System.out.println("Before");}
}
複製代碼

如今咱們也來引入aspectj來實現AOP切點的功能github

引入aspectj並實現aspectj的切點類

首先在pom.xml中加入aspectj的依賴spring

<properties>
    ...
    <aspectj.version>1.8.13</aspectj.version>
</properties>
<dependencies>
	...
	<!-- aspectj -->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>${aspectj.version}</version>
    </dependency>
</dependencies>

複製代碼

接下來就能夠開始實現一個利用aspectj來斷定的切點類,這個類主要是用於判斷aspectj表達式是否匹配一個指定類或者指定方法。express

在zbw.aop包下建立一個類,起名叫ProxyPointcut框架

package com.zbw.aop;

import ...

/** * 代理切點類 */
public class ProxyPointcut {
    /** * 切點解析器 */
    private PointcutParser pointcutParser;

    /** * (AspectJ)表達式 */
    private String expression;

    /** * 表達式解析器 */
    private PointcutExpression pointcutExpression;

    /** * AspectJ語法集合 */
    private static final Set<PointcutPrimitive> DEFAULT_SUPPORTED_PRIMITIVES = new HashSet<>();

    static {
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.ARGS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.REFERENCE);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.THIS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.TARGET);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.WITHIN);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ANNOTATION);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_WITHIN);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_ARGS);
        DEFAULT_SUPPORTED_PRIMITIVES.add(PointcutPrimitive.AT_TARGET);
    }

    public ProxyPointcut() {
        this(DEFAULT_SUPPORTED_PRIMITIVES);
    }

    public ProxyPointcut(Set<PointcutPrimitive> supportedPrimitives) {
        pointcutParser = PointcutParser
                .getPointcutParserSupportingSpecifiedPrimitivesAndUsingContextClassloaderForResolution(supportedPrimitives);
    }

    /** * Class是否匹配切點表達式 */
    public boolean matches(Class<?> targetClass) {
        checkReadyToMatch();
        return pointcutExpression.couldMatchJoinPointsInType(targetClass);
    }

    /** * Method是否匹配切點表達式 */
    public boolean matches(Method method) {
        checkReadyToMatch();
        ShadowMatch shadowMatch = pointcutExpression.matchesMethodExecution(method);
        if (shadowMatch.alwaysMatches()) {
            return true;
        } else if (shadowMatch.neverMatches()) {
            return false;
        }
        return false;
    }

    /** * 初始化切點解析器 */
    private void checkReadyToMatch() {
        if (null == pointcutExpression) {
            pointcutExpression = pointcutParser.parsePointcutExpression(expression);
        }
    }

    public void setExpression(String expression) {
        this.expression = expression;
    }

    public String getExpression() {
        return expression;
    }
複製代碼

這個類中有三個變量:pointcutParser,expression,pointcutExpression測試

其中expression是String類型,用於存放咱們要設定的aspectj表達式,好比execution(* com.zbw.*.service..*Impl.*(..))這樣的。優化

pointcutParserpointcutExpression就是aspectj裏面的類了,pointcutParser用於根據expression中的表達式建立pointcutExpression表達式解析器。而pointcutExpression能夠用來判斷方法或者類是否匹配表達式。this

這個類中最主要的兩個方法就matches(Class<?> targetClass)matches(Method method),這兩個方法分別用於斷定目標的類和方法是否匹配expression中的aspectj表達式。spa

接下來就能夠把ProxyPointcut這個切點類加入到咱們以前實現的AOP功能中了。

實現AOP的切點功能

首先改裝Aspect註解,把以前target()改爲pointcut()來存儲aspectj表達式。

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

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Aspect {
    /** * 切點表達式 */
    String pointcut() default "";
}
	
複製代碼

而後改裝ProxyAdvisor這個類,把切點表達式匹配器放入其中,而且使用匹配器來斷定目標類是否要被加強。

...

public class ProxyAdvisor {

	...

    /** * AspectJ表達式切點匹配器 */
    private ProxyPointcut pointcut;

    /** * 執行代理方法 */
    public Object doProxy(Object target, Class<?> targetClass, Method method, Object[] args, MethodProxy proxy) throws Throwable {
        if (!pointcut.matches(method)) {
            return proxy.invokeSuper(target, args);
        }

        ...
    }
}
複製代碼

doProxy()這個方法的最前面經過pointcut.matches()來斷定目標方法是否匹配這個表達式,若是匹配的話就往下執行以前編寫的各類通知,若是不匹配那麼就直接執行目標方法。經過這種方式來使aspectj表達式控制目標類的加強。

接下來改裝Aop類,因爲改變了匹配目標類的規則,因此要重寫以前的doAop()方法。

...

public class Aop {
	...

    public void doAop() {
        beanContainer.getClassesBySuper(Advice.class)
                .stream()
                .filter(clz -> clz.isAnnotationPresent(Aspect.class))
                .map(this::createProxyAdvisor)
                .forEach(proxyAdvisor -> beanContainer.getClasses()
                        .stream()
                        .filter(target -> !Advice.class.isAssignableFrom(target))
                        .filter(target -> !target.isAnnotationPresent(Aspect.class))
                        .forEach(target -> {
                            if (proxyAdvisor.getPointcut().matches(target)) {
                                Object proxyBean = ProxyCreator.createProxy(target, proxyAdvisor);
                                beanContainer.addBean(target, proxyBean);
                            }
                        }));
    }

    /** * 經過Aspect切面類建立代理通知類 */
    private ProxyAdvisor createProxyAdvisor(Class<?> aspectClass) {
        String expression = aspectClass.getAnnotation(Aspect.class).pointcut();
        ProxyPointcut proxyPointcut = new ProxyPointcut();
        proxyPointcut.setExpression(expression);
        Advice advice = (Advice) beanContainer.getBean(aspectClass);
        return new ProxyAdvisor(advice, proxyPointcut);
    }
}
複製代碼

雖然重寫了doAop()方法,可是實現原理依舊是相同的。只不過如今把建立ProxyAdvisor的過程分離出來單獨寫了一個方法createProxyAdvisor(), 而後再遍歷Bean容器中的除了切面類的全部Bean,若是這個Bean匹配ProxyAdvisor中的切點表達式,那麼就會生成對應的代理類。

引入aspectj實現AOP切點完成了,又到測試用例來測試功能是否成功的時候了。

測試用例

在上一篇文章從零開始實現一個簡易的Java MVC框架(四)--實現AOP中的測試用例的基礎上修改測試用例。

先修改切面類DoodleAspect上的Aspect註解

package com.zbw.bean;
import ...

@Slf4j
@Aspect(pointcut = "execution(* com.zbw.bean.DoodleController.helloForAspect(..))")
public class DoodleAspect implements AroundAdvice {

	...

}
複製代碼

這個Aspect@pointcut()中的值會讓其只匹配DoodleController中的helloForAspect()方法。

接下來在DoodleController添加helloForAspect()方法

...

public class DoodleController {
   	...

    public void helloForAspect() {
        log.info("Hello Aspectj");
    }
}

複製代碼

最後再從新編寫AopTest的測試用例。

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();
        controller.helloForAspect();
    }
}

複製代碼

從結果的圖中能夠看到在DoodleControllerhello()先後沒有打印多餘的日誌,而在helloForAspect()方法的前面和後面都打印了DoodleAspect中的通知方法裏的內容,說明咱們的AOP已經精準的匹配到了想要的目標。


源碼地址:doodle

原文地址:從零開始實現一個簡易的Java MVC框架(五)--引入aspectj實現AOP切點

相關文章
相關標籤/搜索