手写spring12(把aop动态代理整合到spring生命周期)

news2024/10/6 14:36:13

文章目录

  • 目标
  • 设计
  • 项目结构
  • 四、实现
    • 1、定义Advice拦截器链
    • 2、定义Advisor访问者
    • 3、方法前置拦截器——MethodBeforeAdviceInterceptor
    • 4、代理工厂——ProxyFactory
    • 5、融入Bean生命周期的自动代理创建者——InstantiationAwareBeanPostProcessor 、DefaultAdvisorAutoProxyCreator
    • 6、融入到Bean的生命周期
  • 二、测试
    • 1、准备
    • 2、自定义拦截方法
    • 3、spring.xml 配置 AOP
    • 4、单元测试
  • 总结


目标

AOP和spring的整合,最终能通过在 Spring xml配置的方式完成切面的操作


设计

怎么凭借 BeanPostProcessor 把动态代理融入到 Bean 的生命周期中

如何组装各项切点、拦截、前置的功能适配对应的代理器

整体设计结构如下图:

在这里插入图片描述

为了可以让对象创建过程中,能把xml中配置的代理对象也就是切面的一些类对象实例化,就需要用到 BeanPostProcessor 提供的方法,因为这个类的中的方法可以分别作用与 Bean 对象执行初始化前后修改 Bean 的对象的扩展信息

但这里需要集合于 BeanPostProcessor 实现新的接口和实现类,这样才能定向获取对应的类信息。

但因为创建的是代理对象不是之前流程里的普通对象,所以我们需要前置于其他对象的创建,所以在实际开发的过程中,需要在 **AbstractAutowireCapableBeanFactory#createBean 优先完成 Bean 对象的判断,**是否需要代理,有则直接返回代理对象。在Spring的源码中会有 createBean 和 doCreateBean 的方法拆分


项目结构

在这里插入图片描述
整个类关系图中可以看到,在以 BeanPostProcessor 接口实现继承的 InstantiationAwareBeanPostProcessor 接口后,做了一个自动代理创建的类 DefaultAdvisorAutoProxyCreator这个类的就是用于处理整个 AOP 代理融入到 Bean 生命周期中的核心类

DefaultAdvisorAutoProxyCreator 会依赖于拦截器代理工厂Pointcut与Advisor的包装服务, AspectJExpressionPointcutAdvisor,由它提供切面、拦截方法和表达式

Spring 的 AOP 把 Advice 细化了 BeforeAdviceAfterAdviceAfterReturningAdviceThrowsAdvice,目前我们做的测试案例中只用到了 BeforeAdvice,这部分可以对照 Spring 的源码进行补充测试。


四、实现

1、定义Advice拦截器链

/**
 * @desc Advice前置拦截器链
 */
public interface BeforeAdvice extends Advice {
}
/**
 * @desc Advice方法前置拦截器链
 */
public interface MethodBeforeAdvice extends BeforeAdvice {


    void before(Method method, Object[] args, Object target) throws Throwable;

}

在 Spring 框架中,Advice 都是通过方法拦截器 MethodInterceptor 实现的。环绕 Advice 类似一个拦截器的链路,Before Advice、After advice等,不过暂时我们需要那么多就只定义了一个 MethodBeforeAdvice 的接口定义


2、定义Advisor访问者

public interface Advisor {


    Advice getAdvice();

}
/**
 * @desc 切入点访问者
 */
public interface PointcutAdvisor extends Advisor {

    Pointcut getPointcut();
}

PointcutAdvisor 承担了 PointcutAdvice 的组合,Pointcut 用于获取 JoinPoint,而 Advice 决定于 JoinPoint 执行什么操作。

/**
 * @desc 表达式切入点访问者
 */
public class AspectJExpressionPointcutAdvisor implements PointcutAdvisor {

    // 切面
    private AspectJExpressionPointcut pointcut;

    // 具体的拦截方法
    private Advice advice;

    // 表达式
    private String expression;

    public void setExpression(String expression) {
        this.expression = expression;
    }

    @Override
    public Advice getAdvice() {
        return advice;
    }

    @Override
    public Pointcut getPointcut() {
        if (pointcut == null) {
            pointcut = new AspectJExpressionPointcut(expression);
        }
        return pointcut;
    }

    public void setAdvice(Advice advice) {
        this.advice = advice;
    }


}

AspectJExpressionPointcutAdvisor 实现了 PointcutAdvisor 接口,把切面 pointcut、拦截方法 advice具体的拦截表达式包装在一起。这样就可以在 xml 的配置中定义一个 pointcutAdvisor 切面拦截器了。


