Java代理模式

一、Java代理模式

代理是一种常用的设计模式,其目的就是为其他对象提供一个代理以控制对某个对象的访问。代理类负责为委托类预处理消息,过滤消息并转发消息,以及进行消息被委托类执行后的后续处理。

代理模式结构图

代理模式结构图
为了保持行为的一致性,代理类和委托类通常会实现相同的接口,所以在访问者看来两者没有丝毫的区别。通过代理类这中间一层,能有效控制对委托类对象的直接访问,也可以很好地隐藏和保护委托类对象,同时也为实施不同控制策略预留了空间,从而在设计上获得了更大的灵活性。

更通俗的说,代理解决的问题当两个类需要通信时,引入第三方代理类,将两个类的关系解耦,让我们只了解代理类即可,而且代理的出现还可以让我们完成与另一个类之间的关系的统一管理,但是切记,代理类和委托类要实现相同的接口,因为代理真正调用的还是委托类的方法。

代理一般可分为静态代理和动态代理。

静态:由程序员创建代理类或特定工具自动生成源代码再对其编译。在程序运行前代理类的.class文件就已经存在了。
动态:在程序运行时运用反射机制动态创建而成。

二、静态代理

通过大话设计模式的例子来说明静态代理。
首先要有委托类和代理类共同的接口:

package myProxy;
public interface GiveGift {
	public void GiveDolls();
	public void GiveFlowers();
	public void GiveChocolate();
}

定义一个处理对象类:

package myProxy;

public class SchoolGirl {
	private String name;

	public SchoolGirl(String name) {
		super();
		this.name = name;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	@Override
	public String toString() {
		return "SchoolGirl [name=" + name + "]";
	}
	
}

委托类和代理类共同实现接口。
委托类:

package myProxy;

public class Pursuit implements GiveGift {

	SchoolGirl schoolGirl;
	
	public Pursuit(SchoolGirl mm){
		this.schoolGirl =mm;
	}
	@Override
	public void GiveDolls() {
		// TODO Auto-generated method stub
		System.out.println(schoolGirl.getName() + " Pursuit送你洋娃娃");
	}

	@Override
	public void GiveFlowers() {
		// TODO Auto-generated method stub
		System.out.println(schoolGirl.getName() + " Pursuit送你鲜花");
	}

	@Override
	public void GiveChocolate() {
		// TODO Auto-generated method stub
		System.out.println(schoolGirl.getName() + " Pursuit送你巧克力");
	}

}

代理类实现接口,并在方法里调用委托类相应的方法。
代理类:

package myProxy;

public class Proxy implements GiveGift {

	Pursuit p;
	public Proxy(SchoolGirl mm){
		p = new Pursuit(mm);
	}
	@Override
	public void GiveDolls() {
		// TODO Auto-generated method stub
		p.GiveDolls();
	}

	@Override
	public void GiveFlowers() {
		// TODO Auto-generated method stub
		p.GiveFlowers();
	}

	@Override
	public void GiveChocolate() {
		// TODO Auto-generated method stub
		p.GiveChocolate();
	}

}

客户端调用:

package myProxy;

public class Test {
	public static void main(String[] args){
		SchoolGirl gg = new SchoolGirl("小芳");
		Proxy p = new Proxy(gg);
		p.GiveDolls();
		p.GiveFlowers();
		p.GiveChocolate();
	}
}

优点:
代理使客户端不需要知道实现类是什么,怎么做的,而客户端只需知道代理即可(解耦合),对于如上的客户端代码。
缺点:
1)代理类和委托类实现了相同的接口,代理类通过委托类实现了相同的方法。这样就出现了大量的代码重复。如果接口增加一个方法,除了所有实现类需要实现这个方法外,所有代理类也需要实现此方法。增加了代码维护的复杂度。
2)代理对象只服务于一种类型的对象,如果要服务多类型的对象。势必要为每一种对象都进行代理,静态代理在程序规模稍大时就无法胜任了。

