事务、缓存和AOP:理解Spring中的代理使用

工程 | Michael Isvy | 2012年5月23日 | ...

在Spring框架中,许多技术特性都依赖于代理使用。我们将使用三个示例深入探讨这个主题:事务缓存Java配置

这篇博客文章中显示的所有代码示例都可以在我的github帐户上找到。

事务

第一步:无事务

下面的Service类尚未事务化。让我们首先看看它的原样,然后使其事务化。

@Service
public class AccountServiceImpl  implements AccountService {
 //…

//Not specifying a transaction policy here!
 public void create(Account account) {
 entityManager.persist(account);
 }
}

由于“create”方法不是事务性的,它很可能会抛出异常(因为此 Account 对象不应在事务外部持久化)。

以下是运行时的情况:

第二步:添加事务行为配置

现在让我们在 create(...) 方法上添加 @Transactional

@Service
public class AccountServiceImpl  implements AccountService {
 @PersistenceContext
 private EntityManager entityManager;

 @Transactional
 public void create(Account account) {
 entityManager.persist(account);
 }

}

以下是相应的 Spring 配置


<bean id="transactionManager">
 <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<tx:annotation-driven/>

在 Spring 泛型配置中,我们使用了 <tx:annotation-driven />。这意味着所有 @Transactional 注解都应该在启动时被扫描,并且目标方法应该成为事务性的。那么事务行为发生在何处呢?

启动前,我们仍然拥有与之前相同的文件

在启动时,会创建一个名为 proxy 的新类。该类负责添加事务行为,如下所示:

生成的代理类位于 AccountServiceImpl 之上。它为其添加了事务行为[1]

那么如何确保确实使用了代理呢?为了您自己的理解,回溯代码并亲眼看到您确实在使用代理是很有趣的。

一种简单的方法是打印类名



AccountService accountService = (AccountService) applicationContext.getBean(AccountService.class);
String accountServiceClassName = accountService.getClass().getName();
logger.info(accountServiceClassName);

在我的电脑上,它显示以下输出



INFO : transaction.TransactionProxyTest - $Proxy13

这个类是一个动态代理,由 Spring 使用 JDK 反射 API 生成(更多信息在此处)。

在关闭时(例如,当应用程序停止时),代理类将被销毁,您将只在文件系统上拥有 AccountService 和 AccountServiceImpl

Spring 如何将代理连接到目标类中?

让我们考虑一个使用 AccountService 实例的类
```java

@Controller public class AccountController { private AccountService accountService;

private void setAccountService(AccountService accountService) { this.accountService=accountService; }

//… }



<a href="http://blog.springsource.org/wp-content/uploads/2012/05/proxy-and-target1.png"><img class="aligncenter size-full wp-image-11128" title="proxy-and-target" src="http://blog.springsource.org/wp-content/uploads/2012/05/proxy-and-target1.png" alt="" width="316" height="255" /></a>

</div>
<div>

The attribute accountService is of type AccountService (interface). The variable dependency is on the interface type AccountService, not the implementation type, which reduces the coupling between classes. This is a best practice.

As seen before, both AccountServiceImpl and the generated Proxy implement the interface AccountService.
•    If there is a proxy, Spring injects the proxy
•    If not, Spring injects the instance of type AccountServiceImpl.

</div>
&nbsp;
<h3><a name="cache">Caching</a></h3>
<div>

Declarative caching is a new feature in Spring 3.1 that works like Spring’s declarative transaction support.

The @Cacheable annotation should be used in that way:

</div>
<div>
```java

public class AccountServiceImpl  implements AccountService {

@Cacheable(value="accounts", key="#id")
public Account findAccount (long id) {
 // only enter method body if result is not in the cache already
 }
}

您还应该在 Spring 配置中启用缓存,如下所示


<cache:annotation-driven />

以下是预期的结果


accountService.findAccount (1); // Result stored into cache for key “1”
accountService.findAccount (1); // Result retrieved from cache. Target method not called.
accountService.findAccount (2); // Result stored into cache for key “2”

在运行时,使用代理来添加缓存行为。

