Spring陷阱:代理

作为Spring框架的用户和发烧友多年,我遇到了一些关于此堆栈的误解和问题。 另外,在某些地方抽象非常可怕地泄漏,以便有效,安全地利用开发人员需要意识到的所有功能。 这就是为什么我开始Spring陷阱系列的原因。 在第一部分中,我们将仔细研究代理的工作原理。

Bean代理是Spring提供的基本功能,也是最重要的基础结构功能之一。 它是如此重要和低级,以至于在大多数情况下我们甚至都没有意识到它的存在。 但是,如果没有交易,面向方面的编程,高级作用域, @ Async支持以及其他各种国内用例,将无法实现。 那么什么是代理 ?

这是一个示例:将DAO注入服务时,Spring将获取DAO实例并将其直接注入。 而已。 但是有时候,Spring需要了解服务(以及任何其他bean)对DAO的每一次调用。 例如,如果DAO被标记为事务性的,则它需要在调用并提交之前启动事务,或者在之后回滚。 当然,您可以手动执行此操作,但是这很繁琐,容易出错,并且混杂了许多问题。 这就是为什么我们首先使用声明式事务的原因。

那么Spring如何实现这种拦截机制呢? 从最简单到最高级的三种方法。 我不会讨论它们的优缺点,我们将在一个具体示例中很快看到它们。

Java动态代理

最简单的解决方案。 如果DAO实现了任何接口,Spring将创建一个Java 动态代理来实现该接口,并注入它而不是真实的类。 真正的代理仍然存在,并且代理引用了它,但是对于外部世界–代理就是bean。 现在,每次您在DAO上调用方法时,Spring都可以拦截它们,添加一些AOP魔术并调用原始方法。

CGLIB生成的类

Java动态代理的缺点是要求Bean至少实现一个接口。 CGLIB通过动态子类化原始bean并通过覆盖每种可能的方法直接添加拦截逻辑来解决此限制。 可以将其视为原始类的子类并在其中调用超级版本:

class DAO {def findBy(id: Int) = //...
}class DAO$EnhancerByCGLIB extends DAO {override def findBy(id: Int) = {startTransactiontry {val result = super.findBy(id)commitTransaction()result} catch {case e =>rollbackTransaction()throw e}}
}

但是,此伪代码并未说明其在现实中的工作方式-引入了另一个问题,请继续关注。

AspectJ编织

从开发人员的角度来看,这是最具侵入性但也是最可靠和直观的解决方案。 在这种模式下,侦听直接应用于您的类字节码,这意味着您的JVM运行的类与您编写的类不同。 在构建–编译时编织(CTW)或在加载类–加载时间编织(LTW)期间,AspectJ weaver通过直接修改类的字节码来添加拦截逻辑。

如果您对如何在后台实现AspectJ魔术感到好奇,则可以使用预先通过AspectJ编织编译的,经过反编译和简化的.class文件:

public void inInterfaceTransactional()
{try{AnnotationTransactionAspect.aspectOf().ajc$before$1$2a73e96c(this, ajc$tjp_2);throwIfNotInTransaction();}catch(Throwable throwable){AnnotationTransactionAspect.aspectOf().ajc$afterThrowing$2$2a73e96c(this, throwable);throw throwable;}AnnotationTransactionAspect.aspectOf().ajc$afterReturning$3$2a73e96c(this);
}

使用加载时编织,在加载类时,将在运行时发生相同的转换。 如您所见,这里没有任何干扰,实际上,这正是您手动编程事务的方式。 旁注:您还记得病毒在操作系统将可执行文件加载后将其代码附加到可执行文件中或动态注入自身的时候吗?

了解代理技术对于了解代理如何工作以及如何影响代码非常重要。 让我们坚持声明式事务划分示例,这是我们的战场:

trait FooService {def inInterfaceTransactional()def inInterfaceNotTransactional();
}@Service
class DefaultFooService extends FooService {private def throwIfNotInTransaction() {assume(TransactionSynchronizationManager.isActualTransactionActive)}def publicNotInInterfaceAndNotTransactional() {inInterfaceTransactional()publicNotInInterfaceButTransactional()privateMethod();}@Transactionaldef publicNotInInterfaceButTransactional() {throwIfNotInTransaction()}@Transactionalprivate def privateMethod() {throwIfNotInTransaction()}@Transactionaloverride def inInterfaceTransactional() {throwIfNotInTransaction()}override def inInterfaceNotTransactional() {inInterfaceTransactional()publicNotInInterfaceButTransactional()privateMethod();}
}

