@Stateless
public class TradeManagerImpl implements TradeManager {
@EJB TradeLocalHome tradeHome; // JNDI 依賴
public BigDecimal getPrice(String symbol) { /* ... */ }
public void setPrice(String symbol, BigDecimal price) { /* ... */ }
}
public class TradeDao implements TradeManager {
private TradeLocalHome tradeHome;
public TradeDao(TradeLocalHome tradeHome) { this.tradeHome = tradeHome; }
// 純業務邏輯,無 EJB 介面依賴
}
public class TradeManagerEJB extends AbstractStatelessSessionBean {
private TradeManager tradeManager;
protected void onEjbCreate() {
tradeManager = (TradeManager) getBeanFactory().getBean("tradeManager");
}
public BigDecimal getPrice(String symbol) { return tradeManager.getPrice(symbol); }
public void setPrice(String symbol, BigDecimal price) { tradeManager.setPrice(symbol, price); }
}
<bean id="tradeManager" class="com.example.TradeDao">
<constructor-arg>
<bean class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<bean class="org.springframework.beans.factory.config.FieldRetrievingFactoryBean">
<property name="staticField" value="com.example.TradeLocalHome.JNDI_NAME"/>
</bean>
</property>
<property name="proxyInterface" value="com.example.TradeLocalHome"/>
</bean>
</constructor-arg>
</bean>
本地無報價時,自動呼叫 Yahoo Finance API 補全。
public class ForeignTradeAdvice implements MethodInterceptor {
private TradeManager externalFeed; // YahooFeed
public ForeignTradeAdvice(TradeManager externalFeed) { this.externalFeed = externalFeed; }
public Object invoke(MethodInvocation invocation) throws Throwable {
Object result = invocation.proceed(); // 執行原始 getPrice
if (result == null) { // 本地無報價
String symbol = (String) invocation.getArguments()[0];
return externalFeed.getPrice(symbol); // 呼叫外部
}
return result;
}
}
<bean id="yahooFeed" class="com.example.YahooFeed"/>
<bean id="foreignTradeAdvisor" class="org.springframework.aop.support.NameMatchMethodPointcutAdvisor">
<property name="mappedName" value="getPrice"/>
<property name="advice">
<bean class="com.example.ForeignTradeAdvice">
<constructor-arg ref="yahooFeed"/>
</bean>
</property>
</bean>
<!-- 代理目標 Bean -->
<bean id="tradeManager" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="proxyInterfaces" value="com.example.TradeManager"/>
<property name="target" ref="baseTradeManager"/> <!-- 原始 TradeDao -->
<property name="interceptorNames">
<list><value>foreignTradeAdvisor</value></list>
</property>
</bean>
<bean id="yahooFeed" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="com.example.YahooFeed"/>
</property>
<property name="interceptorNames">
<list>
<value>yahooFeedCachingAdvisor</value> <!-- 快取 -->
<value>syncPointAdvisor</value> <!-- 同步防重複請求 -->
</list>
</property>
</bean>
public class SyncPointAdvice implements MethodInterceptor {
private Map<String, Object[]> locks = new ConcurrentHashMap<>();
public Object invoke(MethodInvocation inv) throws Throwable {
String symbol = (String) inv.getArguments()[0];
Object[] lock = locks.computeIfAbsent(symbol, k -> new Object[1]);
synchronized (lock) {
if (lock[0] != null) return lock[0]; // 已有結果
lock[0] = inv.proceed(); // 首次請求
return lock[0];
}
}
}
@Test
public void testForeignTradeAdvice() {
Mock baseMock = mock(TradeManager.class);
Mock yahooMock = mock(TradeManager.class);
when(baseMock.getPrice("IBM")).thenReturn(null);
when(yahooMock.getPrice("IBM")).thenReturn(new BigDecimal("123.45"));
// 使用 BeanPostProcessor 替換 Spring Context 中的 Bean
ApplicationContext ctx = new ClassPathXmlApplicationContext("aop.xml") {
@Override
protected void prepareBeanFactory(ConfigurableListableBeanFactory bf) {
bf.addBeanPostProcessor(new BeanPostProcessor() {
public Object postProcessBeforeInitialization(Object bean, String name) {
if ("baseTradeManager".equals(name)) return baseMock;
if ("yahooFeed".equals(name)) return yahooMock;
return bean;
}
});
}
};
TradeManager proxy = (TradeManager) ctx.getBean("tradeManager");
assertEquals(new BigDecimal("123.45"), proxy.getPrice("IBM"));
verify(baseMock).getPrice("IBM");
verify(yahooMock).getPrice("IBM");
}
AbstractStatelessSessionBean 整合 Spring Context
3. AOP 實現非侵入式功能擴展(外部報價、快取、同步、審計)
4. 切面獨立可測,支援 Mock 驗證邏輯正確性
5. 透過 Spring 配置組裝,無需修改 Java 代碼即可調整行為