[手写spring](5)实现AOP机制(完结)

目录

目标

自定义注解

自定义JoinPoint

定义存储切面类的集合

定义MethodInfo内部类

解析切面表达式

判断是否为目标方法

初始化aspectClass容器

对切面类进行排序

实现AOP

测试

总结


目标

        我们实现和原生spring相类似的AOP机制,即通过在切面类中的方法上面通过注解指定要切入的位置。当然,使用方法肯定也要和原生的一样,实现主要的功能。


自定义注解

        我们首先需要定义@Aspect注解,这个注解用在类上面,表示是一个切面类

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Aspect {
    String value() default "";
}

        然后定义一个@Order注解,用于指定多个切面的顺序问题

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface Order {
    int value() default Integer.MAX_VALUE;
}

         定义一个@Before和@After注解,用来指定切入的位置,@AfterReturning,@AfterThrowing相类似的,这里就不写了。感兴趣可以自己扩展,基本改个名字就行

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Before {
    String value();
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface After {
    String value();
}

自定义JoinPoint

        我们使用spring的AOP的时候,都知道有一个JoinPoint,通过JoinPoint可以获取到各种信息,我们这里也定义一个JoinPoint,当然,我们自定义的就封装一点简单信息。

public class JoinPoint {

    private Method method;
    private Object[] agrs;
    public String getName(){
        return method.getName();
    }
    public Object[] getArgs(){
        return agrs;
    }

    public JoinPoint(Method method, Object[] agrs) {
        this.method = method;
        this.agrs = agrs;
    }
}

         我们通过JoinPoint可以获取到切面方法的名称已有经方法中的参数


定义存储切面类的集合

        我们定义一个Map来存放切面类,然后再定义一个List来存储切面类的名称。

    private List<String> aspectClassNames;
    private Map<String, Integer> aspectClass;

    //代码块初始化集合
    {
        aspectClassNames = new ArrayList<>();
        aspectClass = new HashMap<>();
    }

         这里我们再定义一个Map来对private,protectd,public进行映射

    private Map<String, Integer> map;

{
    map = new HashMap<>();
    map.put("public", 1);
    map.put("private", 2);
    map.put("protect", 4);
}

定义MethodInfo内部类

        这个类用来表示method的各种信息,如方法修饰符,返回值,参数等等


    /**
     * 一个内部类,用于存储方法的信息
     */
    private class MethodInfo {
        public int modify;
        public String returnType;
        public String methodName;
        public Object[] args;
        public String fullClassName;

        public MethodInfo(int modify, String returnType, String methodName, Object[] args, String fullClassName) {
            this.modify = modify;
            this.returnType = returnType;
            this.methodName = methodName;
            this.args = args;
            this.fullClassName = fullClassName;
        }

    }

解析切面表达式

        我们知道,使用AOP,那么肯定就有切面表达式,切面表达式往往比较复杂,并且还支持正则表达式,这里我们就简化一下,简化为 exection(修饰符 返回类型 方法全路径(参数))的形式,我们这里提供一个解析切面表达式的方法,传入一个切面表达式,返回一个MethodInfo内部类对象。

    protected MethodInfo getMethodInfo(String value) {
        int modify = 0;
        String returnType = null;
        String fullClassName = null;
        String methodName = null;
        String[] methodArgs = new String[0];
        try {
            String s = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")")).trim();
            //将多个空格替换为单个空格
            s = s.replaceAll(" +", " ");
            //按照空格分割
            String[] strings = s.split(" ");
            //得到修饰符
            modify = map.get(strings[0]);
            //得到返回类型
            returnType = strings[1];
            //得到方法全路径
            fullClassName = strings[2].substring(0, strings[2].lastIndexOf("."));
            //得到方法名称
            methodName = strings[2].substring(strings[2].lastIndexOf(".") + 1, strings[2].lastIndexOf("("));
            //得到方法参数
            String substring = s.substring(s.lastIndexOf(methodName) + methodName.length() + 1, s.lastIndexOf(")"));
            methodArgs = substring.split(" *, *");
        } catch (Exception e) {
            throw new RuntimeException(value + "解析有误,请查看切面路径是否正确");
        }
        return new MethodInfo(modify, returnType, methodName, methodArgs, fullClassName);
    }