举例说明:代理可以对实现类进行统一的管理,如在调用具体实现类之前,需要打印日志等信息,这样我们只需要添加一个代理类,在代理类中添加打印日志的功能,然后调用实现类,这样就避免了修改具体实现类。满足我们所说的开闭原则。但是如果想让每个实现类都添加打印日志的功能的话,就需要添加多个代理类,以及代理类中各个方法都需要添加打印日志功能(如上的代理方法中删除,修改,以及查询都需要添加上打印日志的功能)
即静态代理类只能为特定的接口(Service)服务。如想要为多个接口服务则需要建立很多个代理类。

在上面的示例中,一个代理只能代理一种类型,而且是在编译器就已经确定被代理的对象。而动态代理是在运行时,通过反射机制实现动态代理,并且能够代理各种类型的对象。

三、动态代理

与静态代理类对照的是动态代理类,动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。动态代理类不仅简化了编程工作,而且提高了软件系统的可扩展性,因为Java 反射机制可以生成任意类型的动态代理类。java.lang.reflect 包中的Proxy类和InvocationHandler 接口提供了生成动态代理类的能力。

提醒:动态代理是个非常重要的知识点,很多框架都有用到动态代理,像spring的aop就是通过动态代理实现增强的,动态代理是程序员必学的知识。

注意:通过jdk实现动态代理的委托类必须实现接口,否则无法通过jdk实现动态代理,没有实现接口的类要实现动态代理可以使用cglib来实现,这个内容以后再写。

JDK动态代理中包含一个类和一个接口:
InvocationHandler接口:

public interface InvocationHandler { 
	public Object invoke(Object proxy,Method method,Object[] args) throws Throwable; 
}

参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数

可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。

Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, 
InvocationHandler h) throws IllegalArgumentException;

参数说明:
ClassLoader loader:类加载器
Class<?>[] interfaces:得到全部的接口
InvocationHandler h:得到InvocationHandler接口的子类实例

Ps:类加载器
在Proxy类中的newProxyInstance()方法中需要一个ClassLoader类的实例,ClassLoader实际上对应的是类加载器,在Java中主要有一下三种类加载器;
Booststrap ClassLoader:此加载器采用C++编写,一般开发中是看不到的;
Extendsion ClassLoader:用来进行扩展类的加载,一般对应的是jre\lib\ext目录中的类;
AppClassLoader:(默认)加载classpath指定的类,是最常使用的是一种加载器。

接口:

package com.dongnao.jdkproxy;

public interface People {
    void eat();
}

委托类:

package com.dongnao.jdkproxy;

public class ZhangSan implements People {
    
    public void eat() {
        System.out.println("我是张三我喜欢吃猪脚!!!");
    }
}

InvocationHandler接口的实现类:

package com.dongnao.jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyHandler implements InvocationHandler {
    
    private People people;
    
    public MyHandler(People people) {
        this.people = people;
    }
    
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        before();
        
        method.invoke(people, args);
        
        after();
        return null;
    }
    
    private void before() {
        System.out.println("我在吃猪脚之前必须要洗手!!!");
    }
    
    private void after() {
        System.out.println("吃完猪脚之后我必须要洗手!!!");
    }
}

客户端调用:

package com.dongnao.jdkproxy;
import java.lang.reflect.Proxy;
import sun.misc.ProxyGenerator;

public class Test {
    
    public static void main(String[] args) {
        People people = (People)Proxy.newProxyInstance(Test.class.getClassLoader(),
                new Class[] {People.class},
                new MyHandler(new ZhangSan()));
        people.eat();
    }
}

运行结果:
我在吃猪脚之前必须要洗手!!!
我是张三我喜欢吃猪脚!!!
吃完猪脚之后我必须要洗手!!!

四、自己实现动态代理

模仿jdk的动态代理方式,自己实现动态代理的效果。
自定义InvocationHandler接口:

package com.dongnao.myproxy;

import java.lang.reflect.Method;

public interface MyInvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable;
}

