Spring AOP解析

news2024/11/29 10:38:00

基本概念

之前写过如何实现方法增强,见链接:一篇文章了解如何实现方法增强,实现原理即采用的是AOP,那么本篇文章就主要是为了了解Spring AOP的实现。

面向切面编程(Aspect Oriented Programming)

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

切面(Aspect)

给业务方法增加到功能,切面泛指交叉业务逻辑。事务处理、日志处理等就可以理解为切面。常用的切面是通知(Advice)。实际就是对主业务逻辑的一种增强。

切入点(Pointcut)

切入点指声明的一个或多个连接点的集合,通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。

通知/增强(Advice)

通知表示切面的执行时间,Advice也叫增强。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。

连接点(JoinPoint)

连接切面的业务方法,连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。

目标对象(Target)

目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。

AOP三要素

AOP中重要的三个要素:Aspect、Pointcut、Advice

意思是说:在Advice的时间、在Pointcut的位置,执行Aspect。

动态代理(Dynamic Proxy)

简介

动态代理是一种在运行时动态生成代理对象的技术。它是一种设计模式,用于在不修改原始对象的情况下,通过代理对象来间接访问原始对象,并在访问前后执行额外的操作。

动态代理通常用于实现横切关注点(Cross-Cutting Concerns),如日志记录、性能监控、事务管理等。它能够在不改变原始对象的代码的情况下,通过代理对象在方法调用前后插入额外的逻辑。

在Java中,动态代理主要通过两个核心类来实现:Proxy和InvocationHandler。Proxy类用于动态创建代理类,而InvocationHandler接口负责处理代理对象的方法调用。通过实现InvocationHandler接口,开发人员可以在代理对象的方法调用前后添加自定义的逻辑,实现对原始对象的控制和增强。

应用场景

  1. 面向切面编程(Aspect Oriented Programming)
    动态代理可以实现横切关注点的功能,如日志记录、性能监控、事务管理等。通过在方法调用前后插入额外的逻辑,可以实现对原始对象的控制和增强。

  2. 远程方法调用(Remote Procedure Call Protocol)
    动态代理可以将远程方法调用封装为本地方法调用,简化远程通信的操作。通过动态代理,开发人员可以像调用本地对象一样调用远程对象的方法。

  3. 消息中间件(Message Middleware)
    动态代理可以用于消息中间件的发布/订阅模型。通过代理对象,可以将消息发送到消息队列并订阅特定的消息,实现解耦和灵活的消息处理。

  4. 数据库连接池(Connection Pooling)
    动态代理可以用于数据库连接池的管理。通过代理对象,可以在获取数据库连接时添加连接池的管理逻辑,如创建、销毁和监控连接。

  5. 缓存(Cache)
    动态代理可以用于实现缓存的功能。通过代理对象,在方法调用前先检查缓存中是否存在结果,避免重复计算或访问。

  6. 安全控制(Safety Control)
    动态代理可以用于实现安全控制的功能。通过代理对象,在方法调用前进行身份验证或权限检查,确保只有授权的用户可以访问敏感操作。

动态代理的优势在于它可以在运行时动态生成代理对象,无需事先知道具体的被代理类,增加了代码的灵活性和可扩展性。

工作原理

  1. 定义接口
    首先,需要定义一个接口,该接口是被代理类和代理类共同实现的。
  2. 实现InvocationHandler接口
    创建一个实现InvocationHandler接口的类,该类负责处理代理对象的方法调用。在该类中,需要重写invoke方法,在方法调用前后插入额外的逻辑。
    InvocationHandler接口的定义如下,
package java.lang.reflect;
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
  1. 创建代理对象
    使用Proxy类的静态方法newProxyInstance创建代理对象。该方法接受三个参数:ClassLoader(类加载器)、Class[](接口数组)和InvocationHandler。它会动态生成一个代理类,并创建一个代理对象。
  2. 方法调用
    当通过代理对象调用方法时,方法调用会被重定向到InvocationHandler的invoke方法。在该方法中,可以在方法调用前后执行额外的逻辑,也可以选择是否调用被代理类的方法。

示例

上面讲了具体的流程,接下来以Mybatis日志框架打印数据库连接的ConnectionLogger类为例,
可以看到它为在执行下面三个方法时执行了实例化打印日志的代理对象

  • prepareStatement
  • prepareCall
  • createStatement
