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

Spring AOP解析

基本概念

之前写过如何实现方法增强,见链接:一篇文章了解如何实现方法增强,实现原理即采用的是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.kler.cn/a/156246.html

相关文章:

  • git没有识别出大写字母改成小写重命名的文件目录
  • 从社交媒体到元宇宙:Facebook未来发展新方向
  • 【大数据测试HBase数据库 — 详细教程(含实例与监控调优)】
  • ubuntu-desktop-24.04上手指南(更新阿里源、安装ssh、安装chrome、设置固定IP、安装搜狗输入法)
  • C#发票识别、发票查验接口集成、电子发票(航空运输电子行程单)
  • GaussDB部署架构
  • 基于Java SSM框架实现美好生活九宫格日志网站系统项目【项目源码+论文说明】
  • Docker push 命令
  • 在CentOS7下安装Docker与Docker Compose
  • 举例说明自然语言处理(NLP)技术。
  • 二分查找算法:搜索有序数组中目标元素的利器
  • 松下、书客、明基护眼台灯值不值得买?热门护眼台灯真实测评!
  • 客户销售目标拆解:数据驱动的方法和策略
  • 【LeeCode】142.环形链表II
  • 开启gitlab中远程连接pgsql
  • 燃料电池汽车市场分析:预计2028年将达到118亿美元
  • 前端需要掌握的技术有哪些方面
  • Kubernetes(K8s)Service详解-07
  • 【数电笔记】17-具体函数的卡诺图填入
  • 关于svn如何上传一个完整的项目
  • OpenAI发生的大事件总结!
  • 含mask的单通道灰度图内容可视化python
  • Android 10.0 状态栏系统图标显示分析
  • JS的空值合并运算符??与逻辑空赋值??=
  • 贝叶斯分类器(Bayesian Classifier)
  • 极智芯 | 解读国产AI算力 璧仞产品矩阵