一,动态代理
Java领域中,常用的动态代理实现方式有两种,一种是利用JDK反射机制生成代理,另外一种是使用CGLIB代理。JDK代理必须要提供接口,而CGLIB则不需要,可以直接代理类。本文主要介绍通过JDK反射机制生成代理;
JDK提供的代理只能针对接口做代理
通过一个例子分析什么是动态代理,可能不太恰当;总体来说就是生成代理对象,然后代理被代理对象执行某个方法或其他功能;
例子:由于张三没时间买火车票,找李四代理;
1,创建一个被代理Java interface
public interface Tickect {
String buy(int page);
}
2,创建被代理对象实现Java interface(Ticket)
public class TickectImp implements Tickect{
private String name;
public TickectImp(String name) {
this.name = name;
}
public String buy(int page) {
// TODO Auto-generated method stub
String s = name + "买" + page + "张火车票";
System.out.println(s);
return s;
}
}
3,创建动态代理(TickectInvocationHandler ),要实现InvocationHandler接口(这是使用Java API实现动态代理必须要实现的),InvocationHandler中有一个invoke()方法,所有执行被代理对象的方法都会被替换成执行invoke()方法。InvocationHandler作用就是,当被代理对象的原本方法被调用的时候,会绑定执行一个方法,这个方法就是InvocationHandler里面定义的invoke,同时会替代原本方法的结果返回。
public class TickectInvocationHandler implements InvocationHandler {
private Object target;//被代理类的引用
public TickectInvocationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.print("李四代");
//执行被代理target对象的方法
String s= (String) method.invoke(target, args);
return s;
}
}
invoke接收三个参数:
proxy,动态代理实例对象。
method,被调用方法。
args,调用时的参数。
4,在测试类中,创建代理对象,并强转为被代理对象实现的接口类型(既然是动态代理,代理对象是不确定的,Proxy.newProxyInstance返回的又是object类型的所以强转成接口(Tickect)类型比较合适);
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
TickectImp ti = new TickectImp("张三");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
TickectInvocationHandler h = new TickectInvocationHandler(ti);
//动态创建代理对象
// Tickect tickect = (Tickect)Proxy.newProxyInstance(TickectImp.class.getClassLoader(), new Class[]{Tickect.class}, h);
// Tickect tickect = (Tickect)Proxy.newProxyInstance(TickectImp.class.getClassLoader(), ti.getClass().getInterfaces(), h);
Tickect tickect = (Tickect)Proxy.newProxyInstance(Tickect.class.getClassLoader(), TickectImp.class.getInterfaces(), h);
// Tickect tickect = (Tickect)Proxy.newProxyInstance(Tickect.class.getClassLoader(), Tickect.class.getInterfaces(), h);//Tickect.class.getInterfaces()这种写法不对,会抛ClassCastException异常;
System.out.println(tickect.buy(2));
}
}
Proxy.newProxyInstance():生成一个实例对象,然后用Proxy的newInstance方法对这个实例对象代理生成一个代理对象。Tickect被代理后生成的动态代理对象,并不属于Tickect接口的任何一个实现类。但是它是基于Tickect接口和TickectImp类加载代理出来的。
执行结果:正是TickectInvocationHandler中的Invoke方法执行的结果;
李四代理张三买了2张火车票
张三买了2张火车票
以上几个步骤也可以这样写:
public static void main(String[] args) {
// TODO Auto-generated method stub
Tickect t = (Tickect)Proxy.newProxyInstance(Tickect.class.getClassLoader(), new Class [] {Tickect.class}, new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
// TODO Auto-generated method stub
System.out.print("李四代");
String s = (String) method.invoke(new Tickect() {
public String buy(int page) {
// TODO Auto-generated method stub
String si = "张三买了" + page + "张火车票";
System.out.println(si);
return si;
}
}, args);//执行被代理匿名内部类对象的方法;
return s;
}
});
System.out.println(t.buy(2));
}
注意:不能这样写method.invoke(proxy,args)因为会进入死循环:原因proxy是代理类的对象,当该对象方法被调用的时候,会触发InvocationHandler,而InvocationHandler里面又调用一次proxy对象的同一个方法(相当于buy方法中又调用了buy方法),所以会不停地循环调用。并且,proxy代理对象对应的方法是没有实现的(是不可访问的)。所以是会循环的不停报错
以上2,3,4还可以这样写:
private static void demo3() throws InstantiationException,
IllegalAccessException, InvocationTargetException,
NoSuchMethodException {
Class tickectProxyClass= Proxy.getProxyClass(Tickect.class.getClassLoader(), new Class[]{Tickect.class});
Tickect t =(Tickect)tickectProxyClass.getConstructor(InvocationHandler.class).newInstance(new InvocationHandler(){
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
System.out.print("李四代");
String s = (String) method.invoke(new Tickect() {
public String buy(int page) {
// TODO Auto-generated method stub
String si = "张三买了" + page + "张火车票";
System.out.println(si);
return si;
}
}, args);//执行被代理匿名内部类对象的方法;
return s;
}
});
System.out.println(t.buy(2));
}
或者:
private static void demo4() throws NoSuchMethodException,
InstantiationException, IllegalAccessException,
InvocationTargetException {
TickectImp ti = new TickectImp("张三");
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");//可以将运行时生成的动态代理Class文件保存下来
TickectInvocationHandler h = new TickectInvocationHandler(ti);
Class tickectProxyClass= Proxy.getProxyClass(Tickect.class.getClassLoader(), new Class[]{Tickect.class});
Constructor c =tickectProxyClass.getConstructor(InvocationHandler.class);
Tickect t = (Tickect) c.newInstance(new TickectInvocationHandler(ti));
System.out.println(t.buy(2));
}
注意:System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");要在生成需保存的字节码代码前面运行,不然不会保存;例如在Class tickectProxyClass= Proxy.getProxyClass(Tickect.class.getClassLoader(), new Class[]{Tickect.class});前面运行;
总结:以上写法都有接口实现类(也可以没有实现类,这样就不能对实现类的方法增强),这种代理可以对方法增强,增加新的逻辑,而不用修改原有实现类的方法;
二,静态代理
静态代理的实现,代理类和被代理都要实现统一的接口;然后代理中类引用被代理类;然后可以对被代理进行方法的增强;
统一接口:
public interface Tickect {
void buy();
}
被代理类:
public class TickectImp implements Tickect{
public void buy() {
// TODO Auto-generated method stub
System.out.println("张三买了一张火车票");
}
}
代理类:代理类代理被代理类做事;
public class ProxyTickectImp implements Tickect {
private TickectImp ti;
public ProxyTickectImp(TickectImp ti) {
super();
this.ti = ti;
}
public void buy() {
// TODO Auto-generated method stub
System.out.println("之前。。。");//这样可也对被代理对象的方法进行增强,而且不用修改被代理对象的方法;
System.out.print("李四代理");
ti.buy();
System.out.println("张三就不用自己取买火车票了");
System.out.println("之后。。");
}
}
测试代理类的使用:
public static void main(String[] args) {
TickectImp ti = new TickectImp();
ProxyTickectImp pti = new ProxyTickectImp(ti);
pti.buy();
}
静态代理优点:可以做到不对被代理对象进行修改的前提下,对被代理对象进行功能的扩展和拦截。
静态代理缺点:代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。即静态代理类只能为特定的接口服务。如想要为多个接口服务则需要建立很多个代理类。
动态代理:不要我们手动创建代理类,是在代码运行期时JDK动态创建的;我们只需要实现InvocationHandler的invoke方法;最大的好处是接口中声明的所有方法都被转移到调用处理器一个集中的方法中处理(InvocationHandler.invoke)