文章目录
代理模式(Proxy pattern)
是指为目标对象提供一种代理,以控制对这个对象的访问,代理对象在客户端和目标对象之间起到中介作用.
主要目的 :
1. 保护目标对象
2. 增强目标对象
静态代理
简单来讲就是代理类拿到目标类的引用,然后在代理类中调用.在调用代理类的前后会有增强目标对象的代码.
示例 : 父亲为儿子findlove
- 共同接口 (Person)
public interface Person{
public void findlove();
}
- 真实对象(Son)
public class Son implements Person {
public void findlove() {
System.out.println("找对象....");
}
}
- 代理类(Father)
public class Father implements Person{
private Person person;
public Father(Person person) {
this.person = person;
}
public void findlove() {
System.out.println("物色");
this.person.findlove();
System.out.println("父母是否同意");
}
}
- 测试类
public class StaticProxyTest {
public static void main(String[] args) {
//父亲只能帮儿子找对象
// 不能帮其他人找对象,没有办法扩展
Son son = new Son();
Father father = new Father(son);
father.findlove();
}
}
从以上示例可以看出,代理类是通过手动完成代理操作的,如果目标类增加新的方法,代理类需要同步新增,违背了开闭原则.
更加明显的是,如果代理类需要代理其他不同的目标类时,1.只能在代理类中生成不同的代理方法.或者2.直接生成不同的代理类.第一种会导致代理类的膨胀,带来很多不必要判断逻辑.第二种会增加代码量,仅仅是因为不同的目标对象.
有没有一种更好的解决方案呢
动态代理
动态代理的出现就是为了解决静态代理的缺点,通过使用动态代理,我们可以在运行时,动态生成一个持有目标对象,并实现代理接口的Proxy,同时注入我们相同的扩展逻辑.
,在这个过程中,哪怕你要代理的是对象,甚至是不同的方法,都能通过动态代理,来扩展功能(Proxy的创建都是自动的并且在运行期生成的).
JDK实现的方式
1.示例 : 专业的事交给专业的人去做,所以我们请了媒婆来findlove
- 目标对象(mimo)
public class mimo implements Person {
// 找对象要求
public void findlove() {
System.out.println("性格好一点");
System.out.println("漂亮一点");
}
}
- 代理对象(JDKmeipo)
public class JDKmeipo implements InvocationHandler {
// 被代理的对象,把引用保存下来
private mimo target;
public Object getInstance(mimo target) throws Exception {
this.target = target;
Class<?> clazz = target.getClass();
// 用来生成一个新的对象(字节码重组实现)
return Proxy.newProxyInstance(clazz.getClassLoader(), clazz.getInterfaces(), this);
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
this.before();
Object object = method.invoke(this.target, args);
this.after();
return object;
}
public void before() {
System.out.println("我是媒婆:我要给你找对象,已经获取到需求");
System.out.println("开始物色");
}
public void after() {
System.out.println("如果合适的话,就准备交往");
}
}
- 测试代码(jdkProxyTest)
public class jdkProxytest {
public static void main(String[] args) {
try {
Person obj = (Person) new JDKmeipo().getInstance(new mimo());
obj.findlove();
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 结果
2. JDK实现代理详细分析
JDK 采用字节重组,重新生成对象代替原始的对象以达到动态代理的目的.
JDK Proxy生成代理对象的步骤:
拿到
被代理对象的引用
,并获取
到它的所有的接口
,通过反射获取- JDK Proxy 类重新生成一个新的类,同时新的类要
实现目标类所有实现的所有的接口
动态生成Java代码
,将新加的业务逻辑方法由一定逻辑代码去调用(增强代码,在代码中体现)编译
新生成的java代码.class- 重新
加载
到JVM中运行
以上这个过程被称为字节码重组,在classpath下,所有以$
开头的.class文件,都是自动生成的,我们想要获取这个自动生成的代码(也就是JDKProxy生成的代理类
),怎么办呢???
通过反编译工具可以获取代理类,然后持久化到磁盘之中
public class jdkProxytest {
public static void main(String[] args) {
try {
Person obj = (Person) new JDKmeipo().getInstance(new mimo());
obj.findlove();
byte[] bytes = ProxyGenerator.generateProxyClass("$Proxy0", new Class[]{Person.class});
FileOutputStream os = new FileOutputStream("D://$Proxy0.class");
os.write(bytes);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
}
生成的代码经jad反编译:
我们发现新生成的类继承了Proxy,并且实现了Person接口,而且重写findlove()方法.
并且在静态块中保存了目标对象所有方法,而且保存了所有的方法的引用,
在重写的方法中会调用这些引用(使用反射的方法来调用目标对象的方法),
这些代码就是JDK为我们自动生成的.
那么Proxy,java这个类是怎么样为我们生成这个代理对象的呢?
Proxy.java详细分析:
首先先分析按自己的理解创建一个代理对象需要的步骤:
- 动态生成源代码.java文件
- Java文件输出到磁盘(
jdk在实现的时候应该会存在内存之中
) - 将生成的.java文件编译成.class文件
- 将编译生成的.class文件加载到JVM中去
- 返回字节码重组之后的代理对象
现在我们就一步一步来分析源码是怎么实现的
以上就是JDK的动态代理使用字节码重组得到代理对象的过程
CJLIB实现的方式
CGLIB是覆盖父类的方法,
示例 :
- 目标类
public class zhangsan {
public void findlove(){
System.out.println("性格好就行");
}
}
- 生成类
public class Cjlbmeipo implements MethodInterceptor {
public Object getInstance(Class<?> clazz) throws Exception {
Enhancer enhancer = new Enhancer();
// 要把哪个类设置为即将生成的新类父类[也就是目标类]
enhancer.setSuperclass(clazz);
//设置生成类(也就是实现增强代码的类)
enhancer.setCallback(this);
return enhancer.create();
}
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
// 业务的增强代码
System.out.println("我是meipo,我要给你找Object");
System.out.println("开始物色");
methodProxy.invokeSuper(o,objects);
System.out.println("合适,准备");
return o;
}
}
- 测试代码
public static void main(String[] args) {
try {
//利用 cglib 的代理类可以将内存中的 class 文件写入本地磁盘
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,
"E://cglib_proxy_class/");
zhangsan obj = (zhangsan) new Cjlbmeipo().getInstance(zhangsan.class);
System.out.println("-------------------------------");
// System.out.println(obj);
obj.findlove();
} catch (Exception e) {
}
}
}
- 结果
CGLIB代理的详细分析 :
将生成的代理类代码反编译后分析代理类代码可知(代码太长了,贴不完) :
-
首先生成的代理类继承了目标类(当前也就是zhangsan)
-
重写了目标类的所有的方法,代理类会获得所有从父类继承来的方法,并会有MethodProxy与之对应.
-
- 调用顺序 :
代理对象调用this.findlove()方法
>调用拦截器(intercept)
>methodProxy.invokeSuper(o,objects);
>CGLIB$setPerson$0;
>被代理对象的findlove()方法
-
MethodProxy起到的作用:
CGLIB动态代理的执行效率之所以比JDK高是因为Cglib采用了FastClass机制:
为代理类和目标类各生成一个Class,这个Class会为代理类或被代理类的方法分配一个index(int).这个index作为入参,FastClass就可以直接定位要调用的方法直接进行调用,这样省去了反射调用,所以调用效率比 JDK动态代理通过反射调用高
总结
- JDK的动态代理实现了被代理类的接口,CGLIB是继承了被代理对象
- JDK和cglib都是在运行期生成字节码,JDK是直接写字节码,CGLIB使用ASM框架写Class字节码,CGLIB代理实现更复杂,
生成代理类效率比JDK低
- JDK调用代理方法,是通过反射机制调用的,CGLIB是通过FastClass机制直接调用方法,
CGLIB执行效率更高
.