(三)Spring 核心之面向切面编程(AOP)—— 代理的创建

目录

一. 前言

二. 代理的创建

2.1. 创建前准备

2.2. 获取所有的 Advisor

2.3. 创建代理的入口方法

2.4. 依据条件创建代理(JDK 或 CGLIB)

三. 动态代理要解决什么问题

3.1. 什么是代理

3.2. 什么是动态代理

四. 总结


一. 前言

    前面两篇文章《(一)Spring 核心之面向切面编程(AOP)—— 配置及使用》和《(二)Spring 核心之面向切面编程(AOP)—— 切面的实现》主要介绍了 Spring AOP 原理解析的切面实现过程,如加载配置,将切面类的所有切面方法根据使用的注解生成对应 Advice,并将 Advice 连同切入点匹配器和切面类等信息一并封装到Advisor。本文在此基础上继续介绍postProcessAfterInitialization 的方法,即代理(JDK 代理和 CGLIB 代理)的创建过程。

二. 代理的创建

2.1. 创建前准备

    创建代理的方法是 postProcessAfterInitialization,如果 Bean 被子类标识为代理,则使用配置的拦截器创建一个代理。

/*** 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() 方法主要用于判断是否需要创建代理,如果 Bean 能够获取到 advisor 才需要创建代理:

/*** Wrap the given bean if necessary, i.e. if it is eligible for being proxied.* @param bean the raw bean instance* @param beanName the name of the bean* @param cacheKey the cache key for metadata access* @return a proxy wrapping the bean, or the raw bean instance as-is*/
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {// 如果bean是通过TargetSource接口获取if (beanName != null && this.targetSourcedBeans.contains(beanName)) {return bean;}// 如果bean是切面类if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {return bean;}// 如果是aop基础类?是否跳过?if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {this.advisedBeans.put(cacheKey, Boolean.FALSE);return bean;}// 重点:获取所有advisor,如果没有获取到,那说明不要进行增强,也就不需要代理了。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;
}

2.2. 获取所有的 Advisor

我们看下获取所有 advisor 的方法 getAdvicesAndAdvisorsForBean():

@Override
@Nullable
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, @Nullable TargetSource targetSource) {List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName);if (advisors.isEmpty()) {return DO_NOT_PROXY;}return advisors.toArray();
}

    通过 findEligibleAdvisors() 方法获取 advisor,如果获取不到返回 DO_NOT_PROXY(不需要创建代理),findEligibleAdvisors() 方法如下:

/*** Find all eligible Advisors for auto-proxying this class.* @param beanClass the clazz to find advisors for* @param beanName the name of the currently proxied bean* @return the empty List, not {@code null},* if there are no pointcuts or interceptors* @see #findCandidateAdvisors* @see #sortAdvisors* @see #extendAdvisors*/
protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) {// 和上文一样,获取所有切面类的切面方法生成AdvisorList<Advisor> candidateAdvisors = findCandidateAdvisors();// 找到这些Advisor中能够应用于beanClass的AdvisorList<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName);// 如果需要,交给子类拓展extendAdvisors(eligibleAdvisors);// 对Advisor排序if (!eligibleAdvisors.isEmpty()) {eligibleAdvisors = sortAdvisors(eligibleAdvisors);}return eligibleAdvisors;
}

获取所有切面类的切面方法生成 Advisor:

/*** Find all candidate Advisors to use in auto-proxying.* @return the List of candidate Advisors*/
protected List<Advisor> findCandidateAdvisors() {Assert.state(this.advisorRetrievalHelper != null, "No BeanFactoryAdvisorRetrievalHelper available");return this.advisorRetrievalHelper.findAdvisorBeans();
}

找到这些 Advisor 中能够应用于 beanClass 的 Advisor:

