java中動態程式設計用到的技術有:反射(動態代理),javassist和ASM,這幾種動態程式設計方法相比較,在效能上Javassist高於反射,但低於ASM,因為Javassist增加了一層抽象。在實現成本上Javassist和反射都很低,而ASM由於直接操作位元組碼,相比Javassist原始碼級別的api實現成本高很多。幾個方法有自己的應用場景,比如Kryo使用的是ASM,追求效能的最大化。而NBeanCopyUtil採用的是Javassist,在物件拷貝的效能上也已經明顯高於其他的庫,並保持高易用性。實際專案中推薦先用Javassist實現原型,若在效能測試中發現Javassist成為了效能瓶頸,再考慮使用其他位元組碼操作方法做優化。
下面以程式碼的方式實現一個動態代理。
目標:
在呼叫持久層UserDAO將業務資料寫入資料庫的前後加入日誌的功能。程式碼結構如下圖所示:
User:
package dynamicproxy.model; public class User { public User(String name, int age) { super(); this.name = name; this.age = age; } private String name; private int age; public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
IUserDAO:
package dynamicproxy.dao; import dynamicproxy.model.User; public interface IUserDAO { String addUser(User user); String updateUser(User user); }
UserDAO:
package dynamicproxy.dao; import dynamicproxy.model.User; public class UserDAO implements IUserDAO { public String addUser(User user) { System.out.println("開始向資料庫中寫入資料..."); return String.format("新增使用者[%s]成功", user.getName()); } public String updateUser(User user) { System.out.println("開始向資料庫中寫入資料..."); return String.format("修改使用者[%s]成功", user.getName()); } }
關鍵的InvocationHandler類LogInterceptor:
package dynamicproxy.interceptor; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class LogInterceptor implements InvocationHandler { private Object target; /* * 在執行指定方法之前呼叫 */ private void beforeMethod(Method method) { System.out.println(String.format("日誌:使用者開始執行[%s]方法...", method.getName())); } /* * 在執行指定方法之後呼叫 */ private void afterMethod(Method method) { System.out.println(String.format("日誌:使用者執行[%s]方法完成.", method.getName())); } public LogInterceptor(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { beforeMethod(method);// 加入日誌處理邏輯 Object result = method.invoke(target, args);// 呼叫被代理物件的指定method方法 afterMethod(method);// 加入日誌處理邏輯 return result; } }
測試程式碼:
package dynamicproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Proxy; import org.junit.Test; import dynamicproxy.dao.IUserDAO; import dynamicproxy.dao.UserDAO; import dynamicproxy.interceptor.LogInterceptor; import dynamicproxy.model.User; public class UserDAOTest { @Test public void test() { IUserDAO dao = new UserDAO(); InvocationHandler logInterceptor = new LogInterceptor(dao);// InvocationHandler dao = (IUserDAO) Proxy.newProxyInstance(dao.getClass().getClassLoader(), dao.getClass().getInterfaces(), logInterceptor); System.out.println(dao.addUser(new User("franson", 21))); System.out.println(System.lineSeparator()); System.out.println(dao.updateUser(new User("franson", 21))); } }
執行JUnit測試結果如下:
日誌:使用者開始執行[addUser]方法...
開始向資料庫中寫入資料...
日誌:使用者執行[addUser]方法完成.
新增使用者[franson]成功
日誌:使用者開始執行[updateUser]方法...
開始向資料庫中寫入資料...
日誌:使用者執行[updateUser]方法完成.
修改使用者[franson]成功
這樣就達到了我們在不修改原有業務邏輯程式碼的情況下實現新增日誌的功能。