第七篇 - 手写Aop(面向切面编程)

第七篇 - 手写Aop(面向切面编程)_第1张图片
第七篇 - 手写Aop(面向切面编程)_第2张图片
Github源码下载地址:https://github.com/chenxingxing6/sourcecode/tree/master/code-springaop


一、前言

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来,通过对这些行为的分离,我们希望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业务逻辑的代码。

Aop相关专业术语:

  1. Aspect(切面): Aspect 声明类似于 Java 中的类声明,在 Aspect 中会包含着一些 Pointcut 以及相应的Advice。
  2. Jointpoint(连接点):表示在程序中明确定义的点,典型的包括方法调用,对类成员的访问以及异常处理程序块的执行等等,它自身还可以嵌套其它joint point。
  3. Pointcut(切点):表示一组 joint point,这些 joint point或是通过逻辑关系组合起来,或是通过通配、正则表达式等方式集中起来,它定义了相应的 Advice 将要发生的地方。
  4. Advice(增强):Advice 定义了在 Pointcut 里面定义的程序点具体要做的操作,它通before、after 和around 来区别是在每个 joint point 之前、之后还是代替执行的代码。 Target(目标对象):织入 Advice的目标对象.。
  5. Weaving(织入):将 Aspect 和其他对象连接起来, 并创建 Adviced object 的过程。

AOP底层使用动态代理实现。包括两种方式:

使用JDK动态代理实现。
使用cglib来实现

通知类型

前置通知:在方法之前执行
后置通知:在方法之后执行
异常通知:方法出现异常执行
最终通知:在后置之后执行
环绕通知:在方法之前和之后执行​​​​​​​


二、自己实现AOP代理(jdk代理)

2.1需求

1.前置通知,后置返回通知,环绕通知,后置通知,异常通知
2.自己实现一个简单版IOC容器
3.通过动态代理 + 通知注解类实现
4.定义切入点,正则表达式 @pointcut

2.2实现思路

服务启动时,将项目中的bean注入到容器里面,对应有切面进行代理的bean,重新生成代理对象,替换原理的对象。

①被代理类;
②被代理类要实现的接口;
③代理类;
④动态创建“代理类的对象”的类;
⑤注解类:
 a. 切面注解类,注解在类上:@Aspect
 b. 各种通知注解,注解在方法上:
   __@Before
   __@AfterReturning
   __@After
   __@AfterThrowing
   __@Around
⑥IOC容器:BeanFactory

2.3项目结构

第七篇 - 手写Aop(面向切面编程)_第3张图片

2.4如何配置,测试项目

本项目配置切面,只支持@MyPointcut方式,支持正则表达式;然后将pointcut放到对应切面通知上。

1.编写Aspect切面
2.测试
只支持jdk动态代理

package com.demo1;

import com.aop.annotation.*;
import com.aop.aspect.AbstractAspect;
import com.aop.aspect.JoinPoint;
import com.ioc.annotation.MyComponent;

/**
 * @Author: cxx
 * @Date: 2019/10/3 11:25
 * 日志切面
 */
@MyAspect
@MyComponent
public class LogAspect extends AbstractAspect{

    @MyPointcut("com.demo1.*")
    private void myPointcut(){
    }

    @MyAround(value = "myPointcut()")
    public Object around(JoinPoint joinPoint){
        Long start = System.currentTimeMillis();
        System.out.println("环绕通知start....");
        Object obj= joinPoint.proceed();
        System.out.println("环绕通知end....");
        Long end = System.currentTimeMillis();
        System.out.println("执行方法耗时:" + String.valueOf(end - start));
        return obj;
    }
}

2.5 核心代码AopBeanContext
package com.aop.core;

import com.aop.aspect.AspectHandler;
import com.aop.aspect.CheckResult;
import com.aop.aspect.IAspectHandler;
import com.ioc.core.MyIoc;
import javafx.util.Pair;

import java.util.HashMap;
import java.util.Map;

/**
 * @Author: cxx
 * @Date: 2019/10/3 13:45
 */
public class AopBeanContext {
    private static IAspectHandler aspectHandler = null;

    static {
        init();
    }

    public static Object getObject(Class clz){
        Object target = MyIoc.getObject(clz);
        if (target == null){
            throw new RuntimeException("容器中获取不到实例");
        }
        Pair<Object, CheckResult> aspectResult = aspectHandler.getAspectInstance(clz.getTypeName());
        // 没有切面
        if (aspectResult == null){
            return target;
        }
        // 创建代理类
        return ProxyBeanFactory.newProxyBean(target, aspectResult);
    }

