為了鞏固
CGLib
的知識,下面我們實現一個稍微復雜一點的例子。
例、請實現一個攔截器,使其能夠檢測一個
JavaBean
的哪些字段改變了。
(
1
)首先定義一個
JavaBean
。
public class PersonInfo
{
???? private String name;
?
???? private String email;
?
???? private int age;
?
???? private String address;
?
???? public String getEmail()
???? {
???????? return email;
???? }
?
???? public void setEmail(String email)
???? {
???????? this.email = email;
???? }
?
???? public String getName()
???? {
???????? return name;
???? }
?
???? public void setName(String name)
???? {
???????? this.name = name;
???? }
?
???? public String getAddress()
???? {
???????? return address;
???? }
?
???? public void setAddress(String address)
???? {
???????? this.address = address;
???? }
?
???? public int getAge()
???? {
???????? return age;
???? }
?
???? public void setAge(int age)
???? {
???????? this.age = age;
???? }
}
(
2
)定義一個
MethodInterceptor
,這一步是最關鍵的
。
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
?
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
?
public class JavaBeanDataChangeInterceptor implements MethodInterceptor
{
???? private static final String SET = "set";
?
???? private Set changedPropSet;
?
???? public JavaBeanDataChangeInterceptor()
???? {
???????? changedPropSet = new HashSet();
???? }
?
???? public Object intercept(Object obj, Method method, Object[] args,
????????????? MethodProxy proxy) throws Throwable
???? {
???????? String name = method.getName();
???????? if (name.startsWith(SET))
???????? {
????????????? String s = name.substring(SET.length());
????????????? changedPropSet.add(s);
???????? }
???????? return proxy.invokeSuper(obj, args);
???? }
?
???? public Set getChangedPropSet()
???? {
???????? return Collections.unmodifiableSet(changedPropSet);
???? }
?
???? public void reset()
???? {
???????? changedPropSet.clear();
???? }
}
定義一個集合
changedPropSet
用來存放修改了的字段名,增加了一個方法
reset
用來清空此集合,增加了一個
getChangedPropSet
方法用來供外界得到修改了的字段,為了防止調用者對
changedPropSet
做修改,因此我們采用
Collections.unmodifiableSet
對返回的集合進行不可修改的修飾。
在
intercept
方法中,我們判斷如果被調用的方法以
set
開頭,則把此字段名放入
changedPropSet
集合中。
(
3
)定義剖析用工具類。
import net.sf.cglib.proxy.Callback;
import net.sf.cglib.proxy.Factory;
?
public class JavaBeanInterceptorUtils
{
???? public static JavaBeanDataChangeInterceptor getInterceptor(
????????????? Object obj)
???? {
???????? if (!(obj instanceof Factory))
???????? {
????????????? return null;
???????? }
???????? Factory f = (Factory) obj;
???????? Callback[] callBacks = f.getCallbacks();
???????? for (int i = 0, n = callBacks.length; i < n; i++)
???????? {
????????????? Callback callBack = callBacks[i];
????????????? if (callBack instanceof JavaBeanDataChangeInterceptor)
????????????? {
?????????????????? return (JavaBeanDataChangeInterceptor) callBack;
????????????? }
???????? }
???????? return null;
???? }
}
這個
JavaBeanInterceptorUtils
只有一個方法
getInterceptor
,這個方法用于從一個被
CGLib
代理的
JavaBean
中取出攔截器
JavaBeanDataChangeInterceptor
。
前邊提到了,
CGLib
實現攔截的方式就是生成被攔截類的子類,這個子類實現了
net.sf.cglib.proxy.Factory
接口,這個接口中有一個非常重要的方法
getCallbacks()
,通過這個方法我們可以得到所有的攔截器
。
(
4
)
主程序
public class MainApp
{
???? public static void main(String[] args)
???? {
???????? Enhancer enhancer = new Enhancer();
???????? enhancer.setSuperclass(PersonInfo.class);
???????? enhancer.setCallback(new JavaBeanDataChangeInterceptor());
?
???????? PersonInfo info = (PersonInfo) enhancer.create();
???????? //
對生成的
JavaBean
做一些初始化
???????? info.setAddress("
地址
1");
???????? info.setAge(21);
???????? info.setName("tom");
?
???????? //
得到攔截器
???????? JavaBeanDataChangeInterceptor interceptor = JavaBeanInterceptorUtils
?????????????????? .getInterceptor(info);
???????? //
復位修改字段記錄集合
???????? interceptor.reset();
?
???????? //
對
JavaBean
做一些修改
???????? editPersonInf(info);
?
???????? //
得到修改了的字段
???????? Iterator it = interceptor.getChangedPropSet().iterator();
???????? while (it.hasNext())
???????? {
????????????? System.out.println(it.next());
???????? }
???? }
?
???? private static void editPersonInf(PersonInfo info)
???? {
???????? info.setName("Jim");
???????? info.setAddress("N.Y Street");
???? }
}???
運行結果:
Address
Name
?
這個“變化字段攔截器”是有一定實際意義的,比如可以用來實現“只保存修改了的字段以提高效率”等功能
。
?
很多資料中都說如果要使用
JDK Proxy
,被代理的對象的類必須要實現接口,這種說法是不嚴謹的。從上邊的例子我們可以看出,正確的說法應該是:如果要使用
JDK Proxy
,那么我們要通過代理調用的方法必須定義在一個接口中。“面向接口編程而不是面向實現編程”是
OOP
開發中的一條基本原則,因此這種限制并不會對我們的開發造成障礙。