顯示具有 Spring 標籤的文章。 顯示所有文章
顯示具有 Spring 標籤的文章。 顯示所有文章
2015-04-27 17:49

[轉載] Spring 使用 Cache、整合 Ehcache

轉載自:Spring使用Cache、整合Ehcache

Spring 使用 Cache


從 3.1 開始,Spring 引入了對 Cache 的支持。其使用方法和原理都類似於 Spring 對事務管理的支持。Spring Cache 是作用在方法上的,其核心思想是這樣的:當我們在調用一個緩存方法時會把該方法參數和返回結果作為一個鍵值對存放在緩存中,等到下次利用同樣的參數來調用該方法時將不再執行該方法,而是直接從緩存中獲取結果進行返回。所以在使用 Spring Cache 的時候我們要保證我們緩存的方法對於相同的方法參數要有相同的返回結果。

使用 Spring Cache 需要我們做兩方面的事:
  • 聲明某些方法使用緩存
  • 配置 Spring 對 Cache 的支持

和 Spring 對事務管理的支持一樣,Spring 對 Cache 的支持也有基於注解和基於 XML 配置兩種方式。下面我們先來看看基於注解的方式。


1 基於注解的支持


Spring 為我們提供了幾個注解來支持 Spring Cache。其核心主要是 @Cacheable@CacheEvict。使用 @Cacheable 標記的方法在執行後 Spring Cache 將緩存其返回結果,而使用 @CacheEvict 標記的方法會在方法執行前或者執行後移除 Spring Cache 中的某些元素。下面我們將來詳細介紹一下 Spring 基於注解對 Cache 的支持所提供的幾個注解。


1.1 @Cacheable


@Cacheable 可以標記在一個方法上,也可以標記在一個類上。當標記在一個方法上時表示該方法是支持緩存的,當標記在一個類上時則表示該類所有的方法都是支持緩存的。對於一個支持緩存的方法,Spring 會在其被調用後將其返回值緩存起來,以保證下次利用同樣的參數來執行該方法時可以直接從緩存中獲取結果,而不需要再次執行該方法。Spring 在緩存方法的返回值時是以鍵值對進行緩存的,值就是方法的返回結果,至於鍵的話,Spring 又支持兩種策略,默認策略和自定義策略,這個稍後會進行說明。需要注意的是當一個支持緩存的方法在對像內部被調用時是不會觸發緩存功能的。@Cacheable 可以指定三個屬性,value、key 和 condition。


1.1.1 value 屬性指定 Cache 名稱


value 屬性是必須指定的,其表示當前方法的返回值是會被緩存在哪個 Cache 上的,對應 Cache 的名稱。其可以是一個 Cache 也可以是多個 Cache,當需要指定多個 Cache 時其是一個陣列。
@Cacheable("cache1") // Cache 是發生在 cache1 上的
public User find(Integer id) {
    return null;
}

@Cacheable({"cache1", "cache2"}) // Cache 是發生在 cache1 和 cache2 上的
public User find(Integer id) {
    return null;
}


1.1.2 使用 key 屬性自定義 key


key 屬性是用來指定 Spring 緩存方法的返回結果時對應的 key 的。該屬性支持 SpringEL 表達式。當我們沒有指定該屬性時,Spring 將使用默認策略生成 key。我們這裡先來看看自定義策略,至於默認策略會在後文單獨介紹。

自定義策略是指我們可以通過 Spring 的 EL 表達式來指定我們的 key。這裡的 EL 表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用 "#參數名" 或者 "#p參數index"。下面是幾個使用參數作為 key 的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
    return null;
}

@Cacheable(value="users", key="#p0")
public User find(Integer id) {
    return null;
}

@Cacheable(value="users", key="#user.id")
public User find(User user) {
    return null;
}

@Cacheable(value="users", key="#p0.id")
public User find(User user) {
    return null;
}

除了上述使用方法參數作為 key 之外,Spring 還為我們提供了一個 root 對像可以用來生成 key。通過該 root 對像我們可以獲取到以下信息。
屬性名稱描述示例
methodName當前方法名#root.methodName
method當前方法#root.method.name
target當前被調用的對像#root.target
targetClass當前被調用的對像的 class#root.targetClass
args當前方法參數組成的陣列#root.args[0]
caches當前被調用的方法使用的 Cache#root.caches[0].name

當我們要使用 root 對像的屬性作為 key 時我們也可以將 "#root" 省略,因為 Spring 默認使用的就是 root 對像的屬性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
    return null;
}


1.1.3 condition 屬性指定發生的條件


有的時候我們可能並不希望緩存一個方法所有的返回結果。通過 condition 屬性可以實現這一功能。condition 屬性默認為空,表示將緩存所有的調用情形。其值是通過 SpringEL 表達式來指定的,當為 true 時表示進行緩存處理;當為 false 時表示不進行緩存處理,即每次調用該方法時該方法都會執行一次。如下示例表示只有當 user 的 id 為偶數時才會進行緩存。
@Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
public User find(User user) {
    System.out.println("find user by user " + user);
    return user;
}


1.2 @CachePut


在支持 Spring Cache 的環境下,對於使用 @Cacheable 標注的方法,Spring 在每次執行前都會檢查 Cache 中是否存在相同 key 的緩存元素,如果存在就不再執行該方法,而是直接從緩存中獲取結果進行返回,否則才會執行並將返回結果存入指定的緩存中。@CachePut 也可以聲明一個方法支持緩存功能。與 @Cacheable 不同的是使用 @CachePut 標注的方法在執行前不會去檢查緩存中是否存在之前執行過的結果,而是每次都會執行該方法,並將執行結果以鍵值對的形式存入指定的緩存中。