/*** Determine the sublist of the {@code candidateAdvisors} list* that is applicable to the given class.* @param candidateAdvisors the Advisors to evaluate* @param clazz the target class* @return sublist of Advisors that can apply to an object of the given class* (may be the incoming List as-is)*/
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) {if (candidateAdvisors.isEmpty()) {return candidateAdvisors;}List<Advisor> eligibleAdvisors = new ArrayList<>();for (Advisor candidate : candidateAdvisors) {// 通过Introduction实现的adviceif (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) {eligibleAdvisors.add(candidate);}}boolean hasIntroductions = !eligibleAdvisors.isEmpty();for (Advisor candidate : candidateAdvisors) {if (candidate instanceof IntroductionAdvisor) {// already processedcontinue;}// 是否能够应用于clazz的Adviceif (canApply(candidate, clazz, hasIntroductions)) {eligibleAdvisors.add(candidate);}}return eligibleAdvisors;
}

2.3. 创建代理的入口方法

    获取所有 advisor 后,如果有 advisor,则说明需要增强,即需要创建代理,创建代理的方法如下:

/*** Create an AOP proxy for the given bean.* @param beanClass the class of the bean* @param beanName the name of the bean* @param specificInterceptors the set of interceptors that is* specific to this bean (may be empty, but not null)* @param targetSource the TargetSource for the proxy,* already pre-configured to access the bean* @return the AOP proxy for the bean* @see #buildAdvisors*/
protected Object createProxy(Class<?> beanClass, @Nullable String beanName,@Nullable Object[] specificInterceptors, TargetSource targetSource) {if (this.beanFactory instanceof ConfigurableListableBeanFactory) {AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass);}ProxyFactory proxyFactory = new ProxyFactory();proxyFactory.copyFrom(this);if (proxyFactory.isProxyTargetClass()) {// Explicit handling of JDK proxy targets (for introduction advice scenarios)if (Proxy.isProxyClass(beanClass)) {// Must allow for introductions; can't just set interfaces to the proxy's interfaces only.for (Class<?> ifc : beanClass.getInterfaces()) {proxyFactory.addInterface(ifc);}}}else {// No proxyTargetClass flag enforced, let's apply our default checks...if (shouldProxyTargetClass(beanClass, beanName)) {proxyFactory.setProxyTargetClass(true);}else {evaluateProxyInterfaces(beanClass, proxyFactory);}}Advisor[] advisors = buildAdvisors(beanName, specificInterceptors);proxyFactory.addAdvisors(advisors);proxyFactory.setTargetSource(targetSource);customizeProxyFactory(proxyFactory);proxyFactory.setFrozen(this.freezeProxy);if (advisorsPreFiltered()) {proxyFactory.setPreFiltered(true);}// Use original ClassLoader if bean class not locally loaded in overriding class loaderClassLoader classLoader = getProxyClassLoader();if (classLoader instanceof SmartClassLoader && classLoader != beanClass.getClassLoader()) {classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();}return proxyFactory.getProxy(classLoader);
}

proxyFactory.getProxy(classLoader):

/*** Create a new proxy according to the settings in this factory.* <p>Can be called repeatedly. Effect will vary if we've added* or removed interfaces. Can add and remove interceptors.* <p>Uses the given class loader (if necessary for proxy creation).* @param classLoader the class loader to create the proxy with* (or {@code null} for the low-level proxy facility's default)* @return the proxy object*/
public Object getProxy(@Nullable ClassLoader classLoader) {return createAopProxy().getProxy(classLoader);
}

2.4. 依据条件创建代理(JDK 或 CGLIB)

DefaultAopProxyFactory.createAopProxy:

@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {if (!NativeDetector.inNativeImage() &&(config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config))) {Class<?> targetClass = config.getTargetClass();if (targetClass == null) {throw new AopConfigException("TargetSource cannot determine target class: " +"Either an interface or a target is required for proxy creation.");}if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {return new JdkDynamicAopProxy(config);}return new ObjenesisCglibAopProxy(config);}else {return new JdkDynamicAopProxy(config);}
}

要点如下:

  1. config.isOptimize() 是通过 optimize 设置,表示配置是自定义的,默认是 false;
  2. config.isProxyTargetClass() 是通过 <aop:config proxy-target-class="true" /> 来配置的,表示优先使用 cglib 代理,默认是 false;
  3. hasNoUserSuppliedProxyInterfaces(config) 表示是否目标类实现了接口。

三. 动态代理要解决什么问题

3.1. 什么是代理

    代理模式(Proxy Pattern):为另一个对象提供一个替身或占位符以控制对这个对象的访问。具体请参见《设计模式 - 代理模式》。

    举个简单的例子:我(Client)如果要买(doOperation)房,可以找中介(Proxy)买房,中介直接和卖方(Target)买房。中介和卖方都实现买卖(doOperation)的操作。中介就是代理(Proxy)。