package org.apache.ibatis.logging.jdbc;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.Statement;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.reflection.ExceptionUtil;

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
	    /**
     * 连接对象,用于与数据库取得连接
     */
    private final Connection connection;

    /**
     * 有参构造函数
     *
     * @param conn         连接对象
     * @param statementLog 日志
     * @param queryStack   查询层数
     */
    private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.connection = conn;
    }

    /**
     * 调用方法
     *
     * @param proxy  代理对象
     * @param method 方法
     * @param params 参数集合
     * @return 调用返回结果
     * @throws Throwable 异常
     */
    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        try {
            // 方法所在的Class对象如果是Object对象,则通过参数集合直接调用该方法
            if (Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, params);
            } else {
                PreparedStatement stmt;
                // 如果是创建向数据库发送预编译SQL的PrepareStatement对象方法,则调用方法并实例化PreparedStatement代理对象
                if ("prepareStatement".equals(method.getName())) {
                    if (this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String) params[0]), true);
                    }

                    stmt = (PreparedStatement) method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                    // 如果是创建执行存储过程的CallableStatement对象方法,则调用方法并实例化PreparedStatement代理对象
                } else if ("prepareCall".equals(method.getName())) {
                    if (this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String) params[0]), true);
                    }

                    stmt = (PreparedStatement) method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                    // 如果是创建向数据库发送SQL的对象方法,则调用方法并实例化Statement代理对象
                } else if ("createStatement".equals(method.getName())) {
                    Statement stmt = (Statement) method.invoke(this.connection, params);
                    stmt = StatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else {
                    // 其他方法则直接调用本身方法
                    return method.invoke(this.connection, params);
                }
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
        InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
        ClassLoader cl = Connection.class.getClassLoader();
        return (Connection)Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
    }

    public Connection getConnection() {
        return this.connection;
    }
}

Spring AOP

上面讲了动态代理和AOP面向切面编程,接下来看看Spring如何实现AOP,它主要是通过基于XML配置或者则基于注解实现。
下面以Spring基于注解的常见方式为例,主要是在应用启动类上加上@EnableAspectJAutoProxy开启Spring AOP,该注解源码如下,

package org.springframework.context.annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Import({AspectJAutoProxyRegistrar.class})
public @interface EnableAspectJAutoProxy {
	// 是否使用CGLIB代理,默认不使用。默认使用JDK动态代理
    boolean proxyTargetClass() default false;
	// 是否将代理类作为线程本地变量(threadLocal)暴露(可以通过AopContext访问),主要设计的目的是用来解决内部调用的问题
    boolean exposeProxy() default false;
}

补充JDK动态代理和CGLIB动态代理区别:

  • JDK 实现:基于接口来创建被代理对象的代理实例。当对象要被代理时,它必须实现一个或多个接口并依赖JDK库。JDK动态代理利用反射机制生成一个包含被代理对象的所有接口的代理类,并覆盖接口中的所有方法,可以对目标对象进行代理。
    JDK代理无需引用第三方库,在JRE运行环境中就可以运行,生成代理对象更加简单、快捷;缺点是仅支持基于接口进行代理,无法对类进行代理,所以它的作用有限。
  • CGLIB 实现:基于继承的方式对被代理类生成子类,从而添加代理逻辑。因为它是继承了被代理类,所以它会受到final类、private、static等不可继承属性的影响。
    Cglib支持对类进行代理,即使没有接口,也可通过设置回调接口间接地实现。性能比JDK动态代理更高,能够代理那些没有实现任何接口的目标对象。

再看@Import导入的类AspectJAutoProxyRegistrar,源码如下,

package org.springframework.context.annotation;

import org.springframework.aop.config.AopConfigUtils;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;

class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar {
    AspectJAutoProxyRegistrar() {
    }
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        // 往Spring容器注入一个用于创建AOP代理类的注册者
        AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
        AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class);
        if (enableAspectJAutoProxy != null) {
            if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) {
                AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
            }
            if (enableAspectJAutoProxy.getBoolean("exposeProxy")) {
                AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry);
            }
        }
    }
}