3、方法前置拦截器——MethodBeforeAdviceInterceptor

/**
 * @desc 方法前置访问拦截器
 */
public class MethodBeforeAdviceInterceptor implements MethodInterceptor {

    private MethodBeforeAdvice advice;

    public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) {
        this.advice = advice;
    }

    public MethodBeforeAdviceInterceptor() {
    }

    public MethodBeforeAdvice getAdvice() {
        return advice;
    }

    public void setAdvice(MethodBeforeAdvice advice) {
        advice = advice;
    }

    @Override
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        this.advice.before(methodInvocation.getMethod(),methodInvocation.getArguments(),methodInvocation.getThis());
        return methodInvocation.proceed();
    }
}

MethodBeforeAdviceInterceptor 实现了 MethodInterceptor 接口,在 invoke 方法中调用 advice 中的 before 方法,传入对应的参数信息。

而这个 advice.before 则是用于自己实现 MethodBeforeAdvice 接口后做的相应处理

其实可以看到具体的 MethodInterceptor 实现类,其实和我们之前做的测试是一样的,只不过现在交给了 Spring 来处理


4、代理工厂——ProxyFactory

/**
 * @desc 代理工厂
 */
public class ProxyFactory {

    private AdvisedSupport advisedSupport;

    public ProxyFactory(AdvisedSupport advisedSupport) {
        this.advisedSupport = advisedSupport;
    }

    public Object getProxy(){
        return createAopProxy().getProxy();
    }

    private AopProxy createAopProxy(){
        if (advisedSupport.isProxyTargetClass()) {
            return new Cglib2AopProxy(advisedSupport);
        }
        return new JdkDynamicAopProxy(advisedSupport);
    }
}

其实这个代理工厂主要解决的是关于 JDK 和 Cglib 两种代理的选择问题,有了代理工厂就可以按照不同的创建需求进行控制。


5、融入Bean生命周期的自动代理创建者——InstantiationAwareBeanPostProcessor 、DefaultAdvisorAutoProxyCreator

定义bean实例化前接口,凭借BeanPostProcessor实现aop,继承spring的关键

public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {

    /**
     * 在 Bean 对象执行初始化方法之前,执行此方法
     *
     * @param beanClass
     * @param beanName
     * @return
     * @throws BeansException
     */
    Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException;

}
/**
 * @desc 融入Bean生命周期的自动代理创建者
 */
public class DefaultAdvisorAutoProxyCreator implements InstantiationAwareBeanPostProcessor, BeanFactoryAware {