3.2. 什么是动态代理

    动态代理就是:在程序运行期间,创建目标对象的代理对象,并对目标对象中的方法进行功能性增强的一种技术。

    在生成代理对象的过程中,目标对象不变,代理对象中的方法是目标对象方法的增强方法。可以理解为运行期间,对象中方法的动态拦截,在拦截方法的前后执行功能操作。

四. 总结

    Spring 默认在目标类实现接口时是通过 JDK 代理实现的,只有非接口的是通过 CGLIB 代理实现的。当设置 proxy-target-class 为 true 时在目标类不是接口或者代理类时优先使用 CGLIB 代理实现。

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

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

相关文章

MyBatis学习总结

MyBatis分页如何实现 分页分为 逻辑分页&#xff1a;查询出所有的数据缓存到内存里面&#xff0c;在从内存中筛选出需要的数据进行分页 物理分页&#xff1a;直接用数据库语法进行分页limit mybatis提供四种方法分页&#xff1a; 直接在sql语句中分页&#xff0c;传递分页参数…

网贷大数据查询多了对征信有影响吗?

网贷大数据在日常的金融借贷中起到很重要的风控作用&#xff0c;不少银行已经将大数据检测作为重要的风控环节。很多人在申贷之前都会提前了解自己的大数据信用情况&#xff0c;那网贷大数据查询多了对征信有影响吗?本文带你一起去看看。 首先要说结论&#xff1a;那就是查询网…

[极客大挑战2019]upload

该题考点&#xff1a;后缀黑名单文件内容过滤php木马的几种书写方法 phtml可以解析php代码&#xff1b;<script language"php">eval($_POST[cmd]);</script> 犯蠢的点儿&#xff1a;利用html、php空格和php.不解析<script language"php"&…

软件测试工程师经典面试题

软件测试工程师&#xff0c;和开发工程师相比起来&#xff0c;虽然前期可能不会太深&#xff0c;但是涉及的面还是比较广的。前期面试实习生或者一年左右的岗位&#xff0c;问的也主要是一些基础性的问题比较多。涉及的知识主要有MySQL数据库的使用、Linux操作系统的使用、软件…

缓存驱动联邦学习架构赋能个性化边缘智能 | TMC 2024

缓存驱动联邦学习架构赋能个性化边缘智能 | TMC 2024 伴随着移动设备的普及与终端数据的爆炸式增长&#xff0c;边缘智能&#xff08;Edge Intelligence, EI&#xff09;逐渐成为研究领域的前沿。在这一浪潮中&#xff0c;联邦学习&#xff08;Federated Learning, FL&#xf…

leetcode hot100零钱兑换Ⅱ

本题可以看出也是背包问题&#xff0c;但区别于之前的01背包问题&#xff0c;这个是完全背包问题的变形形式。 下面介绍01背包和完全背包的区别与联系&#xff1a; 01背包是背包中的物品只能用一次&#xff0c;不可以重复使用&#xff0c;而完全背包则是可以重复使用。01/完全…

一个基于C#开发的、开源的特殊字符输入法

emoji表情在社交网络非常流行&#xff0c;我们在手机也非常方便输入&#xff0c;但是在PC电脑我们一般需要到归集好的网页拷贝&#xff0c;所以今天推荐一个Windows小工具&#xff0c;让你方便输入特殊字符和emoji表情。 01 项目简介 这是一个基于C#开发的开源项目&#xff0…

ansible及其模块

一、ansible是什么&#xff1f; Ansible是一个基于Python开发的配置管理和应用部署工具&#xff0c;现在也在自动化管理领域大放异彩。它融合了众多老牌运维工具的优点&#xff0c;Pubbet和Saltstack能实现的功能&#xff0c;Ansible基本上都可以实现。 Ansible能批量配置、部…

手动实现new操作符

<script>//前置知识// 每一个函数在创建之初就会有一个prototype属性&#xff0c;这个属性指向函数的原型对象// function abc(){// }// abc.prototype--> {constructor: f}// 在JS中任意的对象都有内置的属性叫做[[prototype]]这是一个私有属性&#xff0c;这个私有属…

如何用GPT进行论文写作?

一&#xff1a;AI领域最新技术 1.OpenAI新模型-GPT-5 2.谷歌新模型-Gemini Ultra 3.Meta新模型-LLama3 4.科大讯飞-星火认知 5.百度-文心一言 6.MoonshotAI-Kimi 7.智谱AI-GLM-4 二&#xff1a;GPT最新技术 1.最新大模型GPT-4 Turbo 2.最新发布的高级数据分析&#x…

