七、代理模式(Proxy)
代理模式是指“为其他对象提供一种代理以控制这个对象的访问”。
抽象主题角色(Subject):声明了代理对象和目标对象的公共接口,是任何需要目标对象的地方都使用代理对象。
代理主题角色(ProxySubject):含有目标对象的引用,从而可以在任何时候操作目标对象。
真实主题角色(RealSubject):定义了代理角色所代表的具体对象。
有两种代理模式:静态代理、动态代理
1.静态代理
静态代理在使用时,需要定义接口或者父类,被代理对象与代理对象一起实现相同的接口或者是继承相同父类.
//抽象主题
public interface Subject {
//操作
public void operation();
}
//目标对象角色
public class RealObject implements Subject {
@Override
public void operation() {
//一些操作
System.out.println("一些操作");
}
}
//代理对象角色
public class ProxyObject implements Subject {
RealObject realObject = new RealObject();
@Override
public void operation() {
//调用目标对象之前可以做相关操作
System.out.println("before");
realObject.operation();
//调用目标对象之后可以做相关操作
System.out.println("after");
}
}
2.动态代理
静态代理在运行之前就要写好代理类,这就造成大量的重复,而动态代理在运行时才回生成代理类。
动态代理有两种实现方式,一种是jdk方式,一种是cglib
2.1 jdk实现方式
在jdk的动态代理实现中,有两个非常重要的类和接口Proxy(Class)、InvocationHandler(Interface)。
jdk实现代理的方式其实是代理对象和目标对象实现同一个接口,因此目标对象必须要实现某个接口。
比如我有个人接口,有个findLove方法。
public interface Person {
public String findLove();
}
有一个目标类实现了这个接口
public class Lina implements Person {
@Override
public String findLove() {
System.out.println("高富帅");
System.out.println("正直");
return "完成";
}
}
需要一个生成代理类的类
public class ProxyMeipo implements InvocationHandler {
// 被代理对象
private Object target;
/**
* 获取代理对象
* @param target
* @return
* @throws Exception
*/
public Object getInstance(Object target) throws Exception {
this.target = target; // 将被代理对象保存
Class<?> clazz = target.getClass();
// 创建代理对象
Object proxy = Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
return proxy;
}
/**
* 利用反射调用真实对象的方法
* @param proxy:代理对象
* @param method:被代理对象的某个方法
* @param args:参数
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().startsWith("find")) {
System.out.println("开始物色");
Object obj = method.invoke(target, args);
System.out.println("相亲");
return obj;
}
return null;
}
}
测试代理
public class ProxyTest {
public static void main(String[] args) throws Exception {
ProxyMeipo meipo = new ProxyMeipo();
Person person = (Person) meipo.getInstance(new Lina());
person.findLove();
System.out.println(person.getClass());
}
}
2.2 cglib方式
cglib其实是通过继承目标类实现的代理,所以它不用目标对象一定实现某个接口
任意一个目标类
public class Barry {
public String findLove() {
System.out.println("高富帅");
System.out.println("正直");
return "完成";
}
}
动态生成代理类,需要实现MethodInterceptor,去看源码会发现这个接口只定义了一个方法
public class ProxyMeipo implements MethodInterceptor {
private Object target;
public Object getInstance(Object target) {
this.target = target;
// 工具类
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(target.getClass()); // 设置父类
enhancer.setCallback(this); // 设置回调函数
return enhancer.create();
}
/**
*
* @param o:代理类对象
* @param method:目标对象方法
* @param objects:参数
* @param methodProxy:代理对象方法
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("开始物色");
// 两种调用目标方法的方式
Object obj = methodProxy.invokeSuper(o, objects); // 调用父类的目标方法,也就是目标方法
// Object obj = method.invoke(target, objects);
System.out.println("相亲");
return obj;
}
}
测试
public class ProxyTest {
public static void main(String[] args) {
ProxyMeipo meipo = new ProxyMeipo();
Barry barry = (Barry) meipo.getInstance(new Barry());
System.out.println(barry.findLove());
System.out.println(barry.getClass().getSuperclass());
}
}
2.3 自己动手实现jdk方式的动态代理
看到上面2.1 jdk动态生成代理类的方法(Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this))需要Proxy、ClassLoader和反射调用方法的接口InvocationHandler。
首先我们要一个GPInvocationHandler
public interface GPInvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
其次,我们需要一个将代理类加载到jvm中的GPClassLoader(ClassLoader是一个abstract的类,具体怎么加载以后会再做说明)
public class GPClassLoader extends ClassLoader {
public GPClassLoader() {
}
// 执行父加载器的loadClass方法 如果向上委托父加载器没有加载成功,则通过findClass(String)查找。
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String className = GPClassLoader.class.getPackage().getName() + "." + name;
File classFile = new File(GPClassLoader.class.getResource("").getPath().replaceAll("\\.", "/") + name + ".class");
FileInputStream in = null;
ByteArrayOutputStream os = null;
try {
if (classFile.exists()) {
in = new FileInputStream(classFile);
os = new ByteArrayOutputStream();
byte [] buff = new byte[1024];
int len;
while ((len = in.read(buff)) != -1){
os.write(buff,0,len);
}
// 加载到jvm
return defineClass(className, os.toByteArray(), 0, os.size());
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if(null != in){
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if(os != null){
try {
os.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
最后我们需要一个生成代理类并返回代理对象的GPProxy
public class GPProxy {
public static final String ln = "\r\n"; // 换行
public static Object newProxyInstance(GPClassLoader classLoader, Class<?>[] interfaces, GPInvocationHandler h) {
FileWriter writer = null;
try {
// 1.生成源码
String src = generateSrc(interfaces);
// 2.写入文件
File f = new File(GPProxy.class.getResource("").getPath() + "$Proxy0.java");
writer = new FileWriter(f);
writer.write(src);
writer.flush();
writer.close();
// 3.编译成class文件
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager manager = compiler.getStandardFileManager(null, null, null);
Iterable iterable = manager.getJavaFileObjects(f);
JavaCompiler.CompilationTask task = compiler.getTask(null, manager, null, null, null, iterable);
task.call();
manager.close();
//4、编译生成的.class文件加载到JVM中来
Class proxyClass = classLoader.findClass("$Proxy0");
Constructor c = proxyClass.getConstructor(GPInvocationHandler.class);
f.delete();
//5、返回字节码重组以后的新的代理对象
Object obj = c.newInstance(h);
return obj;
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
private static String generateSrc(Class<?>[] interfaces) {
StringBuffer sb = new StringBuffer();
sb.append("package com.crystal.pattern.proxy.custom;" + ln);
sb.append("import " + interfaces[0].getName() + ";" + ln);
sb.append("import java.lang.reflect.Method;" + ln);
sb.append("public class $Proxy0 implements " + interfaces[0].getSimpleName() + "{" + ln);
sb.append("GPInvocationHandler h;" + ln);
sb.append("public $Proxy0(GPInvocationHandler h) { " + ln);
sb.append("this.h = h;");
sb.append("}" + ln);
for (Method m : interfaces[0].getMethods()){
sb.append("public " + m.getReturnType().getName() + " " + m.getName() + "() {" + ln);
sb.append(m.getReturnType().getName() + " obj = null;" + ln);
sb.append("try{" + ln);
sb.append("Method m = " + interfaces[0].getSimpleName() + ".class.getMethod(\"" + m.getName() + "\",new Class[]{});" + ln);
sb.append("obj = (" + m.getReturnType().getName() + ") this.h.invoke(this,m,null);" + ln);
sb.append("}catch(Throwable e){" + ln);
sb.append("e.printStackTrace();" + ln);
sb.append("}" + ln);
sb.append("return obj;" + ln);
sb.append("}");
}
sb.append("}" + ln);
return sb.toString();
}
}
测试
public class GPTest {
public static void main(String[] args) {
GPProxyMeipo meipo = new GPProxyMeipo();
Person person = (Person) meipo.getInstance(new Lina());
person.findLove();
System.out.println(person.getClass());
}
}
这样我们就实现了动态生成代理对象。
上面讲述了适配器模式,代理模式和装饰模式,感觉好像有点类似,下面我们来看看它们之间有什么区别:
代理模式与适配器模式
模式名 | 代理模式 | 适配器模式 |
---|---|---|
是否替一个对象提供间接性质的访问 | 是 | 是 |
接口 | 实现和目标对象相同的接口 | 主要用来处理接口间不匹配的问题,它往往替所适配的对象提供一个不同的接口 |
代理模式与装饰模式
模式名 | 代理模式 | 装饰模式 |
---|---|---|
是否需要为转调其他对象的前后执行一定的功能 | 是 | 是 |
目的 | 主要目的是控制对对象的访问 | 动态的为某个类型添加新的职责,也就是说为了动态的添加功能 |