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


    目录

    目标

    自定义注解

    自定义JoinPoint

    定义存储切面类的集合

    定义MethodInfo内部类

    解析切面表达式

    判断是否为目标方法

    初始化aspectClass容器

    对切面类进行排序

    实现AOP

    测试

    总结


    目标

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


    自定义注解

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

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

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

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.TYPE})
    3. @Documented
    4. public @interface Order {
    5. int value() default Integer.MAX_VALUE;
    6. }

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

    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.METHOD})
    3. public @interface Before {
    4. String value();
    5. }
    1. @Retention(RetentionPolicy.RUNTIME)
    2. @Target({ElementType.METHOD})
    3. public @interface After {
    4. String value();
    5. }

    自定义JoinPoint

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

    1. public class JoinPoint {
    2. private Method method;
    3. private Object[] agrs;
    4. public String getName(){
    5. return method.getName();
    6. }
    7. public Object[] getArgs(){
    8. return agrs;
    9. }
    10. public JoinPoint(Method method, Object[] agrs) {
    11. this.method = method;
    12. this.agrs = agrs;
    13. }
    14. }

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


    定义存储切面类的集合

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

    1. private List<String> aspectClassNames;
    2. private Map<String, Integer> aspectClass;
    3. //代码块初始化集合
    4. {
    5. aspectClassNames = new ArrayList<>();
    6. aspectClass = new HashMap<>();
    7. }

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

    1. private Map<String, Integer> map;
    2. {
    3. map = new HashMap<>();
    4. map.put("public", 1);
    5. map.put("private", 2);
    6. map.put("protect", 4);
    7. }

    定义MethodInfo内部类

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

    1. /**
    2. * 一个内部类,用于存储方法的信息
    3. */
    4. private class MethodInfo {
    5. public int modify;
    6. public String returnType;
    7. public String methodName;
    8. public Object[] args;
    9. public String fullClassName;
    10. public MethodInfo(int modify, String returnType, String methodName, Object[] args, String fullClassName) {
    11. this.modify = modify;
    12. this.returnType = returnType;
    13. this.methodName = methodName;
    14. this.args = args;
    15. this.fullClassName = fullClassName;
    16. }
    17. }

    解析切面表达式

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

    1. protected MethodInfo getMethodInfo(String value) {
    2. int modify = 0;
    3. String returnType = null;
    4. String fullClassName = null;
    5. String methodName = null;
    6. String[] methodArgs = new String[0];
    7. try {
    8. String s = value.substring(value.indexOf("(") + 1, value.lastIndexOf(")")).trim();
    9. //将多个空格替换为单个空格
    10. s = s.replaceAll(" +", " ");
    11. //按照空格分割
    12. String[] strings = s.split(" ");
    13. //得到修饰符
    14. modify = map.get(strings[0]);
    15. //得到返回类型
    16. returnType = strings[1];
    17. //得到方法全路径
    18. fullClassName = strings[2].substring(0, strings[2].lastIndexOf("."));
    19. //得到方法名称
    20. methodName = strings[2].substring(strings[2].lastIndexOf(".") + 1, strings[2].lastIndexOf("("));
    21. //得到方法参数
    22. String substring = s.substring(s.lastIndexOf(methodName) + methodName.length() + 1, s.lastIndexOf(")"));
    23. methodArgs = substring.split(" *, *");
    24. } catch (Exception e) {
    25. throw new RuntimeException(value + "解析有误,请查看切面路径是否正确");
    26. }
    27. return new MethodInfo(modify, returnType, methodName, methodArgs, fullClassName);
    28. }

    判断是否为目标方法

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

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

    初始化aspectClass容器

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

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

    对切面类进行排序

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

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

    实现AOP

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

    1. protected Object processorAfterMethod(Object o, String beanName) {
    2. for (String postProcessorName : beanPostProcessorNames) {
    3. BeanPostProcessor postProcessor = (BeanPostProcessor) singletonObjects.get(postProcessorName);
    4. Object current = null;
    5. try {
    6. current = postProcessor.postProcessAfterInitialization(o, beanName);
    7. } catch (Exception e) {
    8. e.printStackTrace();
    9. }
    10. if (current != null) {
    11. o = current;
    12. }
    13. }
    14. //进行切面
    15. //对该对象的所有方法进行遍历
    16. String targetMethodFullName = o.getClass().getCanonicalName();
    17. Method[] declaredMethods = o.getClass().getDeclaredMethods();
    18. for (Method declaredMethod : declaredMethods) {
    19. //对所有切面类进行遍历
    20. for (String aspectClassName : aspectClassNames) {
    21. //对切面类的所有方法进行遍历
    22. for (Method method : singletonObjects.get(aspectClassName).getClass().getDeclaredMethods()) {
    23. // System.out.println(o.getClass().getName());
    24. //判断切面类方法是否有@before或者@After注解
    25. if (method.isAnnotationPresent(Before.class)) {
    26. //获取注解配置的value
    27. String value = method.getAnnotation(Before.class).value();
    28. //得到要进行切面的方法信息
    29. MethodInfo methodInfo = getMethodInfo(value);
    30. //判断现在的方法是否就是要进行切面
    31. if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
    32. methodInfo.methodName, methodInfo.args, declaredMethod) &&
    33. methodInfo.fullClassName.equals(targetMethodFullName)) {
    34. //临时变量
    35. Object proxyObject = o;
    36. //返回的代理对象
    37. //更新对象
    38. o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
    39. @Override
    40. public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
    41. if (targetMethod.getName().equals(declaredMethod.getName())) {
    42. //先执行我们定义的@Before的方法
    43. method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
    44. }
    45. //执行目标方法,返回代理对象
    46. return targetMethod.invoke(proxyObject, args);
    47. }
    48. });
    49. }
    50. } else if (method.isAnnotationPresent(After.class)) {
    51. //获取注解配置的value
    52. String value = method.getAnnotation(After.class).value();
    53. //得到要进行切面的方法信息
    54. MethodInfo methodInfo = getMethodInfo(value);
    55. //判断现在的方法是否就是要进行切面
    56. if (isTargetMethod(methodInfo.modify, methodInfo.returnType,
    57. methodInfo.methodName, methodInfo.args, declaredMethod) &&
    58. methodInfo.fullClassName.equals(targetMethodFullName)) {
    59. //临时变量
    60. Object proxyObject = o;
    61. //返回的代理对象
    62. //更新对象
    63. o = Proxy.newProxyInstance(o.getClass().getClassLoader(), o.getClass().getInterfaces(), new InvocationHandler() {
    64. @Override
    65. public Object invoke(Object proxy, Method targetMethod, Object[] args) throws Throwable {
    66. //执行目标方法,返回代理对象
    67. Object result = targetMethod.invoke(proxyObject, args);
    68. if (targetMethod.getName().equals(declaredMethod.getName())) {
    69. //然后我们定义的@After的方法
    70. method.invoke(singletonObjects.get(aspectClassName), new JoinPoint(targetMethod, args));
    71. }
    72. return result;
    73. }
    74. });
    75. }
    76. }
    77. }
    78. }
    79. }
    80. return o;
    81. }

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

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

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

    测试

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

    1. public interface CalUtils {
    2. public int add(int a, int b);
    3. public int sub(int a, int b);
    4. }
    1. @Component("utils")
    2. public class MyCalUtils implements CalUtils{
    3. @Override
    4. public int add(int a, int b) {
    5. System.out.println(a + " + " + b + " = " + (a + b));
    6. return a + b;
    7. }
    8. @Override
    9. public int sub(int a, int b) {
    10. System.out.println(a + " - " + b + " = " + (a - b));
    11. return a - b;
    12. }
    13. }

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

    1. @Aspect
    2. @Component
    3. @Order(300)
    4. public class MyAspect {
    5. @Before(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
    6. public void first(JoinPoint joinPoint) {
    7. System.out.println("切面方法---->before,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    8. }
    9. @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.add(int, int))")
    10. public void second(JoinPoint joinPoint) {
    11. System.out.println("切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    12. }
    13. @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    14. public void thread(JoinPoint joinPoint) {
    15. System.out.println("[MyAspect1]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    16. }
    17. }
    1. @Aspect
    2. @Component
    3. @Order(100)
    4. public class MyAspect2 {
    5. @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    6. public void thread(JoinPoint joinPoint) {
    7. System.out.println("[MyAspect2]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    8. }
    9. }
    1. @Aspect
    2. @Component
    3. @Order(200)
    4. public class MyAspect3 {
    5. @After(value = "execution(public int com.ttpfx.use.utils.MyCalUtils.sub(int, int))")
    6. public void thread(JoinPoint joinPoint) {
    7. System.out.println("[MyAspect3]切面方法---->after,切入方法名:" + joinPoint.getName() + " 目标方法参数:" + Arrays.toString(joinPoint.getArgs()));
    8. }
    9. }

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

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

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

             测试类中的代码如下

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

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


    总结

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

            项目github地址连接


    手写spring系列 

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

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

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

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

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

  • 相关阅读:
    《深度学习推荐系统》王喆 笔记
    单线双线多线服务器有哪些区别
    调研:huggingface-diffusers
    Java网络编程——基本网络支持
    搭建LNMP环境并配置个人博客系统
    mysql和clickhouse数据同步 MaterializeMySQL 引擎
    vue+elementUI
    Docker搭建MusicBrainz
    【工具篇】Unity翻书效果的三种方式
    仪器仪表制造业采购数字化方案:集中采购系统为供采双方打造更高效运转平台
  • 原文地址:https://blog.csdn.net/m0_51545690/article/details/125549292