参数说明:
Object proxy:指被代理的对象。
Method method:要调用的方法
Object[] args:方法调用时所需要的参数
可以将InvocationHandler接口的子类想象成一个代理的最终操作类,替换掉ProxySubject。
动态代理对象要增强的方法(spring概念中的连接点)需要增加什么功能,需要插入一些其他的方法就是在InvocationHandler接口实现类的invoke方法中实现的,而新增代码的方法一般也可以写在这个类中。
自定义InvocationHandler接口的实现类:

package com.dongnao.myproxy;

import java.lang.reflect.Method;

public class MyHandler implements MyInvocationHandler {
    
    private People people;
    
    public MyHandler(People people) {
        this.people = people;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        before();
        
        method.invoke(people, args);
        
        after();
        return null;
    }
    
    private void before() {
        System.out.println("我在吃猪脚之前必须要洗手!!!");
    }
    
    private void after() {
        System.out.println("吃完猪脚之后我必须要洗手!!!");
    }
}

Proxy类:
Proxy类是专门完成代理的操作类,可以通过此类为一个或多个接口动态地生成实现类,此类提供了如下的操作方法:

public static Object newProxyInstance(ClassLoader loader, Class interfaces, 
InvocationHandler h) throws IllegalArgumentException;

参数说明:
ClassLoader loader:类加载器
Class interfaces:得到全部的接口 (jdk中是个数组,这里我们只要管理一类接口即可)
InvocationHandler h:得到InvocationHandler接口的子类实例
自定义Proxy类是实现动态代理最重要的部分,总体步骤如下:
1、我们利用反射得到委托类的一些信息,然后用字符串拼接的方式生成一个 P r o x y 0. j a v a 文 件 。 拼 接 出 来 的 Proxy0.java文件。拼接出来的 Proxy0.javaProxy0.java文件就是和普通的.java文件一样。
P r o x y 0. j a v a 文 件 中 , 定 义 一 个 M y I n v o c a t i o n H a n d l e r 变 量 h , 得 到 由 委 托 类 的 方 法 组 成 的 数 组 后 , 循 环 该 数 组 , 在 Proxy0.java文件中,定义一个MyInvocationHandler变量h,得到由委托类的方法组成的数组后,循环该数组,在 Proxy0.javaMyInvocationHandlerhProxy0.java文件中生成相同名字的方法,调用MyInvocationHandler的invoke方法,把获取到的数组传进去,通过MyInvocationHandler的实现类实现增强。
2、把第一步拼接的java文件写入到磁盘,因为动态代理也是一个类的对象,这个类也要经过编译运行才能生成对象。
3、编译这个$Proxy0.java这个文件。
4、把这个编译成的.class文件加载到内存中。
5、得到构造函数,生成对象并返回该对象。

自定义Proxy:

package com.dongnao.myproxy;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

import javax.tools.JavaCompiler;
import javax.tools.JavaCompiler.CompilationTask;
import javax.tools.StandardJavaFileManager;
import javax.tools.ToolProvider;

public class MyProxy {
    static String rt = "\r\n";
    
