博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从零开始实现一个简易的Java MVC框架(四)--实现AOP
阅读量:6228 次
发布时间:2019-06-21

本文共 7575 字,大约阅读时间需要 25 分钟。

前言

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

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

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

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

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

实现准备

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

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

...
3.2.6
...
cglib
cglib
${cglib.version}

然后在zbw.aop包下创建一个annotation包,然后再创建一个Aspect注解。这个注解是用于标记在''切面''中,即实现代理功能的类上面。

package com.zbw.aop.annotation;import ...;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)public @interface Aspect {    /**     * 目标代理类的范围     */    Class
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@Datapublic 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执行器 */@Slf4jpublic 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
> BEAN_ANNOTATION = Arrays.asList(Component.class, Controller.class, Service.class, Repository.class,Aspect.class);...

测试用例

在上一篇文章中的测试用例的基础上,在实现一个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 ...@Slf4jpublic 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上,只有一个切面能生效,这也不合理。

所以在后面的章节会完善实现这两个问题。


源码地址:

原文地址:

转载地址:http://ktnna.baihongyu.com/

你可能感兴趣的文章
Flymeos插桩适配教程
查看>>
大端模式与小端模式、网络字节顺序与主机字节顺序
查看>>
微信支付申请90%的商户都卡在这儿了,申请微信支付,商户功能设置详细说明...
查看>>
高仿Instagram 页面效果android特效
查看>>
2016 年总结
查看>>
将String转化成Stream,将Stream转换成String
查看>>
【工具使用系列】关于 MATLAB 遗传算法与直接搜索工具箱,你需要知道的事
查看>>
Kali-linux Arpspoof工具
查看>>
PDF文档页面如何重新排版?
查看>>
基于http协议使用protobuf进行前后端交互
查看>>
bash腳本編程之三 条件判断及算数运算
查看>>
php cookie
查看>>
linux下redis安装
查看>>
弃 Java 而使用 Kotlin 的你后悔了吗?| kotlin将会是最好的开发语言
查看>>
JavaScript 数据类型
查看>>
量子通信和大数据最有市场突破前景
查看>>
对‘初学者应该选择哪种编程语言’的回答——计算机达人成长之路(38)
查看>>
如何申请开通微信多客服功能
查看>>
Sr_C++_Engineer_(LBS_Engine@Global Map Dept.)
查看>>
非监督学习算法:异常检测
查看>>