registerAspectJAnnotationAutoProxyCreatorIfNecessary的重载(Overloading)方法中,三个参数的重载方法传入了AnnotationAwareAspectJAutoProxyCreator类,它即是Spring AOP切面的入口类。通过Show Diagram可以查看该类的继承关系如下,
在这里插入图片描述
可以看到该类是BeanPostProcessor的实现类的子类,而BeanPostProcessor是Spring的后置处理器,Spring Bean的生命周期时序图如下,Spring Bean从实例化到准备使用的整个过程,包括Bean的实例化、属性赋值、生命周期方法的执行和后置处理器的调用。
在这里插入图片描述
再去看AbstractAutoProxyCreator类中的postProcessAfterInitialization方法,如果已经代理过则不会重新代理,如果需要代理则进入wrapIfNecessary方法,

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
	/**
	 * Create a proxy with the configured interceptors if the bean is
	 * identified as one to proxy by the subclass.
	 * @see #getAdvicesAndAdvisorsForBean
	 */
	@Override
	public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
		if (bean != null) {
			Object cacheKey = getCacheKey(bean.getClass(), beanName);
			if (this.earlyProxyReferences.remove(cacheKey) != bean) {
				return wrapIfNecessary(bean, beanName, cacheKey);
			}
		}
		return bean;
	}
}

进入wrapIfNecessary方法,判断这个类中有哪些增强的advisor(代码中配置的@Aspect中@Around、@Before、@After等修饰的方法和@Pointcut条件组装的Advisor)

protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
		if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
			return bean;
		}
		if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
			return bean;
		}
		if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
			this.advisedBeans.put(cacheKey, Boolean.FALSE);
			return bean;
		}

		// Create proxy if we have advice.
		Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
		if (specificInterceptors != DO_NOT_PROXY) {
			this.advisedBeans.put(cacheKey, Boolean.TRUE);
			Object proxy = createProxy(
					bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
			this.proxyTypes.put(cacheKey, proxy.getClass());
			return proxy;
		}

		this.advisedBeans.put(cacheKey, Boolean.FALSE);
		return bean;
	}

参考链接

1、https://www.cnblogs.com/daimzh/p/12854380.html
2、https://zhuanlan.zhihu.com/p/640955761
3、https://blog.51cto.com/u_11979904/5948691
4、https://zhuanlan.zhihu.com/p/640098159

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1286239.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Apollo新版Beta技术沙龙,震撼来袭!

在2023年12月2日,我有幸参加百度Apollo举行的技术交流活动,Apollo是一个百度公司开发的开源的自动驾驶项目,这次线下技术交流让我对自动驾驶有了深入的了解。 在活动中,来自Apollo项目的专家们对 Apollo 技术的进行介绍和演示。他…

ValueError: not enough values to unpack (expected 3, got 2)

我在使用cv2.findContours函数中遇到以上错误,经查询找到该错误原因: 在 OpenCV 4.X 中,函数 cv2.findContours()仅有两个返回值, 其语法格式为: contours, hierarchy cv2.findContours( image, mode, method) 若不…

2023版本idea插件开发踩坑记录(一)

在进行idea开发的时候,开始仿照着写第一个插件hello world的时候,运行的时候一直运行不成功。参考了很多博客都是如此 后面对官方文档读了一遍,就发现其中的原委,这个的话估计会有很多人跟我一样踩坑 具体原因是,idea插…

你知道小红书小眼睛的推送机制吗?

明明很用心地发笔记,小眼睛就是不多,是不是你看到自己笔记的小眼睛低就头痛? 首先我们要知道小眼睛是什么!推送机制是什么!搞清楚才能把这项数据提升起来。

【上海大学《面向对象程序设计A》课程小项目报告】抽象向量类模板及其派生类

1 项目内容及要求 本项目通过设计一个抽象向量类模板,以及一个通用的向量类模板和一个字符串类作为其派生类,以满足各种应用场景中的数据存储和处理需求。 项目内容: 抽象向量类模板。派生向量类。派生字符串类。测试及异常处理。联合测试…

Redis SDS 源码

底层数据结构的好处: 杜绝缓冲区溢出。减少修改字符串长度时所需的内存重分配次数。二进制安全。兼容部分C字符串函数。 常用命令: set key value、get key 等 应用场景:共享 session、分布式锁,计数器、限流。 1、给char*定义…

【Java Web学习笔记】4 - DOM文档对象模型

项目代码 https://github.com/yinhai1114/JavaWeb_LearningCode/tree/main/javascript 零、在线文档 JavaScript HTML DOM 一、HTML DOM基本介绍 1. DOM全称是Document Object Model文档对象模型 文档<---映射--->对象 2.就是把文档中的标签&#xff0c;属性&#xf…

Java集合常见问题