方便的throwIfNotInTransaction()方法…如果未在事务中调用,则引发异常。 谁曾想到? 从不同的地方和不同的配置调用此方法。 如果您仔细检查方法的调用方式,那么所有方法都应该起作用。 但是,我们的开发人员的生活往往是残酷的。 第一个障碍是意外的: ScalaTest不支持通过专用运行器进行 Spring集成测试 。 幸运的是,这可以通过简单的特征轻松地移植(处理依赖注入以测试用例和应用程序上下文缓存):

trait SpringRule extends AbstractSuite { this: Suite =>abstract override def run(testName: Option[String], reporter: Reporter, stopper: Stopper, filter: Filter, configMap: Map[String, Any], distributor: Option[Distributor], tracker: Tracker) {new TestContextManager(this.getClass).prepareTestInstance(this)super.run(testName, reporter, stopper, filter, configMap, distributor, tracker)}}

请注意,我们不会像原始测试框架那样开始和回退事务。 不仅因为它会干扰我们的演示,而且因为我发现事务测试有害–将来还会对此产生更多影响。 回到我们的示例,这是一个烟雾测试。 完整的源代码可以在这里从proxy-problem分支下载。 不要抱怨缺少断言–在这里,我们仅测试未引发异常:

@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule{@Resourceprivate val fooService: FooService = nulltest("calling method from interface should apply transactional aspect") {fooService.inInterfaceTransactional()}test("calling non-transactional method from interface should start transaction for all called methods") {fooService.inInterfaceNotTransactional()}}

令人惊讶的是,测试失败。 好吧,如果您阅读我的文章已有一段时间了,您应该不会感到惊讶: Spring AOP谜语和Spring AOP谜语揭开了神秘面纱 。 实际上,Spring参考文档对此进行了详细解释,也请查看此SO问题 。 简而言之,非事务方法调用事务方法,但绕过事务代理。 即使似乎很明显,当inInterfaceNotTransactional()调用inInterfaceTransactional()时,事务也应该开始,但事实并非如此。 抽象泄漏。 顺便说一句,还请查看引人入胜的交易策略:了解交易陷阱的更多信息。

还记得我们展示CGLIB工作原理的例子吗? 也知道多态性是如何工作的,似乎使用基于类的代理应该会有所帮助。 现在,inInterfaceNotTransactional()调用被CGLIB / Spring覆盖的inInterfaceTransactional(),后者依次调用原始类。 没有机会! 这是伪代码的真正实现:

class DAO$EnhancerByCGLIB extends DAO {val target: DAO = ...override def findBy(id: Int) = {startTransactiontry {val result = target.findBy(id)commitTransaction()result} catch {case e =>rollbackTransaction()throw e}}
}

Spring首先创建原始bean,然后创建一个将原始bean(某种Decorator模式)包装在一个后处理器中的子类,而不是对其进行实例化和实例化。 再次,这意味着bean内部的self调用会绕过我们的类的AOP代理。 当然,使用CGLIB会改变bean的行为方式。 例如,我们现在可以注入具体的类而不是接口,实际上甚至不需要接口,并且在这种情况下需要CGLIB代理。 还有一些缺点–不再可以进行构造函数注入,请参阅SPR-3150 ,这是一个遗憾 。 那么,一些更彻底的测试呢?

@RunWith(classOf[JUnitRunner])
@ContextConfiguration
class DefaultFooServiceTest extends FunSuite with ShouldMatchers with SpringRule {@Resourceprivate val fooService: DefaultFooService = nulltest("calling method from interface should apply transactional aspect") {fooService.inInterfaceTransactional()}test("calling non-transactional method from interface should start transaction for all called methods") {fooService.inInterfaceNotTransactional()}test("calling transactional method not belonging to interface should start transaction for all called methods") {fooService.publicNotInInterfaceButTransactional()}test("calling non-transactional method not belonging to interface should start transaction for all called methods") {fooService.publicNotInInterfaceAndNotTransactional()}}

请选择将失败的测试(准确选择两个)。 你能解释为什么吗? 同样,常识表明一切都应该通过,但事实并非如此。 您可以自己玩耍,请参阅基于类的代理分支。

我们不是在这里揭露问题,而是要克服它们。 不幸的是,我们纠缠不清的服务等级只能使用重型火炮来解决-真正的AspectJ编织。 编译和加载时编织均使测试通过。 相应地请参见aspectj-ctw和aspectj-ltw分支。

您现在应该问自己几个问题。 我应该采取哪种方法(或:我真的需要使用AspectJ?),为什么还要打扰? - 在其他人中。 我会说–在大多数情况下,简单的Spring代理就足够了。 但是,您绝对必须知道传播是如何工作的,何时不起作用。 否则会发生坏事。 提交和回滚发生在意外的地方,跨越了意外的数据量,ORM 脏检查不起作用,看不见的记录–相信,这种事情是疯狂发生的。 请记住,我们在这里介绍的主题不仅适用于交易,还适用于AOP的所有方面。

参考: Spring陷阱: NoBlogDefFound博客上的 JCG合作伙伴 Tomasz Nurkiewicz的代理 。

相关文章 :
  • Spring声明式事务示例
  • Spring依赖注入技术的发展
  • Spring和AspectJ的领域驱动设计
  • Spring 3使用JUnit 4进行测试– ContextConfiguration和AbstractTransactionalJUnit4SpringContextTests
  • 使用Spring AOP进行面向方面的编程
  • Java教程和Android教程列表

翻译自: https://www.javacodegeeks.com/2011/11/spring-pitfalls-proxying.html

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

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

相关文章

UVa11925 Generating Premutations

留坑(p.254) 1 #include<cstdio>2 #include<cstring>3 #include<cstdlib>4 #include<algorithm>5 #include<iostream>6 7 using namespace std;8 9 void setIO(const string& s) { 10 freopen((s ".in").c_str(), "r&qu…

xamarin UWP中MessageDialog与ContentDialog的区别

MessageDialog与ContentDialog的异同点解析&#xff1a; 相同点一&#xff1a;都是uwp应用上的一个弹窗控件。都能做为弹出应用。 相异点一&#xff1a;所在命名空间不同&#xff0c;MessageDialog在Windows.UI.Popups.MessageDialog下&#xff0c;而ContentDialog在Windows.UI…

python筛选大量数据_python(数据筛选)

在Python3中&#xff1a;(1)xrange的功能合并到range里面&#xff0c;xrange已经不存在 -> range和xrange用法(2)filter已经不能返回一个list&#xff0c;而是只能返回一个迭代对象&#xff0c;需要套在一个list()里面&#xff0c;且&#xff0c;需要注意的是&#xff0c;fi…

ORA-12514: TNS: 监听程序当前无法识别连接描述符中请求的服务

不指定数据库可以正常连接&#xff1a; 指定数据库和使用PL/SQL Developer都出现错误&#xff1a; 在此说明一下我的环境&#xff1a;Oralce装的是64位的在使用PL/SQL Developer时曾出现过初始化错误&#xff0c;解决办法就是下载oracle 32位客户端并相应的配置。 解决方案一&a…

Devoxx 2011印象

Devoxx 2011结束了&#xff0c;它很棒。 最终&#xff0c;在不得不与妻子和孩子度过周末之后&#xff08;上个星期我很少见过&#xff09;&#xff0c;我找到了写下一些东西的时间。 对我来说&#xff0c;这是第六个Devoxx&#xff0c;我的第一个是2006年-那时我还是一个学生&a…

Ubuntu14.04.3,apt-get出现dpkg: error processing package xxx (--configure)和cups-daemon错误的解决方案...

Ubuntu14.04.3&#xff0c;使用apt-get安装软件的时候&#xff0c;报个莫名其妙的错误&#xff1a; dpkg: error processing package xxx (--configure): balabala...Errors were encountered while processing: cups-daemon cups-core-drivers cups E: Sub-process /usr/bin/d…

实验三 类的继承和多态性

实验三 类的继承和多态性 1.(1)编写一个接口ShapePara&#xff0c;要求&#xff1a; 接口中的方法&#xff1a; int getArea()&#xff1a;获得图形的面积。int getCircumference()&#xff1a;获得图形的周长 (2)编写一个圆类Circle&#xff0c;要求&#xff1a;圆类Circle实现…

ORA-01843:无效的月份

Oracle数据库默认情况下&#xff0c;会以DD-MON-YY的形式显示日期&#xff0c;其中DD是天数&#xff0c;MON是月份的前三个字母&#xff08;大写&#xff09;&#xff0c;而YY是年份的最后两位。数据库实际上会为年份存储4位数字&#xff0c;但是默认情况下只会显示最后两位。 …

贪心策略取得最优解的条件_什么是贪心算法?