    private static void init(){
        createProxyBeanContext(MyIoc.getBeanFactory());
    }

    // 根据切点,创建有代理类的bean容器
    public static void createProxyBeanContext(Map<String, Object> iocMap){
        aspectHandler = new AspectHandler(iocMap);
    }
}

2.6 核心代码BeanInvocationHandler
package com.aop.core;

import com.aop.annotation.AdviceEnum;
import com.aop.aspect.AbstractAspect;
import com.aop.aspect.CheckResult;
import com.aop.aspect.JoinPoint;
import javafx.util.Pair;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @Author: cxx
 * @Date: 2019/10/3 15:35
 * @Desc: 环绕通知先不实现
 */
public class BeanInvocationHandler implements InvocationHandler {
    // 目标类
    private Object target;
    // 切面相关信息
    private Pair<Object, CheckResult> acpectResult;

    public BeanInvocationHandler(Object target, Pair<Object, CheckResult> acpectResult) {
        this.target = target;
        this.acpectResult = acpectResult;
    }

    /**
     * 内部类实现环绕通知
     */
    class MyJoinPoint implements JoinPoint{
        private Method method;
        private Object[] args;

        public MyJoinPoint(Method method, Object[] args) {
            this.method = method;
            this.args = args;
        }

        @Override
        public Object proceed() {
            try {
                return method.invoke(target, args);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
            return null;
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 切面类
        AbstractAspect aspcet = (AbstractAspect) acpectResult.getKey();
        // 通知方式
        CheckResult runType = acpectResult.getValue();

        boolean isAround = runType.isRunAround();
        JoinPoint joinPoint = null;
        if (isAround){
            joinPoint = new MyJoinPoint(method, args);
        }
        Object result = null;
        try {
            // 1.前置通知
            if (runType.isRunBefore()){
                runAspectInstance(AdviceEnum.BEFORE, aspcet, args);
            }
            // 2.环绕通知
            if (isAround){
                result = aspcet.around(joinPoint);
            }else {
                result = method.invoke(target, args);
            }
            // 3.返回通知
            if (runType.isRunAfterReturning()){
                runAspectInstance(AdviceEnum.AFTER_RETURNING, aspcet, args, result);
            }
            return result;
        }catch (Exception e){
            // 4.异常通知
            if (runType.isRunAfterThrowing()){
                runAspectInstance(AdviceEnum.AFTER_THROWING, aspcet, args, e);
            }
        }finally {
            // 5.后置通知
            if (runType.isRunAfter()){
                runAspectInstance(AdviceEnum.AFTER, aspcet, args);
            }
        }
        return result;
    }


    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args){
        this.runAspectInstance(adviceEnum, aspect, args, null, null);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Object result){
        this.runAspectInstance(adviceEnum, aspect, args, null, result);
    }

    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e){
        this.runAspectInstance(adviceEnum, aspect, args, e, null);
    }

    /**
     * 执行切面实例
     * @param adviceEnum
     * @param aspect
     * @param args
     */
    private void runAspectInstance(AdviceEnum adviceEnum, AbstractAspect aspect, Object[] args, Throwable e, Object result){
        try {
            switch (adviceEnum){
                case BEFORE:{
                    aspect.before();
                    break;
                }
                case AFTER_RETURNING:{
                    aspect.afterReturning(result);
                    break;
                }
                case AFTER_THROWING:{
                    aspect.afterThrowable(e);
                    break;
                }
                case AFTER:{
                    aspect.after();
                    break;
                }
                default:{
                    break;
                }
            }
        }catch (Exception ee){
            ee.printStackTrace();
        }
    }
}


三、测试结果

package com.aop;

import com.aop.core.AopBeanContext;
import com.demo1.ILogService;
import com.demo1.LogService;

/**
 * @Author: cxx
 * @Date: 2019/10/3 11:34
 */
public class MainTest {
    public static void main(String[] args) {
        //  01.测试 修改ioc.properties scan.package=com.demo
        //IUserService userService = (IUserService) AopBeanContext.getObject(UserService.class);
        //userService.delete("100");

        // 02.环绕通知测试 修改ioc.properties scan.package=com.demo1
        ILogService logService = (ILogService) AopBeanContext.getObject(LogService.class);
        logService.printLog("test....");
    }
}

第七篇 - 手写Aop(面向切面编程)_第4张图片


你可能感兴趣的:(手写源码)