从静态代理开始
假设我们有个业务类,类的接口和实现是这么定义的:
public interface Player {
void play();
}
public PlayerImpl implements Player {
@Override
public void play(){
System.out.println("playing...");
}
}
如果我想在玩游戏之前和游戏结束之后,记录日志,可是不允许改动PlayerImpl的代码,该怎么办呢?
这时候可以构造一个代理类来做这件事情:
public PlayerProxy implements Player {
private Player player;
public PlayerProxy(){
this.player = new PlayerImpl();
}
@Override
public void play(){
beforeLog();
player.play();
afterLog();
}
private void beforeLog(){
System.out.println("init...");
}
private void afterLog(){
System.out.println("destroy...");
}
}
这就是静态代理,我们可以在不改动源码的情况下,通过代理对类功能进行扩展,很符合开闭原则吧:)
动态代理
静态代理的方式很直观,但是缺点也很明显——它只能针对特定类进行代理,扩展性很差,如果有一百个类要代理,难道要写一百个代理类吗?显然我们应该拥有更加通用和高效的解决方案,就是动态代理啦。
JDK动态代理
JDK动态代理,需要实现InvocationHandler接口;下面我们用JDK代理的方式重写上面的代理类:
public PlayerProxyFactory implements InvocationHandler {
private Object target;
public PlayerProxyFactory(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
before();
Object result = method.invoke(target, args);
after();
return result;
}
public Object buildProxy(){
return Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
this
);
}
// 省略beforeLog和afterLog方法
}
实际使用:
public static void main(String[] args) {
Player player = new PlayerImpl();
// 构造实现
PlayerProxyFactory proxy = new PlayerProxyFactory(player);
// 生成代理
Player playerProxy = (Player) proxy.buildProxy();
playerProxy.play();
}
可以看出来,动态代理可以通过接口自动构造代理类(其实就是一个工厂)。
cglib动态代理
JDK InvocationHandler虽好,却仍有一个缺陷,无法代理未实现接口的类。如果刚好有这样的需求,该怎么解决呢?这就得靠cglib了。
cglib是一个在运行期动态生成字节码的工具。我承认我第一次看到这个介绍的时候被吓到了,不过撇开原理不说,使用起来与InvocationHandler非常类似:
public class CGLibProxy implements MethodInterceptor {
@Override
public Object intercept(Object target, Method method, Object[] args, MethodProxy proxy) throws Throwable {
before();
Object result = proxy.invokeSuper(target, args);
after();
return result;
}
public <T> T getProxy(Class<T> clazz) {
return (T) Enhancer.create(clazz, this);
}
// 省略beforeLog和afterLog方法
}
实际使用:
public static void main(String[] args) {
Player playerProxy = new CGLibProxy().getProxy(PlayerImpl.class);
playerProxy.play();
}
JAVASSIST
Javassist是一个开源的分析、编辑和创建Java字节码的类库。是由东京工业大学的数学和计算机科学系的 Shigeru Chiba (千叶 滋)所创建的。它已加入了开放源代码JBoss 应用服务器项目,通过使用Javassist对字节码操作为JBoss实现动态"AOP"框架。
Javassist是一个十分简单粗暴的工具,它的用法简单来看是这样的:
public static void main(String[] args) throws Exception {
ClassPool pool = ClassPool.getDefault();
//创建Programmer类
CtClass cc= pool.makeClass("com.samples.Programmer");
//定义code方法
CtMethod method = CtNewMethod.make("public void code(){}", cc);
//插入方法代码
method.insertBefore("System.out.println(\"I'm a Programmer,Just Coding.....\");");
cc.addMethod(method);
//保存生成的字节码
cc.writeFile("f://myProxy");
}
这么灵活,写个代理还不是分分钟......
JAVA动态代理比较
JDK动态代理 | CGLIB | JAVASSIST | |
---|---|---|---|
实现原理 | 利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理 | 利用asm开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理 | 通过修改字节码生成类来处理 |
目标类是否需要实现接口 | 是 | 否 | 否 |
网友评论