手写Spring框架第五天

本文详细介绍了如何在Spring框架中实现AOP(面向切面编程),包括自定义注解、execution表达式的使用、修改ApplicationContext以管理切面类,以及如何在Bean后处理器的特定时机执行切面方法。

目录

自定义注解

execution表达式

修改ApplicationContext类

编写切面类

测试 

出现的问题

代码地址


本文实现Spring的AOP机制

自定义注解

首先我们创建出AOP需要使用到的注解

  • @Aspect:添加在类上,意为标记该类为切面类
  • @Order:用来处理多个切面类的顺序问题
  • @Before、@After等:添加在方法上,用来告诉该方法的扩展在何时执行
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAspect {
    String value() default "";
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyOrder {
    int value() default Integer.MAX_VALUE;
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAfter {
    String value();
}

在Spring的AOP机制中,需要一个@Pointcut注解来指定切入点,我们的实现方式没有添加该注解,直接在Before与After中直接指定需要扩展的方法位置。这里我们采用的是execution表达式来指定方法位置。

execution表达式

execution表达式的使用方法与正则表达式差别不大。使用方式为

execution="([修饰符]  [返回结果类型]  [类全路径地址或是能指定全局唯一性].方法名.(方法参数))"

具体使用方法我准备另开一篇文章,不在此赘述。

修改ApplicationContext类

我们需要将切面类除了存储单例池之外,还需要存储在一个新的集合中,方便后续对该集合进行处理。

添加两个新的集合

    //用来存放切面类名,并解决顺序处理问题
    private List<String> aspectClassNames;
    //用来存放切面类
    private Map<String, Integer> aspectClass;

    {
        aspectClassNames = new ArrayList<>();
        aspectClass = new HashMap<>();
    }

 接下来需要创建一个内部类用来存储execution表达式的信息,方便后续匹配需要扩展的方法

    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;
        }
    }

 这里修饰符之所以使用int类型而不是String类型是因为在Java中的Method类中,也是使用int来表示修饰符。

因此再添加一个Map集合来映射修饰符

    private Map<String, Integer> map;

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

接下来我们需要一个解析execution表达式的方法,这里我们采用最简单的指定到具体的方法 ,无法解析*或是..等符号的含义,将解析出来的表达式保存在MethodInfo中,后续需要将bean中的方法对其进行对比。

    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(")"));
            if (!("").equals(substring)) {
                methodArgs = substring.split(" *, *");
            }


        } catch (Exception e) {
            throw new RuntimeException(value + "解析有误,请查看切面路径是否正确");
        }
        return new MethodInfo(modify, returnType, methodName, methodArgs, fullClassName);
    }

提供一个判断bean中的方法是否与MethodInfo中的保存的信息相同的方法

    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;
    }

