设计模式中的代理模式应用很广,网上这方面资料很多,客户端代码不想或不能够直接访问被调用对象时,代理对象可以在客户端和目标对象之间起到中介作用,常用的场景:
- 创建一个系统开销很大的对象(延迟创建或真正调用时再创建)
- 被调用对象在远程主机上
- 目标对象的功能还不足以满足需求(增强功能)
java很容易就可以实现代理模式,每一个代理类在编译之后都会生成一个class文件,代理类所实现的接口和所代理的方法都被固定,这种代理被称之为静态代理(Static Proxy)。那么有没有一种机制能够让系统在运行时动态创建代理类?从JDK 1.3开始,Java语言提供了对动态代理的支持。下面举例比较两种代理方式
一、静态代理
定义两个接口和对应的实现类
接口1:Account.java
package com.css.sword.proxy;
public interface Account {
public void query();
public int sum();
}
实现类:AccountImpl.java
package com.css.sword.proxy;
public class AccountImpl implements Account {
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("开始查询账目……");
}
@Override
public int sum() {
// TODO Auto-generated method stub
System.out.println("开始合计……");
return 0;
}
}
接口2:Cook.java
package com.css.sword.proxy;
public interface Cook {
public void fire();
public void cut();
}
实现类:CookImpl.java
package com.css.sword.proxy;
public class CookImpl implements Cook {
@Override
public void fire() {
// TODO Auto-generated method stub
System.out.println("开始加火……");
}
@Override
public void cut() {
// TODO Auto-generated method stub
System.out.println("开始切菜……");
}
}
代理类:每个接口需创建一个代理类,注意代理类要实现一样的接口
AccountProxy.java
package com.css.sword.proxy;
public class AccountProxy implements Account {
private Account account;
AccountProxy(Account account){
this.account=account;
}
@Override
public void query() {
// TODO Auto-generated method stub
System.out.println("开始调用query方法");
account.query();
System.out.println("query方法调用结束");
}
@Override
public int sum() {
// TODO Auto-generated method stub
System.out.println("开始调用sum方法");
account.sum();
System.out.println("sum方法调用结束");
return 0;
}
}
CookProxy.java
package com.css.sword.proxy;
public class CookProxy implements Cook {
private Cook cook;
CookProxy(Cook cook)
{
this.cook=cook;
}
@Override
public void fire() {
// TODO Auto-generated method stub
System.out.println("开始调用fire方法");
cook.fire();
System.out.println("fire方法调用结束");
}
@Override
public void cut() {
// TODO Auto-generated method stub
System.out.println("开始调用cut方法");
cook.cut();
System.out.println("cut方法调用结束");
}
}
测试类:TestStaticProxy.java
package com.css.sword.proxy;
public class TestStaticProxy {
public static void main(String[] args)
{
AccountProxy accountProxy = new AccountProxy(new AccountImpl());
accountProxy.query();
accountProxy.sum();
System.out.println("-------------------------------------------------");
CookProxy cookProxy = new CookProxy(new CookImpl());
cookProxy.fire();
cookProxy.cut();
}
}
结果:
这里可以看到静态代理每个接口都需要一个代理类,如果需要每个方法在执行前和后加入的是相同的操作,代理类中每个方法都需要加入相同的代码,这样程序中就有太多重复的代码。动态代理可以有效解决这个问题。
二、动态代理
接口及实现类不变,动态代理只需一个代理类:
package com.css.sword.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class DynamicProxy implements InvocationHandler {
private Object target;
public Object getProxy(Object target)
{
this.target=target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); //要绑定接口(这是一个缺陷,cglib弥补了这一缺陷)
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
String methodName = method.getName();
System.out.println("开始调用"+methodName+"方法");
Object result = method.invoke(target, args);
System.out.println(methodName+"方法调用结束");
return result;
}
}
测试结果:
动态代理类需实现InvocationHandler接口,并实现其invoke方法,每当通过Proxy.newProxyInstance(……)方法获得某个接口的代理并调用方法时,程序就会回调invoke(……)方法。这样就不用为每个接口配置一个代理类,也不用为每个方法填写相同的增强代码。这里也可以看到jdk本身实现的动态代理有个弊端,那就是只能为接口生成代理,如果一个类没有实现接口,又想生成其代理类有什么办法?cglib可以解决这个问题
三、cglib动态代理
要想使用cglib需要下载相关包,网上一般会提供两种包,例如下图:
起初我一直不知道该用哪个包,后来在查阅相关资料和对两个包做出的对比,得出以下结论(但不确定是否正确,如果有什么不对希望大家批评指正):
cglib底层是用asm框架来处理字节码,如果使用cglib-x.x.x.jar的话需要再下载asm相关包,而cglib-nodep-x.x.x.jar中已包含asm需要的相关类,就不需要再下载其它包。经过测试得出的结果也是这样的。单独引cglib包需要注意的cglib版本和asm版本。在测试过程中总是因为这两个包的版本不能匹配而报错,最终测试通过的版本是cglib-2.2.3和asm-all-4.0.jar
新建一个类(不实现接口):AccountService.java
package com.css.sword.proxy;
public class AccountService {
public void query() {
// TODO Auto-generated method stub
System.out.println("开始查询账目……");
}
public int sum() {
// TODO Auto-generated method stub
System.out.println("开始合计……");
return 0;
}
}
代理类:CglibProxy.java
package com.css.sword.proxy;
import java.lang.reflect.Method;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
public class CglibProxy implements MethodInterceptor {
private Object target;
public Object getProxy(Object target){
this.target = target;
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(this.target.getClass());
// 回调方法
enhancer.setCallback(this);
// 创建代理对象
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable {
// TODO Auto-generated method stub
String methodName = method.getName();
System.out.println("开始调用"+methodName+"方法");
proxy.invokeSuper(obj, args);
System.out.println(methodName+"方法调用结束");
return null;
}
}
测试类:TestCglibProxy.java
package com.css.sword.proxy;
public class TestCglibProxy {
public static void main(String[] args){
CglibProxy cProxy = new CglibProxy();
AccountService accountProxy = (AccountService) cProxy.getProxy(new AccountService());
accountProxy.query();
accountProxy.sum();
}
}
测试结果: