本篇文章的参考链接
1.Java两种动态代理JDK动态代理和CGLIB动态代理
一、代理模式
代理模式的核心思想是:A对象通过持有B对象,然后使A对象同样也具有B对象的能力,并且A对象可以拓展B对象的能力;或者类A通过继承类B然后重写B的方法来增强B的能力,并且通过super.父类方法让A同样也具有B的能力
1. 静态代理
静态代理的实现原理:通过抽象出一个接口;然后实现类实现接口;然后代理类持有实现类去实现接口,然后代理类可以在调用实现类的方法前后都可以做其他事情增强该方法。因为类B写死持有A,所以B就是A的静态代理,如果B代理的对象是不确定的,那么我们就称之为动态代理,以下是伪代码:
interface Api {
void run();
}
/** A是实现类 **/
public A implements Api {
public void run (){
sysout("我是A");
}
}
/** B是代理,通过持有A拓展A的功能 **/
public B implements Api {
public A a = new A(); //类B写死持有A,所以B就是A的静态代理。
public void run (){
sysout("我是B,执行A的前置方法");
a.run(); //如果类B的持有的实现类定义成Object类型,那么只能通过invoke去调用,那么invoke的方法参数也是要写死的,那么写死的就不是动态代理了
sysout("我是B,执行A的后置方法");
}
}
public Test {
Api api = new B();
public static void main(String[] args){
//调用的时候,通过注入代理实现类,则可以在不改变原来的方法之上,增强该方法。
api.run();
}
}
2. 动态代理
2.1 JDK动态代理
JDK的动态代理代码:
/**
* jdk动态代理的接口
* 明星会唱歌跳舞
*/
public interface Star {
void sing();
void dance(int minutes);
}
/**
* 明星接口实现,刘德华
*/
public class LiuDeHua implements Star {
@Override
public void sing() {
System.out.println("刘德华在唱歌");
}
@Override
public void dance(int minutes) {
System.out.println("刘德华在跳舞" + minutes + "分钟");
}
}
public class StarProxy implements InvocationHandler {
private Object star;
StarProxy(Star star){
this.star = star;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("经纪人收钱");
Object result = method.invoke(star, args);
this.clean();
return result;
}
// 生成代理类
public Object CreatProxyedObj() {
Object invocationHandlerProxy = Proxy.newProxyInstance(star.getClass().getClassLoader(), star.getClass().getInterfaces(), this);
return invocationHandlerProxy;
}
public void clean(){
System.out.println("经纪人请人搞卫生");
}
}
public class Main {
public static void main(String[] args) {
StarProxy proxy = new StarProxy(new LiuDeHua());
Star star = (Star)proxy.CreatProxyedObj();
star.dance(10);
}
}
Proxy代理了InvocationHandler,InvocationHandler代理了我们的实现类(因为InvocationHandler代理的对象是Object类型,只有Object才可以包容所有不确定类型的对象,即动态代理)
那为什么Proxy对象能有实现类的方法呢?
// Proxy 类里能替我们生成一个代理类对象的,就是 newProxyInstance() 方法。现在回过头看它的三个参数。
newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
/**
1.第一个 ClassLoader 是为了生成 B 类的 Class 对象。作用是根据一组类的字节码 byte[] 直接生成这个类的 Class 对象。
2.第二个参数是由委托类实现的接口的 Class 对象数组。主要是包含了最重要的代理类需要实现的接口方法的信息。
3.最后一个最重要的就是一个实现了 InvocationHandler 接口的对象。InvocationHandler 接口在 java.lang.reflect 包里。最主要的就是定义了 invoke( ) 方法。它就是假设在已经动态生成了最后的 proxy 代理对象,以及所有接口定义的方法 Method 对象以及方法的参数的情况下,定义我们要怎么调用这些方法的地方。
这三个参数的分工用大白话讲就是:第一参数 ClassLoader 和第二参数接口的 Class 对象是用来动态生成委托类的包括类名,方法名,继承关系在内的一个空壳。用 B 类的例子演示就像下面这样
**/
class $ProxyN implements interfaces{
@Override
public void dance(int minutes){
//do something...
h.invoked(this, interfaces.getMethod("dance", Integer.class), new Object[]{minutes})
}
}
从原理可以看出,JDK动态代理是通过持有对象来实现的,JDK这么设计是因为:
1.动态代理的实现类的类型是不固定的,所以只能是Object,因为Object能包容一切
2.增强的代码要留给开发人员自己写,所以不能写死
2.2 cglib动态代理
cglib的思想是:通过继承父类所有公开的方法,然后重写这些方法,在重写时增强这些方法。
public class CglibProxy implements MethodInterceptor {
// 根据一个类型产生代理类,此方法不要求一定放在MethodInterceptor中
public Object CreatProxyedObj(Class<?> clazz)
{
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(clazz);
enhancer.setCallback(this);
return enhancer.create();
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("收钱");
Object result = proxy.invokeSuper(obj, args); //调用实现类的方法
return result;
}
}
3. 结论
性能对比:
- jdk创建对象的速度远大于cglib,这是由于cglib创建对象时需要操作字节码。
- cglib执行速度略大于jdk,所以比较适合单例模式。
- 另外由于cglib的大部分类是直接对Java字节码进行操作,这样生成的类会在JVM的永久代中。如果动态代理操作过多,容易造成永久代满,触发OutOfMemory。
核心思想:
jdk是通过持有对象来实现增强的;cglib是通过继承来实现增强的
值得注意的是
spring默认使用jdk动态代理,如果类没有接口,在使用cglib实现动态代理。