攔截器已經是各個開源軟件必不可少的功能。 在討論各種問題的時候也經常聽說這個對象被攔截了等等。那麼在JAVA的世界裏, 是怎麼實現攔截器的功能的呢 ? 要了解這些, 必須先從代理類(Proxy)說起,但是我們在這裏不打算從這裏介紹,我們直接上Mybatis的測試代碼。
- public class PluginTest {
- @Test
- public void mapPluginShouldInterceptGet() {
- Map map = new HashMap();
- map = (Map) new AlwaysMapPlugin().plugin(map);
- assertEquals("Always", map.get("Anything"));
- }
- @Test
- public void shouldNotInterceptToString() {
- Map map = new HashMap();
- map = (Map) new AlwaysMapPlugin().plugin(map);
- assertFalse("Always".equals(map.toString()));
- }
- @Intercepts({
- @Signature(type = Map.class, method = "get", args = {Object.class})})
- public static class AlwaysMapPlugin implements Interceptor {
- public Object intercept(Invocation invocation) throws Throwable {
- return "Always";
- }
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- public void setProperties(Properties properties) {
- }
- }
- }
MyBatis 直接抽象了一個plugin的概念,中文意思就是“插頭”吧,很形象。我要對一個對象攔截,我就要把這個插頭插入到目標對象中:
- map = (Map) new AlwaysMapPlugin().plugin(map);
這裏生成了一個AlwaysMapPlugin,並調用plugin方法,把自己插入到Map的一個實例對象中;我們先不管plugin方法裏面到底做了什麼,我們只要知道它還是給我返回了一個Map的實例。
- assertEquals("Always", map.get("Anything"));
接着我們就調用了Map實例的get方法,我們納悶了,我們並沒有給Map實例中添加任何數據,但是卻能得到"Always”。
很神奇吧...
要解開這個神奇,還是要回到AlwaysMapPlugin().plugin(Object object)的方法上來。
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
直接委託給了Plugin類的一個靜態方法:
- public static Object wrap(Object target, Interceptor interceptor) {
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
- Class<?> type = target.getClass();
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
- if (interfaces.length > 0) {
- return Proxy.newProxyInstance(
- type.getClassLoader(),
- interfaces,
- new Plugin(target, interceptor, signatureMap));
- }
- return target;
- }
我們先拋開亂七八糟的邏輯,我們看到在滿足一定的條件的情況下,會返回一個Proxy.newProxyInstance類的一個實例。也就是說AlwaysMapPlugin().plugin(Object object)的方法返回的實例,是有可能和原來的實例不是同一個,而是原來實例的一個代理類。
- public static Object newProxyInstance(ClassLoader loader,
- Class<?>[] interfaces,
- InvocationHandler h)
這個是Proxy.newProxyInstance方法的簽名,不明白的同志自己去了解下。
我們已經得到了目標對象的代理類,可是我們只想對目標對象的某幾個方法進行攔截, 是怎麼做到的呢?
這次我們把目光轉向我們的“插頭”吧。
- @Intercepts({
- @Signature(type = Map.class, method = "get", args = {Object.class})})
- public static class AlwaysMapPlugin implements Interceptor {
- public Object intercept(Invocation invocation) throws Throwable {
- return "Always";
- }
- public Object plugin(Object target) {
- return Plugin.wrap(target, this);
- }
- public void setProperties(Properties properties) {
- }
- }
這裏通過註解告訴我們,會對Map.get(Object obj)的方法進行攔截。而且在攔截之後,直接返回了Always, 這就是最終的原因了。
羅裏吧嗦一大堆,我們直接看MyBatis裏面抽象的幾個相關類吧:
1. Plugin 類:也就是我們說的“插板”類, 繼承了InvocationHandler 接口,主要的職責是將目標對象、攔截器組裝成一個新的代理類。 簡單的代碼如下:
- public class Plugin implements InvocationHandler {
- private Object target; // 目標對象
- private Interceptor interceptor; //攔截器對象
- private Map<Class<?>, Set<Method>> signatureMap; //目標對象的方法簽名
- private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
- this.target = target;
- this.interceptor = interceptor;
- this.signatureMap = signatureMap;
- }
- public static Object wrap(Object target, Interceptor interceptor) {
- Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
- Class<?> type = target.getClass();
- Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
- if (interfaces.length > 0) {
- return Proxy.newProxyInstance( // 返回一個目標對象的代理類
- type.getClassLoader(),
- interfaces,
- new Plugin(target, interceptor, signatureMap));
- }
- return target;
- }
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- try {
- Set<Method> methods = signatureMap.get(method.getDeclaringClass());
- if (methods != null && methods.contains(method)) {
- return interceptor.intercept(new Invocation(target, method, args));//對目標對象中的某個方法進行攔截,將相關的實現給到了攔截器的實現類
- }
- return method.invoke(target, args);
- } catch (Exception e) {
- throw ExceptionUtil.unwrapThrowable(e);
- }
- }
2. Interceptor 攔截器的接口:
- public interface Interceptor {
- Object intercept(Invocation invocation) throws Throwable; // 對方法進行攔截的抽象方法
- Object plugin(Object target); //把攔截器插入到目標對象的方法
- void setProperties(Properties properties);
- }
3. Invocation 真正對目標類方法的攔截的實現, 這裏沒有什麼說的。 值得一提的是, 如果我們想實現類似spring的攔截器,比如說前置通知、後置通知、環繞通知等,應該是可以在這裏做文章的。
- public class Invocation {
- private Object target;
- private Method method;
- private Object[] args;
- public Invocation(Object target, Method method, Object[] args) {
- this.target = target;
- this.method = method;
- this.args = args;
- }
- public Object getTarget() {
- return target;
- }
- public Method getMethod() {
- return method;
- }
- public Object[] getArgs() {
- return args;
- }
- public Object proceed() throws InvocationTargetException, IllegalAccessException {
- return method.invoke(target, args);
- }
- }
4. 搞定。
總結:其實攔截器還是很簡單的, 只需要熟識了Proxy類、InvocationHandler接口, 基本都能寫出來,只是寫的好與壞,是不是低耦合、高內聚的代碼。類的命名是不是讓別人一看就懂,這些都決定着一個人水平的高低;
攔截器使用的場景: 比如說 權限、日誌記錄、緩存等等;
先分享到這裏.....
原文地址:http://robert-wei.iteye.com/blog/1630132