@CachePut 也可以標注在類上和方法上。使用 @CachePut 時我們可以指定的屬性跟 @Cacheable 是一樣的。
@CachePut("users") // 每次都會執行方法,並將結果存入指定的緩存中
public User find(Integer id) {
    return null;
}


1.3 @CacheEvict


@CacheEvict 是用來標注在需要清除緩存元素的方法或類上的。當標記在一個類上時表示其中所有的方法的執行都會觸發緩存的清除操作。@CacheEvict 可以指定的屬性有 value、key、condition、allEntries 和 beforeInvocation。其中 value、key 和 condition 的語義與 @Cacheable 對應的屬性類似。即 value 表示清除操作是發生在哪些 Cache 上的(對應 Cache 的名稱);key 表示需要清除的是哪個 key,如未指定則會使用默認策略生成的 key;condition 表示清除操作發生的條件。下面我們來介紹一下新出現的兩個屬性 allEntries和 beforeInvocation。


1.3.1 allEntries 屬性


allEntries 是 boolean 類型,表示是否需要清除緩存中的所有元素。默認為 false,表示不需要。當指定了 allEntries 為 true 時,Spring Cache 將忽略指定的 key。有的時候我們需要 Cache 一下清除所有的元素,這比一個一個清除元素更有效率。
@CacheEvict(value="users", allEntries=true)
public void delete(Integer id) {
    System.out.println("delete user by id: " + id);
}


1.3.2 beforeInvocation 屬性


清除操作默認是在對應方法成功執行之後觸發的,即方法如果因為拋出異常而未能成功返回時也不會觸發清除操作。使用 beforeInvocation 可以改變觸發清除操作的時間,當我們指定該屬性值為 true 時,Spring 會在調用該方法之前清除緩存中的指定元素。
@CacheEvict(value="users", beforeInvocation=true)
public void delete(Integer id) {
    System.out.println("delete user by id: " + id);
}

其實除了使用 @CacheEvict 清除緩存元素外,當我們使用 Ehcache 作為實現時,我們也可以配置 Ehcache 自身的驅除策略,其是通過 Ehcache 的配置文件來指定的。由於 Ehcache 不是本文描述的重點,這裡就不多贅述了,想了解更多關於 Ehcache 的信息,請查看我關於 Ehcache 的專欄


1.4 @Caching


@Caching 注解可以讓我們在一個方法或者類上同時指定多個 Spring Cache 相關的注解。其擁有三個屬性:cacheable、put 和 evict,分別用於指定 @Cacheable@CachePut@CacheEvict
@Caching(
    cacheable = @Cacheable("users"),
    evict = {
        @CacheEvict("cache2"),
        @CacheEvict(value = "cache3", allEntries = true)
    }
)
public User find(Integer id) {
    return null;
}


1.5 使用自定義注解


Spring 允許我們在配置可緩存的方法時使用自定義的注解,前提是自定義的注解上必須使用對應的注解進行標注。如我們有如下這麼一個使用 @Cacheable 進行標注的自定義注解。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}

那麼在我們需要緩存的方法上使用 @MyCacheable 進行標注也可以達到同樣的效果。
@MyCacheable
public User findById(Integer id) {
    System.out.println("find user by id: " + id);

    User user = new User();
    user.setId(id);
    user.setName("Name" + id);

    return user;
}



2 配置 Spring 對 Cache 的支持


2.1 聲明對Cache的支持


2.1.1 基於注解


配置 Spring 對基於注解的 Cache 的支持,首先我們需要在 Spring 的配置文件中引入 cache 命名空間,其次通過 <cache:annotation-driven/> 就可以啟用 Spring 對基於注解的 Cache 的支持。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:cache="http://www.springframework.org/schema/cache"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/cache
        http://www.springframework.org/schema/cache/spring-cache.xsd"
>
    <cache:annotation-driven/>
</beans>

<cache:annotation-driven/> 有一個 cache-manager 屬性用來指定當前所使用的 CacheManager 對應的 bean 的名稱,默認是 cacheManager,所以當我們的 CacheManager 的 id 為 cacheManager 時我們可以不指定該參數,否則就需要我們指定了。

<cache:annotation-driven/> 還可以指定一個 mode 屬性,可選值有 proxy 和 aspectj。默認是使用 proxy。當 mode 為 proxy 時,只有緩存方法在外部被調用的時候 Spring Cache 才會發生作用,這也就意味著如果一個緩存方法在其聲明對像內部被調用時 Spring Cache 是不會發生作用的。而 mode 為 aspectj 時就不會有這種問題。另外使用 proxy 時,只有 public 方法上的 @Cacheable 等標注才會起作用,如果需要非 public 方法上的方法也可以使用 Spring Cache 時把 mode 設置為 aspectj。

此外,<cache:annotation-driven/> 還可以指定一個 proxy-target-class 屬性,表示是否要代理 class,默認為 false。我們前面提到的 @Cacheable@cacheEvict 等也可以標注在接口上,這對於基於接口的代理來說是沒有什麼問題的,但是需要注意的是當我們設置 proxy-target-class 為 true 或者 mode 為 aspectj 時,是直接基於 class 進行操作的,定義在接口上的 @Cacheable 等 Cache 注解不會被識別到,那對應的 Spring Cache 也不會起作用了。

需要注意的是 <cache:annotation-driven/> 只會去尋找定義在同一個 ApplicationContext 下的 @Cacheable 等緩存注解。


2.1.2 基於 XML 配置


