模拟实现 Spring AOP

前言

Spring 是一种 Java 开发框架,其主要功能有两个:IoC(DI)和AOP。《模拟实现Spring AOP》是本人的一个编程训练项目,为了提升本人的编程能力、JAVA 编程思想,基于框架的角度出发,对 Spring AOP有一个更深层次的认识,动态代理模式的底层实现逻辑有更深的理解。

博主本人初入 Java 不久,能力有限,只将 Spring AOP完成到:实现了基于代理机制的拦截器链以及基于正则的配置;

Spring AOP

AOP(面向切面编程),通过预编译方式和运行期动态代理来实现程序功能的统一与扩展的技术。

Spring AOP 概述

AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,它的主要目的是将通用功能从业务逻辑中抽离出来,以达到代码复用和模块化的目的。AOP的基本概念包括:

  • 切面(Aspect):切面是一个模块化的关注点,通常包含一个或多个通知(Advice)和切入点(Pointcut)的组合。切面的作用是将横切关注点模块化,以便于重用和维护。
  • 通知(Advice):通知是切面中执行的具体操作。通知有五种类型:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  • 切入点(Pointcut):切入点是一个表达式,用于定义在哪些方法上应用通知。切入点可以使用通配符匹配方法名、参数类型和返回值类型。
  • 连接点(JoinPoint):连接点是程序执行过程中的某个特定的点,例如方法执行、异常抛出等。切面可以在连接点附近织入通知。
  • 织入(Weaving):织入是将切面代码插入到目标类中的过程。织入可以在编译期(Compile-time)、类加载期(Load-time)或运行期(Runtime)完成。

AOP可以对业务逻辑部分进行隔离,从而使业务逻辑耦合降低,提高代码复用和开发效率。是基于动态代理实现的,如果目标对象实现了接口,就用 JDK 动态代理,未实现接口就用 CGLIB 动态代理。通过 AOP 技术,在不修改源代码的情况下,为程序添加了新的功能,是对程序的非侵入式扩展。

Spring IoC 技术难点

  • 拦截器的实现(代理模式的应用)
  • 基于正则的配置

Spring IoC 框架思考

需求分析

  • 代理对象的获取

    • JDKProxy 代理模式实现

      public class JDKProxy {
      	private static IntercepterChain chain;
      
      	public JDKProxy() {
      	}
      	
      	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
      		JDKProxy.chain.addIntercepter(targetMethod, intercepter);
      	}
      
      	public <T> T getProxy(Object object) {
      		Class<?> klass = object.getClass();
      		ClassLoader loader = klass.getClassLoader();
      		Class<?>[] interfaces = klass.getInterfaces();
      		
      		InvocationHandler h = new InvocationHandler() {
      			@Override
      			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      				return JDKProxy.chain.doInvoker(object, method, args);
      			}
      		};
      		
      		@SuppressWarnings("unchecked")
      		T proxy = (T) Proxy.newProxyInstance(loader, interfaces, h);
      		
      		return proxy;
      	}
      }
      
    • CGLibProxy 代理模式实现

      public class CGLibProxy {
      
      	public CGLibProxy() {
      	}
      
      	@SuppressWarnings("unchecked")
      	public <T> T getProxy(Object object) {
      		Class<?> klass = object.getClass();
      		Enhancer enhancer = new Enhancer();
      		enhancer.setSuperclass(klass);
      		
      		enhancer.setCallback(new InvocationHandler() {
      			@Override
      			public Object invoke(Object obj, Method method, Object[] args) throws Throwable {
      				return null;
      			}
      		});
      		
      		return (T) enhancer.create();
      	}
      }
      
  • 切点的匹配以及拦截器链的实现

  • 对代理对象执行方法的参数以及结果的获取与处理