    private DefaultListableBeanFactory beanFactory;


    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = (DefaultListableBeanFactory) beanFactory;
    }

    /**
     * @desc: 在 Bean 对象执行初始化方法之前,执行此方法(这个类最核心的方法)
     **/
    @Override
    public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        if(isInfrastructureClass(beanClass)){
            return null;
        }
        Collection<AspectJExpressionPointcutAdvisor> advisors  = beanFactory.getBeansOfType(AspectJExpressionPointcutAdvisor.class).values();
        for (AspectJExpressionPointcutAdvisor advisor : advisors) {
            ClassFilter classFilter = advisor.getPointcut().getClassFilter();
            if(!classFilter.matches(beanClass)){
                continue;
            }
            // 切面包装类初始化
            AdvisedSupport advisedSupport = new AdvisedSupport();
            TargetSource targetSource = null;
            try {
                // 实例化beanClass
                targetSource = new TargetSource(beanClass.getDeclaredConstructor().newInstance());
            } catch (Exception e) {
                e.printStackTrace();
            }
            // 设置代理模式
            advisedSupport.setProxyTargetClass(false);
            // 被代理的对象
            advisedSupport.setTargetSource(targetSource);
            // 方法拦截器
            advisedSupport.setMethodInterceptor((MethodInterceptor) advisor.getAdvice());
            // 方法匹配器
            advisedSupport.setMethodMatcher(advisor.getPointcut().getMethodMatcher());
            return new ProxyFactory(advisedSupport).getProxy();
        }
        return null;
    }


    /**
     * @desc: 是否为基础设施类,底层C实现
     **/
    private boolean isInfrastructureClass(Class<?> beanClass) {
        return Advice.class.isAssignableFrom(beanClass) || Pointcut.class.isAssignableFrom(beanClass) || Advisor.class.isAssignableFrom(beanClass);
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

这个 DefaultAdvisorAutoProxyCreator 类的主要核心实现在于 postProcessBeforeInstantiation 方法中,从通过 beanFactory.getBeansOfType 获取 AspectJExpressionPointcutAdvisor 开始。

获取了 advisors 以后就可以遍历相应的 AspectJExpressionPointcutAdvisor 填充对应的属性信息,包括:目标对象、拦截方法、匹配器,之后返回代理对象即可。

那么现在调用方获取到的这个 Bean 对象就是一个已经被切面注入的对象了,当调用方法的时候,则会被按需拦截,处理用户需要的信息。


6、融入到Bean的生命周期

public abstract class  AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {

    private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();

    /**
     * 创建bean
     */
    @Override
    protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeansException {
        Object bean = null;
        try {
            // 判断是否返回代理bean对象(新增代码)
            bean = resolveBeforeInstantiation(beanName,beanDefinition);
            if(bean != null){
                return bean;
            }
            
            bean = createBeanInstance(beanName,beanDefinition,args);
            // 属性填充
            applyPropertyvalues(beanName,bean,beanDefinition);
            // 执行 Bean 的初始化方法和 BeanPostProcessor 的前置和后置处理方法
            bean = initializeBean(beanName, bean, beanDefinition);
        } catch (Exception e) {
            throw new BeansException("Instantiation of bean failed", e);
        }
        // 注册实现了 DisposableBean 接口的 Bean 对象
        registerDisposableBeanIfNecessary(beanName, bean, beanDefinition);

        // 判断 SCOPE_SINGLETON,SCOPE_PROTOTYPE
        if(beanDefinition.isSingleton()){
            registerSingleton(beanName,bean);
        }
        return bean;
    }

    // 在实例化前应用Bean后处理器 ——新增方法,与 “applyBeanPostProcessorBeforeInitialization” 是两个方法
    public Object applyBeanPostProcessorBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
        for (BeanPostProcessor processor : getBeanPostProcessors()) {
            // 在 Bean 对象执行初始化方法之前,执行此方法
            if (processor instanceof InstantiationAwareBeanPostProcessor) {
                Object result = ((InstantiationAwareBeanPostProcessor) processor).postProcessBeforeInstantiation(beanClass, beanName);
                if (result != null) {
                    return result;
                }
            }
        }
        return null;
    }

    // 新增方法,解析实例化前处理
    protected Object resolveBeforeInstantiation(String beanName, BeanDefinition beanDefinition) {
        // 实例化前处理
        Object bean = applyBeanPostProcessorBeforeInstantiation(beanDefinition.getBeanClass(), beanName);
        if (bean != null) {
            // 如果是代理对象,则直接进行初始化后处理
            bean = applyBeanPostProcessorsAfterInitialization(bean,beanName);
        }
        return bean;
    }

	// 省略与本章节无关代码


}

因为创建的是代理对象,不是之前流程里的普通对象,所以我们需要前置于其他对象的创建,即需要在 AbstractAutowireCapableBeanFactory#createBean 优先完成 Bean 对象的判断,是否需要代理,有则直接返回代理对象,且执行出初始化后的逻辑。


二、测试

1、准备

public class UserService implements IUserService {

    public String queryUserInfo() {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "ljc,100001,上海";
    }

    public String register(String userName) {
        try {
            Thread.sleep(new Random(1).nextInt(100));
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "注册用户:" + userName + " success!";
    }
}

后面我们的测试过程,会给这个两个方法添加我们的拦截处理


2、自定义拦截方法

public class UserServiceBeforeAdvice implements MethodBeforeAdvice {

    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("拦截方法:" + method.getName());
    }
}

与上一章节的拦截方法相比,我们不再是实现 MethodInterceptor 接口,而是实现 MethodBeforeAdvice 环绕拦截

在这个方法中我们可以获取到方法的一些信息,如果还开发了它的 MethodAfterAdvice 则可以两个接口一起实现


3、spring.xml 配置 AOP

<?xml version="1.0" encoding="UTF-8"?>
<beans>

    <bean id="userService" class="springframework.test.bean.UserService"/>

    <bean class="cn.ljc.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"/>

    <bean id="beforeAdvice" class="springframework.test.bean.UserServiceBeforeAdvice"/>

    <bean id="methodInterceptor" class="cn.ljc.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor">
        <property name="advice" ref="beforeAdvice"/>
    </bean>

    <bean id="pointcutAdvisor" class="cn.ljc.springframework.aop.aspectj.AspectJExpressionPointcutAdvisor">
        <property name="expression" value="execution(* springframework.test.bean.IUserService.*(..))"/>
        <property name="advice" ref="methodInterceptor"/>
    </bean>

</beans>