除了使用注解來聲明對 Cache 的支持外,Spring 還支持使用 XML 來聲明對 Cache 的支持。這主要是通過類似於 aop:advice 的 cache:advice 來進行的。在 cache 命名空間下定義了一個 cache:advice 元素用來定義一個對於 Cache 的 advice。其需要指定一個 cache-manager 屬性,默認為 cacheManager。 cache:advice 下面可以指定多個 cache:caching 元素,其有點類似於使用注解時的 @Caching 注解。cache:caching 元素下又可以指定 cache:cacheable、cache:cache-put 和 cache:cache-evict 元素,它們類似於使用注解時的 @Cacheable@CachePut@CacheEvict。下面來看一個示例:
<cache:advice id="cacheAdvice" cache-manager="cacheManager">
    <cache:caching cache="users">
        <cache:cacheable method="findById" key="#p0"/>
        <cache:cacheable method="find" key="#user.id"/>
        <cache:cache-evict method="deleteAll" all-entries="true"/>
    </cache:caching>
</cache:advice>

上面配置定義了一個名為 cacheAdvice 的 cache:advice,其中指定了將緩存 findById 方法和 find 方法到名為 users 的緩存中。這裡的方法還可以使用通配符 "*",比如 "find*" 表示任何以 "find" 開始的方法。

有了cache:advice 之後,我們還需要引入 aop 命名空間,然後通過 aop:config 指定定義好的 cacheAdvice 要應用在哪些 pointcut 上。如:

<aop:config proxy-target-class="false">
    <aop:advisor advice-ref="cacheAdvice" pointcut="execution(* com.xxx.UserService.*(..))"/>
</aop:config>

上面的配置表示在調用 com.xxx.UserService 中任意公共方法時將使用 cacheAdvice 對應的 cache:advice 來進行 Spring Cache 處理。更多關於 Spring AOP 的內容不在本文討論範疇內。


2.2 配置 CacheManager


CacheManager 是 Spring 定義的一個用來管理 Cache 的接口。Spring 自身已經為我們提供了兩種 CacheManager 的實現,一種是基於 Java API 的 ConcurrentMap,另一種是基於第三方 Cache 實現—— Ehcache,如果我們需要使用其它類型的緩存時,我們可以自己來實現 Spring 的 CacheManager 接口或 AbstractCacheManager 抽像類。下面分別來看看 Spring 已經為我們實現好了的兩種 CacheManager 的配置示例。


2.2.1 基於 ConcurrentMap 的配置


<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <property name="caches">
        <set>
            <bean class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean" p:name="xxx"/>
        </set>
    </property>
</bean>

上面的配置使用的是一個 SimpleCacheManager,其中包含一個名為 "xxx" 的 ConcurrentMapCache。


2.2.2 基於 Ehcache 的配置


<!-- Ehcache 實現 -->
<bean id="ehCacheManagerFactory" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
    <property name="configLocation" value="/WEB-INF/ehcache.xml"/>
</bean>

<bean id="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
    <property name="cacheManager" ref="ehCacheManagerFactory"/>
</bean>

上面的配置使用了一個 Spring 提供的 EhCacheCacheManager 來生成一個 Spring 的 CacheManager,其接收一個 Ehcache 的 CacheManager,因為真正用來存入緩存數據的還是 Ehcache。Ehcache 的 CacheManager 是通過 Spring 提供的 EhCacheManagerFactoryBean 來生成的,其可以通過指定 ehcache 的配置文件位置來生成一個 Ehcache 的 CacheManager。若未指定則將按照 Ehcache 的默認規則取 classpath 根路徑下的 ehcache.xml 文件,若該文件也不存在,則獲取 Ehcache 對應 jar 包中的 ehcache-failsafe.xml 文件作為配置文件。更多關於 Ehcache 的內容這裡就不多說了,它不屬於本文討論的內容,欲了解更多關於 Ehcache 的內容可以參考我之前發布的 Ehcache 系列文章,也可以參考官方文檔等。



3 鍵的生成策略


鍵的生成策略有兩種,一種是默認策略,一種是自定義策略。


3.1 默認策略


默認的 key 生成策略是通過 KeyGenerator 生成的,其默認策略如下:
  • 如果方法沒有參數,則使用 0 作為 key
  • 如果只有一個參數的話則使用該參數作為 key
  • 如果參數多餘一個的話則使用所有參數的 hashCode 作為 key

如果我們需要指定自己的默認策略的話,那麼我們可以實現自己的 KeyGenerator,然後指定我們的 Spring Cache 使用的 KeyGenerator 為我們自己定義的 KeyGenerator。

使用基於注解的配置時是通過 cache:annotation-driven 指定的.
<cache:annotation-driven key-generator="userKeyGenerator"/>
<bean id="userKeyGenerator" class="com.xxx.cache.UserKeyGenerator"/>

而使用基於 XML 配置時是通過 cache:advice 來指定的。
<cache:advice id="cacheAdvice" cache-manager="cacheManager" key-generator="userKeyGenerator">
</cache:advice>

需要注意的是此時我們所有的 Cache 使用的 Key 的默認生成策略都是同一個 KeyGenerator。


3.1 自定義策略


自定義策略是指我們可以通過 Spring 的 EL 表達式來指定我們的 key。這裡的 EL 表達式可以使用方法參數及它們對應的屬性。使用方法參數時我們可以直接使用 "#參數名" 或者 "#p參數index"。下面是幾個使用參數作為 key 的示例。
@Cacheable(value="users", key="#id")
public User find(Integer id) {
    return null;
}

@Cacheable(value="users", key="#p0")
public User find(Integer id) {
    return null;
}

@Cacheable(value="users", key="#user.id")
public User find(User user) {
    return null;
}

@Cacheable(value="users", key="#p0.id")
public User find(User user) {
    return null;
}

