交易、快取與 AOP:理解 Spring 中的 Proxy 用法

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

在 Spring framework 中,許多技術特性都依賴 Proxy 的使用。我們將使用三個例子深入探討這個主題:交易 (Transactions)快取 (Caching) Java 配置 (Java Configuration)

本部落格文章中顯示的所有程式碼範例都可以在 我的 github 帳戶上找到。

交易 (Transactions)

第一步:沒有交易

下面的 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。 這個類別負責添加交易行為,如下所示

生成的 Proxy 類別位於 AccountServiceImpl 之上。 它為其添加了交易行為 [1]

那麼如何確定是否確實使用了 Proxy 呢? 為了您自己的理解,回到程式碼中並親眼目睹您確實正在使用 Proxy 是很有趣的。

一種簡單的方法是印出類別名稱



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

在我的電腦上,它顯示以下輸出



INFO : transaction.TransactionProxyTest - $Proxy13

這個類別是一個 Dynamic Proxy,由 Spring 使用 JDK Reflection API 生成(更多資訊請參考 這裡)。

在關閉時(例如,當應用程式停止時),Proxy 類別將被銷毀,並且您將只在檔案系統上擁有 AccountService 和 AccountServiceImpl

Spring 如何將 Proxy 連接到目標類別呢?

讓我們考慮一個使用 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”

在運行時,使用 Proxy 來添加快取行為。

*注意:Spring 3.1 嵌入了一個相當簡單的快取實作。 通常建議使用其他實作,例如 ehcache。 在此處提供的範例應用程式 (https://github.com/michaelisvy/proxy-samples) 中,您會找到一些使用嵌入式快取實作和 ehcache 的範例。*

如果 Bean 類別沒有實作任何介面怎麼辦?

在前面的例子中,我們提到 Proxy 應該實作與您的 Bean 相同的介面。 值得慶幸的是,Spring 也可以 Proxy 沒有介面的 Bean。 在許多情況下,實作介面並不是最好的方法。

預設情況下,如果您的 Bean 沒有實作介面,Spring 會使用技術繼承:在啟動時,會建立一個新的類別。 它繼承自您的 Bean 類別,並在子方法中添加行為。

為了生成這樣的 Proxy,Spring 使用了一個稱為 cglib 的第三方函式庫。 不幸的是,這個專案不再活躍。 在 Spring 3.2 中,Spring 很可能會預設使用 Javassist 代替 (在此處查看更多詳細資訊)。

Java 配置 (Java Configuration)

注意:本節需要一些 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 社群中所有即將到來的活動。

查看所有