这回再使用 AOP 就可以像 Spring 中一样,通过在 xml 中配置即可。因为我们已经把 AOP 的功能融合到 Bean 的生命周期里去了,你的新增拦截方法都会被自动处理。


4、单元测试

@Test
public void test_aop() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");
    IUserService userService = applicationContext.getBean("userService", IUserService.class);
    System.out.println("测试结果:" + userService.queryUserInfo());
}

在单元测试中你只需要按照正常获取和使用 Bean 对象即可,不过这个时候如果被切面拦截了,那么其实你获取到的就是对应的代理对象里面的处理操作了。

测试结果

拦截方法:queryUserInfo
测试结果:ljc,100001,上海

通过测试结果可以看到,我们已经让拦截方法生效了,也不需要自己手动处理切面、拦截方法等内容。


总结

到此,实现了aop切面和spring的整合,只需要配置xml,就可以了,不需要手动处理

实现的关键点则是依赖于beanPostProcessor,实例化前进行代理的处理

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

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

相关文章

什么是3dMax中的“块”?如何在3dMax中使用“块”?

3dMax 块简介 3dMax 是一款用于设计 3d 模型的软件,在 3d 建模图形专业人士中最受欢迎。我们可以在此软件中导入不同类型的预设计 3d 模型,以简化我们的工作。块是不同类型的 3d 模型,您可以从互联网上下载,然后将它们导入到 3dMax 软件的项目工作中,以节省您的时间。在本…

Python和OpenCV创建超快的“for”像素循环

这篇博客将介绍如何使用Python和OpenCV创建超快的“for”像素循环&#xff08;逐像素循环&#xff09;&#xff0c;即Cython快速优化for循环&#xff1b; 使用Python和OpenCV逐像素循环图像是一个非常缓慢的操作&#xff0c;即使图像在内部由NumPy数组表示。 为什么会这样&am…

C语言-指针进阶-qsort函数的学习与模拟实现(9.3)

目录 思维导图&#xff1a; 回调函数 qsort函数介绍 模拟实现qsort 写在最后&#xff1a; 思维导图&#xff1a; 回调函数 什么是回调函数&#xff1f; 回调函数是一个通过函数指针调用的函数。 将一个函数指针作为参数传递给一个函数&#xff0c;当这个指针被用来调用…

57、JDBC和连接池

目录 一、JDBC基本介绍 二、JDBC快速入门 三、JDBC API 1、ResultSet [结果集] 2、Statement 3、PreparedStatement 4、DriverManager 四、封闭JDBCUtils 五、事务 六、批处理 七、数据库连接池 4、数据库连接池种类 &#xff08;1&#xff09; c3p0数据库连接池&…

MacBookPro 遇到pip: command not found问题的解决

学习Pyhton的时候&#xff0c;需要安装第三方插件pyecharts,执行以下命令&#xff1a; pip install pyecharts总是报错 pip: command not found 我很郁闷&#xff0c;于是上网搜索尝试各种命令&#xff0c; 命令1:curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py 命…

Codeforces Round 507(div. 1) C(分类讨论,并查集)

题目链接&#xff1a; Problem - C - Codeforceshttps://codeforces.com/contest/1039/problem/C 题意&#xff1a; 计算机网络由个服务器组成&#xff0c;每个服务器有到范围内的加密秘钥。设是分配给第i台服务器的加密密钥。对服务器通过数据通信通道直接连接。由于加密算…

高性能分布式缓存Redis-第二篇章

高性能分布式缓存Redis-第二篇章一、持久化原理1.1、持久化流程&#xff08;落盘&#xff09;1.2、RDB详解1.2.1、介绍1.2.2、触发&原理1.2.3、实现1.2.4、RDB总结1.3、AOF详解1.3.1、概念1.3.2、AOF 持久化的实现1.3.2、开启1.3.4、命令追加1.3.5、文件写入和同步&#xf…

SQL 别名

通过使用 SQL&#xff0c;可以为表名称或列名称指定别名。 SQL 别名 通过使用 SQL&#xff0c;可以为表名称或列名称指定别名。 基本上&#xff0c;创建别名是为了让列名称的可读性更强。 列的 SQL 别名语法 SELECT column_name AS alias_name FROM table_name; 表的 SQL …

dubbo学习笔记4(小d课堂)

dubbo高级特性 服务分组及其配置 我们再来创建一个实现类&#xff1a; 接下来我们在xml中去进行配置&#xff1a; 现在我们去运行看是否会有错误呢&#xff1f; 我们有两个服务实现类&#xff0c;那运行的时候到底执行哪个呢&#xff1f; 但是我们想的是可以指定执行哪个实现…