除了上述使用方法參數作為 key 之外,Spring 還為我們提供了一個 root 對像可以用來生成 key。通過該 root 對像我們可以獲取到以下信息。
屬性名稱描述示例
methodName當前方法名#root.methodName
method當前方法#root.method.name
target當前被調用的對像#root.target
targetClass當前被調用的對像的 class#root.targetClass
args當前方法參數組成的陣列#root.args[0]
caches當前被調用的方法使用的 Cache#root.caches[0].name

當我們要使用 root 對像的屬性作為 key 時我們也可以將 "#root" 省略,因為 Spring 默認使用的就是 root 對像的屬性。如:
@Cacheable(value={"users", "xxx"}, key="caches[1].name")
public User find(User user) {
    return null;
}



4 Spring 單獨使用 Ehcache


前面介紹的內容是 Spring 內置的對 Cache 的支持,其實我們也可以通過 Spring 自己單獨的使用 Ehcache 的 CacheManager 或 Ehcache 對像。通過在 Application Context 中配置 EhCacheManagerFactoryBean 和 EhCacheFactoryBean,我們就可以把對應的 EhCache 的 CacheManager 和 Ehcache 對像注入到其它的 Spring bean 對像中進行使用。


4.1 EhCacheManagerFactoryBean


EhCacheManagerFactoryBean 是 Spring 內置的一個可以產生 Ehcache 的 CacheManager 對像的 FactoryBean。其可以通過屬性 configLocation 指定用於創建 CacheManager 的 Ehcache 配置文件的路徑,通常是 ehcache.xml文件的路徑。如果沒有指定 configLocation,則將使用默認位置的配置文件創建 CacheManager,這是屬於 Ehcache 自身的邏輯,即如果在 classpath 根路徑下存在 ehcache.xml 文件,則直接使用該文件作為 Ehcache 的配置文件,否則將使用 ehcache-xxx.jar 中的 ehcache-failsafe.xml 文件作為配置文件來創建 Ehcache 的 CacheManager。此外,如果不希望創建的 CacheManager 使用默認的名稱(在 ehcache.xml 文件中定義的,或者是由 CacheManager 內部定義的),則可以通過 cacheManagerName 屬性進行指定。下面是一個配置 EhCacheManagerFactoryBean 的示例。
<!-- 定義 CacheManager -->
<bean id="cacheManager" 
       class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

    <!-- 指定配置文件的位置 -->
    <property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>

    <!-- 指定新建的 CacheManager 的名稱 -->
    <property name="cacheManagerName" value="cacheManagerName"/>
</bean>


4.2 EhCacheFactoryBean


EhCacheFactoryBean 是用來產生 Ehcache 的 Ehcache 對像的 FactoryBean。定義 EhcacheFactoryBean 時有兩個很重要的屬性我們可以來指定。一個是 cacheManager 屬性,其可以指定將用來獲取或創建 Ehcache 的 CacheManager 對像,若未指定則將通過 CacheManager.create() 獲取或創建默認的 CacheManager。另一個重要屬性是 cacheName,其表示當前 EhCacheFactoryBean 對應的是 CacheManager 中的哪一個 Ehcache 對像,若未指定默認使用 beanName 作為 cacheName。若 CacheManager 中不存在對應 cacheName 的 Ehcache 對像,則將使用 CacheManager 創建一個名為 cacheName 的 Cache 對像。此外我們還可以通過 EhCacheFactoryBean 的 timeToIdle、timeToLive 等屬性指定要創建的 Cache 的對應屬性,注意這些屬性只對 CacheManager 中不存在對應 Cache 時新建的 Cache 才起作用,對已經存在的 Cache 將不起作用,更多屬性設置請參考 Spring 的 API 文檔。此外還有幾個屬性是對不管是已經存在還是新創建的 Cache 都起作用的屬性:statisticsEnabled、sampledStatisticsEnabled、disabled、blocking 和 cacheEventListeners,其中前四個默認都是 false,最後一個表示為當前 Cache 指定 CacheEventListener。下面是一個定義 EhCacheFactoryBean 的示例。
<!-- 定義 CacheManager -->
<bean id="cacheManager" 
       class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">

    <!-- 指定配置文件的位置 -->
    <property name="configLocation" value="/WEB-INF/config/ehcache.xml"/>
    <!-- 指定新建的 CacheManager 的名稱 -->
    <property name="cacheManagerName" value="cacheManagerName"/>
</bean>

<!-- 定義一個 Ehcache -->
<bean id="userCache" 
       class="org.springframework.cache.ehcache.EhCacheFactoryBean">
    <property name="cacheName" value="user"/>
    <property name="cacheManager" ref="cacheManager"/>
</bean>


(注:本文是基於 Spring 3.1.0 所寫)
2015-03-06 11:57

Spring JavaMail 筆記

Gmail via SSL
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="smtp.gmail.com" />
    <property name="port" value="465" />
    <property name="username" value="smtp-user" />
    <property name="password" value="smtp-passwd" />

    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtp.socketFactory.class">javax.net.ssl.SSLSocketFactory</prop>
            <prop key="mail.smtp.auth">true</prop>
        </props>
    </property>
</bean>


Gmail via TLS
<bean id="mailSender" class="org.springframework.mail.javamail.JavaMailSenderImpl">
    <property name="host" value="smtp.gmail.com" />
    <property name="port" value="587" />
    <property name="username" value="smtp-user" />
    <property name="password" value="smtp-passwd" />
    <property name="javaMailProperties">
        <props>
            <prop key="mail.smtp.starttls.enable">true</prop>
            <prop key="mail.smtp.auth">true</prop>
        </props>
    </property>
</bean>


Sample Code
package test_mail;

import javax.mail.Message;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;

import org.springframework.context.support.AbstractApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.mail.SimpleMailMessage;
import org.springframework.mail.javamail.JavaMailSender;
import org.springframework.mail.javamail.MimeMessageHelper;

public class TestSpringMail {

    public static void main( String[] args ) throws Exception {

        AbstractApplicationContext context
            = new ClassPathXmlApplicationContext("test_mail/spring-mail.xml");

        JavaMailSender mailSender 
            = (JavaMailSender) context.getBean("mailSender");

        sample1(mailSender);
        sample2(mailSender);
        sample3(mailSender);

        context.close();
    }


    public static void sample1(JavaMailSender mailSender) throws Exception {
        MimeMessage mimeMessage = mailSender.createMimeMessage();

        mimeMessage.setFrom("from@no-spam.com");
        mimeMessage.setRecipients(
            Message.RecipientType.TO, "to@no-spam.com"
        );
        mimeMessage.setSubject("Testing Subject");
        mimeMessage.setContent(
            "<b>Testing Content.</b>", 
            "text/html; charset=utf-8"
        );

        mailSender.send(mimeMessage);
    }


    public static void sample2(JavaMailSender mailSender) throws Exception {
        MimeMessage mimeMessage = mailSender.createMimeMessage();
        MimeMessageHelper message = new MimeMessageHelper(mimeMessage, "utf-8");

        message.setFrom("from@no-spam.com");
        message.setTo("to@no-spam.com");
        message.setSubject("Testing Subject");
        message.setText("<b>Testing Content.</b>", true);

        mailSender.send(mimeMessage);
    }


    public static void sample3(JavaMailSender mailSender) {
        SimpleMailMessage message = new SimpleMailMessage();
        message.setFrom("from@no-spam.com");
        message.setTo("to@no-spam.com");
        message.setSubject("Testing Subject");
        message.setText("Testing Content.");

        mailSender.send(message);
    }

}


參考自:
JavaMail API – Sending email via Gmail SMTP example : Mkyong
Spring – Sending e-mail with attachment : Mkyong
Spring – Define an E-mail template in bean configuration file : Mkyong
Spring – Sending E-mail via Gmail SMTP server with MailSender : Mkyong
2015-03-05 10:24

用 Spring 取得 JSP 執行後的字串內容

為了取得執行後的字串內容,我們需要建立 Response 並且替換 PrintWriter,這樣才有辦法取得執行後的內容。

接著可以從 Spring 的 ViewResolver 去取得 View,再透過 View 去處理 render 前的包裝,最後才由 dispatcher 真正去處理 render 的動作。

想到要建立 Request 跟 Response 就感覺讓人頭痛,還好 Spring 有提供 Mock 的類別可以簡單地去建立 Request 跟 Response。


JspRenderer

package com.orion.webmvc.util;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.Locale;
import java.util.Map;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletContext;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.ui.Model;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;


public class JspRenderer {

    @Autowired
    private ServletContext servletContext;


    private ViewResolver viewResolver;

    public void setViewResolver(ViewResolver viewResolver) {
        this.viewResolver = viewResolver;
    }


    public String render(String viewName, Model model) 
        throws IOException 
    {
        return render(viewName, model.asMap());
    }

    public String render(String viewName, Map<String,Object> modelMap) 
        throws IOException 
    {

        RendererRequest request = new RendererRequest(servletContext);
        RendererResponse response = new RendererResponse();

        try {
            /* 透過 ViewResolver 取得 View 進行 render 的動作 */
            View view = viewResolver.resolveViewName(
                viewName, Locale.getDefault()
            );
            view.render(modelMap, request, response);

            return response.getContentAsString();
        }
        catch(Exception e) {
            throw new IOException(e);
        }
    }
}


class RendererRequest extends MockHttpServletRequest {

    private ServletContext servletContext;

    public RendererRequest(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    @Override
    public RequestDispatcher getRequestDispatcher(String path) {
        /* 需要透過真實的 RequestDispatcher 進行 Render */
        return servletContext.getRequestDispatcher(path);
    }
}


class RendererResponse extends MockHttpServletResponse {

    private StringWriter writer = new StringWriter();

    @Override
    public PrintWriter getWriter() throws UnsupportedEncodingException {
        /* 用 StringWriter 作為輸出的容器 */
        return new PrintWriter(writer);
    }

    @Override
    public boolean isCommitted() {
        /* true 是為了讓 View 可以採用 include 方式 Render 到 Response */
        return true;
    }

    @Override
    public String getContentAsString() throws UnsupportedEncodingException {
        /* 取得 Render 後的內容 */
        return writer.getBuffer().toString();
    }
}


配置 spring.xml

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/views/" />
    <property name="suffix" value=".jsp" />
</bean>

<bean id="jspRenderer" class="com.orion.webmvc.util.JspRenderer">
    <property name="viewResolver" ref="viewResolver"/>
</bean>


使用範例

//@Autowired
//private JspRenderer jspRenderer;

Map<String,Object> jspMap = new HashMap<String,Object>();
jspMap.put("jspMessage", "中文訊息測試");
jspMap.put("costMessage", 4567.89);

String jspOutput = jspRenderer.render("/mailer/test", jspMap);
System.out.println(jspOutput);


參考自:Render and capture the output of a JSP as a String | Technological Oddity
2015-03-03 13:34

[轉載] Spring Collections (List, Set, Map, and Properties)

轉載自:Spring Collections (List, Set, Map, and Properties) example

Spring examples to show you how to inject values into collections type (List, Set, Map, and Properties). 4 major collection types are supported :
  • List – <list/>
  • Set – <set/>
  • Map – <map/>
  • Properties – <props/>


Spring beans

A Customer object, with four collection properties.
package com.mkyong.common;

import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

public class Customer
{
    private List<Object> lists;
    private Set<Object> sets;
    private Map<Object, Object> maps;
    private Properties pros;

    //...
}
See different code snippets to declare collection in bean configuration file.


1. List example

<property name="lists">
    <list>
        <value>1</value>
        <ref bean="PersonBean" />
        <bean class="com.mkyong.common.Person">
            <property name="name" value="mkyongList" />
            <property name="address" value="address" />
            <property name="age" value="28" />
        </bean>
    </list>
</property>


2. Set example

<property name="sets">
    <set>
        <value>1</value>
        <ref bean="PersonBean" />
        <bean class="com.mkyong.common.Person">
            <property name="name" value="mkyongSet" />
            <property name="address" value="address" />
            <property name="age" value="28" />
        </bean>
    </set>
</property>


3. Map example

<property name="maps">
    <map>
        <entry key="Key 1" value="1" />
        <entry key="Key 2" value-ref="PersonBean" />
        <entry key="Key 3">
            <bean class="com.mkyong.common.Person">
                <property name="name" value="mkyongMap" />
                <property name="address" value="address" />
                <property name="age" value="28" />
            </bean>
        </entry>
    </map>
</property>


4. Properties example

<property name="pros">
    <props>
        <prop key="admin">admin@nospam.com</prop>
        <prop key="support">support@nospam.com</prop>
    </props>
</property>


Full Spring’s bean configuration file.

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">

    <bean id="CustomerBean" class="com.mkyong.common.Customer">

        <!-- java.util.List -->
        <property name="lists">
            <list>
                <value>1</value>
                <ref bean="PersonBean" />
                <bean class="com.mkyong.common.Person">
                    <property name="name" value="mkyongList" />
                    <property name="address" value="address" />
                    <property name="age" value="28" />
                </bean>
            </list>
        </property>

        <!-- java.util.Set -->
        <property name="sets">
            <set>
                <value>1</value>
                <ref bean="PersonBean" />
                <bean class="com.mkyong.common.Person">
                    <property name="name" value="mkyongSet" />
                    <property name="address" value="address" />
                    <property name="age" value="28" />
                </bean>
            </set>
        </property>

        <!-- java.util.Map -->
        <property name="maps">
            <map>
                <entry key="Key 1" value="1" />
                <entry key="Key 2" value-ref="PersonBean" />
                <entry key="Key 3">
                    <bean class="com.mkyong.common.Person">
                        <property name="name" value="mkyongMap" />
                        <property name="address" value="address" />
                        <property name="age" value="28" />
                    </bean>
                </entry>
            </map>
        </property>

        <!-- java.util.Properties -->
        <property name="pros">
            <props>
                <prop key="admin">admin@nospam.com</prop>
                <prop key="support">support@nospam.com</prop>
            </props>
        </property>

    </bean>

    <bean id="PersonBean" class="com.mkyong.common.Person">
        <property name="name" value="mkyong1" />
        <property name="address" value="address 1" />
        <property name="age" value="28" />
    </bean>
</beans>

Run it…
package com.mkyong.common;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {

    public static void main( String[] args ) {

        ApplicationContext context 
            = new ClassPathXmlApplicationContext("SpringBeans.xml");

        Customer cust = (Customer)context.getBean("CustomerBean");
        System.out.println(cust);
    }
}

Output
Customer [
    lists=[
        1,
        Person [address=address 1, age=28, name=mkyong1],
        Person [address=address, age=28, name=mkyongList]
    ],

    maps={
        key 1=1,
        key 2=Person [address=address 1, age=28, name=mkyong1],
        key 3=Person [address=address, age=28, name=mkyongMap]
    },

    pros={
        admin=admin@nospam.com,
        support=support@nospam.com
    },

    sets=[
        1,
        Person [address=address 1, age=28, name=mkyong1],
        Person [address=address, age=28, name=mkyongSet]
    ]
]
2015-03-01 15:36

Spring Security 存取控制表示式

常用内建表示式

ps: 定義在 SecurityExpressionRoot

表示式說明
hasRole('role')當前的 User 擁有指定的 Role 就回傳 true
hasAnyRole('role1', 'role2')當前的 User 擁有任一個 Role 就回傳 true
principal當前的 User 的 Principal 物件
authentication當前的 User 的 Authentication 物件
permitAll總是為 true
denyAll總是為 false
isAnonymous()當前的 User 是匿名登入就回傳 true
isRememberMe()當前的 User 是透過 remember-me 登入就回傳 true
isAuthenticated()當前的 User 不是匿名登入就回傳 true
isFullyAuthenticated()當前的 User 不是匿名登入或 remember-me 登入就回傳 true



在方法執行前的驗證


驗證 User 角色
@PreAuthorize("hasRole('ROLE_USER')")
public void create(Contact contact);

驗證參數值是否等於 User 名稱
@PreAuthorize("#contact.name == authentication.name")
public void doSomething(Contact contact);

驗證 User 角色以及來源 IP 區間
@PreAuthorize("hasRole('admin') and hasIpAddress('192.168.1.0/24')")
public void doSomething(Contact contact);



在方法內的驗證


取得角色驗證
@RequestMapping("/index")
public void index(HttpServletRequest request) {
    System.out.println(request.isUserInRole("ROLE_USER"));

    if (request.isUserInRole("admin")) {
        System.out.println("is admin");
    }
}



在 JSP 的驗證


取得 User 名稱
<sec:authentication property="name"/>
<sec:authentication property="principal.username"/>

取得 User IP
<sec:authentication property="details.remoteAddress"/>

取得 User SessionId
<sec:authentication property="details.sessionId"/>

驗證角色為 admin 才顯示
<sec:authorize access="hasRole('admin')">
    <div>is admin</div>
</sec:authorize>

驗證角色為 admin 存入變數 isAdmin
<sec:authorize var="isAdmin" access="hasRole('admin')" />
<c:if test="isAdmin">
    <div>is admin</div>
</c:if>


參考自:15. Expression-Based Access Control

2015-02-10 15:52

JAX-WS RI + Spring 4 自動 Exporting Service

Spring 4 的部分我就不多說了,請自行配置 jar 檔,這裡我用 jaxws-ri-2.2.10.zipjaxws-spring-1.9.jar 進行配置。


AccountService.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;

@WebService
public interface AccountService {

