略談 cglib


Java 本身的動態代理,必須要基於介面定義,若類別並沒有實作特定介面,就無法使用 Java 動態代理機制,這時可以透過 cglib(Code Generation Library),它的底層基於 ASM,可於執行時期修改位元組碼、動態生成代理物件。

若要將〈動態代理〉的專案範例改用 cglib,可以在 gradle.build 中加入相依:

compile 'cglib:cglib-nodep:'3.2.9'

接著實作一個攔截器,它實作了 MethodInterceptor

package cc.openhome.interceptor;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.logging.Logger;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class LoggingInterceptor implements MethodInterceptor {
    @Override
    public Object intercept(Object target, Method method, Object[] args,
            MethodProxy proxy) throws Throwable {
        Logger.getLogger(target.getClass().getName())
              .info(String.format("%s.%s(%s)",
                  target.getClass().getName(), method.getName(), Arrays.toString(args)));
        return proxy.invokeSuper(target, args);
    }
}

然後 AppConfig 基於 cglib 來建立代理物件:

package cc.openhome;

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.interceptor.LoggingInterceptor;
import cc.openhome.model.AccountDAO;
import net.sf.cglib.proxy.Enhancer;

@Configuration
@ComponentScan
public class AppConfig {     
    @Autowired
    @Qualifier("accountDAOJdbcImpl")
    private AccountDAO accountDAO;

    @Autowired
    private DataSource dataSource;

    @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() {
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(accountDAO.getClass());
        enhancer.setCallback(new LoggingInterceptor());
        return (AccountDAO) enhancer.create(
                    new Class[] {DataSource.class}, 
                    new Object[] {dataSource}
               );
    }
}

在進行動態代理時,Spring 底層預設會採用 Java 動態代理,若目標對象沒有實作介面則改用 cglib,實際上,AOP 是個概念,各廠商會有各自的實現,為此,AOP Alliance 定義了一套介面標準,MethodInterceptor 在 AOP Alliance 的定義則是:

package org.aopalliance.intercept;

public interface MethodInterceptor {
    Object invoke(MethodInvocation methodInvocation) throws Throwable;
}

若使用 Spring AOP,你也可以實作這個介面,這部份可參考我舊版文件中的 AroundAdvice 中的範例。