AOP即面向切面编程,实现的方式有很多,这篇文章主要介绍一下动态代理实现AOP的方式。主要从动态代理的原理进行分析。
1. jdk自带动态代理
代理分为静态代理和动态代理,静态代理这里就不多说了,直接看动态代理,下面看个小例子。
假设有个Login接口和LoginImpl实现类
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;
}
}
现在我们对login方法进行动态代理。动态代理实现方式有两种,jdk自带的动态代理和cglib方式。jdk自带的动态代理方式要求被代理对象必须实现至少一个接口,cglib则没有这个限制。但是cglib也有其自身的限制,就是被代理对象不能是final修饰的,同时final修饰的方法也是不能被代理的。看到这里,可能有些读者已经明白了其中的道理,其实试想一下,我们如果要实现对某个对象的代理,就要能拿到被代理对象的方法,大致有以下两种思路:
- 通过接口,被代理类和代理类均实现相同的接口,代理类通过接口可以很轻松的拿到被代理类的方法。
- 继承的方式,如果代理类继承了被代理类,那么很明显,通过子类进行方法增强,可以达到aop的目的,但是final类不能被继承。
猜测jdk自带的动态代理应该采用的是思路一,cglib应该采用的是思路二,是不是这样呢,我们去探索一下答案,先来看jdk自带的动态代理模式。实现起来很简单,首先需要实现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方法创建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);
}
}
结果如下:可见我们成功的实现了动态代理。
before->login
userName:lanjunjian passwd:1234
after->login
true
下面我们看一下动态代理是怎么实现的。动态代理其实就涉及到两个类,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,然后调用代理对象的只含InvocationHandler的构造函数生成实例。接着看一下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;
}
篇幅原因,省略了部分代码,代码很清晰,没有太多可以解释的,主要是进行收集接口,然后转交给ProxyGenerator的generateProxyClass生成字节码的byte数组。ProxyGenerator类并不属于J2SE规范,代码位于sun.misc包下。我们大致看一下ProxyGenerator源码,字节码生成的过程是在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 sigmethods : proxyMethods.values()) {
checkReturnTypes(sigmethods);
}
try {
methods.add(generateConstructor());
for (List 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类并实现了Login接口,在静态代码块中加载了所有需要代理的方法,方法的调用都是通过InvocationHandler的invoke方法转发的。这里也看出来了由于java的单继承的限制,jdk自带的动态代理是无法代理普通类的,换句话说即使某个类实现了某个接口,但主要不是接口内定义的方法都是无法进行代理的。此外,动态代理后方法的调用只能通过反射来进行,性能上会有一些开销。
2. cglib动态代理
为了弥补jdk自带动态代理的限制,出现了cglib,可以实现类的动态代理,像spring框架就是jdk动态代理和cglib结合进行的,jdk动态代理搞不定的利用cglib进行,cglib引入了ASM库来进行底层字节码生成。这里简单的介绍一下。
同样定义一个Login类,不实现任何接口
public class Login {
public boolean login(String name,String passwd){
System.out.println("name->:"+name+"passwd->:"+passwd);
return false;
}
}
第一步实现一个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;
}
}
第二步直接调用
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系统,所以cglib的具体实现过程就不详细说了。具体原理可以参考这篇文章说说cglib动态代理。简单理解cglib是采用继承的方式进行代理。生成的代理类是继承自被代理类的。
public class Test1$$EnhancerByCGLIB$$d42d7d8c extends Test1 implements Factory
此外,cglib采用了FastClass机制,FastClass就是根据方法签名保存了代理类和被代理类的索引信息,然后为每个方法生成一个MethodProxy,proxy中有Invoke和invokeSuper两个方法,当我们调用invokeSuper时,根据方法签名去FastClass可以找到被代理类的方法,然后直接进行调用。所以cglib和动态代理很大的区别是 cglib使用的是直接调用,jdk是利用的反射。也有人利用dexmaker实现了android上的cglib,项目地址:MethodInterceptProxy。
3.动态代理在android上的简单应用
代理可以理解为是hook的一种手段,例如插件框架中替换Instrumention,实际上采用的是静态代理的方式,但是很多情况下,接口或类可能是hide的,我们无法通过继承或者接口实现等方式构造代理类,这种情况下我们就没法使用静态代理,可以酌情考虑动态代理。
我认为进行动态代理最大的难点在于hook的点很难找,主要能找到hook点,一切也好办。一般情况下,hook点最好是静态或者是单例,有些时候很难找到实例对象,而且往往我们都需要借助反射来获取被代理对象。下面就以发送通知为例,假设我要拦截每次发送通知的内容该怎么做呢?
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);
上一段是我们典型的发送通知的代码。整个发送的过程是由NotificationManager来控制的,我们知道通知的发送是一个跨进程的操作,这里由于篇幅原因,不去详细谈Binder相关的内容,只是为了从主观上感受下动态代理在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();
}
}
当调用manager.notify时,会调用到NotificationManager的notifyAsUser方法,可以看到整个发送流程都是通过INotificationManager接口进行的。一看到接口感觉应该可以做点什么,我们进一步看一下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机制由asInterface方法转换成了本地的代理对象,INotificationManager是一个由AIDL接口生成的本地代理对象,正好sService是一个static变量,我们通过反射获取到该对象然后替换成我们的Proxy是不是就能实现通知的拦截了呢?想一下我们动态代理的实现过程,操作一下。我们需要三个要素,接口的Class对象,获取被代理对象和一个InvocationHandler的子类。。
- 获取接口class对象很简单,反射即可。
Class> INotificationManagerClazz = Class.forName("android.app.INotificationManager");
- 获取被代理对象,也就是要拿到sService,可以通过反射拿到,我们可以通过发射sService变量拿到,也可以通过反射调用getService方法获取,如果直接发射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的子类直接实现一个即可。
准备工作做完了,我们直接进行代理的生成,同时要记得用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);
我们怎么拦截内容呢,观察notifyAsUser方法中会调用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;
}
}
这里说明一下,通知的内容在不同版本里获取方式不太一样,这里只是为了直观的体现动态代理的作用,没有进行适配,下面给出完整的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();
}
}
最后总结一下,这篇文章比较基础,主要想搞明白以下三个知识点,希望对大家有所帮助。
- jdk动态代理的原理以及为什么只能对接口做代理
- cglib与jdk动态代理的区别
- 在android的应用场景
参考文献
- java动态代理机制详解
- Android插件化开发-hook 系统服务(通过binder修改粘贴板服务行为)
目前本人在公司负责热修复相关的工作,主要是基于robust的热修复相关工作。感兴趣的同学欢迎进群交流。