当前位置: 首页 > news >正文

04-spring-手写spring-demo-aop0V1

1 目标

简单实现spring的aop(面向切面的管理);在符合某一类特征的方法执行前、执行后、发生异常后去执行某些操作。本文主要是以输出日志的方式做这块的demo。

2 代码

代码分支
https://gitee.com/huyanqiu6666/customize-spring.git20250812-springV2.0-aop-simple

2 aop使用

@Aspect声明一个切面
@Pointcut声明连接点的表达式
@Before前置通知,方法执行前调用
@After后置通知,方法执行后执行(无论是否异常)
@AfterReturning返回通知:方法正常返回后执行
@AfterThrowing异常通知:方法抛出异常后执行
@Around环绕通知:可以在方法执行前后自定义处理,且能控制方法是否执行

package com.example.aspect;import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import java.util.Arrays;/*** 日志切面类,实现对UserService的增强*/
@Aspect
@Component
public class LogAspect {// 定义切点:匹配UserService接口的所有方法@Pointcut("execution(* com.example.service.UserService.*(..))")public void userServicePointcut() {}// 前置通知:方法执行前执行@Before("userServicePointcut()")public void logBefore(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("[前置通知] 方法: " + methodName + ", 参数: " + Arrays.toString(args));}// 后置通知:方法执行后执行(无论是否异常)@After("userServicePointcut()")public void logAfter(JoinPoint joinPoint) {String methodName = joinPoint.getSignature().getName();System.out.println("[后置通知] 方法: " + methodName + " 执行结束");}// 返回通知:方法正常返回后执行@AfterReturning(value = "userServicePointcut()", returning = "result")public void logAfterReturning(JoinPoint joinPoint, Object result) {String methodName = joinPoint.getSignature().getName();System.out.println("[返回通知] 方法: " + methodName + ", 返回值: " + result);}// 异常通知:方法抛出异常后执行@AfterThrowing(value = "userServicePointcut()", throwing = "ex")public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {String methodName = joinPoint.getSignature().getName();System.out.println("[异常通知] 方法: " + methodName + ", 异常: " + ex.getMessage());}// 环绕通知:可以在方法执行前后自定义处理,且能控制方法是否执行@Around("userServicePointcut()")public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {String methodName = joinPoint.getSignature().getName();long startTime = System.currentTimeMillis();System.out.println("[环绕通知开始] 方法: " + methodName + ", 开始执行");// 执行目标方法Object result = joinPoint.proceed();long endTime = System.currentTimeMillis();System.out.println("[环绕通知结束] 方法: " + methodName + ", 执行耗时: " + (endTime - startTime) + "ms");return result;}
}

3 手写aop实现思路

大致流程如下:在创建对象(instantiateBean)的时候获取到aop的配置信息,然后判断当前类下的方法是否符合连接点规则,负责则创建代理对象,否则就创建原生对象。为了后期调用方便事先存储好对象方法和切面方法的对应关系。

4 重点类介绍

类名描述
BonnieApplicationContextSpring ioc容器上下文,aop入口instantiateBean方法下
BonnieAdvisedSupport工具类,缓存aop配置,创建对象的实例以及类,缓存实例方法和切面方法的对应关系
BonnieDefaultAopProxyFactory代理对象的工厂,策略,有接口则走jdk代理否则走cglib代理
BonnieAopProxy接口,代理类,getProxy
BonnieJdkDynamicAopProxyjdk动态代理实现,具体调用时在这块加强
BonnieCglibAopProxycglib动态代理
BonnieAdvice包含切面方法、切面实例,异常名称等属性,缓存切面方法的对象
BonnieAopConfigaop在properties的配置对象

5 代码

5.1 aop配置

# 包扫描路径
componentScan=com.bonnie#配置一个简单的切面, service包下的任意类
pointCut=service
#切面类
aspectClass=com.bonnie.aop.LogAspect
#前置通知回调方法
aspectBefore=before
#后置通知回调方法
aspectAfter=after
#异常通知回调方法
aspectAfterThrow=afterThrowing
#异常类型捕获
aspectAfterThrowingName=java.lang.Exception

5.2 aop入口代码

BonnieApplicationContext类中

