静态代理
我们平时去餐厅吃饭,不是直接告诉厨师做什么菜的,而是先告诉服务员点什么菜,然后由服务员传到给厨师,相当于服务员是厨师的代理,我们通过代理让厨师炒菜,这就是代理模式。代理模式需要三个东西:
真实对象——厨师(chef),用户真正需要去用到的对象。
调用者——客人(Client),需要用到真实对象的对象。
代理对象——服务员(waiter),代理真实对象与调用者沟通的对象。
厨师需要有个炒菜的方法,服务员负责代理厨师,所以也需要有一个炒菜方法,为了保证两个对象的炒菜方法一致,需要一个接口。
public interface Cook {
public void cook(String name);//传入菜品名字
}
然后chef实现这个接口
public class Chef implements Cook{
@Override
public void cook(String name) {
System.out.println("正在炒"+name);
}
}
waiter也需要实现这个接口
public class Waiter implements Cook{
Chef chef;
public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
this.chef = chef;
}
@Override
public void cook(String name) { //自己不实现做菜,而是调用chef的方法
chef.cook("鱼香肉丝");
}
}
设置好后,客人就来点餐了
public class Client {
public static void main(String[] args) {
Chef chef=new Chef();
Waiter waiter=new Waiter(chef);
waiter.cook("鱼香肉丝");
}
}
这里client没有直接调用chef的cook方法,而是通过waiter调用的cook方法。这样waiter就起到了代理的作用。代理的优点是可以让真实对象处理的业务更加纯粹,不再去关注一些公共的事情,公共的业务由代理来完成。
比如这个点餐的例子,可以让厨师专心做饭,不用去管点餐的事情,如果还要增加收钱功能,就可以让服务员去完成。
public class Waiter implements Cook{
Chef chef;
public Waiter(Chef chef) { //通过构造函数与获取chef的实例对象
this.chef = chef;
}
@Override
public void cook(String name) { //自己不实现做菜,而是调用chef的方法
chef.cook("鱼香肉丝");
}
public void pay(){
System.out.println("结账");
}
}
动态代理
** 动态代理是spring的难点和重点。务必好好掌握。**
但是静态代理的缺点也很明显,多了代理类,工作量变大,开发效率降低。为了弥补这些缺点,就可以使用动态代理。动态代理又有几种实现
基于接口动态代理——jdk动态代理
基于类动态代理——cglib动态代理
还有用javasist来生成动态代理
先说jdk动态代理,动态代理只需要一个代理类就可以代理所有真实对象。静态代理是这么怎么做的呢?上面例子中,静态代理类(Waiter)代理了真实对象(Chef)。为了保证真实对象的方法(cook)与代理类的方法(cook)一致,所以需要都实现同一个接口(Cook),那如果还需要代理其他类呢?比如代理老板收钱,又需要实现pay接口,代理会计算账,又需要实现reckon接口。就会很麻烦。
动态代理只需要代理类实现一个InvocationHandler接口。就可以在代理类里面动态设置代理对象。API里InvocationHandLer是这样描述的
InvocationHandler是由代理实例的调用处理程序实现的接口 。 在这个例子中,代理实例就是指服务员(waiter),调用处理程序指调用厨师(chef)的做菜(cook),就是chef.cook("鱼香肉丝")。这里还提到了invoke方法
处理代理实例上的方法调用并返回结果。 当在与之关联的代理实例上调用方法时,将在调用处理程序中调用此方法。结合例子,当你想点餐(处理代理实例上的方法)时,就可以调用这个方法。然后结果点餐结果也会告诉你(并返回结果),因为这个方法可以代理很多真实对象,所以返回的Object。
还有一个需要了解的Proxy类,API中的描述
注意这里断句,Proxy提供了创建动态代理类和实例,的静态方法。这里的静态方法就是
第一个参数——类加载器,java去执行某一个类的时候,需要将这个.clss文件加载到java虚拟机里面去,这个加载的过程就需要类加载器去加载。
第二个参数——要实现接口的列表,接口也有class。所以是class<?>[]。
第三个参数——这个InvocationHandler就和之前的联系上了。
大概了解后可以配合代码理解了。我们在上面点餐的例子上更改。动态代理指动态生成的代理类,因此接口(Cook)依然存在,真实对象(Chef)也存在。然后是代理类(waiter),代理类需要去实现InvocationHandler接口,因为可以实现动态代理了,稍微改个名字Waiter2。
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Waiter2 implements InvocationHandler{
private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了
public void setCook(Cook cook) {
this.cook = cook;
}
/**
*代理方法逻辑
* @param proxy 代理对象(chef)
* @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
* @param args 当前方法的参数 (菜品名)
* @return 返回代理结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj=method.invoke(cook,args);//第一个参数是真实对象
return obj;
}
/**
* 生成代理类
* @return 返回代理类
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
}
}
详细解释
private Cook cook;//真实对象,因为chef实现了Cook,所以创建cook就可以了
public void setCook(Cook cook) {
this.cook = cook;
}
代理对象依然因为需要代理真实对象,所以还是需要先创建真实对象,真实对象厨师Chef继承了接口Cook的烹饪方法cook,所以创建接口Cook就可以了。然后通过set方法来设置真实对象。
/**
*代理方法逻辑
* @param proxy 代理对象(chef)
* @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
* @param args 当前方法的参数 (菜品名)
* @return 返回代理结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object obj=method.invoke(cook,args);//第一个参数是真实对象
return obj;
}
之前提到过,invoke是处理代理实例上的方法调用并返回结果,可以理解成让代理对象与真实对象建立逻辑关系。
这里的method.invoke()是通过反射去调用真实对象,第一个参数cook就是真实对象,第二个参数就是传的参数值。
然后返回代理结果就可以了。
之前还提到过Proxy有一个静态方法可以生成代理类。
/**
* 生成代理类
* @return 返回代理类
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),cook.getClass().getInterfaces(),this);
}
newProxyInstance的第一个参数是类加载器,直接传入当前类的类加载器。
第二参数是接口,这里传入cook的接口。
第三个参数是InvocationHandler,本身这个类就实现了InvocationHandler接口,所以传入当前类。
然后是客户端
public class Client {
public static void main(String[] args) {
Chef chef=new Chef(); //获取真实对象的实例
Waiter2 waiter2=new Waiter2();//获取代理的实例
waiter2.setCook(chef); //设置代理去代理哪个真实实例
Cook proxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
proxy.cook("宫保鸡丁");//通过代理去控制真实对象。
}
}
Cook proxy=(Cook) waiter2.getProxy();这一行是用来创建代理的,proxy就相当于之前的服务员。创建为Cook类型是为了调用cook方法。之前的静态代理中,用的是Waiter waiter去创建代理,因为Waiter也实现了Cook接口。所以可以用Waiter类型。
运行结果
如果要添加公共方法到代理对象中
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("来了,老弟");
Object obj=method.invoke(cook,args);//第一个参数是真实对象
return obj;
}
之前说静态代理有个坏处,有很多类,动态代理可以解决这个问题。现在好像还看不出来。我们稍微改一下,让动态代理类可以代理所有类。
这个例子中代理的是厨师,也可以加入收钱功能,或者打扫卫生功能。真实对象怎么变,我们的目的不变,代理真实对象,那么就可以把Waiter中的真实对象Cook,换成Object。Waiter2代码如下
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class Waiter2 implements InvocationHandler{
private Object target;//真实对象
public void setTarget(Object target) {
this.target = target;
}
/**
*代理方法逻辑
* @param proxy 代理对象(chef)
* @param method 当前调度方法,代理对象的调用处理程序方法的对象(cook())
* @param args 当前方法的参数 (菜品名)
* @return 返回代理结果
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("服务员,麻烦你");//公共方法
Object obj=method.invoke(target,args);//第一个参数是真实对象
return obj;
}
/**
* 生成代理类
* @return 返回代理类
*/
public Object getProxy(){
return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
}
新增两个接口,打扫和付钱,和它们的接口实现类
public interface Clean {
public void clean();
}
public class CleanImpl implements Clean{
public void clean(){
System.out.println("打扫");
}
}
public interface Pay {
public void paymoney(int i);
public void free();
}
public class PayImpl implements Pay{
public void paymoney(int i){
System.out.println("付钱诶!一共"+i);
}
public void free(){
System.out.println("能不能免单");
}
}
然后再客户端测试一下
public class Client {
public static void main(String[] args) {
Chef chef=new Chef(); //获取真实对象的实例
Pay pay=new PayImpl();
Clean clean=new CleanImpl();
Waiter2 waiter2=new Waiter2();//获取代理的实例
waiter2.setTarget(chef); //设置代理去代理哪个真实实例
Cook cookproxy=(Cook) waiter2.getProxy();//创建代理,getProxy返回的是object,所以转换一下。
cookproxy.cook("宫保鸡丁");//通过代理去控制真实对象。
waiter2.setTarget(pay);
Pay payproxy=(Pay) waiter2.getProxy();
payproxy.paymoney(50);
payproxy.free();
waiter2.setTarget(clean);
Clean cleanproxy=(Clean) waiter2.getProxy();
cleanproxy.clean();
}
}
这样这个代理类就可以代理各种各样的类。注意被代理的类必须实现接口,因为在newProxyInstance方法里面第二个参数就是传入真实对象的接口。
来源:oschina
链接:https://my.oschina.net/u/4000133/blog/2989188