目录 Java集合 1.前言2.集合3.Collection接口类3.1 List接口3.1.1 ArrayList&#xff08;常用&#xff09;3.1.2 LinkedList&#xff08;常用&#xff09;3.1.3 Vector&#xff08;不常用&#xff09; 3.2 Set接口3.2.1 HashSet&#xff08;常用&#xff09;3.2.2 LinkedHash…

最高性能、最低错误率!一年沉寂,IBM王者归来

周一&#xff0c;国际商业机器公司&#xff08;IBM&#xff09;发布了首台量子计算机&#xff0c;它拥有1000多个量子比特&#xff08;相当于普通计算机中的数字比特&#xff09;。但该公司表示&#xff0c;现在它将转变思路&#xff0c;专注于提高机器的抗错能力&#xff0c;而…

Ruff智能物联网网关助力工厂数智化运营,实现产量提升5%

数字化转型是大势所趋&#xff0c;以工业互联网为代表的数实融合是发展数字经济的重要引擎&#xff0c;也是新质生产力的一大助力。工业互联网是新工业革命的重要基石&#xff0c;加快工业互联网规模化应用&#xff0c;是数字技术和实体经济深度融合的关键支撑&#xff0c;是新…

【面试HOT200】二叉树的构建二叉搜索树篇

系列综述&#xff1a; &#x1f49e;目的&#xff1a;本系列是个人整理为了秋招面试的&#xff0c;整理期间苛求每个知识点&#xff0c;平衡理解简易度与深入程度。 &#x1f970;来源&#xff1a;材料主要源于【CodeTopHot200】进行的&#xff0c;每个知识点的修正和深入主要参…

免费SSL证书靠谱吗?

首先&#xff0c;我们需要明确一点&#xff0c;那就是免费SSL证书并非完全没有价值。它们同样能够提供基本的数据加密功能&#xff0c;确保用户的信息在传输过程中不会被第三方截获。因此&#xff0c;如果你的网站不需要处理敏感信息&#xff0c;例如个人身份信息、银行卡号等&…

地方招商策略:招商招哪些,如何选择理想的企业?

招商引资是推动地方经济发展的不二选择&#xff0c;通过吸引优质企业入驻&#xff0c;不仅可以带来直接的投资和税收&#xff0c;还可以为地方创造更多的就业机会&#xff0c;引入高端人才、先进的技术及管理经验&#xff0c;同时&#xff0c;招商引资还能够促进地方的产业升级…

深度优先搜索(DFS)LeetCode 2477. 到达首都的最少油耗

2477. 到达首都的最少油耗 给你一棵 n 个节点的树&#xff08;一个无向、连通、无环图&#xff09;&#xff0c;每个节点表示一个城市&#xff0c;编号从 0 到 n - 1 &#xff0c;且恰好有 n - 1 条路。0 是首都。给你一个二维整数数组 roads &#xff0c;其中 roads[i] [ai,…

回溯总结(一)基础概念及模板

1.回溯是什么&#xff1f; 回溯&#xff0c;也叫回溯搜索法&#xff0c;搜索的一种方式。回溯搜索实际上也是一种暴力搜索&#xff08;本质是穷举&#xff09;&#xff08;对于有些问题是唯一可以解决的办法了&#xff0c;for循环是不适用的&#xff09;和别的搜索不同之处在于…

基于混沌算法的图像加密解密系统

1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 研究背景与意义&#xff1a; 随着信息技术的迅猛发展&#xff0c;图像的传输和存储已经成为现代社会中不可或缺的一部分。然而&#xff0c;随着互联网的普及和信息的快速传播&am…

Node-red的节点离线安装

Node-red节点离线安装 前言 目前越来越高的数据安全的要求&#xff0c;因此我们很多的生产类服务器是无法进行在线化部署的&#xff0c;为了解决这一问题&#xff0c;我们需要在无法连接外部网络环境的情况下&#xff0c;实现Node-red的节点的安装&#xff0c;以满足项目的需…

el-dialog 垂直居中

写文章总是在想引言&#xff0c;怎么开头才会显的更加优雅&#xff0c;更加让读者朋友给我点赞。看到有人点赞&#xff0c;我就觉的进行技术经验分享是一件非常愉快的事情&#xff0c;可是打小作文写的不好不会组织语句&#xff0c;就喜欢直来直去。老师说让写春天的作文&#…

vue2 element-ui select下拉框 选择传递多个参数

<el-select v-model"select" slot"prepend" placeholder"请选择" change"searchPostFn($event,123)"> <el-option :label"item.ziDianShuJu" :value"{value:item.id, label:item.ziDianShuJu}" v-for&qu…