代理模式:静态代理、JDK动态代理、Cglib动态代理
一、代理模式
1. 概念:为一个对象提供一种代理,用代理控制对象的访问,而不是直接访问对象
2. 使用场景:
a. 远程代理:位于两个不同的地址空间对象的访问
b. 虚拟代理:消耗资源较少的对象代表消耗资源多的对象
c. 缓冲代理:缓存
d. 保护代理:控制对一个对象的不同级别的访问权限
e. 智能引用:为一个对象的访问(引用)提供一些额外的操作,比如校验、记日志
二、静态代理
1. 编译时确定了代理与被代理者的关系,又叫编译时增强
2. 静态代理由接口、业务实现类、业务代理类组成
a. 业务实现类实现业务接口,负责具体的业务
b. 业务代理类也要实现业务接口,负责拦截、过滤、预处理
3. 使用时,不是通过调用业务实现类的方法,而是通过调用业务代理类的同名方法,先创建业务实现类的对象,传入业务代理类的构造方法里
4. 缺点:一个业务代理类只能对一个业务实现类进行包装,不能对多个;多个业务代码会有重复
三、动态代理:
1. 运行时才真正确定代理关系,又叫运行时增强
2. 有一个代理类,能代理所有业务实现类的方法调用
3. 根据传进来的业务实现类和方法名进行具体调用
4. 与静态代理比较:可以对多个对象进行代理,比静态代理更灵活,不用写多余代码;而静态代理对多个对象进行代理需要写多个代理类
四、JDK动态代理,针对接口,核心是InvocationHandler
1. 在程序调用到代理类对象时,才由JVM真正创建
2. JVM根据业务实现类对象和方法名,动态创建一个代理类.class文件被字节码执行,在调用方法时通过这个代理类对象里的方法调用
3. 业务类实现业务接口,如果没有业务接口,不能使用JDK动态代理
4. 代理类通过实现InvocationHandler接口创建动态代理类,而不需要实现业务接口,所有代理类都一个写法是通用的
public class BookFacadeProxy implements InvocationHandler { // InvocationHandler接口,被动态代理类实现,负责处理被代理对象的操作
private Object target;//这其实业务实现类对象,用来调用具体的业务方法
/**
* 绑定业务对象并返回一个代理类
*/
public Object bind(Object target) {
this.target = target; //接收业务实现类对象参数
// 通过反射机制,创建一个代理类对象实例并返回。用户进行方法调用时使用
// 创建代理对象时,需要传递该业务类的类加载器(用来获取业务实现类的元数据,在包装方法是调用真正的业务方法)、接口、handler实现类
return Proxy.newProxyInstance(target.getClass().getClassLoader(),
target.getClass().getInterfaces(), this); }
/**
* 包装调用方法:进行预处理、调用后处理
*/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
Object result=null;
System.out.println("预处理操作——————");
//调用真正的业务方法
result=method.invoke(target, args);
System.out.println("调用后处理——————");
return result;
}
}
5. 经过上面两步,已经创建了业务实现类对象和代理类对象,将其绑定,绑定的时候就是通过反射,然后代理使用
public static void main(String[] args) {
BookFacadeImpl bookFacadeImpl=new BookFacadeImpl();
BookFacadeProxy proxy = new BookFacadeProxy(); // 创建动态代理类实例
BookFacade bookfacade = (BookFacade) proxy.bind(bookFacadeImpl); // 绑定才能动态代理
bookfacade.addBook();
}
5. JDK动态代理的缺点:只能代理实现了接口的业务类方法,如果业务类自己定义的方法,则不能代理
五、Cglib(Code Generation Library)动态代理,针对类和接口,核心是MethodInterceptor
1. Cglib是代码生成类库,可以在运行期间动态扩展类和接口,底层使用Java字节码操作框架ASM实现
2. 原理:对要代理的业务类,生成一个子类,并覆盖业务类里的所有非final方法,在子类中采用方法拦截的技术,拦截所有父类方法的调用,顺势织入横切逻辑
3. 缺点:对于final方法,无法进行代理
4. 使用:
a. 定义业务类,不需要实现任何接口
b. 定义拦截器,在调用目标方法时,Cglib会回调MethodInterceptor接口里的intercept方法进行拦截
public class BookFacadeCglib implements MethodInterceptor {
private Object target;//业务类对象,供代理方法中进行真正的业务方法调用
//相当于JDK动态代理中的绑定
public Object getInstance(Object target) {
this.target = target; //给业务对象赋值
Enhancer enhancer = new Enhancer(); //创建加强器,用来创建动态代理类,核心实现
enhancer.setSuperclass(this.target.getClass()); //为加强器指定要代理的业务类(即:为下面生成的代理类指定父类)
//设置回调:对于代理类上所有方法的调用,都会调用CallBack,而Callback则需要实现intercept()方法进行拦
enhancer.setCallback(this);
// 创建动态代理类对象并返回
return enhancer.create();
}
// 实现回调方法
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("预处理——————");
proxy.invokeSuper(obj, args); //调用业务类(父类中)的方法
System.out.println("调用后操作——————");
return null;
}
六、总结
1. 静态代理比动态代理更符合OOP原则,在日常开发中使用也比较多
2. 动态代理在开发框架的时候用的比较多
3. Cglib动态代理运行方法时的性能,比使用反射的JDK动态代理要快,但是在创建对象的时候比较慢,所以适用场景不一样,无需频繁创建代理对象时(比如单例的代理对象),使用cglib,其他场景使用JDK
4. Spring AOP默认使用JDK,当没有接口时,会强制使用cglib
参考:
https://blog.csdn.net/ShuSheng0007/article/details/80864854