意图:为其他对象提供一种代理以控制对这个对象的访问。
主要解决:在直接访问对象时带来的问题,比如说:要访问的对象在远程的机器上。在面向对象系统中,有些对象由于某些原因(比如对象创建开销很大,或者某些操作需要安全控制,或者需要进程外的访问),直接访问会给使用者或者系统结构带来很多麻烦,我们可以在访问此对象时加上一个对此对象的访问层。
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
静态代理
静态代理的被代理对象和代理对象都要实现接口。
写一个老师类的接口
public interface Teacher {
public void teach();
}
写一个被代理对象体育老师
public class PE_teacher implements Teacher {
@Override
public void teach() {
System.out.println("上体育课");
}
}
接下来数学老师需要去代理体育老师,数学老师就是一个handler代理类
public class Math_teacher implements Teacher {
private PE_teacher teacher;
public Math_teacher(PE_teacher teacher){
this.teacher = teacher;
}
@Override
public void teach() {
System.out.println("开始代理");
teacher.teach();
System.out.println("额外补课");
System.out.println("代理结束");
}
}
缺点
由于代理只能为一个类服务,如果需要代理的类很多,那么就需要编写大量的代理类,比较繁琐
动态代理
JDK动态代理
步骤
- 自定义代理对象工厂类,构造参数为被代理类
- 通过
Proxy.newProxyInstance
获得动态代理类 - 通过被代理类实现的接口接收代理类
- 通过代理对象调用目标方法;
//自己封装一个代理类工厂
public class ProxyFactory {
//目标对象
private Object target;
public ProxyFactory(Object target){
this.target = target;
}
//返回增强后的类
public Object getProxyInstance(){
//参数:1.目标对象的类加载器 2.目标对象实现的接口类型 3.匿名内部类,执行目标对象的方法时触发invoke方法
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理开始");
//1. 目标对象 2. 参数
Object invoke = method.invoke(target, args);
System.out.println("开始补课");
return invoke;
}
});
}
}
工厂类的代理构造器需要被代理对象,getProxyInstance返回的是代理对象。返回的是Proxy.newProxyInstance方法new出来的对象。此方法参数是固定的,第三个参数是一个类,里面重写的方法便是具体的增强代理实现。
public class Client {
public static void main(String[] args) {
Object proxyInstance = new ProxyFactory(new PE_Teacher()).getProxyInstance();
//强转代理对象
Teacher teacher = (Teacher)proxyInstance;
teacher.teach();
}
}
区别
JDK静态代理是通过直接编码创建的,而JDK
动态代理是利用反射机制在运行时创建代理类的。
其实在动态代理中,核心是InvocationHandler
。每一个代理的实例都会有一个关联的调用处理程序(InvocationHandler)。对待代理实例进行调用时,将对方法的调用进行编码并指派到它的调用处理器(InvocationHandler)的invoke
方法
对代理对象实例方法的调用都是通过InvocationHandler中的invoke方法来完成的,而invoke方法会根据传入的代理对象、方法名称以及参数决定调用代理的哪个方法。
CGLIB
CGLIB包的底层是通过使用一个小而快的字节码处理框架ASM
,来转换字节码并生成新的类
步骤
- 首先实现一个MethodInterceptor,方法调用会被转发到该类的intercept()方法。
- 然后在需要使用的时候,通过CGLIB动态代理获取代理对象。
代码
public class HelloService {
public HelloService() {
System.out.println("HelloService构造");
}
/**
* 该方法不能被子类覆盖,Cglib是无法代理final修饰的方法的
*/
final public String sayOthers(String name) {
System.out.println("HelloService:sayOthers>>"+name);
return null;
}
public void sayHello() {
System.out.println("HelloService:sayHello");
}
}
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 自定义MethodInterceptor
*/
public class MyMethodInterceptor implements MethodInterceptor{
/**
* sub:cglib生成的代理对象
* method:被代理对象方法
* objects:方法入参
* methodProxy: 代理方法
*/
@Override
public Object intercept(Object sub, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("======插入前置通知======");
Object object = methodProxy.invokeSuper(sub, objects);
System.out.println("======插入后者通知======");
return object;
}
}
public class Client {
public static void main(String[] args) {
// 代理类class文件存入本地磁盘方便我们反编译查看源码
System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY, "D:\\code");
// 通过CGLIB动态代理获取代理对象的过程
Enhancer enhancer = new Enhancer();
// 设置enhancer对象的父类
enhancer.setSuperclass(HelloService.class);
// 设置enhancer的回调对象
enhancer.setCallback(new MyMethodInterceptor());
// 创建代理对象
HelloService proxy= (HelloService)enhancer.create();
// 通过代理对象调用目标方法
proxy.sayHello();
}
}
区别
JDK代理要求被代理的类必须实现接口,有很强的局限性。
而CGLIB动态代理则没有此类强制性要求。简单的说,CGLIB
会让生成的代理类继承被代理类,并在代理类中对代理方法进行强化处理(前置处理、后置处理等)。
- 代理类会为委托方法生成两个方法,一个是与委托方法签名相同的方法,它在方法中会通过
super
调用委托方法;另一个是代理类独有的方法 - 当执行代理对象的方法时,会首先判断一下是否存在实现了
MethodInterceptor
接口的CGLIB$CALLBACK_0
;,如果存在,则将调用MethodInterceptor
中的intercept
方法
在CGLIB中,方法的调用并不是通过反射来完成的,而是直接对方法进行调用:通过FastClass机制对Class对象进行特别的处理,比如将会用数组保存method的引用,每次调用方法的时候都是通过一个index下标来保持对方法的引用
FastClass机制就是对一个类的方法建立索引,通过索引来直接调用相应的方法
哪个快?
在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率。只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理
Spring用JDK还是CGLIB?
- 当Bean实现接口时,Spring就会用JDK的动态代理。
- 当Bean没有实现接口时,Spring使用CGlib实现。