AOP即面向切面編程,實現(xiàn)的方式有很多炒瘸,這篇文章主要介紹一下動態(tài)代理實現(xiàn)AOP的方式膘滨。主要從動態(tài)代理的原理進(jìn)行分析。
1. jdk自帶動態(tài)代理
代理分為靜態(tài)代理和動態(tài)代理捶枢,靜態(tài)代理這里就不多說了握截,直接看動態(tài)代理,下面看個小例子烂叔。
假設(shè)有個Login接口和LoginImpl實現(xiàn)類
public interface Login {
boolean login(String userName,String passwd);
}
public class LoginImpl implements Login{
@Override
public boolean login(String userName, String passwd) {
System.out.println("userName:"+userName+"passwd:"+passwd);
return true;
}
}
現(xiàn)在我們對login方法進(jìn)行動態(tài)代理谨胞。動態(tài)代理實現(xiàn)方式有兩種,jdk自帶的動態(tài)代理和cglib方式长已。jdk自帶的動態(tài)代理方式要求被代理對象必須實現(xiàn)至少一個接口畜眨,cglib則沒有這個限制。但是cglib也有其自身的限制术瓮,就是被代理對象不能是final修飾的康聂,同時final修飾的方法也是不能被代理的“模看到這里恬汁,可能有些讀者已經(jīng)明白了其中的道理,其實試想一下辜伟,我們?nèi)绻獙崿F(xiàn)對某個對象的代理氓侧,就要能拿到被代理對象的方法脊另,大致有以下兩種思路:
- 通過接口,被代理類和代理類均實現(xiàn)相同的接口约巷,代理類通過接口可以很輕松的拿到被代理類的方法偎痛。
- 繼承的方式,如果代理類繼承了被代理類独郎,那么很明顯踩麦,通過子類進(jìn)行方法增強(qiáng),可以達(dá)到aop的目的氓癌,但是final類不能被繼承谓谦。
猜測jdk自帶的動態(tài)代理應(yīng)該采用的是思路一,cglib應(yīng)該采用的是思路二贪婉,是不是這樣呢反粥,我們?nèi)ヌ剿饕幌麓鸢福葋砜磈dk自帶的動態(tài)代理模式疲迂。實現(xiàn)起來很簡單才顿,首先需要實現(xiàn)InvocationHandler接口
public class ProxyHandler implements InvocationHandler{
private Object target;
public ProxyHandler(Object obj){
this.target=obj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object ret=null;
System.out.println("before->"+method.getName());
ret=method.invoke(target, args);
System.out.println("after->"+method.getName());
return ret;
}
}
接著通過Proxy的newProxyInstance方法創(chuàng)建Proxy對象
public class Main {
public static void main(String[] args)throws Exception
{
LoginImpl imp=new LoginImpl();
Login login=(Login)Proxy.newProxyInstance(imp.getClass().getClassLoader(), imp.getClass().getInterfaces(), new ProxyHandler(imp));
boolean ret=login.login("lanjunjian", "1234");
System.out.println(ret);
}
}
結(jié)果如下:可見我們成功的實現(xiàn)了動態(tài)代理。
before->login
userName:lanjunjian passwd:1234
after->login
true
下面我們看一下動態(tài)代理是怎么實現(xiàn)的尤蒿。動態(tài)代理其實就涉及到兩個類娜膘,InvocationHandler和Proxy。InvocationHandler是個接口优质,只包含invoke方法竣贪,這里沒什么可看的,直接查看下Proxy的newProxyInstance方法巩螃。
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
if (h == null) {
throw new NullPointerException();
}
Class cl = getProxyClass(loader, interfaces);
try {
Constructor cons = cl.getConstructor(constructorParams);
return (Object) cons.newInstance(new Object[] { h });
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString());
} catch (IllegalAccessException e) {
throw new InternalError(e.toString());
} catch (InstantiationException e) {
throw new InternalError(e.toString());
} catch (InvocationTargetException e) {
throw new InternalError(e.toString());
}
}
看起來很清晰演怎,通過getProxyClass方法生成了代理對象的Class,然后調(diào)用代理對象的只含InvocationHandler的構(gòu)造函數(shù)生成實例避乏。接著看一下getProxyClass方法爷耀。
private final static String proxyClassNamePrefix = "$Proxy";
String proxyName = proxyPkg + proxyClassNamePrefix + num;
public static Class<?> getProxyClass(ClassLoader loader,
Class<?>... interfaces)
throws IllegalArgumentException
{
if (interfaces.length > 65535) {
throw new IllegalArgumentException("interface limit exceeded");
}
Class proxyClass = null;
Set interfaceSet = new HashSet(); // for detecting duplicates
for (int i = 0; i < interfaces.length; i++) {
String interfaceName = interfaces[i].getName();
Class interfaceClass = null;
try {
interfaceClass = Class.forName(interfaceName, false, loader);
} catch (ClassNotFoundException e) {
}
if (interfaceClass != interfaces[i]) {
throw new IllegalArgumentException(
interfaces[i] + " is not visible from class loader");
}
interfaceSet.add(interfaceClass);
interfaceNames[i] = interfaceName;
}
...
...
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces);
proxyClass = defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
proxyClasses.put(proxyClass, null);
...
...
return proxyClass;
}
篇幅原因,省略了部分代碼拍皮,代碼很清晰歹叮,沒有太多可以解釋的,主要是進(jìn)行收集接口铆帽,然后轉(zhuǎn)交給ProxyGenerator的generateProxyClass生成字節(jié)碼的byte數(shù)組咆耿。ProxyGenerator類并不屬于J2SE規(guī)范,代碼位于sun.misc包下爹橱。我們大致看一下ProxyGenerator源碼,字節(jié)碼生成的過程是在generateClassFile中完成的萨螺。
private final static boolean saveGeneratedFiles =
java.security.AccessController.doPrivileged(
new GetBooleanAction(
"sun.misc.ProxyGenerator.saveGeneratedFiles")).booleanValue();
/**
* Generate a proxy class given a name and a list of proxy interfaces.
*/
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Object run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + ".class");
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
private byte[] generateClassFile() {
addProxyMethod(hashCodeMethod, Object.class);
addProxyMethod(equalsMethod, Object.class);
addProxyMethod(toStringMethod, Object.class);
for (int i = 0; i < interfaces.length; i++) {
Method[] methods = interfaces[i].getMethods();
for (int j = 0; j < methods.length; j++) {
addProxyMethod(methods[j], interfaces[i]);
}
}
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
try {
methods.add(generateConstructor());
for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
for (ProxyMethod pm : sigmethods) {
// add static field for method's Method object
fields.add(new FieldInfo(pm.methodFieldName,
"Ljava/lang/reflect/Method;",
ACC_PRIVATE | ACC_STATIC));
methods.add(pm.generateMethod());
}
}
methods.add(generateStaticInitializer());
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
if (methods.size() > 65535) {
throw new IllegalArgumentException("method limit exceeded");
}
if (fields.size() > 65535) {
throw new IllegalArgumentException("field limit exceeded");
}
cp.getClass(dotToSlash(className));
cp.getClass(superclassName);
for (int i = 0; i < interfaces.length; i++) {
cp.getClass(dotToSlash(interfaces[i].getName()));
}
cp.setReadOnly();
ByteArrayOutputStream bout = new ByteArrayOutputStream();
DataOutputStream dout = new DataOutputStream(bout);
try {
dout.writeInt(0xCAFEBABE);
dout.writeShort(CLASSFILE_MINOR_VERSION);
dout.writeShort(CLASSFILE_MAJOR_VERSION);
cp.write(dout);
dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
dout.writeShort(cp.getClass(dotToSlash(className)));
dout.writeShort(cp.getClass(superclassName));
dout.writeShort(interfaces.length);
// u2 interfaces[interfaces_count];
for (int i = 0; i < interfaces.length; i++) {
dout.writeShort(cp.getClass(
dotToSlash(interfaces[i].getName())));
}
dout.writeShort(fields.size());
for (FieldInfo f : fields) {
f.write(dout);
}
dout.writeShort(methods.size());
for (MethodInfo m : methods) {
m.write(dout);
}
dout.writeShort(0); // (no ClassFile attributes for proxy classes)
} catch (IOException e) {
throw new InternalError("unexpected I/O Exception");
}
return bout.toByteArray();
}
generateClassFile展示了Proxy[num].class文件揍鸟,num一般情況下為0。
public static void saveGeneratedJdkProxyFiles() throws Exception {
Field field = System.class.getDeclaredField("props");
field.setAccessible(true);
Properties props = (Properties) field.get(null);
props.put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
}
下面我們反編譯一下$Proxy0.class句旱。
public final class $Proxy0 extends Proxy implements Login
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy0(final InvocationHandler invocationHandler) {
super(invocationHandler);
}
public final boolean equals(final Object o) {
try {
return (boolean)super.h.invoke(this, $Proxy0.m1, new Object[] { o });
}
catch (Error | RuntimeException t) {
throw t;
}
catch (Throwable t2) {
throw new UndeclaredThrowableException(t2);
}
}
public final String toString() {
try {
return (String)super.h.invoke(this, $Proxy0.m2, null);
}
catch (Error | RuntimeException t) {
throw t;
}
catch (Throwable t2) {
throw new UndeclaredThrowableException(t2);
}
}
public final boolean login(final String s, final String s2) {
try {
return (boolean)super.h.invoke(this, $Proxy0.m3, new Object[] { s, s2 });
}
catch (Error | RuntimeException t) {
throw t;
}
catch (Throwable t2) {
throw new UndeclaredThrowableException(t2);
}
}
public final int hashCode() {
try {
return (int)super.h.invoke(this, $Proxy0.m0, null);
}
catch (Error | RuntimeException t) {
throw t;
}
catch (Throwable t2) {
throw new UndeclaredThrowableException(t2);
}
}
static {
try {
$Proxy0.m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
$Proxy0.m2 = Class.forName("java.lang.Object").getMethod("toString", (Class<?>[])new Class[0]);
$Proxy0.m3 = Class.forName("com.ljj.Login").getMethod("login", Class.forName("java.lang.String"), Class.forName("java.lang.String"));
$Proxy0.m0 = Class.forName("java.lang.Object").getMethod("hashCode", (Class<?>[])new Class[0]);
}
catch (NoSuchMethodException ex) {
throw new NoSuchMethodError(ex.getMessage());
}
catch (ClassNotFoundException ex2) {
throw new NoClassDefFoundError(ex2.getMessage());
}
}
}
可以看到$Proxy繼承了Proxy類并實現(xiàn)了Login接口阳藻,在靜態(tài)代碼塊中加載了所有需要代理的方法,方法的調(diào)用都是通過InvocationHandler的invoke方法轉(zhuǎn)發(fā)的谈撒。這里也看出來了由于java的單繼承的限制腥泥,jdk自帶的動態(tài)代理是無法代理普通類的,換句話說即使某個類實現(xiàn)了某個接口啃匿,但主要不是接口內(nèi)定義的方法都是無法進(jìn)行代理的蛔外。此外,動態(tài)代理后方法的調(diào)用只能通過反射來進(jìn)行溯乒,性能上會有一些開銷夹厌。
2. cglib動態(tài)代理
為了彌補jdk自帶動態(tài)代理的限制,出現(xiàn)了cglib裆悄,可以實現(xiàn)類的動態(tài)代理矛纹,像spring框架就是jdk動態(tài)代理和cglib結(jié)合進(jìn)行的,jdk動態(tài)代理搞不定的利用cglib進(jìn)行光稼,cglib引入了ASM庫來進(jìn)行底層字節(jié)碼生成或南。這里簡單的介紹一下。
同樣定義一個Login類艾君,不實現(xiàn)任何接口
public class Login {
public boolean login(String name,String passwd){
System.out.println("name->:"+name+"passwd->:"+passwd);
return false;
}
}
第一步實現(xiàn)一個MethodInterceptor類似于InvocationHandler
public class Hacker implements MethodInterceptor{
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
System.out.println("xxxxxxbefore");
Object ret=proxy.invokeSuper(obj, args);
System.out.println("xxxxxxafter");
return ret;
}
}
第二步直接調(diào)用
public static void main(String[] args)throws Exception
{ System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "/Users/ljj/Documents/workspace/HookTest");
Test1 test=new Test1();
Hacker hacker=new Hacker();
Enhancer enhancer=new Enhancer();
enhancer.setSuperclass(test.getClass());
enhancer.setCallback(hacker);
Test1 proxy=(Test1)enhancer.create();
proxy.login("lanjunjian", "12346");
}
由于android中加載的是dex文件采够,不是class文件,cglib不支持android系統(tǒng)冰垄,所以cglib的具體實現(xiàn)過程就不詳細(xì)說了吁恍。具體原理可以參考這篇文章說說cglib動態(tài)代理。簡單理解cglib是采用繼承的方式進(jìn)行代理。生成的代理類是繼承自被代理類的冀瓦。
public class Test1$$EnhancerByCGLIB$$d42d7d8c extends Test1 implements Factory
此外伴奥,cglib采用了FastClass機(jī)制,F(xiàn)astClass就是根據(jù)方法簽名保存了代理類和被代理類的索引信息翼闽,然后為每個方法生成一個MethodProxy拾徙,proxy中有Invoke和invokeSuper兩個方法,當(dāng)我們調(diào)用invokeSuper時感局,根據(jù)方法簽名去FastClass可以找到被代理類的方法尼啡,然后直接進(jìn)行調(diào)用。所以cglib和動態(tài)代理很大的區(qū)別是 cglib使用的是直接調(diào)用询微,jdk是利用的反射崖瞭。也有人利用dexmaker實現(xiàn)了android上的cglib,項目地址:MethodInterceptProxy。
3.動態(tài)代理在android上的簡單應(yīng)用
代理可以理解為是hook的一種手段撑毛,例如插件框架中替換Instrumention书聚,實際上采用的是靜態(tài)代理的方式,但是很多情況下藻雌,接口或類可能是hide的雌续,我們無法通過繼承或者接口實現(xiàn)等方式構(gòu)造代理類,這種情況下我們就沒法使用靜態(tài)代理胯杭,可以酌情考慮動態(tài)代理驯杜。
我認(rèn)為進(jìn)行動態(tài)代理最大的難點在于hook的點很難找,主要能找到hook點做个,一切也好辦鸽心。一般情況下,hook點最好是靜態(tài)或者是單例居暖,有些時候很難找到實例對象再悼,而且往往我們都需要借助反射來獲取被代理對象。下面就以發(fā)送通知為例膝但,假設(shè)我要攔截每次發(fā)送通知的內(nèi)容該怎么做呢冲九?
Intent intent=new Intent();
Notification build = new Notification.Builder(this)
.setContentTitle("hook")
.setContentText("攔截通知")
.setAutoCancel(true)
.setSmallIcon(R.mipmap.ic_launcher)
.setWhen(System.currentTimeMillis())
.setContentIntent(PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT))
.build();
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify((int) (System.currentTimeMillis()/1000L), build);
上一段是我們典型的發(fā)送通知的代碼。整個發(fā)送的過程是由NotificationManager來控制的跟束,我們知道通知的發(fā)送是一個跨進(jìn)程的操作莺奸,這里由于篇幅原因,不去詳細(xì)談Binder相關(guān)的內(nèi)容冀宴,只是為了從主觀上感受下動態(tài)代理在android方面怎么使用灭贷。
public void notifyAsUser(String tag, int id, Notification notification, UserHandle user)
{
int[] idOut = new int[1];
INotificationManager service = getService();
String pkg = mContext.getPackageName();
// Fix the notification as best we can.
Notification.addFieldsFromContext(mContext, notification);
if (notification.sound != null) {
notification.sound = notification.sound.getCanonicalUri();
if (StrictMode.vmFileUriExposureEnabled()) {
notification.sound.checkFileUriExposed("Notification.sound");
}
}
fixLegacySmallIcon(notification, pkg);
if (mContext.getApplicationInfo().targetSdkVersion > Build.VERSION_CODES.LOLLIPOP_MR1) {
if (notification.getSmallIcon() == null) {
throw new IllegalArgumentException("Invalid notification (no valid small icon): "
+ notification);
}
}
if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
final Notification copy = Builder.maybeCloneStrippedForDelivery(notification);
try {
service.enqueueNotificationWithTag(pkg, mContext.getOpPackageName(), tag, id,
copy, idOut, user.getIdentifier());
if (localLOGV && id != idOut[0]) {
Log.v(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]);
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
當(dāng)調(diào)用manager.notify時,會調(diào)用到NotificationManager的notifyAsUser方法略贮,可以看到整個發(fā)送流程都是通過INotificationManager接口進(jìn)行的甚疟。一看到接口感覺應(yīng)該可以做點什么仗岖,我們進(jìn)一步看一下getService方法。
private static INotificationManager sService;
/** @hide */
static public INotificationManager getService()
{
if (sService != null) {
return sService;
}
IBinder b = ServiceManager.getService("notification");
sService = INotificationManager.Stub.asInterface(b);
return sService;
}
首先在ServiceManager通過getService方法獲取到了一個原聲的IBinder對象览妖,然后通過AIDL機(jī)制由asInterface方法轉(zhuǎn)換成了本地的代理對象,INotificationManager是一個由AIDL接口生成的本地代理對象轧拄,正好sService是一個static變量,我們通過反射獲取到該對象然后替換成我們的Proxy是不是就能實現(xiàn)通知的攔截了呢讽膏?想一下我們動態(tài)代理的實現(xiàn)過程檩电,操作一下。我們需要三個要素府树,接口的Class對象俐末,獲取被代理對象和一個InvocationHandler的子類。奄侠。
- 獲取接口class對象很簡單卓箫,反射即可。
Class<?> INotificationManagerClazz = Class.forName("android.app.INotificationManager");
- 獲取被代理對象垄潮,也就是要拿到sService烹卒,可以通過反射拿到,我們可以通過發(fā)射sService變量拿到,也可以通過反射調(diào)用getService方法獲取魂挂,如果直接發(fā)射sService變量,此時有可能獲取到的為null馁筐,所以采用反射getService方法獲取涂召。
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Method method = notificationManager.getClass().getDeclaredMethod("getService");
method.setAccessible(true);
final Object sService = method.invoke(notificationManager);
- InvocationHandler的子類直接實現(xiàn)一個即可。
準(zhǔn)備工作做完了敏沉,我們直接進(jìn)行代理的生成果正,同時要記得用proxy替換原來的sService,所有的工作就完成了盟迟。
Object proxy = Proxy.newProxyInstance(INotificationManagerClazz.getClassLoader(),
new Class[]{INotificationManagerClazz},new ProxyHandler(sService));
Field target = notificationManager.getClass().getDeclaredField("sService");
target.setAccessible(true);
target.set(notificationManager, proxy);
我們怎么攔截內(nèi)容呢秋泳,觀察notifyAsUser方法中會調(diào)用enqueueNotificationWithTag方法,我們只需要攔截這個方法即可攒菠。
class ProxyHandler implements InvocationHandler {
private Object mObject;
public ProxyHandler(Object mObject) {
this.mObject = mObject;
}
@RequiresApi(api = Build.VERSION_CODES.KITKAT)
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals("enqueueNotificationWithTag")) {
for (int i = 0; i < args.length; i++) {
if(args[i] instanceof Notification){
Notification notification=(Notification)(args[i]);
String content=notification.extras.getString(Notification.EXTRA_TEXT);
Log.i("ljj", "invoke: "+content);
}
}
return method.invoke(mObject, args);
}
return null;
}
}
這里說明一下迫皱,通知的內(nèi)容在不同版本里獲取方式不太一樣,這里只是為了直觀的體現(xiàn)動態(tài)代理的作用辖众,沒有進(jìn)行適配卓起,下面給出完整的hook代碼。
public void hookNotificationContent(Context context) {
try {
NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
Method method = notificationManager.getClass().getDeclaredMethod("getService");
method.setAccessible(true);
final Object sService = method.invoke(notificationManager);//獲取到Nofificiaton原來的sService對象
Class<?> INotificationManagerClazz = Class.forName("android.app.INotificationManager");
Object proxy = Proxy.newProxyInstance(INotificationManagerClazz.getClassLoader(),
new Class[]{INotificationManagerClazz},new ProxyHandler(sService));
Field target = notificationManager.getClass().getDeclaredField("sService");
target.setAccessible(true);
target.set(notificationManager, proxy);
} catch (Exception e) {
e.printStackTrace();
}
}
最后總結(jié)一下凹炸,這篇文章比較基礎(chǔ)戏阅,主要想搞明白以下三個知識點,希望對大家有所幫助。
- jdk動態(tài)代理的原理以及為什么只能對接口做代理
- cglib與jdk動態(tài)代理的區(qū)別
- 在android的應(yīng)用場景
參考文獻(xiàn)