设计模式——访问者模式

访问者模式一、基本思想二、结构图一、基本思想 将作用于某种数据结构中的各元素的操作分离出来封装成独立的类&#xff0c;使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作&#xff0c;为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进…

【安全硬件】Chap.7 对实体芯片采取物理手段破解;芯片IC逆向工程和拆分制造;物理上对芯片的攻击分类;侧信道攻击;Kocher针对RSA的计时攻击

【安全硬件】Chap.7 对实体芯片采取物理手段破解&#xff1b;芯片IC逆向工程和拆分制造&#xff1b;物理上对芯片的攻击分类&#xff1b;侧信道攻击&#xff1b;Kocher针对RSA的计时攻击前言1. 逆向工程Reverse Engineering逆向工程识别芯片上2输入NAND门逆向工程技术Decapulat…

CSS 实例系列

Hello 小伙伴们早上、中午、下午、晚上和深夜好&#xff0c;这里是 jsliang~本 CSS 系列文章&#xff1a;主推学以致用。结合面试题和工作实例&#xff0c;让小伙伴们深入体验 61 个工作常见的 CSS 属性和各种 CSS 知识。主推纯 CSS。尽可能使用 HTML CSS 完成学习目的&#x…

nohup命令详解

nohup命令详解一、背景说明&#xff1a;启动服务的时候&#xff0c;如果使用如下命令&#xff0c;则会在start.sh脚本所在的目录下&#xff0c;产生一个名为 nohup.out 的输出文件nohup ./startup.sh &可以看到下面这个例子&#xff0c;一开始当前目录是没有nohup.out文件的…

RocketMQ 多语言 SDK 开源贡献召集令

作者&#xff1a;艾阳坤 目前 Apache RocketMQ 5.0 SDK [ 1] 正在社区开源&#xff0c;开发与迭代也在火热进行中&#xff0c;欢迎广大社区的朋友们能够参与其中。我们欢迎任何形式的贡献&#xff0c;包括但不限于新 feature、bugfix、代码优化、生态集成、测试工作、文档撰写…

我与 CSDN 的 2022 年终总结

&#x1f482; 个人网站:【海拥】【摸鱼游戏】【神级源码资源网】&#x1f91f; 前端学习课程&#xff1a;&#x1f449;【28个案例趣学前端】【400个JS面试题】&#x1f485; 想寻找共同学习交流、摸鱼划水的小伙伴&#xff0c;请点击【摸鱼学习交流群】 转眼间2023年已经过去…

《后端技术面试 38 讲》学习笔记 Day 02

《后端技术面试 38 讲》学习笔记 Day 02 08丨软件设计的方法论&#xff1a;软件为什么要建模&#xff1f; 原文摘抄 所谓软件建模&#xff0c;就是为要开发的软件建造模型。模型是对客观存在的抽象&#xff0c;我们常说的数学建模&#xff0c;就是用数学公式作为模型&#xf…

flask + Pandas + echarts 使用饼状图等将二手房数据进行分析+可视化

目录 一、实战场景 二、知识点 python 基础语法 python 文件读写 pandas 数据处理 flask web 框架 echarts 图表 bootstrap jinja 模版 三、菜鸟实战 初始化 Flask 框架&#xff0c;设置路由 各行政区房屋数量柱状图分析 区域二手房房源朝向分布情况 二手房单价最…

Higress Kruise Rollout: 渐进式交付为应用发布保驾护航

作者&#xff1a;扬少 前言 在业务高速发展过程中&#xff0c;如何最大化保障功能迭代过程中业务流量无损一直是开发者比较关心的问题。通常在应用发布新功能阶段&#xff0c;我们会采用灰度发布的思想对新版本进行小流量验证&#xff0c;在符合预期之后再进行全量发布&#…

11、JS笔记-内置对象

1.内置对象 js中对象分为三种&#xff1a; 自定义对象、内置对象、浏览器对象&#xff08;js独有&#xff09; 内置对象&#xff1a; js语言自带的对象&#xff0c;供开发者使用&#xff0c;提供一些常用或基本的功能&#xff08;属性和方法&#xff09; 2.Math对象 Math中所…

【云原生】k8s配置资源管理

内容预知 1.Secret的资源配置 1.1 Secret配置的相关说明 1.2 陈述式创建Secret配置 1.3 声明式base64编码创建Secret 1.4 将secret以volume形式挂载到pod中 1.5 将Secret导入到pod中&#xff0c;充当环境变量 1.6 实战运用&#xff1a;使用secret配置免密交互拉取habor…