private Object instantiateBean(String beanName, BonnieBeanDefinition bonnieBeanDefinition) {// 拿到实例化的对象的类名String beanClassName = bonnieBeanDefinition.getBeanClassName();Object instance = null;try {// 假设默认是单例,暂时不考虑非单例情形if (factoryBeanObjectCache.containsKey(beanName)) {instance = factoryBeanObjectCache.get(beanName);} else {Class<?> clazz = Class.forName(beanClassName);// 反射创建出对象instance = clazz.newInstance();// TODO  AOP在这块创建对象/*** 解析aop配置,判断当前实例是否符合aop切面规则,如果是,则生成代理对象*/BonnieAopConfig aopConfig = new BonnieAopConfig();aopConfig.setPointCut(this.reader.getContextPropertiesConfig().getProperty("pointCut"));aopConfig.setAspectClass(this.reader.getContextPropertiesConfig().getProperty("aspectClass"));aopConfig.setAspectBefore(this.reader.getContextPropertiesConfig().getProperty("aspectBefore"));aopConfig.setAspectAfter(this.reader.getContextPropertiesConfig().getProperty("aspectAfter"));aopConfig.setAspectAfterThrow(this.reader.getContextPropertiesConfig().getProperty("aspectAfterThrow"));aopConfig.setAspectAfterThrowingName(this.reader.getContextPropertiesConfig().getProperty("aspectAfterThrowingName"));BonnieAdvisedSupport advisedSupport = new BonnieAdvisedSupport(aopConfig, clazz, instance);// 符合切面表达式,需要代理 TODO 硬编码if (advisedSupport.pointCutMath()) {instance = proxyFactory.createAopProxy(advisedSupport).getProxy();}System.out.println(beanName+":"+instance);factoryBeanObjectCache.put(beanName, instance);factoryBeanObjectCache.put(bonnieBeanDefinition.getFactoryBeanName(), instance);}} catch (Exception e) {e.printStackTrace();}return instance;}

5.3 创建的对象是否符合连接点

package spring.framework.aop.support;import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import spring.framework.aop.config.BonnieAopConfig;import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;@Data
public class BonnieAdvisedSupport {private BonnieAopConfig aopConfig;/*** 目标类 UserController*/private Class targetClass;/*** 目标实例 UserController*/private Object targetInstance;private Pattern pointCutClassPattern;// 保存回调通知和目标切点方法之间的关系,chain // TODO 目前这快只做简单实现,后面贴合aop改成chain形式private Map<Method, Map<String, BonnieAdvice>> methodCacheMap = new ConcurrentHashMap<>();public BonnieAdvisedSupport(BonnieAopConfig aopConfig, Class<?> targetClass, Object targetInstance) {this.aopConfig = aopConfig;this.targetClass = targetClass;this.targetInstance = targetInstance;// 解析配置文件parseConfig();}/*** 解析配置文件*/private void parseConfig() {//        // 变成正则表达式 public .* com.bonnie.service.service..*Service..*(.*)
//        String pointCut = this.aopConfig.getPointCut().replaceAll("\\.", "\\\\.")
//                .replaceAll("\\\\.\\*", ".*")
//                .replaceAll("\\(", "\\\\(")
//                .replaceAll("\\)", "\\\\)");
//
//        // public .* com\.bonnie\.service\.service\..*Service
//        String pointCutForClassRegex = pointCut.substring(0, pointCut.lastIndexOf("\\(") - 4);
//        // com.bonnie.service.service..*Service
//        this.pointCutClassPattern = Pattern.compile("class " + pointCutForClassRegex.substring(pointCutForClassRegex.lastIndexOf(" ") + 1));try {// 处理切面配置Map<String,Method> aspectMethodMap = new HashMap<>();Class<?> aspectClass = Class.forName(aopConfig.getAspectClass());Object aspectInstance = aspectClass.newInstance();Method[] methods = aspectInstance.getClass().getMethods();for (Method method : methods) {aspectMethodMap.put(method.getName(), method);}// 缓存Method和Advice的对应关系Method[] targetMethodArray = targetClass.getMethods();for (Method targetMethod : targetMethodArray) {String targetMethodString = targetMethod.toString();if(targetMethodString.contains("throws")){targetMethodString = targetMethodString.substring(0, targetMethodString.lastIndexOf("throws")).trim();}// 目标方法符合切面表达式,则存储改方法和切面方法的关系// 硬编码if (isInvoke(targetClass)) {System.out.println(">>>>>匹配>>>>>" + aopConfig.getAspectClass() + ":" + targetMethod.toString());Map<String, BonnieAdvice> adviceMap = new HashMap<>();if (StringUtils.isNotEmpty(this.aopConfig.getAspectBefore())) {BonnieAdvice advice = BonnieAdvice.builder().aspectInstance(aspectInstance).aspectMethod(aspectMethodMap.get(this.aopConfig.getAspectBefore())).build();adviceMap.put("before", advice);}if (StringUtils.isNotEmpty(this.aopConfig.getAspectAfter())) {BonnieAdvice advice = BonnieAdvice.builder().aspectInstance(aspectInstance).aspectMethod(aspectMethodMap.get(this.aopConfig.getAspectAfter())).build();adviceMap.put("after", advice);}if (StringUtils.isNotEmpty(this.aopConfig.getAspectAfterThrow())) {BonnieAdvice advice = BonnieAdvice.builder().aspectInstance(aspectInstance).aspectMethod(aspectMethodMap.get(this.aopConfig.getAspectAfterThrow())).throwName(this.aopConfig.getAspectAfterThrowingName()).build();adviceMap.put("afterThrowing", advice);}// TODO 目前这快只做简单实现,后面贴合aop改成chain形式methodCacheMap.put(targetMethod, adviceMap);}}} catch (Exception e) {e.printStackTrace();}}/*** 当前类是否符合切面表达式, 目前这块只做做基础的实现* @return*/public boolean pointCutMath() {return isInvoke(targetClass);}public Map<String, BonnieAdvice> getAdvices(Method method, Class targetClass) throws Exception {Map<String, BonnieAdvice> adviceMap = methodCacheMap.getOrDefault(method, new HashMap<>());if (adviceMap.isEmpty()) {// targetClass如果是代理对象,则拿到它原生的对象Method m = targetClass.getMethod(method.getName(), method.getParameterTypes());adviceMap = methodCacheMap.get(m);if (!(adviceMap == null || adviceMap.isEmpty())) {methodCacheMap.put(method, adviceMap);}}return adviceMap;}private Boolean isInvoke(Class targetClass) {String[] split1 = aopConfig.getPointCut().split(";");for (String str : split1) {if (targetClass.getName().contains("."+str+".")) {return Boolean.TRUE;}}return Boolean.FALSE;}}

5.4 创建代理类

策略工厂

package spring.framework.aop;import spring.framework.aop.support.BonnieAdvisedSupport;public class BonnieDefaultAopProxyFactory {public BonnieAopProxy createAopProxy(BonnieAdvisedSupport advisedSupport) {Class[] interfaces = advisedSupport.getTargetClass().getInterfaces();// 有实现接口则使用jdk动态代理if (interfaces.length > 0) {return new BonnieJdkDynamicAopProxy(advisedSupport);}// 没有实现接口则使用cglib动态代理return new BonnieCglibAopProxy(advisedSupport);}}
package spring.framework.aop;public interface BonnieAopProxy {Object getProxy();Object getProxy(ClassLoader classLoader);}
package spring.framework.aop;import spring.framework.aop.support.BonnieAdvice;
import spring.framework.aop.support.BonnieAdvisedSupport;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;public class BonnieJdkDynamicAopProxy implements BonnieAopProxy, InvocationHandler {private BonnieAdvisedSupport advised;public BonnieJdkDynamicAopProxy(BonnieAdvisedSupport advisedSupport) {this.advised = advisedSupport;}@Overridepublic Object getProxy() {return getProxy(this.advised.getTargetClass().getClassLoader());}@Overridepublic Object getProxy(ClassLoader classLoader) {return Proxy.newProxyInstance(classLoader, this.advised.getTargetClass().getInterfaces(),this);}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {// 真实触发操作的地方Map<String, BonnieAdvice> advices = advised.getAdvices(method, this.advised.getTargetClass());if (advices.containsKey("before")) {BonnieAdvice advice = advices.get("before");advice.getAspectMethod().invoke(advice.getAspectInstance());}Object result = null;try {result = method.invoke(this.advised.getTargetInstance(), args);} catch (Exception e) {if (advices.containsKey("afterThrowing")) {BonnieAdvice advice = advices.get("afterThrowing");advice.getAspectMethod().invoke(advice.getAspectInstance());}}if (advices.containsKey("after")) {BonnieAdvice advice = advices.get("after");advice.getAspectMethod().invoke(advice.getAspectInstance());}return result;}
}
package spring.framework.aop;import spring.framework.aop.support.BonnieAdvisedSupport;public class BonnieCglibAopProxy implements BonnieAopProxy {private BonnieAdvisedSupport advisedSupport;public BonnieCglibAopProxy(BonnieAdvisedSupport advisedSupport) {this.advisedSupport = advisedSupport;}@Overridepublic Object getProxy() {return null;}@Overridepublic Object getProxy(ClassLoader classLoader) {return null;}
}

http://www.dtcms.com/a/330302.html

相关文章:

  • Canal解析MySQL Binlog原理与应用
  • Unity、C#常用的时间处理类
  • Laravel 使用ssh链接远程数据库
  • 使用 Simple Floating Menu 插件轻松实现浮动联系表单
  • AI一周事件(2025年8月6日-8月12日)
  • [ Mybatis 多表关联查询 ] resultMap
  • ResourcelessTransactionManager的作用
  • 第三天-如何在DBC中描述CAN Signal的“负数/值”
  • JetPack系列教程(六):Paging——让分页加载不再“秃”然
  • 理财学习资料推荐
  • 谈一些iOS组件化相关的东西
  • C# 多线程:并发编程的原理与实践
  • C++中的STL标准模板库和string
  • Heterophily-aware Representation Learning on Heterogeneous Graphs
  • AI - 工具调用
  • AI智能体记忆策略
  • 10 ABP 模块系统
  • [转]SURREAL数据集国内下载链接
  • Deep Agents:用于复杂任务自动化的 AI 代理框架
  • nm命令和nm -D命令参数
  • 19. 重载的方法能否根据返回值类型进行区分
  • Java之String类
  • 3.Cursor提效应用场景实战
  • UEdior富文本编辑器接入AI
  • 算法篇----分治(归并排序)
  • 云电竞盒子对游戏性能有影响吗?
  • 手游业务怎么做防护
  • 智慧城市数字孪生:城市管理的“平行宇宙”
  • 补环境基础(四) Hook插件
  • 黎阳之光立体物业透明管理:开启智慧物业新时代