    public static Object createProxyInstance(ClassLoader loader, Class intf,
            MyInvocationHandler h) {
        
        //1、我们用字符串拼接的方式生成一个$Proxy0.java文件
        String proxyClass = get$Proxy0(intf);
        
        //2、把java文件写入到磁盘
        String fileName = "D:/eclipse/eclipseworkspace/Proxy/src/com/dongnao/myproxy/$Proxy0.java";
        try {
            File f = new File(fileName);
            FileWriter fw = new FileWriter(f);
            fw.write(proxyClass);
            fw.flush();
            fw.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
        
        //3、我们必须要编译这个$Proxy0.java这个文件
        complierJava(fileName);
        
        //4、我们必须要把这个编译成的.class文件加载到内存中
        MyClassLoader loader1 = new MyClassLoader(
                "D:/eclipse/eclipseworkspace/Proxy/src/com/dongnao/myproxy");
        
        try {
            Class<?> proxy0Class = loader1.findClass("$Proxy0");
            //            proxy0Class.newInstance();
            //5、得到构造函数,生成对象并返回该对象。
            Constructor m = proxy0Class.getConstructor(MyInvocationHandler.class);
            Object o = m.newInstance(h);
            
            return o;
        }
        catch (ClassNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (NoSuchMethodException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (SecurityException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InstantiationException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalAccessException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (IllegalArgumentException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        catch (InvocationTargetException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return null;
    }
    
    private static void complierJava(String fileName) {
        try {
            JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
            StandardJavaFileManager fileMgr = compiler.getStandardFileManager(null,
                    null,
                    null);
            Iterable units = fileMgr.getJavaFileObjects(fileName);
            CompilationTask t = compiler.getTask(null,
                    fileMgr,
                    null,
                    null,
                    null,
                    units);
            t.call();
            
            fileMgr.close();
        }
        catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    
    private static String get$Proxy0(Class intf) {
        
        Method[] methods = intf.getMethods();
        
        String proxyClass = "package com.dongnao.myproxy;" + rt
                + "import java.lang.reflect.Method;" + rt
                + "public class $Proxy0 implements " + intf.getName() + "{"
                + rt + "MyInvocationHandler h;" + rt
                + "public $Proxy0(MyInvocationHandler h) {" + rt
                + "this.h = h;" + rt + "}" + getMethodStr(methods, intf) + rt
                + "}";
        return proxyClass;
    }
    
    private static String getMethodStr(Method[] methods, Class intf) {
        
        String proxyMethod = "";
        
        for (Method method : methods) {
            proxyMethod += "public void " + method.getName()
                    + "() throws Throwable {" + rt + "Method md = "
                    + intf.getName() + ".class.getMethod(\"" + method.getName()
                    + "\",new Class[]{});" + rt
                    + "this.h.invoke(this,md,null);" + rt + "}" + rt;
        }
        
        return proxyMethod;
    }
}

补充:动态拼接方法的时候getMethod需要传入两个参数,第一个为委托类的一个方法名method.getName(),第二个是该方法里的参数类型。

MyClassLoader类加载器:

package com.dongnao.myproxy;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class MyClassLoader extends ClassLoader {
    
    private File dir;
    
    MyClassLoader(String path) {
        dir = new File(path);
    }
    
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        
        if (dir != null) {
            File clazzFile = new File(dir, name + ".class");
            if (clazzFile.exists()) {
                FileInputStream input = null;
                
                try {
                    input = new FileInputStream(clazzFile);
                    
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    
                    byte[] buffer = new byte[1024];
                    
                    int len;
                    
                    while ((len = input.read(buffer)) != -1) {
                        baos.write(buffer, 0, len);//写到内存中
                    }
                    
                    return defineClass("com.dongnao.myproxy." + name,
                            baos.toByteArray(),
                            0,
                            baos.size());
                }
                catch (FileNotFoundException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        
        return super.findClass(name);
    }
    
}

MyClassLoader类把编译好的类加载到内存中。
委托类和代理类的共同接口:

package com.dongnao.myproxy;

public interface People {
    void eat() throws Throwable;
}

委托类:

package com.dongnao.myproxy;

public class ZhangSan implements People {
    
    public void eat() {
        System.out.println("我是张三我喜欢吃猪脚!!!");
    }
}

客户端实现:

package com.dongnao.myproxy;

public class Test {
    
    public static void main(String[] args) throws Throwable {
        People people = (People)MyProxy.createProxyInstance(Test.class.getClassLoader(),
                People.class,
                new MyHandler(new ZhangSan()));
        
        people.eat();
    }
}

运行结果:
我在吃猪脚之前必须要洗手!!!
我是张三我喜欢吃猪脚!!!
吃完猪脚之后我必须要洗手!!!
运行后除了输出结果外,我们还能在对应路径下发现多了两个文件,一个是 P r o x y 0. j a v a 文 件 , 一 个 是 Proxy0.java文件,一个是 Proxy0.javaProxy0.class文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值