您的位置:首页 > 编程语言 > Java开发

SpringAOP原理之---Java动态代理

2016-11-17 18:51 519 查看
前言:

前两天在学习Spring的AOP时,看到Spring默认使用JDK动态代理来实现AOP,于是对Java的动态代理原理产生了疑惑和兴趣,便查找了一些资料来解惑。现将理解记录如下。

参考资料:

1. Java 动态代理机制分析及扩展,第 1 部分

2. 使用ProxyGenerator类生成字节码

3. 说说 cglib 动态代理

4. ProxyGenerator.java源码

一、代理的概念

1. 什么是代理模式

代理模式是常用的设计模式之一,按照代理的创建时期可以分为两种:

- 静态代理:编译前代码就已创建或生成,编译后代理类的.class文件就生成了。

- 动态代理:在程序运行时,代理类的.class文件由JVM利用反射机制生成或由ASM(字节码生成框架)动态创建。在Java中,动态代理的实现方式有两种:JDK动态代理,使用反射机制生成代理类的字节码文件,限于实现了接口的委托类;CGLIB动态代理,利用ASM框架生成代理类的字节码文件,限于没有使用final修饰的类。限制的具体原因看完后面的内容后即可明白。

2. 为什么要用代理

解决在直接访问对象时带来的问题,如:要访问的对象不在本机器上;直接访问造成系统开销过大等

二、静态代理

静态代理,直接编码实现代理类。假如有委托类B,其实现了接口A的doSomething()方法,那么代理类C同样要实现接口A,不过要持有委托类B的实例引用,以便在doSomething()方法中做分派转发,在B.doSomething()前后可以织入一些其他的动作。

1. 实现

/**
*委托类和代理类的接口
*/
public interface Count {
public void count();
}
/**
*委托类,这里做实际的业务逻辑
*/
public class CountImpl implements Count {

@Override
public void count() {
System.out.println("CountImpl do something...");
}
}
public static void main(String[] args) throws Exception {
CountImpl countImpl = new CountImpl();
Count count = new CountProxy(countImpl);
count.count();
}


输出:

before count…

CountImpl do something…

after count…

2. 优点

编码简洁明了

3. 缺点和解决方案

对于多个代理来说,代码重用率低,灵活性不足。使用动态代理来解决。

三、JDK动态代理

1. 分析

我想在实现之前,先浅析一下JDK动态代理的基本原理,了解原理之后再去看实现,会清晰明白许多。

在JDK动态代理中,使用到了两个类:java.lang.reflect.Proxy和java.lang.reflect.InvocationHandler。

InvocationHandler是个十分简单的接口,只有一个方法:

/**
*该方法会在JDK利用反射生成代理类字节码文件时,在接口的实现方法(如接口A的doSomething()方法)中调用,因为生成的动态代理类中有一个InvocationHandler的引用。
*该接口的用法是,我们实现该接口,并在实现类内部持有一个委托类的引用,在invoke方法中围绕着委托类的方法做一些前置、后置操作。
*Object proxy 生成的动态代理类的实例
*Method method 委托类的方法对象
*Object[] args 委托类的方法所需要用到的参数列表
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;


Proxy是个比较复杂的类,方法众多,不过我们在利用其实现动态代理时往往只需要关注一个方法即可:

/**
*该静态方法用来动态生成代理类对象,该对象继承于Proxy类并实现了委托类的所有接口,
*内部持有我们自定义的InvocationHandler对象的引用。
*ClassLoader loader 类加载器,一般使用委托类的类加载器,不过基本上用户类都是AppClassLoader加载的
*Class<?>[] interfaces 委托类所实现的所有接口,这样代理类的实例就可以由委托类的某个接口来持有了
*InvocationHandler h InvocationHandler实现类对象,代理类在调用方法时实际上都分派转发给了h.invoke
*/
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)throws IllegalArgumentException{
//InvocationHandler不能为空
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
//检测类加载器和要实现的接口的合法性
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
// 生成动态代理类的Class对象
Class<?> cl = getProxyClass0(loader, intfs);
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
//获取动态代理类的构造函数,构造函数的的唯一参数即InvocationHandler
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
//使用构造器获取动态代理类对象
return cons.newInstance(new Object[]{h});
}//catch块省略
}


最后我们来看一下JDK利用反射生成的代理类的代码:

/**
*异常处理的代码都省略了
*/
public final class $Proxy extends Proxy implements Count{
private static Method m3;
private static Method m1;
private static Method m2;

public $Proxy(InvocationHandler paramInvocationHandler) {
super(paramInvocationHandler);
}

public final int count() {
return ((Integer)this.h.invoke(this, m3, new Object[] { paramUser })).intValue();
}
public final boolean equals(Object paramObject){
return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
}
public final String toString(){
return (String)this.h.invoke(this, m2, null);
}

static{
m3 = Class.forName("com.rambo.proxy.Count").getMethod("count");
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
return;
}
}


看到这,是否对JDK的动态代理瞬间理清楚了呢? 其实cglib的动态代理和这个类似,只不过cglib是用asm框架生成的字节码文件,而且生成的动态代理类是委托类的子类,如果委托类是final的,cglib就无能为力了。

2. 实现

public class CountIH implements InvocationHandler {
private Count count;
/**1
* 内部持有实现类的引用
*/
public CountIH(Count countImpl) {
this.count = countImpl;
}
//将调用分派转发到委托类的方法上,可在方法执行前后做一些前置、后置操作。如记录日志、安全控制、事务控制等等
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("before count...");
Object reslut = method.invoke(count, args);
System.out.println("after count...");
return reslut;
}
}

public static void main(String[] args) throws Exception {
CountImpl countImpl = new CountImpl();
InvocationHandler handler = new CountIH(countImpl);
//对于Spring中如何强转类型,还有疑问,继续看SpringAOP时再去寻找答案吧
Count count = (Count) Proxy.newProxyInstance(Count.class.getClassLoader(), countImpl.getClass().getInterfaces(), handler);
count.count();
}


3. 优点

解决静态代理的问题

4. 缺点和解决方案

如果委托类没有实现接口,则无法使用JDK动态代理,这时候就要用到cglib了;而cglib对于final类无能为力,所以这两个动态代理方法是相辅相成的。

四、cglib动态代理

/**
*利用增强器直接生成动态代理类对象,我推测this作为参数实际上和JDK动态代理中的newProxyInstance
*方法中的InvocationHandler h参数作用类似,都是为了代理类进行分派转发。
*JDK动态代理中是为了调用h.invoke方法,cglib中是为了调用this.intercept方法。
*/
public class CountProxyCglib implements MethodInterceptor {
public Object getProxyInstance(Object target){
return Enhancer.create(target.getClass(), this);
}

@Override
public Object intercept(Object target, Method method, Object[] params, MethodProxy proxy) throws Throwable {
System.out.println("before count...");
//调用父类的方法
Object result = proxy.invokeSuper(target, params);
System.out.println("after count...");
return result;
}
}
/**
*net.sf.cglib.proxy.Enhancer类的静态方法
*/
public static Object create(Class type, Callback callback) {
Enhancer e = new Enhancer();
e.setSuperclass(type);
e.setCallback(callback);
return e.create();
}


五、SpringAOP和动态代理

Spring使用JDK动态代理或CGLIB代理来实现,Spring缺省使用JDK动态代理来实现,从而任何接口都可别代理,如果被代理的对象实现不是接口将默认使用CGLIB代理,不过CGLIB代理当然也可应用到接口。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息