一、什么是贪心算法贪心算法是指&#xff0c;在对问题求解时&#xff0c;总是做出在当前看来是最好的选择。(局部最优解&#xff0c;而不是整体最优解)贪心算法没有固定的算法框架&#xff0c;算法设计的关键是贪心策略的选择。必须注意的是&#xff0c;贪心算法不是对所有问题…

Devoxx第1天

参加Devoxx给我带来了足够的动力来发布我的第一篇博客文章。 我是第一次来这里&#xff0c;它的组织方式给我留下了深刻的印象。 目前有记录的最高发言人。 对我来说&#xff0c;选择演示文稿来参加是一个问题。 但是感谢组织者&#xff0c;所有活动都将在12月下旬在parleys.co…

Oracle 事务的开始与结束

事务是用来分割数据库活动的逻辑工作单元&#xff0c;事务即有起点&#xff0c;也有终点&#xff1b; 事物的处理就是保证数据操作的完整性&#xff0c;所有的操作要么成功要么同时失败。当下列事件之一发生时&#xff0c;事务就开始了&#xff1a;连接到数据库上&#xff0c;并…

http tcp联系区别

术语TCP/IP代表传输控制协议/网际协议&#xff0c;指的是一系列协议。“IP”代表网际协议&#xff0c;TCP和UDP使用该协议从一个网络传送数据包到另一个网络。把IP想像成一种高速公路&#xff0c;它允许其它协议在上面行驶并找到到其它电脑的出口。TCP和UDP是高速公路上的“卡车…

python控件随窗口变化而适配_Tkinter窗口/控件比例调整

我目前正在为一个编程类开发一个pythongui版本的Reversi。我已经对游戏逻辑进行了编程&#xff0c;目前我正在尝试使用Tkinter实现GUI。我有一些问题&#xff0c;调整游戏板(根窗口)和它的一切(画布和形状)成比例。这款游戏目前还不错&#xff0c;但我试图让棋盘正确调整大小的…

Java递归基础

对于那些不知道递归是什么的人&#xff08;并且像个笑声一样&#xff09;&#xff0c;请单击以下链接&#xff1a;Google搜索&#xff1a;递归&#xff0c;然后单击“您的意思是……”项。 希望您终于弄清楚了递归是指其自身的任何内容&#xff08;如果不是&#xff0c;那么您可…

我是最棒的,我一定会成功!

有人曾经做过这样一个实验&#xff1a;他往一个玻璃杯里放进一只跳蚤&#xff0c;发现跳蚤立即轻易地跳了出来。再重复几遍&#xff0c;结果还是一样。根据测试&#xff0c;跳蚤跳的高度一般可达它身体的400倍左右&#xff0c;所以说跳蚤可以称得上是动物界的跳高冠军。     …

头部ct能检查出什么_【安全用药】做CT检查时应注意什么?

点击蓝字 关注我们安安徽徽&#xff0c;你知道做CT检查时应注意什么&#xff1f;上腹部CT检查前患者至少禁食6小时、检查前15分钟喝温开水充盈胃部、CT检查时&#xff0c;患者会受到一定量X射线辐射&#xff0c;应避免过度扫描......本期安全用药&#xff0c;大家一起来了解了解…

JAXB,SAX,DOM性能

这篇文章探讨了使用多种不同方法将XML文档编组为Java对象的性能。 XML文档非常简单。 它包含一个Person实体的集合。 <?xml version"1.0" encoding"UTF-8" standalone"yes"?> <persons><person><id>person0</id>…

虚拟机Linux图形界面配置NAT-桥接

点开“虚拟机->设置->桥接模式&#xff08;勾选复制物理网络连接状态&#xff09;->确认” 点击“右上角扇形网络图标->Edit Connections->Wired->选中->Delete->Add->IPv4 Settings->Method(Manual)->Add->输入IP&#xff0c;子网掩码&am…

年轻人应该谨记的十点

有个朋友的孩子今年大学毕业&#xff0c;托我帮他找个“好工作”&#xff0c;而且再三强调&#xff0c;这关系到孩子的前途命运&#xff0c;要我一定要全力以赴。他&#xff0c;一个非名牌大学的计算机网络专业应届毕业生&#xff0c;没有工作经验&#xff0c;他能找一个什么样…

python自动化构建工具_Python自动化构建工具scons使用入门笔记

这段时间用到了scons&#xff0c;这里总结下&#xff0c;也方便我以后查阅。一、安装sconsLinux环境(以CentOS为例)1、yum安装yum install scons2、源码安装下载scons&#xff1a;http://http://jaist.dl.sourceforge.net/project/scons/scons/2.3.0/scons-2.3.0.zip安装scons&…