在〈在 AOP 之前〉中,雖然可以對 AccountDAOJdbcImpl
的每個方法呼叫進行日誌,而又不用修改 AccountDAOJdbcImpl
,然而,AccountDAOLoggingProxy
只能服務 AccountDAO
型態,如果能有個方式能為不同型態建立代理物件的話就好了。
Reflection API 中提供動態代理相關類別,可讓你不必為特定介面實作特定代理物件,使用動態代理機制,可使用一個處理器代理多個介面的實作物件。
處理者類別必須實作 java.lang.reflect.InvocationHandler
介面,底下以實例進行說明。例如設計一個 LoggingProxy
類別:
package cc.openhome.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Logger;
public class LoggingProxy implements InvocationHandler {
private Object target;
private Logger logger;
public LoggingProxy(Object target) {
this.target = target;
logger = Logger.getLogger(target.getClass().getName());
}
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Object result = null;
try {
logger.info(String.format("%s.%s(%s)",
target.getClass().getName(), method.getName(), Arrays.toString(args)));
result = method.invoke(target, args);
} catch (IllegalAccessException | IllegalArgumentException |
InvocationTargetException e){
throw new RuntimeException(e);
}
return result;
}
}
接著就可以使用 Proxy.newProxyInstance
方法建立代理物件,呼叫時必須指定類別載入器,告知要代理的介面,以及介面上定義方法被呼叫時的處理者(InvocationHandler
實例),Proxy.newProxyInstance
方法會在執行時期生成代理物件,代理物件實作了指定的介面。
package cc.openhome;
import java.lang.reflect.Proxy;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
import cc.openhome.model.AccountDAO;
import cc.openhome.proxy.LoggingProxy;
@Configuration
@ComponentScan
public class AppConfig {
@Autowired
@Qualifier("accountDAOJdbcImpl")
private AccountDAO accountDAO;
@Bean(destroyMethod="shutdown")
public DataSource dataSource(){
return new EmbeddedDatabaseBuilder()
.setType(EmbeddedDatabaseType.H2)
.addScript("classpath:schema.sql")
.addScript("classpath:testData.sql")
.build();
}
@Bean
public AccountDAO accountDAO() {
return (AccountDAO) Proxy.newProxyInstance(
accountDAO.getClass().getClassLoader(),
accountDAO.getClass().getInterfaces(),
new LoggingProxy(accountDAO)
);
}
}
如果操作 Proxy.newProxyInstance
傳回的代理物件,在每次操作時會呼叫處理者(InvocationHandler
實例)的 invoke
方法,並傳入代理物件、被呼叫方法的 Method
實例與參數值。
搭配上頭的 AppConfig
,UserService
可以修改一下:
...略
@Component
public class UserService {
private final AccountDAO acctDAO;
private final MessageDAO messageDAO;
@Autowired
public UserService(@Qualifier("accountDAO") AccountDAO acctDAO, MessageDAO messageDAO) {
this.acctDAO = acctDAO;
this.messageDAO = messageDAO;
}
...略
LoggingProxy
不再專用於特定型態,如果想再進一步能夠設定哪些方法被呼叫時才進行日誌,可以在 LoggingProxy
的 invoke
中進行條件判斷。
LoggingProxy
不再專用於特定型態,如果想再進一步能夠設定哪些方法被呼叫時才進行日誌,可以在 LoggingProxy
的 invoke
中進行條件判斷,若想更進階一些,想使用設定檔來設定要日誌的方法也是可行的,試著這麼發展下去,慢慢就會形成一個具有 AOP 概念的程式庫,當然,Spring AOP 就具備有這類的能力,直接拿來用就省事多了。
你可以在 BeforeAOP2 找到以上的範例專案。