AOP的切入时机为Bean后处理器的后方法执行完毕后,因此在executeBeanPostAfter方法中,在最后添加切入代码。

        /**
         * 当执行完Bean后处理器的后方法后,对切面方法进行扩展
         * 暴力匹配需要对哪些方法进行扩展
         */
        Method[] declaredMethods = o.getClass().getDeclaredMethods();
        //对该bean的所有方法进行匹配,是否存在切面类中的符合表达式的方法
        for (Method declaredMethod : declaredMethods) {
            //遍历所有的切面类
            for (String aspectClassName : aspectClassNames) {
                Object o1 = singletonObjects.get(aspectClassName);
                //遍历切面类的所有切面方法
                for (Method method : o1.getClass().getDeclaredMethods()) {
                    //如果存在@MyBefore注解
                    if (method.isAnnotationPresent(MyBefore.class)) {
                        String value = method.getAnnotation(MyBefore.class).value();
                        //解析execution表达式将方法信息存储在MethodInfo中
                        MethodInfo methodInfo = getMethodInfo(value);
                        //将切面方法的的匹配信息与Bean方法的信息进行对比
                        if (isTargetMethod(methodInfo.modify, methodInfo.returnType, methodInfo.methodName, methodInfo.args, declaredMethod)) {
                            System.out.println("存在一个目标方法" + methodInfo.methodName);
                            //如果有匹配的方法那么对该bean对象进行扩建后返回
                            Object proxyObject = o;
                            System.out.println(proxyObject.getClass().getName()+"这是没代理之前的proxyObject");
                            o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
                                @Override
                                public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
                                    //先执行Before注解的方法
                                    if (targetMethod.getName().equals(declaredMethod.getName())) {//必须要加该判断条件
                                        method.invoke(o1, new JoinPoint(targetMethod, args));
                                    }
                                    return targetMethod.invoke(proxyObject, args);
                                }
                            });
                        }
                    } else if (method.isAnnotationPresent(MyAfter.class)) {
                        String value = method.getAnnotation(MyAfter.class).value();
                        MethodInfo methodInfo = getMethodInfo(value);
                        //将切面方法的的匹配信息与Bean方法的信息进行对比
                        if (isTargetMethod(methodInfo.modify, methodInfo.returnType, methodInfo.methodName, methodInfo.args, declaredMethod)) {
                            System.out.println("存在一个目标方法" + methodInfo.methodName);

                            //如果有匹配的方法那么对该bean对象进行扩建后返回
                            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 {
                                    //先执行After注解的方法
                                    Object result = targetMethod.invoke(proxyObject, args);
                                    if (targetMethod.getName().equals(declaredMethod.getName())) {
                                        method.invoke(o1, new JoinPoint(targetMethod, args));
                                    }
                                    return result;
                                }
                            });
                        }
                    }
                }

            }
        }

 还需要注意的问题就是,切面类不需要经理Bean后处理器处理,因此需要在Bean后处理器的前后执行方法中添加过滤代码

        if (o.getClass().isAnnotationPresent(MyAspect.class)) {
            return o;
        }

编写切面类

@MyAspect
@MyComponent
@MyOrder(100)
public class MyAspect1 {
    @MyBefore(value = "execution(public    void com.zmt.test.Lisi.method1(int))")
    public void first(JoinPoint joinPoint) {
        System.out.println("切面方法---->before,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    //execution((public    void com..*.method*(..))
    @MyAfter(value = "execution(public    int com.zmt.test.Lisi.method2())")
    public void second(JoinPoint joinPoint) {
        System.out.println("切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }

    @MyBefore(value = "execution(public    int com.zmt.test.Lisi.method2())")
    public void thread(JoinPoint joinPoint) {
        System.out.println("[MyAspect1]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }
}
@MyAspect
@MyComponent
@MyOrder(101)
public class MyAspect2 {
    @MyBefore(value = "execution(public    int com.zmt.test.Lisi.method2())")
    public void thread(JoinPoint joinPoint) {
        System.out.println("[MyAspect2]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    }
}

测试 

接下来创建一个接口与实现类来测试切面类中的切面方法是否执行

public interface Student {
    void method1(int a);

    int method2();
}
@MyComponent
public class Lisi implements Student{

    @Override
    public void method1(int a) {
        System.out.println("李四执行了方法1");
    }

    @Override
    public int method2() {
        System.out.println("李四执行了方法2");
        return 0;
    }
}
public class AspectTest {
    public static void main(String[] args) throws Exception {
        SpringApplicationContext4 springApplicationContext4 = new SpringApplicationContext4(MySpringConfig.class);
        Student lisi = springApplicationContext4.getBean("lisi", Student.class);
        lisi.method1(1);
        lisi.method2();
    }
}

根据结果可以看出,MyAspect1先执行其次再执行MyAspect2。 

出现的问题

        我第一次测试时,使用的是上一篇文章中的People类中的方法进行切面,在创建出代理对象后无法强转为People类。这是因为People类并没有实现任何接口所导致的。

        代理对象是根据原对象的类加载器与原对象的接口反射出一个新的对象,由于People没有实现接口,那么只能强转为默认的Object类型。后来使用了Lisi类实现了Student接口,那么代理类也实现了该接口,自然可以强转为Student类型。

代码地址

gitee:spring_demo01 · zmbwcx/Spring简单实现 - 码云 - 开源中国 (gitee.com)

评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zmbwcx2003

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

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

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

打赏作者

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

抵扣说明:

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

余额充值