资讯专栏INFORMATION COLUMN

从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

wupengyu / 1517人阅读

摘要:接下来就可以把这个切点类加入到我们之前实现的功能中了。实现的切点功能首先改装注解,把之前改成来存储表达式。测试用例在上一篇文章从零开始实现一个简易的框架四实现中的测试用例的基础上修改测试用例。

前言

在上一节从零开始实现一个简易的Java MVC框架(四)--实现AOP中我们实现了AOP的功能,已经可以生成对应的代理类了,但是对于代理对象的选择只能通过指定的类,这样确实不方便也不合理。这一节我们就利用aspectj来实现功能更强大的切点。

在spring初期的时候AOP功能使用起来也是很繁琐麻烦的,到了后面整合了aspectj才有了现在这么方便的AOP功能,比如下面这样的代码,很简便并且直观的定义了切点。

@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切点的功能

引入aspectj并实现aspectj的切点类

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


    ...
    1.8.13


    ...
    
    
        org.aspectj
        aspectjweaver
        ${aspectj.version}
    

接下来就可以开始实现一个利用aspectj来判定的切点类,这个类主要是用于判断aspectj表达式是否匹配一个指定类或者指定方法。

在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 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 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可以用来判断方法或者类是否匹配表达式。

这个类中最主要的两个方法就matches(Class targetClass)matches(Method method),这两个方法分别用于判定目标的类和方法是否匹配expression中的aspectj表达式。

接下来就可以把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已经精准的匹配到了想要的目标。

从零开始实现一个简易的Java MVC框架(一)--前言

从零开始实现一个简易的Java MVC框架(二)--实现Bean容器

从零开始实现一个简易的Java MVC框架(三)--实现IOC

从零开始实现一个简易的Java MVC框架(四)--实现AOP

从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

从零开始实现一个简易的Java MVC框架(六)--加强AOP功能

从零开始实现一个简易的Java MVC框架(七)--实现MVC

从零开始实现一个简易的Java MVC框架(八)--制作Starter

从零开始实现一个简易的Java MVC框架(九)--优化MVC代码

源码地址:doodle

原文地址:从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/71612.html

相关文章

  • 从零开始实现一个简易Java MVC框架(六)--加强AOP功能

    摘要:在前面的文章中实现的功能时,目标类都只能被一个切面代理,如果想要生成第二个代理类,就会把之前的代理类覆盖。改装原有功能现在要改装原来的的实现代码,让的功能加入到框架中为了让切面能够排序,先添加一个注解,用于标记排序。 前言 在前面从零开始实现一个简易的Java MVC框架(四)--实现AOP和从零开始实现一个简易的Java MVC框架(五)--引入aspectj实现AOP切点这两节文章...

    Loong_T 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架

    摘要:不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的框架。原文地址从零开始实现一个简易的框架 前言 最近在看spring-boot框架的源码,看了源码之后更是让我感受到了spring-boot功能的强大。而且使用了很多的设计模式,让人在看的时候觉得有点难以下手。 不过仔细了解了一段时候发现,其实他的原理是很简单的,所以想要自己也动手实现一个功能类似的...

    neuSnail 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(八)--制作Starter

    摘要:服务器相关配置启动类资源目录目录静态文件目录端口号目录目录实现内嵌服务器在上一章文章从零开始实现一个简易的框架七实现已经在文件中引入了依赖,所以这里就不用引用了。 spring-boot的Starter 一个项目总是要有一个启动的地方,当项目部署在tomcat中的时候,经常就会用tomcat的startup.sh(startup.bat)的启动脚本来启动web项目 而在spring-b...

    AprilJ 评论0 收藏0
  • 从零开始实现一个简易Java MVC框架(九)--优化MVC代码

    摘要:前言在从零开始实现一个简易的框架七实现中实现了框架的的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 前言 在从零开始实现一个简易的Java MVC框架(七)--实现MVC中实现了doodle框架的MVC的功能,不过最后指出代码的逻辑不是很好,在这一章节就将这一部分代码进行优化。 优化的目标是1.去除DispatcherServlet请求分发器中的http逻...

    ruicbAndroid 评论0 收藏0
  • Spring之旅第七站:面向切面编程(AOP)

    摘要:面向切面的本章主要内容面向切面编程的基本原理通过创建切面使用注解为切面注入依赖。什么是面向切面编程切面能够帮我们模块化横切关注点。在使用面向切面编程时,我们仍然在一个地方定义通知功能,而无需修改受影响的类。切面切面是通知和切点的结合。 面向切面的Spring 本章主要内容: 面向切面编程的基本原理 通过POJO创建切面 使用@Aspect注解 为AspectJ切面注入依赖。 说明 ...

    赵连江 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<