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

栏目: 编程工具 · 发布时间: 6年前

内容简介:AOP全称是Aspect Oriented Programming,叫做面向切面编程,和面向对象编程(OOP)一样也是一种编程思想,也是spring中一个重要的部分。其实现基于代理模式,对原来的业务进行增强。比如说原来的功能是增删改查,想要不修改源代码的情况下增强原来的功能,那么就可以对原来的业务类生成一个代理的对象,在代理对象中实现方法对原来的业务增强。而代理又分静态代理和动态代理,通常我们都是用动态代理,因为静态代理都是硬编码,不适合拿来用在实现框架这种需求里。在java中通常有两种代理方式,一个是j

AOP全称是Aspect Oriented Programming,叫做面向切面编程,和面向对象编程(OOP)一样也是一种编程思想,也是spring中一个重要的部分。

其实现基于代理模式,对原来的业务进行增强。比如说原来的功能是增删改查,想要不修改源代码的情况下增强原来的功能,那么就可以对原来的业务类生成一个代理的对象,在代理对象中实现方法对原来的业务增强。

而代理又分静态代理和动态代理,通常我们都是用动态代理,因为静态代理都是硬编码,不适合拿来用在实现框架这种需求里。在 java 中通常有两种代理方式,一个是jdk自带的代理,另一个是cglib实现的代理方式,这两个代理各有特点,不大了解的话可以自行查找资料看看。

在spring的底层这两种代理方式都支持,在默认的情况下,如果bean实现了一个接口,spring会使用jdk代理,否则就用cglib代理。

在doodle框架里用了cglib代理的方式,因为这种方式代理的类不用实现接口,实现更灵活

实现准备

在具体实现AOP功能前,先做一些准备。

因为cglib代理不是jdk自带的,所以先在pom.xml引入cglib。

<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容器中。

以上我们最基本的AOP功能就实现了,接下来写个测试用例测试一下

测试用例

在上一篇文章 从零开始实现一个简易的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();
    }
}

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

可以看到在执行 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


以上所述就是小编给大家介绍的《从零开始实现一个简易的Java MVC框架(四)--实现AOP》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

人工智能

人工智能

李开复、王咏刚 / 文化发展出版社 / 2017-5-10 / CNY 55.00

人工智能已经来了,它就在我们身边,几乎无处不在。 人工智能技术正在彻底改变人类的认知,重建人机相互协作的关系。史无前例的自动驾驶正在重构我们头脑中的出行地图和人类生活图景,今天的人工智能技术也正在翻译、写作、绘画等人文和艺术领域进行大胆的尝试。 我们真的知道什么是人工智能吗? 我们真的准备好与人工智能共同发展了吗? 我们该如何在心理上将人和机器摆在正确的位置? 我们该......一起来看看 《人工智能》 这本书的介绍吧!

CSS 压缩/解压工具
CSS 压缩/解压工具

在线压缩/解压 CSS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具