判断是否为目标方法

        我们通过MethodInfo对象和一个method,判断该method是否为目标方法

    protected boolean isTargetMethod(int modify, String returnType, String name, Object[] paramsType, Method method) {
        //判断方法名是否相等
        if (!method.getName().equals(name)) return false;
        //判断方法修饰符是否一样
        if (method.getModifiers() != modify) return false;
        //判断方法返回值是否相等
        if (!method.getReturnType().getName().equals(returnType)) return false;
        //获取该方法的所有参数
        Class<?>[] parameterTypes = method.getParameterTypes();
        //判断方法参数长度是否相等
        if (parameterTypes.length != paramsType.length) return false;
        //判断顺序和类型是否相同
        for (int i = 0; i < paramsType.length; i++) {
            if (!parameterTypes[i].getName().equals(paramsType[i])) return false;
        }
        return true;
    }

初始化aspectClass容器

        我们在initSingletonObjects方法中对beanDefinitionMap进行遍历的时候,我们也需要初始化aspectClass,如果是切面类就加入Map

//将切面类名字和order存入map中
if (bean.getClass().isAnnotationPresent(Aspect.class)) {
    int order = Integer.MAX_VALUE;
    if (bean.getClass().isAnnotationPresent(Order.class)) {
        order = bean.getClass().getAnnotation(Order.class).value();
    }
    aspectClass.put(name, order);
}

对切面类进行排序

        我们按照切面类的优先级来对切面类进行排序,将优先级高的放入前面,这个是在aspectClass的Map初始化完成后进行的

        //将切面类按照order进行排序,存储进list
        List<Map.Entry<String, Integer>> list = new ArrayList<>(aspectClass.entrySet());
        list.sort((o1, o2) -> -o1.getValue().compareTo(o2.getValue()));
        for (Map.Entry<String, Integer> t : list) {
            aspectClassNames.add(t.getKey());
        }

实现AOP

        经过上面的准备,现在我们已经可以实现AOP机制了,AOP就是通过后置处理器来进行实现的,在后置处理器的postProcessAfterInitialization执行完成后我们就可以进行切面,也就是在我们写的processorAfterMethod中进行。

    protected Object processorAfterMethod(Object o, String beanName) {
        for (String postProcessorName : beanPostProcessorNames) {
            BeanPostProcessor postProcessor = (BeanPostProcessor) singletonObjects.get(postProcessorName);
            Object current = null;
            try {
                current = postProcessor.postProcessAfterInitialization(o, beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
            if (current != null) {
                o = current;
            }
        }
        //进行切面
        //对该对象的所有方法进行遍历
        String targetMethodFullName = o.getClass().getCanonicalName();
        Method[] declaredMethods = o.getClass().getDeclaredMethods();
        for (Method declaredMethod : declaredMethods) {
            //对所有切面类进行遍历
            for (String aspectClassName : aspectClassNames) {
                //对切面类的所有方法进行遍历
                for (Method method : singletonObjects.get(aspectClassName).getClass().getDeclaredMethods()) {
//                    System.out.println(o.getClass().getName());
                    //判断切面类方法是否有@before或者@After注解
                    if (method.isAnnotationPresent(Before.class)) {
                        //获取注解配置的value
                        String value = method.getAnnotation(Before.class).value();
                        //得到要进行切面的方法信息
                        MethodInfo methodInfo = getMethodInfo(value);
                        //判断现在的方法是否就是要进行切面
                        if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
                                methodInfo.methodName, methodInfo.args, declaredMethod) &&
                                methodInfo.fullClassName.equals(targetMethodFullName)) {
                            //临时变量
                            Object proxyObject = o;
                            //返回的代理对象
                            //更新对象
                            o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
                                    if (targetMethod.getName().equals(declaredMethod.getName())) {
                                        //先执行我们定义的@Before的方法
                                        method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
                                    }
                                    //执行目标方法,返回代理对象
                                    return targetMethod.invoke(proxyObject, args);
                                }
                            });
                        }
                    } else if (method.isAnnotationPresent(After.class)) {
                        //获取注解配置的value
                        String value = method.getAnnotation(After.class).value();
                        //得到要进行切面的方法信息
                        MethodInfo methodInfo = getMethodInfo(value);
                        //判断现在的方法是否就是要进行切面
                        if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
                                methodInfo.methodName, methodInfo.args, declaredMethod) &&
                                methodInfo.fullClassName.equals(targetMethodFullName)) {
                            //临时变量
                            Object proxyObject = o;
                            //返回的代理对象
                            //更新对象
                            o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
                                    //执行目标方法,返回代理对象
                                    Object result = targetMethod.invoke(proxyObject, args);
                                    if (targetMethod.getName().equals(declaredMethod.getName())) {
                                        //然后我们定义的@After的方法
                                        method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
                                    }
                                    return result;
                                }
                            });
                        }
                    }
                }
            }
        }
        return o;
    }

         我这里比较暴力,就是直接对当前类的所有方法进行判断是否为切面方法,当然,原生spring肯定不是这样的,但是这里我们就不要陷入算法的泥潭,我们重点是理解spring的机制。

        一个注意点,如果我们的类是切面类,那么就不会执行后置构造器,所有我们需要在执行后置处理器之前加入以下代码,其实以前也加了,只不过只考虑了后置处理器 

