网站设计学什么专业,台州建站服务,p2f网站系统,重庆建设银行官方网站首页作为Spring框架的用户和发烧友多年#xff0c;我遇到了一些关于此堆栈的误解和问题。 另外#xff0c;在某些地方抽象非常可怕地泄漏#xff0c;以便有效#xff0c;安全地利用开发人员需要意识到的所有功能。 这就是为什么我开始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