Spring IoC 技术难点实现

  • 拦截器的实现(代理模式的应用)

    拦截器:是一种可以在方法执行前、后、异常发生时执行的逻辑,并且,这些逻辑可以“堆叠”,形成拦截器链,或者,拦截器堆栈。

    可以通过“增加拦截器”或者“删除拦截器”的方式,对拦截器进行编辑;拦截器应该是针对某个、某些方法的。

    无论如何,拦截器技术是基于代理机制的。那么,必须提供“取得代理对象”的方法。

    前置拦截器:前置拦截器可以决定是否继续执行目标方法,也就是说,前置拦截器应该允许返回逻辑值,以确定是否继续执行目标方法;而且,对于由多个拦截器组成的拦截器链,任何一个拦截器返回值若为 false,则,所有其后的前置拦截器,以及方法本身,都不再执行!这可以称为:方法的中断。

    通常情况下,前置拦截器按照拦截器添加顺序执行(即,队列模式————先进先服务);后置拦截器按照添加逆序执行(即,堆栈模式————先进后服务)。

    public class IntercepterChain {
    	private Intercepter intercepter;
    	private IntercepterChain next;
    	
    	public IntercepterChain() {
    		this.intercepter = null;
    		this.next = null;
    	}
    	//增加拦截器
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		if (this.intercepter == null) {
    			this.intercepter = new Intercepter(targetMethod, intercepter);
    			return;
    		}
    		
    		if (this.next == null) {
    			this.next = new IntercepterChain();
    		}
    		this.next.addIntercepter(targetMethod, intercepter);
    	}
    	//反射执行方法
    	public Object doInvoker(Object object, Method method, Object[] args) throws Throwable {
    		Object result = null;
    		
    		if (this.intercepter == null) {
    			return method.invoke(object, args);
    		}
    		
    		if (this.intercepter.before(method, args)) {
    			if (this.next != null) {
    				result = this.next.doInvoker(object, method, args);
    			}
    			
    			if (this.next == null) {
    				result = method.invoke(object, args);
    			}
    			
    			result = this.intercepter.after(method, result);		
    		}	
    		return result;
    	}
    }
    
  • 基于正则的配置

    由于想匹配到切点就必须提供方法的全类名以及参数的全类名,对于使用者来说很不方便,采用正则表达式来处理匹配切点。用户只需要提供一个普通的类名、或方法名及参数,对于其他的不确定的地方使用‘*’来代替,当用户调用到添加拦截器时,对于用户传入的简单切点名进行转化,MethodInvoker 类中提供了处理用户提交的普通的类名与参数名的 toRegex 方法,此方法把简单的字符串转换为正则表达式,从而找到符合的切面的切点。完成切点的匹配。

    /**
         * 用户提供切点名,*.ClassName.methodName(*)
         * 例:*.MyInterface.*(*String,*int) 表示任意长度的前一个字符,前面必须有字符
         *  故 .* 表示任意字符
         *  用户只需输入简单的参数以及方法名,不确定的地方用*代替
         *  .*IMyInterface..*\(.*String,.*int\)
         *
         */
        private String toRegex(String methodName) {
            StringBuffer stringBuffer = new StringBuffer();
            //将不确定的地方替换为.*表示匹配任意字符
            String buffer = methodName.replace("*", ".*");
     
            int left =buffer.indexOf("(");
            int right = buffer.indexOf(")");
            //得到类名与方法名
            stringBuffer.append(".*").append(buffer.substring(0, left)).append("\\(");
     
            //按照逗号分割出参数字符串数组
            String[] args = buffer.substring(left + 1, right).split(",");
            for(int i = 0; i < args.length; i++) {
                stringBuffer.append((i == 0 ? "" : ",")).append(".*").append(args[i]);
            }
            stringBuffer.append("\\)");
            return stringBuffer.toString();
        }
    

模拟实现 AOP 具体代码

为了在目标方法前后执行新的方法,且不对源代码进行修改,所以要用动态代理,让代理去执行目标方法,然后在执行方法前进行前置拦截,在执行方法后进行后置拦截。

  • JDK 动态代理

    public class JDKProxy {
    	private static IntercepterChain chain;
    
    	public JDKProxy() {
    	}
    	
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		JDKProxy.chain.addIntercepter(targetMethod, intercepter);
    	}
    
    	public <T> T getProxy(Object object) {
    		Class<?> klass = object.getClass();
    		ClassLoader loader = klass.getClassLoader();
    		Class<?>[] interfaces = klass.getInterfaces();
    		
    		InvocationHandler h = new InvocationHandler() {
    			@Override
    			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    				return JDKProxy.chain.doInvoker(object, method, args);
    			}
    		};
    		
    		@SuppressWarnings("unchecked")
    		T proxy = (T) Proxy.newProxyInstance(loader, interfaces, h);
    		
    		return proxy;
    	}
    }
    
  • Result 类

    public class Result {
    	private static Object result;
    	
    	Result() {
    	}
    	……getter、setter
    }
    
  • 拦截器链实现

    public class IntercepterChain {
    	private Intercepter intercepter;
    	private IntercepterChain next;
    	
    	public IntercepterChain() {
    		this.intercepter = null;
    		this.next = null;
    	}
    	//增加拦截器
    	public void addIntercepter(String targetMethod, IIntercepter intercepter) {
    		if (this.intercepter == null) {
    			this.intercepter = new Intercepter(targetMethod, intercepter);
    			return;
    		}
    		
    		if (this.next == null) {
    			this.next = new IntercepterChain();
    		}
    		this.next.addIntercepter(targetMethod, intercepter);
    	}
    	//反射执行方法
    	public Object doInvoker(Object object, Method method, Object[] args) throws Throwable {
    		Object result = null;
    		
    		if (this.intercepter == null) {
    			return method.invoke(object, args);
    		}
    		
    		if (this.intercepter.before(method, args)) {
    			if (this.next != null) {
    				result = this.next.doInvoker(object, method, args);
    			}
    			
    			if (this.next == null) {
    				result = method.invoke(object, args);
    			}
    			
    			result = this.intercepter.after(method, result);		
    		}	
    		return result;
    	}
    }
    
    
  • 接口

    public interface IIntercepter {
    	boolean before();	//前置拦截
    	void after();		//后置拦截
    }
    
  • 拦截器

    //获取代理对象,并且在before()内部采用正则表达式来有选择的进行拦截处理。
    //当before()方法返回值为true时,则可以正常执行目标方法,否则不执行。
    public class Intercepter {
    	private String targetMethod;		//拦截目标
    	private IIntercepter intercepter;	
    	
    	Intercepter(String targetMethod, IIntercepter intercepter) {
    		this.targetMethod = targetMethod;
    		this.intercepter = intercepter;
    	}
    	//前置拦截:如果返回值为false则为不允许执行下面的方法
    	boolean before(Method method, Object[] args) {
    		// 正则,TODO
    		if (!method.toString().equals(this.targetMethod)) {
    			return true;
    		}
    		
    		Arguments.setArgs(args);
    		return this.intercepter.before();
    	}
    	后置拦截主要针对于方法执行的结果
    	Object after(Method method, Object result) {
    		// 正则,TODO
    		if (!method.toString().equals(this.targetMethod)) {
    			return result;
    		}
    		
    		Result.setResult(result);
    		this.intercepter.after();
    		
    		return Result.getResult();
    	}	
    }
    

这里只是作为本人训练项目,简单的实现了 Spring AOP 中的核心功能,还需要进行更深入的学习相关知识,有问题处望大佬指出。

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
首先,我们需要定义一个自定义注解 `@RequiresPermissions`,用于标识需要授权访问的方法,例如: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiresPermissions { String[] value(); // 权限值 } ``` 然后,我们需要实现一个切面,用于拦截被 `@RequiresPermissions` 标识的方法,并进行权限校验,例如: ```java @Component @Aspect public class PermissionCheckAspect { @Autowired private AuthService authService; @Around("@annotation(requiresPermissions)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions) throws Throwable { // 获取当前用户 User user = authService.getCurrentUser(); if (user == null) { throw new UnauthorizedException("用户未登录"); } // 获取当前用户的权限列表 List<String> permissions = authService.getUserPermissions(user); // 校验权限 for (String permission : requiresPermissions.value()) { if (!permissions.contains(permission)) { throw new ForbiddenException("没有访问权限:" + permission); } } // 执行目标方法 return joinPoint.proceed(); } } ``` 在切面中,我们首先通过 `AuthService` 获取当前用户及其权限列表,然后校验当前用户是否拥有被 `@RequiresPermissions` 标识的方法所需的所有权限,如果没有则抛出 `ForbiddenException` 异常,如果有则继续执行目标方法。 最后,我们需要在 Spring 配置文件中启用 AOP 自动代理,并扫描切面所在的包,例如: ```xml <aop:aspectj-autoproxy /> <context:component-scan base-package="com.example.aspect" /> ``` 这样,我们就通过 Spring AOP 和自定义注解模拟实现了类似 Shiro 权限校验的功能。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

HB0o0

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

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

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

打赏作者

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

抵扣说明:

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

余额充值