    @WebMethod
    void insertAccount(String acc);

    @WebMethod
    String[] getAccounts(String role);
}
先來定義一個 interface,這主要是給 Client 用的。


AccountServiceImpl.java
package test.jaxws.webservice;

import javax.jws.WebMethod;
import javax.jws.WebService;
import org.springframework.stereotype.Component;

@Component
@WebService(serviceName="AccountService")
public class AccountServiceImpl implements AccountService {

    @Override
    @WebMethod
    public void insertAccount(String acc) {
    }

    @Override
    @WebMethod
    public String[] getAccounts(String role) {
        return new String[] {"tom", "jax"};
    }
}
接者是 Web Service 的主體,@Component 是為了讓 Spring component-scan 自動建立 Bean,@WebService 則是用定義要 Exporting 到 JAX-WS 的 serviceName,由於是透過 Spring 去管理 bean 所以可以用 @Autowired 引用其他資源。


JaxWsRtServletServiceExporter.java
package test.jaxws.webservice;

import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.jws.WebService;
import javax.xml.ws.WebServiceProvider;

import org.jvnet.jax_ws_commons.spring.SpringService;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.CannotLoadBeanClassException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.beans.factory.config.SingletonBeanRegistry;
import com.sun.xml.ws.transport.http.servlet.SpringBinding;

public class JaxWsRtServletServiceExporter implements BeanFactoryAware, InitializingBean {

    private ListableBeanFactory beanFactory;
    private String basePath;

    public void setBasePath(String basePath) {
        this.basePath = basePath;
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        if (!(beanFactory instanceof ListableBeanFactory)) {
            throw new IllegalStateException(getClass().getSimpleName() + " requires a ListableBeanFactory");
        }
        this.beanFactory = (ListableBeanFactory) beanFactory;
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        if (this.beanFactory instanceof SingletonBeanRegistry == false) { return; }

        SpringService service = new SpringService();

        Set<String> beanNames = new LinkedHashSet<String>(this.beanFactory.getBeanDefinitionCount());
        beanNames.addAll(Arrays.asList(this.beanFactory.getBeanDefinitionNames()));

        if (this.beanFactory instanceof ConfigurableBeanFactory) {
            beanNames.addAll(Arrays.asList(((ConfigurableBeanFactory) this.beanFactory).getSingletonNames()));
        }

        SingletonBeanRegistry factory = (SingletonBeanRegistry)this.beanFactory;

        for (String beanName : beanNames) {
            try {
                Class<?> type = this.beanFactory.getType(beanName);
                if (type == null || type.isInterface()) { continue; }

                WebService wsAnnotation = type.getAnnotation(WebService.class);
                WebServiceProvider wsProviderAnnotation = type.getAnnotation(WebServiceProvider.class);
                if (wsAnnotation == null && wsProviderAnnotation == null) { continue; }


                String serviceName;
                if (wsAnnotation != null) {
                    serviceName = wsAnnotation.serviceName();
                } else {
                    serviceName = wsProviderAnnotation.serviceName();
                }

                service.setImpl(type);
                service.setBean(this.beanFactory.getBean(beanName));

                SpringBinding springBinding = new SpringBinding();
                springBinding.setBeanName(serviceName + "Binding");
                springBinding.setUrl(this.basePath + "/" + serviceName);
                springBinding.setService(service.getObject());

                factory.registerSingleton(serviceName + "Binding", springBinding);
            }
            catch (CannotLoadBeanClassException ex) {
                // ignore beans where the class is not resolvable
            }
        }
    }
}
為了可以自動 Exporting Service 到 JaxWs RI Servlet 需要建立一個 Exporter,這裡將具有 @WebService 或 @WebServiceProvider 的 Bean 包裝成 SpringBinding 再註冊到 Spring BeanFactory 裡,這樣 WSSpringServlet 就會取到這邊註冊 SpringBinding Bean 進行 Exporting。


web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 xmlns="http://java.sun.com/xml/ns/javaee" 
 xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
 xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" 
 version="3.0"
>
 <display-name>test-jaxws-rt</display-name>

 <listener>
  <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 </listener>
 <context-param>
  <param-name>contextConfigLocation</param-name>
  <param-value>/WEB-INF/jaxws.xml</param-value>
 </context-param>

 <context-param>
     <param-name>webAppRootKey</param-name>
     <param-value>test-jaxws-rt.root</param-value>
   </context-param> 
 
 <servlet>
  <servlet-name>dispatcher</servlet-name>
  <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  <init-param>
   <param-name>contextConfigLocation</param-name>
   <param-value>/WEB-INF/dispatcher.xml</param-value>
  </init-param>
  <load-on-startup>1</load-on-startup>
 </servlet>
 <servlet-mapping>
  <servlet-name>dispatcher</servlet-name>
  <url-pattern>/</url-pattern>
 </servlet-mapping>