安宝特AR汽车行业解决方案系列1-远程培训

在汽车行业中&#xff0c;AR技术的应用正悄然改变着整个产业链的运作方式&#xff0c;应用涵盖培训、汽修、汽车售后、PDI交付、质检以及汽车装配等&#xff0c;AR技术为多个环节都带来了前所未有的便利与效率提升。 安宝特AR将以系列推文的形式为读者逐一介绍在汽车行业中安宝…

使用 npm/yarn 等命令的时候会,为什么会发生 Error: certificate has expired

缘起 昨天&#xff0c;我写了一篇文章&#xff0c;介绍如何使用项目模板&#xff0c;构建一个 Electron 项目的脚手架&#xff0c;我发现我自己在本地无法运行成功&#xff0c;出现了错误。 ✖ Failed to install modules: ["electron-forge/plugin-vite^7.2.0",&qu…

多维时序 | Matlab实现BiLSTM-MATT双向长短期记忆神经网络融合多头注意力多变量时间序列预测模型

多维时序 | Matlab实现BiLSTM-MATT双向长短期记忆神经网络融合多头注意力多变量时间序列预测模型 目录 多维时序 | Matlab实现BiLSTM-MATT双向长短期记忆神经网络融合多头注意力多变量时间序列预测模型预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.多维时序 | Matlab…

第六十四天 服务攻防-框架安全CVE复现Apache shiroApache Solr

第六十四天 服务攻防-框架安全&CVE复现Apache shiro&Apache Solr 知识点: 中间件及框架列表: IIS,Apache,Nginx,Tomcat,Docker,K8s,Weblogic.JBoos,WebSphere, Jenkins,GlassFish,Jetty,Jira,Struts2,Laravel,Solr,Shiro,Thinkphp,Spring, Flask,jQuery等 1、开发框…

【DDD】学习笔记-薪资管理系统的场景驱动设计

场景驱动设计的起点是领域场景&#xff0c;它不一定需要与事件风暴结合&#xff0c;只要识别并确定了领域场景&#xff0c;就可以进行任务分解。每个分解出来的子任务都可以视为是职责。分配职责时&#xff0c;场景驱动设计规定了履行职责的角色构造型&#xff0c;其中&#xf…

嵌入式学习 Day 22

一. 时间获取: 1.time time_t time(time_t *tloc); 功能: 返回1970-1-1到现在的秒数&#xff08;格林威治时间&#xff09; 参数: tloc:存放秒数空间首地址 返回值: 成功返回秒数 失败返回-1 注意&#xff1a;两种方式都可…

144. Binary Tree Preorder Traversal(二叉树的前序遍历)

问题描述 给你二叉树的根节点 root &#xff0c;返回它节点值的 前序 遍历。 问题分析 因为要以数组的形式返回前序遍历序列所以与普通的前序遍历多了一步将返回的数组合并的过程&#xff0c;将当前二叉树的左子树&#xff0c;根&#xff0c;右子树按照顺序合并成为一个数组…

【漏洞复现】大华DSS视频管理系统信息泄露漏洞

Nx01 产品简介 大华DSS数字监控系统是一个在通用安防视频监控系统基础上设计开发的系统&#xff0c;除了具有普通安防视频监控系统的实时监视、云台操作、录像回放、报警处理、设备治理等功能外&#xff0c;更注重用户使用的便利性。 Nx02 漏洞描述 大华DSS视频管理系统存在信…

人工智能_CPU安装运行ChatGLM大模型_安装清华开源人工智能AI大模型ChatGlm-6B_004---人工智能工作笔记0099

上一节003节我们安装到最后,本来大模型都可以回答问题了,结果, 5分钟后给出提示,需要GPU,我去..继续看官网,如何配置CPU运行 没办法继续看: https://github.com/THUDM/ChatGLM-6B 这里是官网可以看到 需要gcc的版本是11.3.0,这里我们先没有去安装,直接试试再说 yum instal…

支持Intel Core i5/i7的高性能三防加固平板|亿道三防onerugged

大家好&#xff01;今天我要向大家推荐一款真正强悍的三防加固平板电脑&#xff0c;它支持Intel Core i5/i7高性能处理器&#xff0c;给你带来无与伦比的高性能体验。是不是心动了呢&#xff1f;那就让我为你揭开这款亿道三防onerugged系列的产品的神秘面纱吧&#xff01; 首先…