//如果是自身就是后置处理器或者是一个切面类,跳过
if (o instanceof BeanPostProcessor || o.getClass().isAnnotationPresent(Aspect.class)) 
     continue;

测试

        创建一个utils包,里面定义一个CalUtils接口,然后再写一个实现类

public interface CalUtils {

    public int add(int a, int b);

    public int sub(int a, int b);
}
@Component("utils")
public class MyCalUtils implements CalUtils{

    @Override
    public int add(int a, int b) {
        System.out.println(a + " + " + b + " = " + (a + b));
        return a + b;
    }

    @Override
    public int sub(int a, int b) {
        System.out.println(a + " - " + b + " = " + (a - b));
        return a - b;
    }
}

        创建一个aspect包,里面写3个切面类,用于测试,内容如下

@Aspect
@Component
@Order(300)
public class MyAspect {

    @Before(value = "execution(public    int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
    public void first(JoinPoint joinPoint) {
        System.out.println("切面方法---->before,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
    public void second(JoinPoint joinPoint) {
        System.out.println("切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    public void thread(JoinPoint joinPoint) {
        System.out.println("[MyAspect1]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }
}
@Aspect
@Component
@Order(100)
public class MyAspect2 {
    
    @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    public void thread(JoinPoint joinPoint) {
        System.out.println("[MyAspect2]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }
}
@Aspect
@Component
@Order(200)
public class MyAspect3 {

    @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    public void thread(JoinPoint joinPoint) {
        System.out.println("[MyAspect3]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }
}

        在上面几个类中,我们分配了不同的优先级,并且对方法进行了切入。

        由于在上一篇文章中我们将包扫描路径改了,所以我们要把路径改回来,并且由于我们创建了一些后置处理器,输出特别多,影响查看,我们直接删掉,所以现在的扫描路径和项目结构如下

@ComponentScan(path = "com.ttpfx.use")
public class ComponentScanPathConfig {
}

         测试类中的代码如下

public class MySpringTest {

    public static void main(String[] args) {
        ApplicationContext ioc = new ApplicationContext(ComponentScanPathConfig.class);
        CalUtils utils = ioc.getBean("utils", CalUtils.class);
        utils.sub(1, 2);
        System.out.println("-----------------------");
        utils.add(1, 2);
    }
}

         控制台输出如下,说明代码没有问题,我们成功实现了AOP


总结

        到这里,我们spring的核心机制,IOC/DI,AOP,都成功实现了,虽然代码不是特别完善,但是我们的基本功能都是没有问题的,其他就是一些细节性的问题,感兴趣可以自己扩展,最后给出我们自己写的spring的代码下载链接

        项目github地址连接


手写spring系列 

[手写spring](1)构建框架,实现包扫描

[手写spring](2)初始化BeanDefinitionMap

[手写spring](3)初始化singletonObjects,实现依赖注入

[手写spring](4)实现后置处理器

[手写spring](5)实现AOP机制(完结)  

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秃头披风侠.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值