    <servlet>
        <servlet-name>jaxws-servlet</servlet-name>
        <servlet-class>com.sun.xml.ws.transport.http.servlet.WSSpringServlet</servlet-class>
  <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>jaxws-servlet</servlet-name>
        <url-pattern>/service/*</url-pattern>
    </servlet-mapping>
</web-app>
在 web.xml 同時註冊兩個 servlet,分別是 Spring MVC 的 DispatcherServlet 跟 JAX-WS 的 WSSpringServlet,這邊主要是透過 ContextLoaderListener 去啟用全域的 ApplicationContext 去管理所有的 Bean。


jaxws.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.webservice" />

    <bean id="jaxWsRtServletServiceExporter" class="test.jaxws.webservice.JaxWsRtServletServiceExporter">
        <property name="basePath" value="/service"/>
    </bean>
</beans>
jaxws.xml 是給 ContextLoaderListener 的設定,這裡用 component-scan 去找出 test.jaxws.webservice 下有 @Component 的 Bean,以及用 JaxWsRtServletServiceExporter 去轉換所有的 @WebService 到 SpringBinding。


dispatcher.xml
<?xml version="1.0" encoding="utf-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
                        http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                        http://www.springframework.org/schema/context
                        http://www.springframework.org/schema/context/spring-context-4.0.xsd"
>
    <context:component-scan base-package="test.jaxws.controller" />

    <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <property name="prefix" value="/WEB-INF/views/" />
        <property name="suffix" value=".jsp" />
    </bean>

    <bean id="accountWebService" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean" scope="request">
        <property name="serviceInterface" value="test.jaxws.webservice.AccountService"/>
        <property name="wsdlDocumentUrl" value="http://localhost:8090/test-jaxws-rt/service/AccountService?wsdl"/>
        <property name="namespaceUri" value="http://webservice.jaxws.test/"/>
        <property name="serviceName" value="AccountService"/>
        <property name="portName" value="AccountServiceImplPort"/>
    </bean>
</beans>
在這裡用 JaxWsPortProxyFactoryBean 去建立 WebService Client,因為是同一個 Web Content 所以用 scope="request" 延遲建立。


HomeController.java
package test.jaxws.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.ModelAndView;

import test.jaxws.webservice.AccountService;

@Controller
public class HomeController {

    @Autowired
    private WebApplicationContext appContent;

    @RequestMapping("/")
    public ModelAndView index() {
        AccountService service = appContent.getBean("accountWebService", AccountService.class);
        String[] users = service.getAccounts("user");

        ModelAndView model = new ModelAndView("index");
        model.addObject("users", users);

        return model;
    }
}
接下來就是連接的 Client,這邊用 WebApplicationContext 延遲取得 AccountService,在 http://localhost:8080/test-jaxws-rt/service/AccountService 就可以看到 service 的主頁面。


參考資料:
JAX-WS + Spring integration example
22. Remoting and web services using Spring
2015-02-06 13:01

[轉載] spring 的配置文件中 mvc:view-controller path 使用方法

1、重定向
<mvc:view-controller path="/" view-name="redirect:/admin/index"/>
即如果当前路径是 / 则重定向到 /admin/index


2、view name
<mvc:view-controller path="/" view-name=admin/index"/>
如果当前路径是 / 则交给相应的视图解析器直接解析为视图


<bean id="defaultViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver" p:order="2">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="contentType" value="text/html"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
则得到的视图时 /WEB-INF/jsp/admin/index.jsp


不想进 controller,可以在 spring-mvc.xml 中配置静态访问路径
<!-- 访问静态资源文件  -->
<mvc:resources mapping="/images/**" location="/images/" cache-period="31556926"/>

像这样,jsp 文件若放在静态路径 /images 下,可以直接访问,而不经过 controller。
2015-02-06 12:48

[轉載] Spring Bean Scope

轉載自:Spring Bean Scope 學習

在 Spring 中定義一個 Bean 時,可以針對其 scope 加以設定。在新版的 Spring 中,共有五種不同的 scope 可以設定,分別為:
singleton
在 Spring IoC Container,該 bean 只會有單一實例(a single instance),此為 Spring 預設值
prototype
在 Spring IoC Container 中,該 bean 可以有多個實例(any number of object instances)
request
在每一次的 HTTP Request,spring container 會根據 loginAction bean 的定義來建立一個全新的 instance,而且僅在目前的 request 中有效,所以可以放心的去更改 instance 的內部狀態,請求結束,request scope 的 bean instance 會被 destroy
session
針對某個 HTTP Session,spring container 會根據 userPreference bean 的定義來建立一個全新的 instance,同樣的,和 request scope 一樣,可以放心的去更改 instance 內部狀態。
global-session
僅在 portlet 為基礎的 Web 應用下有作用。Porlet 的規範中定義了 global session 的概念。


在定義 scope 時,會在 bean 中定義如下:
<bean id="helloWorld" class="HelloWorld" scope="singleton">

當 scope 設定為 singleton 時,整個 container 只會有一個 instance,所以下方的 obj 和 obj1 都會是同一個 instance。
ApplicationContext context = new ClassPathXmlApplicationContext("Bean.xml");
HelloWorld obj = (HelloWorld) context.getBean("helloWorld");
obj.setMessage("hello");
obj.getMessage();
HelloWorld obj1 = (HelloWorld) context.getBean("helloWorld");
obj1.getMessage();


<bean id="helloWorld" class="HelloWorld" scope="prototype">

如果設定為 prototype 時,obj 和 obj1 將會是不同的 instances,可以分別進行操作。在這裡,singleton 的單一 instance 是用 id 來進行識別,不同 id、相同 class 時,還是可以有多個 instances(getBean傳入不同的id即可)。


request、session 和 global-session 三種 scope 只能在 web 環境中使用,如果在非 web 環境設定,會出現【No Scope registered for scope 'request'】的錯誤。