1.什么是代理模式?
百度百科:所谓的代理者是指一个类别可以作为其它东西的接口。代理者可以作任何东西的接口:网上连接、存储器中的大对象、文件或其它昂贵或无法复制的资源。
代理模式给某一个对象提供一个代理对象,并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。
代理模式在Java中十分常见,有为扩展某些类的功能而使用静态代理,也有如Spring实现AOP而使用动态代理,更有RPC实现中使用的调用端调用的代理服务。
2.为什么使用代理模式?
中介隔离作用:代理对象可以在客户端和目标对象之间起到中介的作用,这样起到了中介的作用和保护了目标对象的作用。
开闭原则(扩展开放,修改关闭):代理类除了是客户类和委托类的中介之外,我们还可以通过给代理类增加额外的功能来扩展委托类的功能,这样做我们只需要修改代理类而不需要再修改委托类,符合代码设计的开闭原则,使系统具有高扩展性。
3.如何实现代理模式?
代理模式分为静态代理和动态代理(jdk动态代理、cglib动态代理、Spring和AspectJ),静态代理是在程序运行前,代理类.class文件已经生成,而动态代理是程序运行中通过反射机制动态创建的.
3.1静态代理
静态代理一般有两种实现方式:继承和聚合.
为了分别用代码去实现这两种方式,我先写个Demo,接下来继承和聚合都分别代理这个Demo去实现,代码我尽量简单,重在理解和原理:
先定义一个接口:
public interface Move {
void move();
}
实现这个接口:
public class Car implements Move {
public void move() {
Long begainTime = System.currentTimeMillis();
System.out.println("汽车开始行驶...");
try {
System.out.println("汽车行驶中...");
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
Long endTime = System.currentTimeMillis();
System.out.println("汽车行驶完毕,行驶了:"+(endTime-begainTime)+"毫秒");
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Move move = new Car();
move.move();
}
}
效果如图:
3.1.1继承
下面通过继承方式,实现不修改原来代码的基础上,实现对move接口提供的内容进行修改.
/**
* 静态代理-继承实现
*/
public class Car2 extends Car{
@Override
public void move() {
System.out.println("驾驶员打开了左转向灯...");
super.move();//通过继承直接调用父类方法
System.out.println("驾驶员下车了...");
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Move move = new Car2();
move.move();
}
}
效果如图:
3.1.2聚合
下面通过聚合方式,实现不修改原来代码的基础上,实现对move接口提供的内容进行修改.
/**
* 静态代理-聚合
*/
public class Car3 implements Move {
private Car car;
public Car3(Car car){
this.car = car;
}
public void move() {
System.out.println("前面有山洞,驾驶员打开了近光灯...");
car.move();
System.out.println("车子故障了,驾驶员下了车...");
}
}
测试一下:
public class Test {
public static void main(String[] args) {
Car car = new Car();
Move move = new Car3(car);
move.move();
}
}
效果如图:
总结:无论是继承还是聚合,都实现了预期效果,但哪种更好用呢?不难发现,通过聚合这种方式实现会更加灵活,在多个代理的情况下,代理之间可以以构造参数的形式传递,从而实现相互调用,但无论如何静态代理还是不够灵活,如果我现在有辆自行车而不是car,我也需要用代理实现跟car一样的功能,那么我还需要为自行车也创建代理,以后要有火车,马车…都得去写代理,那也太麻烦了,有没有什么办法?下面要讲的动态代理就能灵活的解决这些弊端.
3.2动态代理
动态代理有两种实现方式:一种是基于jdk提供的方式去动态创建代理对象,另一种是通过cglib的实现,两者各有使用场景,前者只适用于实现接口的被代理对象,如果被代理对象并没有实现接口,就不能使用jdk动态代理,只能使用后者,但因为后者采用的是继承,所以不能对final修饰的类进行代理,两者各有适用场景,spring aop的aspectj采用的就是以上两种方式的结合.
3.2.1基于JDK实现
①创建一个类,实现InvocationHandler接口:
public class JdkproxyHandler implements InvocationHandler {
private Object object;//这里的object其实就是被代理对象
public JdkproxyHandler(Object object){
this.object = object;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("前面有交警,驾驶员绕道了...");
method.invoke(object);//调用被代理对象的方法
System.out.println("交警追上来了,驾驶员吓尿了...");
return object;
}
}
②调用Proxy.newProxyInstance方法创建一个代理类:
public class Test {
public static void main(String[] args) {
Move jdkProxy = (Move) Proxy.newProxyInstance(Car.class.getClassLoader(),new Class[]{Move.class},new JdkproxyHandler(new Car()));
jdkProxy.move();//执行代理后的方法
}
}
※ Proxy.newProxyInstance()方法接受三个参数:
▶ClassLoader loader
:指定当前目标对象使用的类加载器,获取加载器的方法是固定的
▶Class<?>[] interfaces
:指定目标对象实现的接口的类型,使用泛型方式确认类型
▶InvocationHandler:
指定
动态处理器,
执行目标对象的方法时,会触发事件处理器的方法
来看一下效果:
为什么基于Jdk创建代理对象时,被代理对象必须实现接口?
原因是因为java是单继承多实现的,所以jdk官方采用第二种方式写了动态代理的源码,要求传入的参数必须是接口.
public class $Proxy1 extends Proxy implements 传入的接口{
//
}
这里就简单抛转引玉一下,因为要从jdk源码开始讲清楚为什么,其实一篇博客都不一定真正讲得完,感兴趣的可以参考下面这篇博文:
https://www.cnblogs.com/frankliiu-java/articles/1896443.html
3.2.2基于cglib实现
①先创建一个类,提供一个需要被代理的方法,这个类不需要实现接口,但不能加final修饰:
public class Train {
public void move(){
System.out.println("开火车了,污...");
}
}
②创建代理类,实现MethodInterceptor接口:
public class CglibProxy implements MethodInterceptor {
private Enhancer enhancer = new Enhancer();
/**
* 获取代理对象的实例
*
* @param clazz 目标对象的字节码文件
* @return 代理对象的实例
*/
public Object getProxy(Class clazz) {
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
/**
* @param o 目标类的实例
* @param method 目标方法的反射对象
* @param objects 目标方法的参数
* @param methodProxy 代理类的实例
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("检票进站了...");
//代理类调用父类的方法
methodProxy.invokeSuper(o, objects);
System.out.println("火车开往幼儿园...");
return null;
}
}
③测试一下:
public class Test {
public static void main(String[] args) {
CglibProxy proxy = new CglibProxy();
Train train = (Train) proxy.getProxy(Train.class);
train.move();
}
}
测试结果:
CGLIB创建的动态代理对象比JDK创建的动态代理对象的性能更高,但是CGLIB创建代理对象时所花费的时间却比JDK多得多。所以对于单例的对象,因为无需频繁创建对象,用CGLIB合适,反之使用JDK方式要更为合适一些。同时由于CGLib由于是采用动态创建子类的方法,对于final修饰的方法无法进行代理.
两种代理方式各有适用场景,那有没有一种”万能”的代理实现方式?有,而且你绝对很熟悉,那就是spring的aop,其本质也是对这两种方式的代理实现进行了封装,使其能够适应各种场景,当然当你的方法既没有实现任何接口,还加了final修饰,那spring的aop也无能为力,在这种情况下会失效.
4.手写实现JDK动态代理(基础版)
4.1实现思路
①创建Proxy类,创建newProxyInstance静态方法.
②手写Proxy类中的源码,将其生成.java文件.
③编译.java文件并Load至内存中.
④返回代理对象,并测试.
4.2实现
创建InvocationHandler并实现它
public interface InvocationHandler {
void invoke(Object proxy, Method method);
}
public class CarHandler implements InvocationHandler {
private Object target;
public CarHandler(Object target) {
this.target = target;
}
@Override
public void invoke(Object proxy, Method method) {
try {
System.out.println("测试手写代理...");
method.invoke(target);
System.out.println("jdk源码太高深了,吓得我车都停了...");
} catch (Exception e) {
e.printStackTrace();
}
}
}
创建Proxy类:
public class Proxy {
public static Object newProxyInstance(Class inface, InvocationHandler h) throws Exception {
String methodStr = "";
for (Method method : inface.getMethods()) {
methodStr += " public void " + method.getName() + "() {\n" +
" try{ \n" +
" Method md = " + inface.getName() + ".class.getMethod(\""
+ method.getName() + "\");\n" +
" h.invoke(this,md);\n" +
" }catch(Exception e){\n" +
" e.printStackTrace();\n" +
" }\n" +
" }\n";
}
String str = "package com.xpc.myproxy;\n" +
"\n" +
"import com.xpc.proxy.Moveable;\n" +
"import com.xpc.proxy.InvocationHandler;\n" +
"import java.lang.reflect.Method;\n" +
"public class $Proxy0 implements" + " " + inface.getSimpleName() + "{\n" +
"\n" +
" private InvocationHandler h;\n" +
"\n" +
" public $Proxy0(InvocationHandler h) {\n" +
" this.h = h;\n" +
" }\n" +
"\n" + methodStr + "}";
//产生java文件
String fileName = System.getProperty("user.dir") + "\\src\\main\\java\\com\\xpc\\myproxy\\$Proxy0.java";
System.out.println(fileName);
File file = new File(fileName);
FileUtils.writeStringToFile(file, str);
//编译
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
StandardJavaFileManager fileManager = compiler.getStandardFileManager(null, null, null);
Iterable units = fileManager.getJavaFileObjects(fileName);
JavaCompiler.CompilationTask task = compiler.getTask(null, fileManager, null, null, null, units);
task.call();
fileManager.close();
//lodad到内存中
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
Class clzz = classLoader.loadClass("com.xpc.myproxy.$Proxy0");
Constructor constructor = clzz.getConstructor(InvocationHandler.class);
return constructor.newInstance(h);
}
}
其实上面这一堆代码主要是为了生成下面这两个文件:
其中$Proxy0的内容如下:
不难发现,其实跟我们手写的静态代理的聚合方式是一样的,动态代理也不是多么高大上的东西,本质还是个静态代理,只不过这个静态的代理类可以动态地生成.
然后将编译后的$Proxy0.class文件加载到内存中,然后调用生成我们需要的代理对象,最后调用代理对象中的方法.
public class Test {
public static void main(String[] args) throws Exception {
Car car = new Car();
InvocationHandler h = new CarHandler(car);
Moveable moveable = (Moveable) Proxy.newProxyInstance(Moveable.class,h);
Thread.sleep(100);//模拟将创建的class文件加载至内存中的延迟
moveable.move();
}
}
测试结果:
没错,跟使用jdk提供的代理用起来几乎一模一样,当然手写版终究是手写版,不能跟jdk大神写的相提并论,jdk源码中对所传递的参数,接口大小,都作了验证,加载也作了缓存,几乎该优化的都优化完了,本篇仅是精简版的代理源码,留作帮助理解用.
项目中所涉及依赖:
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>3.2.7</version>
</dependency>
<dependency>
<groupId>org.zenframework.z8.dependencies.commons</groupId>
<artifactId>commons-io-2.2</artifactId>
<version>2.0</version>
</dependency>
更多设计模式,请关注本博.