注意:Spring 3.1 内置了一个相当简单的缓存实现。通常建议使用其他实现,例如 ehcache。在此处可用的示例应用程序 (https://github.com/michaelisvy/proxy-samples) 中,您会找到同时使用内置缓存实现和 ehcache 的一些示例。

如果 bean 类没有实现任何接口怎么办?

在前面的示例中,我们提到代理应该实现与您的 bean 相同的接口。幸运的是,Spring 也可以代理没有接口的 bean。在许多情况下,实现接口并不是最佳选择。

默认情况下,如果您的 bean 没有实现接口,Spring 会使用技术继承:在启动时,会创建一个新类。它继承自您的 bean 类并在子方法中添加行为。

为了生成此类代理,Spring 使用了一个名为 cglib 的第三方库。不幸的是,这个项目不再活跃。在 Spring 3.2 中,Spring 很可能会默认使用 Javassist 代替(有关更多详细信息,请参见此处)。

Java 配置

注意:本节需要一些 Spring Java 配置的背景知识。如果您不熟悉这种新的配置风格,请随意跳过。

您可以在此处了解有关 Java 配置的更多信息。还有一篇很棒的文章讨论了各种配置风格,可以在此处找到。

使用 Java 配置时,配置存储在一个 Java 类中,例如这个
```java

@Configuration public class JavaConfig {

@Bean public AccountService accountService() {

return new AccountServiceImpl((accountRepository()); } @Bean public AccountRepository accountRepository () { //… }

}



Spring calls the method accountService() every time it needs to wire an instance of the bean “accountService” and this one returns a “new” object of type AccountService. If 10 beans are using a dependency of type AccountService, this method is called 10 times.

However, no matters the Spring configuration has been made using Java Configuration or not, every bean should be a singleton by default. How is that possible and where is the magic happening?
This diagram explains how things work internally:

</div>
<div><a href="http://blog.springsource.org/wp-content/uploads/2012/05/java-config.png"><img class="aligncenter size-full wp-image-11131" title="java-config" src="http://blog.springsource.org/wp-content/uploads/2012/05/java-config.png" alt="" width="507" height="328" /></a></div>
<div>

So the Proxy is adding behavior there. In the case that your bean should be a singleton, the action to turn your Plain Old Java Object into a singleton is performed by a child class (Proxy).

</div>
<div>
&nbsp;
<h3>Conclusion</h3>
We’ve seen some use-cases on how proxies are used inside the Spring framework. There are many other examples: Aspect Oriented Programming, Security (using Spring Security), thread safety, scopes, etc…

If you would like to know more on the impact on performance when using proxies, you can read <a href="http://blog.springsource.org/2007/07/19/debunking-myths-proxies-impact-performance/">Alef Arendsen’s blog entry here</a>.

</div>
<div>

<hr size="1" />

<div><a name="note">[1]</a>to be exact: the proxy class does not contain the transaction code internally. It delegates transaction handling to some classes that are part of the Spring framework. Spring will then handle transactions according to the Transaction Manager you have declared.&nbsp;

</div>
</div>

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有

版权所有 © 2005 -2025Broadcom。保留所有权利。“Broadcom”一词指 Broadcom Inc. 和/或其子公司。
使用条款 隐私 商标指南

Apache®、Apache Tomcat®、Apache Kafka®、Apache Cassandra™ 和 Apache Geode™ 是 Apache Software Foundation 在美国和/或其他国家的商标或注册商标。Java™、Java™ SE、Java™ EE 和 OpenJDK™ 是 Oracle 和/或其关联公司的商标。Kubernetes® 是 Linux Foundation 在美国和其他国家的注册商标。Linux® 是 Linus Torvalds 在美国和其他国家的注册商标。Windows® 和 Microsoft® Azure 是 Microsoft Corporation 的注册商标。“AWS”和“Amazon Web Services”是 Amazon.com Inc. 或其关联公司的商标或注册商标。所有其他商标和版权均为其各自所有者的财产,仅为提供信息之目的提及。其他名称可能是其各自所有者的商标。