摊牌了,我要手写一个AOP

it2025-02-18  2

前言

在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

相信Java开发者对AOP都不陌生,尤其是使用Spring框架的开发者,几乎是面试必问。 网上给出的答案都是使用代理来实现的,那么大家有没有很好的去理解,使用代理模式到底该如何实现呢?

今天就记录一下笔者手写AOP的过程,顺便和大家分享一下AOP底层的实现。


手写AOP

先不着急写代码,理清了思路才好开干。

实现思路

自定义注解,为后续定义AOP、切点、各种通知。解析切面类,获取切入点,解析表达式,明确哪些方法需要执行哪些通知。为Bean生成代理对象返回IOC容器,在代理对象中执行通知。解析方法的入参和出参,封装后分别传递给对应的通知方法。

实战

1、自定义必要的注解

@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented // 定义切面类 public @interface Aop { } @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented // 切点的定义 public @interface MyPointcut { String value() default "";// 切点表达式 } @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented // 前置增强 public @interface Before { } @Target({ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented // 后置增强 public @interface After { }

2、定义方法增强描述

public class Advice { private String classMethodName;//类名+方法名 private Object aopBean;//切面类实例 private Method before;//前置通知 private Method after;//后置通知 public Advice(String classMethodName, Object aopBean, Method before, Method after) { this.classMethodName = classMethodName; this.aopBean = aopBean; this.before = before; this.after = after; } // 省略getter }

3、编写AOP代理类

/** * @author: pch * @description: AOP代理 * @date: 2020/10/9 **/ public class AopProxy implements InvocationHandler { // 参数名称解析,Java代码编译后,参数名变成了无意义的arg0、arg1,通过Spring的工具类解析参数名称 private ParameterNameDiscoverer parameterNameDiscoverer = new LocalVariableTableParameterNameDiscoverer(); // 原生对象 private final Object origin; // 增强 private final Advice advice; public AopProxy(Object origin, Advice advice) { this.origin = origin; this.advice = advice; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 前置增强,封装参数 String[] parameterNames = parameterNameDiscoverer.getParameterNames(origin.getClass().getMethod(method.getName(), method.getParameterTypes())); Map<String, Object> params = new HashMap<>(); for (int i = 0; i < args.length; i++) { params.put(parameterNames[i], args[i]); } // 执行 前置通知 advice.getBefore().invoke(advice.getAopBean(), origin.getClass(), method, params); // 执行本身的方法 Object result = method.invoke(origin, args); // 执行 后置通知,返回结果作为参数传递 advice.getAfter().invoke(advice.getAopBean(), origin.getClass(), method, result); return result; } }

4、实现BeanPostProcessor,解析切面类,给需要增强的类生成代理对象。

/** * @author: pch * @description: 为需要AOP增强的类生成代理对象 * @date: 2020/10/8 **/ @Component public class AopBeanPostProcessor extends ApplicationObjectSupport implements BeanPostProcessor { // AOP实例 private Collection<Object> AOP_BEANS; // 切点对应的增强类 private Map<String, Advice> adviceMap = new ConcurrentHashMap<>(); @PostConstruct public void init() throws Exception { AOP_BEANS = getApplicationContext().getBeansWithAnnotation(Aop.class).values(); for (Object bean : AOP_BEANS) { Class<?> beanClass = bean.getClass(); for (Field field : beanClass.getDeclaredFields()) { // 解析AOP实例,获取切入点表达式 if (field.getDeclaredAnnotation(MyPointcut.class) != null) { field.setAccessible(true); String pointcut = (String) field.get(bean); Method before = null; Method after = null; for (Method method : beanClass.getMethods()) { if (method.getDeclaredAnnotation(Before.class) != null) { before = method; } if (method.getDeclaredAnnotation(After.class) != null) { after = method; } } adviceMap.put(pointcut, new Advice(pointcut, bean, before, after)); } } } } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { Class<?> beanClass = bean.getClass(); Advice advice = adviceMap.values().iterator().next(); // 给IOC的实例生成代理对象,在代理对象中做AOP增强 if (advice.getClassMethodName().startsWith(beanClass.getName())) { for (Method method : beanClass.getDeclaredMethods()) { if (advice.getClassMethodName().equalsIgnoreCase(beanClass.getName() + "@" + method.getName())) { // 需要增强的方法 return Proxy.newProxyInstance(beanClass.getClassLoader(), beanClass.getInterfaces(), new AopProxy(bean, advice)); } } } return bean; } }

至此,一个只支持【前置、后置】通知功能的简单的AOP就实现了。

功能测试

1、编写Controller、Service。

@RestController @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("get") public User getById(Long userId) { return userService.getById(userId); } } @Service public class UserServiceImpl implements UserService { @Override public User getById(Long userId) { // 模拟从db查询 ThreadUtil.sleep(ThreadLocalRandom.current().nextInt(1000)); return new User(userId, "张三", 18); } }

2、编写切面类。

@Aop @Component public class LogInterceptor { protected Logger log = LoggerFactory.getLogger(LogInterceptor.class); private ThreadLocal<Long> execTime = new ThreadLocal<>(); // 切入点 这里为了简单,直接指明一个方法,不做复杂的表达式解析 @MyPointcut private final String pointcut = "top.javap.service.UserServiceImpl@getById"; /** * 前置通知 AOP会将哪个类、哪个方法,及入参 带过来 * @param clazz * @param method * @param args */ @Before public void before(Class<?> clazz, Method method, Map<String,Object> args) { log.info("{}()开始执行,参数:{}", clazz.getName() + "@" + method.getName(), JSONObject.toJSONString(args)); execTime.set(System.currentTimeMillis()); } /** * 后置通知 AOP会将哪个类、哪个方法,及出参 带过来 * @param clazz * @param method * @param result * @return */ @After public Object after(Class<?> clazz, Method method, Object result) { long time = System.currentTimeMillis() - execTime.get(); log.info("{}()执行结束,结果:{},耗时:{}ms", clazz.getName() + "@" + method.getName(), JSONObject.toJSONString(result), time); return result; } }

3、请求接口,测试日志AOP是否正常工作。

尾巴

一句话总结:AOP就是为需要增强的类生成代理对象,在代理对象中可以选择性的去触发原生方法,以及在原方法的前、后、环绕、异常的地方执行特定的通知方法。

最新回复(0)